No description
Find a file
2026-10-15 23:49:58 +02:00
packages Update modifications 2026-10-15 23:49:58 +02:00
.gitignore Mises à jour des fichiers 2026-10-15 22:43:50 +02:00
llm_avis.md Modifications locales 2026-10-15 23:32:32 +02:00
MAIA.md Refactor: reorganize packages and remove deprecated extensions 2026-10-15 01:12:15 +02:00
package.json Refactor: reorganize packages and remove deprecated extensions 2026-10-15 01:12:15 +02:00
pnpm-lock.yaml Refactor: reorganize packages and remove deprecated extensions 2026-10-15 01:12:15 +02:00
pnpm-workspace.yaml Initial commit 2026-10-11 02:11:20 +02:00
README.md Update modifications 2026-10-15 23:49:58 +02:00
screenshot.png Update screenshot 2026-10-15 01:15:18 +02:00
settings.example.json Modifications locales 2026-10-15 23:32:32 +02:00
tsconfig.base.json Migration Ink 2026-10-12 00:41:17 +02:00
tsconfig.json Migration Ink 2026-10-12 00:41:17 +02:00

MAIA

MAIA

Assistant IA en ligne de commande : un seul agent (MAIA), le LLM se choisit parmi ceux de LiteLLM, l'interface utilise Ink (React pour CLI).

Démarrer

pnpm install
mkdir -p ~/.maia && cp settings.example.json ~/.maia/settings.json   # puis renseigner l'URL de LiteLLM
pnpm dev                                  # le modèle est choisi dans la liste au démarrage
pnpm dev -- --model litellm:gpt-4o        # nom du modèle tel que LiteLLM le connaît
pnpm test                                 # test de la boucle agent (provider factice)
pnpm build && node packages/cli/dist/index.js

Lancer MAIA depuis un autre dossier

pnpm dev s'exécute depuis la racine du dépôt : le dossier de travail est alors cette racine (./MAIA.md, ./.maia/skills… y sont cherchés). Pour utiliser MAIA sur un autre projet ./X, lancez-le depuis ./X :

pnpm build
cd packages/cli && pnpm link --global     # une fois : installe la commande `maia`
cd ~/X && maia                       # le dossier de travail est ~/X
# ou sans installer :  cd ~/X && node /chemin/vers/maia/packages/cli/dist/index.js
# ou en développement : MAIA_CWD=~/X pnpm --dir /chemin/vers/maia dev

Pourquoi pas pnpm --filter … dev : pnpm exécute alors le script depuis packages/cli, pas depuis le dossier où vous avez tapé la commande. MAIA tient compte de MAIA_CWD, puis de INIT_CWD (dossier où pnpm/npm a été lancé), puis de process.cwd().

Architecture

Paquet Rôle
core Contrats (LLMProvider, Tool, AgentUI…), boucle agent, sessions, !, @fichier, §skill
providers Adaptateurs LLM : litellm (provider unique : requêtes au format OpenAI vers LiteLLM, voir ci-dessous)
ui Interface Ink/React (journal, saisie, listes de choix, confirmations, spinner)
agent L'agent MAIA : contexte (MAIA.md / CLAUDE.md), skills .maia/skills + outils Read/Glob/Grep/Write/Edit/Bash/TodoWrite/WebFetch/CurrentModel
cli Point d'entrée : configuration, boucle de conversation

Résolution du modèle (pas de défaut : s'il n'est pas défini, il est demandé) : option --model > variable MAIA_MODEL

.maia/config.json (projet) > ~/.maia/config.json > défauts.

Il n'y a plus de notion de mode ni d'extensions : un seul agent, MAIA.

Commandes

Saisie Effet
/help aide (commandes, skills)
/permissions affiche les règles de permissions chargées, par fichier : badges ALLOW, ASK, DENY suivis des règles
/model recharge la liste des modèles LiteLLM et change de modèle (conversation conservée)
/clear supprime le contexte de la session et recharge MAIA.md / CLAUDE.md et les skills, comme au démarrage (la session garde son nom)
/rename <nom> · /load [nom] · /new sessions (voir plus bas)
/exit (ou Ctrl+D) quitte (la session est sauvegardée)
!commande · @fichier · §skill commande shell, fichier injecté, skill (voir plus bas)

