MagicCode/README.md

18 KiB
Raw Blame History

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
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)
/model recharge la liste des modèles LiteLLM et change de modèle (conversation conservée)
/clear efface la conversation courante
/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 ; 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 : sans settings.json, il n'y a plus de règles de permissions ni de variables d'environnement venant de la configuration de l'agent, et plus de slash-commandes personnalisées. Les outils qui demandent confirmation (Write, Edit, Bash, WebFetch) la demandent toujours.

Outils TodoWrite et WebFetch (lot 3, première partie)

  • 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 ».
  • 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 à chaque appel (les règles de permissions de l'ancien settings.json ne sont plus lues).
    • 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 : règles de permissions, variables d'environnement, slash-commandes personnalisées (le code existe encore dans packages/agent/src/{permissions,settings,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).