MagicCode/README.md

174 lines
14 KiB
Markdown
Raw Normal View History

2026-10-12 23:23:59 +00:00
![MagicCode](screenshot.png)
2026-10-11 00:11:20 +00:00
2026-10-12 23:23:59 +00:00
# MagicCode
2026-10-12 07:24:54 +00:00
2026-10-11 00:11:20 +00:00
Assistant IA en ligne de commande. Le **mode** (comportement de l'agent) vient d'une **extension**,
2026-10-11 22:41:17 +00:00
le **LLM** se choisit indépendamment, l'interface utilise **Ink** (React pour CLI).
2026-10-11 00:11:20 +00:00
## Démarrer
```bash
pnpm install
cp btp_AI_tpl.json btp_AI.json # puis renseigner url, basic, serviceurls.AI_API_URL
pnpm dev # mode claudecode ; le modèle est choisi dans la liste au démarrage
pnpm dev -- --model sapaicore:anthropic--claude-4.5-sonnet
pnpm dev -- --model anthropic:opus # API Anthropic directe (ANTHROPIC_API_KEY)
pnpm test # test de la boucle agent (provider factice)
pnpm build && node packages/cli/dist/index.js
```
## Lancer MagicCode depuis un autre dossier
`pnpm dev` s'exécute depuis la racine du dépôt : le dossier de travail est alors cette racine (`./btp_AI.json`, `./CLAUDE.md`… y sont cherchés).
Pour utiliser MagicCode sur un autre projet `./X`, lancez-le depuis `./X` :
```bash
pnpm build
cd packages/cli && pnpm link --global # une fois : installe la commande `magiccode`
cd ~/X && magiccode # le dossier de travail est ~/X
# ou sans installer : cd ~/X && node /chemin/vers/magiccode/packages/cli/dist/index.js
# ou en développement : MAGICCODE_CWD=~/X pnpm --dir /chemin/vers/magiccode 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. MagicCode
tient compte de `MAGICCODE_CWD`, puis de `INIT_CWD` (dossier où pnpm/npm a été lancé), puis de `process.cwd()`.
## Architecture
| Paquet | Rôle |
| --- | --- |
| `core` | Contrats (`LLMProvider`, `AgentMode`, `Extension`, `Tool`, `AgentUI`), boucle agent, registre, chargement des extensions |
| `providers` | Adaptateurs LLM : `sapaicore` (premier provider, voir ci-dessous), `anthropic` (direct), `openai` et `ollama` (à faire) |
2026-10-11 22:41:17 +00:00
| `ui` | Interface Ink/React (journal, saisie, listes de choix, confirmations, spinner) |
| `ext-claudecode` | Mode par défaut : CLAUDE.md, dossiers `.claude` (settings, commandes, skills) + outils Read/Glob/Grep/Write/Edit/Bash/TodoWrite/WebFetch |
2026-10-11 00:11:20 +00:00
| `cli` | Point d'entrée : configuration, extensions, boucle de conversation |
Résolution du mode et du modèle (le modèle n'a pas de défaut : s'il n'est pas défini, il est demandé) : flags CLI > variables `MAGICCODE_MODE` / `MAGICCODE_MODEL`
> `.magiccode/config.json` (projet) > `~/.magiccode/config.json` > défauts.
Extensions tierces : `~/.magiccode/extensions/<nom>/index.js` (export default d'un objet `Extension`).
2026-10-11 22:41:17 +00:00
## Interface (Ink)
L'interface est une application React rendue par [Ink](https://github.com/vadimdemedes/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 sans bibliothèque tierce, pour ne dépendre que d'`ink` et de `react`.
Un terminal interactif (TTY) est requis.
2026-10-12 23:23:59 +00:00
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, rose). 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é ;
2026-10-11 22:41:17 +00:00
les messages d'information sont en bleu clair (`LIGHT_BLUE` dans `packages/ui/src/colors.ts`) ; une ligne vide sépare chaque message.
2026-10-12 23:23:59 +00:00
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](https://github.com/sindresorhus/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 `!`).
2026-10-11 22:41:17 +00:00
| 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`, `/mode`, 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 |
2026-10-11 00:11:20 +00:00
## Dossier de base
Le dossier où MagicCode 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), `./btp_AI.json`, `.magiccode/config.json`, `CLAUDE.md` et `.claude/`. 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. Seuls `~/.claude` et `~/.magiccode` (config et extensions utilisateur) sont lus depuis le dossier personnel.
## Commande `!` : exécuter une commande shell
Quel que soit le mode chargé (ClaudeCode, Copilot…), `!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 ») avec les variables `env` du mode ; 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.
2026-10-12 23:23:59 +00:00
## Références `@fichier`
Quel que soit le mode chargé (ClaudeCode, Copilot…), `@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 `.claude/commands/*.md` 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, valable pour tous les agents (claudecode, copilot…).
- **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 mode d'origine est rétabli, 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). `/mode` démarre aussi une nouvelle session.
Chaque session est un fichier JSON : elle peut contenir des extraits de fichiers ou de sorties de commandes. `.maia/` est ajouté au `.gitignore`.
2026-10-11 00:11:20 +00:00
## Skills : appel direct avec `§`
Quel que soit le mode chargé, `§nom_du_skill [arguments]` lance un skill de l'agent (ex. `§sk_test`) ; `§` seul liste les skills disponibles.
Chaque mode 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. En mode ClaudeCode, ce prompt contient
les instructions du `SKILL.md` (avec `$ARGUMENTS` / `ARGUMENTS:`), sans passer par l'outil `Skill` du modèle. Les extensions Copilot, opencode, etc.
n'ont qu'à renvoyer leurs propres skills sous cette forme.
## Provider SAP AI Core / Generative AI Hub
Configuration : `./btp_AI.json` (ou `$MAGICCODE_BTP_CONFIG`), modèle fourni dans `btp_AI_tpl.json`.
Ne jamais versionner ce fichier (il est dans `.gitignore`).
1. OAuth client_credentials : `GET {url}/oauth/token` avec `Authorization: Basic {basic}` (token mis en cache, renouvelé sur expiration ou HTTP 401).
2. Découverte : `GET {AI_API_URL}/v2/lm/deployments` (header `AI-Resource-Group`, `default` sauf `resourceGroup` dans le JSON).
3. Appel du modèle sur son `deploymentUrl` selon la famille : Claude → `/converse` (Bedrock), GPT → `/chat/completions` (Azure OpenAI). Les autres familles (Gemini, embeddings…) sont ignorées.
Choix du modèle : au démarrage (sauf si `--model`, `MAGICCODE_MODEL` ou une config le définit), MagicCode interroge
tous les providers (déploiements SAP AI Core, et API Anthropic si `ANTHROPIC_API_KEY` est définie) et demande de choisir
le modèle de la session. `/model` rouvre cette liste en cours de session (la conversation est conservée).
Un provider injoignable donne un avertissement sans bloquer les autres. Spécification explicite : `sapaicore:<nom du déploiement>`.
## Extension ClaudeCode : dossiers `.claude` (lot 2)
Les dossiers `.claude` sont lus dans cet ordre (le plus spécifique l'emporte) : `~/.claude`, puis ceux de la racine jusqu'au dossier courant.
- **`settings.json` / `settings.local.json`** : `permissions.allow|ask|deny` au format Claude Code (`Bash(npm run test:*)`, `Edit(src/**)`, `Read(./.env)`, `Write`) et `env` (transmis à Bash). Priorité deny > ask > allow. Pour Bash, une règle allow ne couvre une commande composée (`&&`, `;`, `|`) que si chaque partie est autorisée, et jamais une commande avec redirection ou substitution (`>`, `$(…)`, backticks). Les règles deny sont du « meilleur effort » : elles ne remplacent pas un vrai bac à sable.
- **`commands/**/*.md`** : slash-commandes. `$ARGUMENTS`, `$1`…`$9` ; en-tête `description` et `argument-hint`. Un sous-dossier préfixe le nom (`commands/git/commit.md` → `/git:commit`). Les noms `help`, `clear`, `mode`, `model`, `exit` sont réservés.
- **`skills/<nom>/SKILL.md`** : en-tête `name` et `description`. Seule la liste est mise dans le prompt ; le contenu est chargé à la demande par l'outil `Skill`.
## Outils TodoWrite et WebFetch (lot 3, première partie)
2026-10-11 22:41:17 +00:00
- **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, sauf règle `allow` dans `settings.json` : `"WebFetch(domain:docs.example.com)"` (nom d'hôte exact, ou motif `*.example.com`) ; `deny` et `ask` fonctionnent de même.
- 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é.
2026-10-11 22:41:17 +00:00
2026-10-11 00:11:20 +00:00
## Reste à faire
- Provider SAP AI Core : streaming (actuellement une réponse complète par appel), autres familles (Gemini), validation sur un vrai service
- `ext-claudecode` : lots 1 et 2 terminés, lot 3 : TodoWrite et WebFetch faits (reste : sous-agents `agents/` + outil Task, hooks, serveurs MCP) (Glob/Grep en JS pur sans ripgrep ; à l'initialisation, ce qui est trouvé et chargé est listé, sinon un avertissement). Pas encore gérés dans les commandes : `allowed-tools`, `model`, `@fichier`, `!`commande``
- `ext-claudecode` lot 3 : TodoWrite, WebFetch, sous-agents, hooks, MCP
2026-10-11 00:11:20 +00:00
- Providers OpenAI et Ollama
- Sessions (reprise), compaction du contexte, annulation (AbortSignal) depuis l'UI