Option de lancement : --model <spec> (ex. litellm:gpt-4o).

Interface (Ink)

L'interface est une application React rendue par Ink (Ink 7 : React ≥ 19.2 ; vérifiez la version de Node qu'il exige). Le CLI garde une façade impérative (InkUI : info, select, readInput, confirm, withSpinner…) au-dessus d'un UIStore sans dépendance à React, ce qui rend la logique testable (store.test.ts). Les composants (saisie, liste, confirmation, spinner) sont écrits à la main ; seuls les badges viennent de @inkjs/ui. Un terminal interactif (TTY) est requis.

Présentation : la bannière d'accueil reprend le style « MAIA » : masque pixel-art à gauche (demi-blocs ▀▄█, 20 × 20 pixels), titre « Maia » en gros caractères pixel avec ombre et dégradé arc-en-ciel à droite, puis deux textes (vert : « Assistant IA en ligne de commande… », rose : « Un seul agent, plusieurs modèles »). Terminal d'au moins 46 colonnes pour la version complète ; entre 25 et 45 : sans le masque ; en dessous : cadre à double bordure compact, titre centré ; une ligne vide sépare chaque message.

Messages du journal, avec le composant Badge de @inkjs/ui :

Badge Usage Texte
INFO (bleu) information bleu clair (LIGHT_BLUE)
WARN (jaune) avertissement par défaut
FAIL (rouge) erreur #DC8ADD (ERROR_TEXT)
ASK (vert) question posée à l'utilisateur (liste de choix, confirmation o/n), à gauche de la question gras

Les couleurs sont dans packages/ui/src/colors.ts. Une fois la réponse validée, la question est remplacée par la ligne ◆ Question : choix dans le journal.

La zone de saisie est encadrée par une bordure en pointillés (┈) au-dessus et au-dessous seulement, en greenBright, sans bords gauche ni droit. À sa gauche, le spinner arrow3 de cli-spinners s'anime en continu. Pendant l'attente de la réponse après validation de la saisie, « En réflexion » s'affiche avec le spinner dots10 à sa gauche, en yellowBright (même spinner pour les autres attentes : chargement des modèles, exécution d'une commande !).

Contexte Touches
Saisie Entrée : envoyer · ←/→, Ctrl+A/E : se déplacer · ↑/↓ : historique · Ctrl+U/W : effacer · Ctrl+C ou Ctrl+D (saisie vide) : quitter · le collage multi-lignes est conservé
Liste (/model, /load, choix initial) ↑/↓ (ou j/k) · Entrée : valider · Échap : annuler
Confirmation d'une action (Write, Edit, Bash…) o : autoriser · n ou Échap : refuser · Entrée seule est ignorée (réponse explicite exigée)
Pendant un tour de l'agent Ctrl+C : interrompt la requête et restaure la conversation avant cette demande

Dossier de base

Le dossier où MAIA est lancé (ex. ./X) est la base de tous les chemins relatifs : fichiers lus, créés ou modifiés par les outils (Read, Write, Edit, Glob, Grep, Bash), .maia/config.json, MAIA.md / CLAUDE.md et .maia/skills/. Un chemin complet est utilisé tel quel (~/… désigne le dossier personnel). Ce dossier est capturé une fois au démarrage, affiché (« Dossier de travail : … ») et transmis aux outils et aux providers ; les règles de permissions (Read(./.env), Edit(src/**)) sont elles aussi relatives à ce dossier. Seul ~/.maia (config, settings.json et skills utilisateur) est lu depuis le dossier personnel.

Commande ! : exécuter une commande shell

!commande exécute la commande directement, sans passer par le modèle. Deux syntaxes :

Saisie Commande exécutée
!ls · !ls -la src tout ce qui suit le !
!'ls -la' le texte entre guillemets simples (délimite la commande avec ses paramètres)
!'ls' -la src · !'ls -la' | wc -l le texte entre guillemets, puis ce qui suit est ajouté comme paramètres : ls -la src, ls -la | wc -l
!'echo "l\'ami"' \' donne un guillemet simple littéral dans la commande
!'ls -la erreur (guillemet non fermé) : rien n'est exécuté

Le reste du comportement :

  • elle tourne dans le dossier de lancement (voir « Dossier de base ») ; un cd n'a donc pas d'effet durable ;
  • stdout et stderr sont affichés dans l'ordre d'arrivée, avec le code de sortie en cas d'échec ; durée maximale 2 minutes ;
  • aucune confirmation n'est demandée (c'est vous qui la tapez) et les règles de permissions des outils ne s'appliquent pas ;
  • le résultat est ajouté au contexte de votre prochaine demande (jusqu'à 30 000 caractères, milieu tronqué au-delà), pour que le modèle sache ce qui a été lancé. /clear l'efface ;
  • Ctrl+C interrompt la commande ; ! seul affiche l'aide. Les programmes interactifs (vim, less, saisie au clavier…) ne sont pas pris en charge.

Références @fichier

@chemin/vers/fichier dans votre message injecte le contenu du fichier dans le contexte de la demande (en plus de le laisser dans le texte) :

Saisie Effet
explique @src/a.ts injecte src/a.ts
compare @a.ts et @ b.ts plusieurs fichiers ; la forme @ chemin (avec espace) est acceptée
lis @'mon dossier/a b.txt' guillemets simples pour un chemin avec espaces (\' : guillemet littéral)
résume @docs un dossier : la liste de ses entrées est injectée
@/chemin/complet.txt · @~/notes.md chemin complet, ou depuis le dossier personnel
  • Chemins relatifs au dossier de lancement (voir « Dossier de base »). Aucune confirmation : c'est vous qui citez le fichier, les règles de permissions des outils ne s'appliquent pas.
  • Un @mot qui ne désigne aucun fichier existant reste du texte ordinaire (@mention, @Override, jean@exemple.fr) ; un avertissement n'apparaît que s'il ressemble à un chemin (@src/x.ts introuvable).
  • Limites : 2 Mo par fichier, 50 000 caractères injectés par fichier (milieu tronqué au-delà) et 150 000 par message ; les fichiers binaires sont ignorés ; un même fichier cité plusieurs fois n'est injecté qu'une fois.
  • Le contenu est marqué comme donnée, pas comme instructions. Les @fichier écrits dans le corps d'une commande personnalisée ne sont pas encore traités (seuls ceux de votre saisie le sont).
  • Pas d'autocomplétion des chemins à la saisie pour l'instant.

Sessions (./.maia/.sessions)

Au lancement, ./.maia et ./.maia/.sessions sont créés dans le dossier d'exécution s'ils n'existent pas. Les sessions sont une fonctionnalité de l'application.

  • Sauvegarde automatique à la sortie (/exit, Ctrl+D, SIGTERM/SIGHUP). Une session sans aucun message n'est pas enregistrée.
  • Nom par défaut : session_{date}_{heure}_{minutes}_{secondes} (ex. session_2026-10-13_01_04_05).
  • /rename <nom> : renomme la session courante ; ce nom sera utilisé à la sauvegarde.
  • /load : sans argument, liste les sessions enregistrées (sélection) ; /load <nom> recharge cette session (le modèle courant est conservé).
  • /new : nouvelle session, nouveau contexte (MAIA.md / CLAUDE.md et skills rechargés) ; l'utilisateur est averti que la session quittée est sauvegardée (sous son nom).

Chaque session est un fichier JSON : elle peut contenir des extraits de fichiers ou de sorties de commandes. .maia/ est ajouté au .gitignore.

Skills : appel direct avec §

§nom_du_skill [arguments] lance un skill de l'agent (ex. §sk_test) ; § seul liste les skills disponibles. L'agent expose ses skills via ModeInitResult.skills (contrat AgentSkill : name, description, invoke(args)), ce qui rend l'appel indépendant du format propre à l'agent. invoke retourne le prompt envoyé au LLM. Ce prompt contient les instructions du SKILL.md (.maia/skills/<nom>/) (avec $ARGUMENTS / ARGUMENTS:), sans passer par l'outil Skill du modèle.

Accès aux LLMs : LiteLLM

MAIA ne parle qu'à un serveur LiteLLM : toutes les requêtes de tous les agents sont envoyées au format OpenAI (chat/completions, en streaming, avec appels d'outils) vers le endpoint OpenAI de LiteLLM.

Au démarrage, le fichier ~/.maia/settings.json (dossier de l'utilisateur, pas celui de lancement) est chargé :

{
  "LiteLLM_URL": "http://localhost:4000",
  "OpenAI_EndPoint": "/v1/chat/completions",
  "LiteLLM_Models": "/v1/models"
}
  • LiteLLM_URL : URL du serveur LiteLLM.
  • OpenAI_EndPoint : suffixe ajouté à LiteLLM_URL pour envoyer une requête au format OpenAI.
  • LiteLLM_Models : suffixe ajouté à LiteLLM_URL pour récupérer la liste des modèles disponibles.
  • Optionnel : LiteLLM_API_Key (ou variable d'environnement LITELLM_API_KEY), envoyée en Authorization: Bearer …. Ce fichier peut donc contenir un secret : ne pas le versionner.

Si le fichier est absent ou incomplet, MAIA s'arrête avec un message qui en donne un exemple.

Choix du modèle : au démarrage (sauf si --model ou MAIA_MODEL le définit), MAIA récupère les modèles auprès de LiteLLM et demande de choisir celui de la session. /model rouvre cette liste en cours de session (la conversation est conservée). Spécification explicite : litellm:<nom du modèle> (ou le nom seul).

Agent MAIA : contexte et skills

Au lancement, le contexte de départ est chargé depuis le dossier de lancement : MAIA.md s'il existe, sinon CLAUDE.md (s'il existe). Le fichier chargé est indiqué par un message INFO ; si aucun des deux n'existe, un WARN le signale. Seul ce dossier est regardé (pas les dossiers parents).

Les dossiers .claude ne sont plus lus (ni settings.json, ni commands/, ni skills/, ni .claude/CLAUDE.md, ni ~/.claude).

  • Skills : .maia/skills/<nom_du_skill>/SKILL.md (ou skill.md), chargés depuis trois emplacements. Le plus spécifique l'emporte en cas de même nom :

    1. le dossier utilisateur : ~/.maia/skills ;
    2. le dossier parent du dossier de lancement : ex. /X/.maia/skills pour un lancement dans /X/Y ;
    3. le dossier de lancement : ex. /X/Y/.maia/skills.

    Le fichier commence par un en-tête YAML : description (objectif du skill) et name (optionnel ; s'il est présent, il doit être identique au nom du dossier, qui fait foi). Seule la liste (nom + description) est mise dans le prompt ; le contenu est chargé à la demande par l'outil Skill, ou lancé directement avec §nom.

---
name: sk_test          # optionnel, = nom du dossier
description: Lance les tests du projet et résume les échecs
---
Instructions du skill… ($ARGUMENTS = arguments passés avec §sk_test)

Conséquence : il n'y a plus de variables d'environnement venant de la configuration de l'agent ni de slash-commandes personnalisées. Les permissions sont décrites dans la section suivante.

Permissions

Les permissions s'appliquent aux outils (intégrés ou non). Trois types de règles :

Type Effet
allow l'outil est utilisé sans approbation manuelle
ask l'utilisateur doit confirmer chaque utilisation (badge ASK, réponse o/n)
deny l'outil ne peut pas être utilisé (l'agent reçoit un refus)

Ordre d'évaluation : deny, puis ask, puis allow. La première famille qui correspond décide, quelle que soit la précision des règles : un allow étroit comme Bash(aws s3 ls) ne fait pas exception à un deny large comme Bash(aws *), ni à un ask plus large. Sans règle applicable, c'est ask qui s'applique (pour tous les outils, y compris Read, Glob, Grep…).

Où les définir : ~/.maia/settings.json (utilisateur) et ./.maia/settings.json (dossier de lancement). Les listes des deux fichiers s'additionnent. Le fichier ../.maia/settings.json du dossier parent n'est pas pris en compte. Dans chaque fichier, la propriété permissions contient une liste par type, tous facultatifs :

"permissions": {
  "allow": ["Bash(allow stop *)"],
  "ask":   ["Bash(allow ask *)"],
  "deny":  ["Bash(allow deny *)", "Read(./.deny.md)"]
}

Format d'une règle : Outil (tous les appels de l'outil) ou Outil(motif) :

Outil Le motif porte sur Exemples
Bash la commande ; * = n'importe quelle suite ; cmd * couvre cmd avec ou sans arguments (mais pas cmdx) ; sans * : commande exacte Bash(allow stop *), Bash(git status), Bash(npm run test:*)
Read, Write, Edit, Glob, Grep le chemin (relatif au dossier de lancement, motifs glob) Read(./.deny.md), Edit(src/**)
WebFetch le domaine WebFetch(domain:docs.example.com), WebFetch(domain:*.example.org)
Skill le nom du skill Skill(pdf)
autres outils (ex. ajoutés plus tard) une valeur texte de l'entrée de l'outil (* accepté) mcp__db__query(SELECT *)

Précisions sur Bash :

  • commande composée (&&, ;, |) : un deny ou un ask s'applique dès qu'une des parties correspond, y compris cachée dans $(…), des backticks ou des parenthèses ; un allow ne s'applique que si toutes les parties sont couvertes ;
  • un allow ne couvre jamais une commande avec redirection (>) ou substitution ($(…), backticks) : elle repasse en ask.

Au démarrage, MAIA indique (INFO) les fichiers chargés et le nombre de règles, ou qu'aucune règle n'est définie ; un JSON invalide ou une règle mal formée donne un WARN et est ignoré. Les deny sont du « meilleur effort » : ils ne remplacent pas un vrai bac à sable. Les commandes ! et les références @fichier tapées par l'utilisateur ne passent pas par ces règles.

Attention : ./.maia/settings.json d'un dépôt tiers peut contenir des règles allow larges ; relisez-le avant de lancer MAIA dans un projet que vous ne connaissez pas.

La commande /permissions affiche les règles actives, fichier par fichier, avec un badge ALLOW (vert), ASK (jaune) et DENY (rouge) suivi des règles du type, une par ligne ; seuls les types non vides sont montrés. Après avoir modifié un settings.json, /clear recharge la configuration.

Exemple pour éviter les confirmations sur les outils en lecture seule : voir settings.example.json.

Outils TodoWrite, WebFetch et CurrentModel

  • TodoWrite : liste de tâches de la session (pending / in_progress / completed, forme activeForm pour la tâche en cours). Chaque appel remplace la liste ; le résultat (☐ ▶ ☑) est affiché à l'utilisateur et renvoyé au modèle. Un avertissement est ajouté si plusieurs tâches sont « en cours ».
  • CurrentModel : renvoie le modèle LLM utilisé par la session (valeur à jour après un /model). Sans paramètre, sans confirmation ; le modèle est lu au moment de l'appel.
  • WebFetch : télécharge une URL http(s) (HTML converti en Markdown avec turndown, JSON mis en forme, texte brut ; 5 Mo et 30 s maximum, max_length caractères renvoyés). Pas de JavaScript exécuté, pas de PDF ni d'images.
    • Confirmation demandée par défaut ; règle allow possible : WebFetch(domain:docs.example.com) (voir « Permissions »).
    • Une redirection vers un autre domaine n'est pas suivie : l'outil renvoie la nouvelle URL, que le modèle doit rappeler (ce qui repasse par la confirmation).
    • Le contenu est signalé au modèle comme non fiable (risque d'injection de prompt) ; il ne passe par aucun second modèle de résumé.

Reste à faire

  • Provider LiteLLM : validation sur un vrai serveur (streaming SSE et appels d'outils testés seulement sur des flux simulés) ; compatibilité de @inkjs/ui avec Ink 7 à confirmer à l'usage (l'affichage des badges a été vu fonctionner par l'utilisateur).
  • Agent : sous-agents + outil Task, hooks, serveurs MCP.
  • Éventuellement rétablir sous .maia ce que supprimait l'abandon de .claude : variables d'environnement et slash-commandes personnalisées (le code des commandes existe encore dans packages/agent/src/commands.ts, il n'est plus appelé).
  • @fichier dans le corps d'une commande personnalisée ; autocomplétion des chemins @ à la saisie.
  • Liste à cocher (choix multiples) : n'existe pas encore ; elle recevrait le badge ASK.
  • Nerd Fonts (icônes) : proposé comme réglage optionnel dans settings.json, non réalisé.
  • Tests : les suites vitest et le typecheck sont à lancer après pnpm install (écrits sans avoir pu être exécutés dans l'environnement de développement).

Travaux réalisés

Journal des évolutions, dans l'ordre :

  1. Squelette : monorepo pnpm (TypeScript strict, tsup, vitest), contrats LLMProvider / Tool / AgentUI, boucle agent avec appels d'outils, agent par défaut de type « Claude Code » (outils Read, Write, Edit, Bash).
  2. Provider SAP AI Core puis choix du modèle au démarrage et commande /model. (Remplacé ensuite par LiteLLM, voir 12.)
  3. Outils Glob et Grep (JavaScript pur, sans ripgrep).
  4. Configuration de l'agent (lot 2) : fichiers de contexte, permissions, slash-commandes, skills. (Abandonné avec les dossiers .claude, voir 13.)
  5. §skill : appel direct d'un skill de l'agent.
  6. Dossier de lancement : tous les chemins relatifs partent du dossier où MAIA est lancé (MAIA_CWD > INIT_CWD > cwd).
  7. Interface Ink (remplace Clack.js) : bannière « MAIA » avec masque pixel-art et titre arc-en-ciel, messages en bleu clair, zone de saisie en pointillés greenBright avec spinner arrow3, attente « En réflexion » avec dots10 en yellowBright.
  8. Outils TodoWrite et WebFetch (lot 3, première partie).
  9. !commande (avec la syntaxe !'commande avec paramètres') et @fichier.
  10. Renommage en MAIA (titre « Maia ») et sessions : ./.maia/.sessions, sauvegarde automatique, /rename, /load (avec réaffichage du contenu), /new.
  11. Corrections : import manquant de normalizeSessionName, session.tools est une Map, en-tête de skill avec espace avant les deux-points (description : …).
  12. LiteLLM : un seul provider, requêtes au format OpenAI en streaming vers le endpoint OpenAI de LiteLLM ; configuration ~/.maia/settings.json (LiteLLM_URL, OpenAI_EndPoint, LiteLLM_Models, LiteLLM_API_Key optionnel). Suppression de SAP AI Core, Anthropic, OpenAI et Ollama.
  13. Renommage technique magiccode → maia (packages @maia/*, commande maia, variables MAIA_*, dossier .maia) ; textes « MagicCode » → « MAIA » ; bannière « Un seul agent, plusieurs modèles ».
  14. Skills dans .maia/skills (dossier de lancement, dossier parent, dossier utilisateur) ; les dossiers .claude ne sont plus lus.
  15. Badges @inkjs/ui : INFO, WARN, FAIL (texte d'erreur #DC8ADD) et ASK devant les questions.
  16. Suppression des modes et des extensions : un seul agent MAIA (packages/agent), contexte de départ MAIA.md sinon CLAUDE.md (message INFO, ou WARN si aucun).
  17. Outil CurrentModel : indique au modèle quel LLM est utilisé dans la session.
  18. /clear : supprime le contexte et recharge MAIA.md / CLAUDE.md (et les skills) comme au démarrage ; /new fait de même.
  19. Permissions granulaires : règles allow / ask / deny par outil (Outil ou Outil(motif)), évaluées dans l'ordre deny > ask > allow, ask par défaut ; définies dans ~/.maia/settings.json et ./.maia/settings.json (pas le dossier parent).
  20. /permissions : affiche les permissions chargées, par fichier et par type (badges ALLOW / ASK / DENY) ; les fichiers sont listés l'un sous l'autre au démarrage.