🛰️ Ce que tu reçois : les 13 prompts dans l'ordre exact, la règle qui empêche Claude Code de partir dans tous les sens pendant 6 heures, et le tableau des 12 murs que je me suis pris (avec le contournement de chacun).
L'Agentic OS, c'est une interface web qui tourne sur ta machine (localhost:3000) et qui te montre ce que Claude Code fait vraiment : tes tokens réels, tes sessions en direct, tes agents, tes skills, tes projets, ton kanban. Et depuis chaque projet, un chat qui lance un vrai process claude dans le bon dossier.
Tout est local. Aucune clé API, aucune base de données, rien d'exposé sur le réseau.
🎬 Inspiré de l'AGENT OS de Mike Codeur (la vidéo), reconstruit de zéro et orienté dev. Ce guide ne copie pas son code : il te donne la procédure et les prompts qui m'ont permis de le refaire moi-même en une journée.
🧠 La règle n°1 (celle qui change tout)
⛔ On ne réimplémente RIEN de ce que Claude Code fait déjà.
Tes agents, tes skills, ta mémoire, tes MCP, tes permissions vivent déjà sur ton disque dans~/.claude/. L'OS les lit et les édite. Il ne les recopie jamais dans une base.
On ne développe que ce qui manque au runtime : le kanban, le mission control, le cockpit tokens, le registre de projets.
Sans cette règle, tu passes trois jours à réécrire un runtime d'agent qui existe déjà, et ton OS est périmé à la prochaine mise à jour de Claude Code.
🧱 Prérequis
- Claude Code installé et déjà utilisé (il te faut des transcripts sur le disque, sinon l'OS affichera des écrans vides)
- Node.js 20+ et npm
- Un dossier de projets rangé en
~/Documents/Claude/Projects/<ORGANISATION>/<projet>: c'est le registre que l'OS va lire - Savoir ouvrir un terminal. Tu n'écriras aucune ligne de code : tout est généré par Claude Code à partir des prompts ci-dessous.
⏱️ Compter une bonne journée. Le guide est découpé en 4 lots ; chaque lot marche tout seul, tu peux t'arrêter entre deux.
🗺️ Ce que tu vas obtenir
| Écran | Ce qu'il montre | Source réelle sur ton disque |
|---|---|---|
| Dashboard | Tokens, coût équivalent API, répartition par modèle | champ usage des messages assistant des transcripts |
| Live | Les sessions Claude Code au travail, rafraîchi toutes les 3 s | fin des transcripts modifiés dans les 15 dernières minutes |
| Mission Control | Kanban 4 colonnes, drag & drop | cases à cocher des tasks/todo.md de chaque projet |
| Agents | Liste + éditeur du .md | ~/.claude/agents/*.md + agents des plugins |
| Skills | Registre, recherche, aperçu | ~/.claude/skills/*/SKILL.md + skills des plugins |
| Sessions | Liste + trace événement par événement | un fichier .jsonl = une session |
| Projets | État git, tâches ouvertes, tokens par projet, + un chat par projet | registre disque + git status + process claude |
LOT 0 · Le fichier CLAUDE.md avant le code
💡 Ne demande pas « construis-moi un dashboard ». Commence par faire écrire à Claude Code le contrat du projet. C'est ce fichier qui l'empêchera de partir dans tous les sens pendant les 6 heures suivantes.
Crée un dossier vide, ouvre Claude Code dedans, et colle :
On va construire un cockpit web local pour piloter Claude Code : tokens, sessions en direct, agents, skills, kanban, projets.
Avant d'écrire une seule ligne de code, écris le CLAUDE.md du projet. Il doit contenir :
1. Le principe directeur, non négociable : on ne réimplémente RIEN de ce que Claude Code fait déjà. Les agents, skills, mémoire, MCP et permissions vivent dans ~/.claude sur le disque. L'app les LIT et les ÉDITE, elle ne les duplique jamais en base. On ne développe que ce qui manque au runtime : kanban, mission control, cockpit tokens, registre de projets.
2. La stack : Next.js (App Router) + TypeScript + Tailwind v4. Aucune librairie de composants (primitives maison), aucune librairie de charts (SVG à la main), npm.
3. Un tableau « d'où vient chaque chiffre » : une ligne par écran, une colonne avec la source réelle sur le disque. Si un chiffre n'a pas de source, l'écran ne l'affiche pas.
4. La contrainte : tout reste sur 127.0.0.1, aucune clé API, jamais exposé.
5. La règle de fin de tâche : `npm run build` doit être vert avant toute annonce de « c'est fait ».
Avant d'écrire ce fichier, vérifie ma machine pour de vrai : version de node, version du CLI `claude`, présence de tmux, présence de pnpm. Écris les versions RÉELLES dans le CLAUDE.md, pas des suppositions.
🔍 Pourquoi le faire vérifier ta machine : chez moi il a « vu » pnpm 11.12.0 alors que c'était npm derrière un||. Demande-luiwhich pnpm, pas seulementpnpm -v.
LOT 1 · Le squelette (scaffold, types, design)
⚡ Prompt 1 · Scaffolder
Scaffolde l'app : Next.js dernière version, TypeScript, Tailwind v4, App Router, alias @/*, npm.
Attention : create-next-app refuse un dossier dont le nom contient un espace ou une majuscule. Si c'est mon cas, scaffolde dans un dossier temporaire, déplace le contenu ici, et corrige le champ "name" du package.json.
Ensuite : `npm run build` doit passer, et `npx eslint src --max-warnings=0` doit être propre. Ne me dis pas que c'est fait avant de l'avoir lancé.
Termine en créant deux fichiers :
- tasks/todo.md : les lots à venir en cases à cocher
- tasks/lessons.md : vide, avec une ligne d'intro. Tu y écriras une ligne à chaque fois que tu te prends un mur.
⚡ Prompt 2 · Le contrat de types
🧩 C'est le prompt le plus rentable du guide. Un contrat de types écrit AVANT les écrans, et une couche d'accès déjà asynchrone, font que passer du faux au vrai ne touchera aucun composant.
Écris le contrat du domaine dans src/lib/types.ts, et rien d'autre pour l'instant. Une interface par objet réel : Agent, Skill, Task, Run, RunEvent, TokenUsage, Project, UsagePoint, ModelUsage, DashboardStats, LiveSession.
Règles :
- TokenUsage décompose input / output / cacheWrite / cacheRead, plus total et costUsd. Les tokens de cache sont facturés à un tarif différent : on ne les fond JAMAIS dans un seul chiffre.
- Chaque champ porte un commentaire disant de quelle source disque il vient.
- Aucun champ décoratif : si je ne peux pas le lire sur le disque, il n'existe pas.
Puis crée src/lib/data/index.ts : une fonction par lecture (getAgents, getSkills, getTasks, getRuns, getProjects, getStats), toutes ASYNC dès maintenant même si elles renvoient encore du faux. C'est le SEUL point d'entrée des données de toute l'app : aucun composant ne lira jamais le disque directement.
⚡ Prompt 3 · Le design system
Design system avant les écrans. C'est un cockpit dense, sombre, non theme-switchable : la lisibilité prime sur la neutralité.
- Des tokens de couleur dans globals.css : 4 paliers de fond aux écarts volontairement faibles, 3 niveaux d'encre, UNE teinte d'accent qui porte l'identité et l'état « vivant », et 5 couleurs strictement sémantiques (outil/sous-agent, info/git, attente, erreur, succès) qui ne servent JAMAIS de décoration.
- La hiérarchie vient des bordures et de la typo, pas de contrastes de fond violents.
- Les chiffres en tabular-nums : un cockpit affiche surtout des nombres, ils doivent s'aligner en colonne.
- src/components/ui.tsx : Panel, Badge, Stat, Table, Field… primitives maison, aucune librairie.
- src/lib/ui-map.ts : les correspondances statut → couleur centralisées, pour que tous les écrans parlent le même langage visuel.
Montre-moi une page de démo avec toutes les primitives avant de continuer.
LOT 2 · Les écrans, d'abord sur du faux
🎭 Oui, on mocke d'abord. Le parsing réel coûte cher à écrire ; l'ergonomie, non. Si l'écran ne tient pas debout sur des données inventées, il ne tiendra pas sur les vraies. Le faux vit dans un seul fichier qu'on supprimera entièrement au lot 3.
Construis maintenant les écrans sur données mockées, dans cet ordre, un écran par message : Dashboard, Mission Control (kanban), Agents, Skills, Sessions, Projets.
Le faux vit dans UN seul fichier src/lib/data/mock.ts, appelé uniquement par src/lib/data/index.ts. Il doit être réaliste : vrais noms de modèles, vrais ordres de grandeur de tokens, vrais chemins de fichiers. Aucun composant n'importe mock.ts.
Le graphe de tokens du dashboard se dessine en SVG à la main. Pas de librairie de charts.
Contraintes de layout à respecter sur TOUS les écrans :
- colonnes de grille en minmax(0,1fr) et min-w-0 sur l'enfant, sinon un chemin long fait déborder la page horizontalement au lieu de tronquer ;
- items-start sur les grilles, sinon un panneau court s'étire à la hauteur de son voisin ;
- listes longues : hauteur calée en h-[calc(100vh-Xrem)] + min-h-0 + overflow-y-auto interne. Sinon la page s'étire à l'infini et le cockpit perd sa moitié basse.
Après chaque écran : build vert, lint propre, route en HTTP 200. Puis coche la ligne dans tasks/todo.md.
LOT 3 · Brancher le réel, source par source
🎯 Un prompt = une source. Ne demande jamais « branche toutes les vraies données » : tu obtiens un truc à moitié faux que tu ne sauras pas déboguer.
⚡ Prompt 4 · Le parseur de transcripts (le cœur)
On branche le réel. Premier morceau, le plus important : les sessions et les tokens.
Source : ~/.claude/projects/**/*.jsonl : un fichier = une session Claude Code. Chaque ligne est un événement JSON ; les messages "assistant" portent un champ "usage".
Écris src/lib/server/sessions.ts (server-only) :
- parsing en streaming ligne par ligne, jamais un JSON.parse du fichier entier ;
- IGNORE les lignes isSidechain: true. Ce sont les sous-agents, et leur usage est DÉJÀ compté dans la session parente : les inclure double les tokens ;
- le titre lisible d'une session vient d'une ligne {"type":"ai-title","aiTitle":...}, pas du premier prompt. Même chose pour le permission-mode ;
- mémo par fichier sur (mtime, taille) + cache stale-while-revalidate. Sans ça, scanner quelques centaines de transcripts coûte 20 secondes à CHAQUE requête ;
- une fenêtre de rétention en jours, réglable par variable d'environnement, pour borner le coût.
Puis src/lib/server/pricing.ts : les tarifs officiels par modèle, avec les 4 lignes (input, output, cache write, cache read).
Deux choses à écrire noir sur blanc dans l'UI :
- le coût affiché est un ÉQUIVALENT API. Sur abonnement, le coût réel est nul.
- aucun quota d'abonnement n'est affiché : il n'est pas dérivable des transcripts.
⚡ Prompt 5 · Le registre de projets
Branche l'écran Projets sur le vrai disque.
Deux sources qui se recoupent : le registre ~/Documents/Claude/Projects/<ORGANISATION>/<projet> (rends la racine réglable par variable d'env), et les cwd réellement rencontrés dans les transcripts.
- Regroupe les sessions par racine git (git rev-parse --show-toplevel) : plusieurs sous-dossiers d'un même repo ne font qu'un projet.
- ATTENTION : si mon dossier Projects est lui-même un repo git, ce regroupement écrase tout en une seule carte. La racine ne doit jamais remonter au-dessus de <racine>/<organisation>/<projet>.
- Déduplique le registre sur égalité exacte de chemin uniquement. Une déduplication par préfixe supprime tous les projets à cause du point précédent.
- Un cwd qui n'existe plus (disque externe débranché, iCloud) s'affiche avec exists: false, il ne disparaît pas silencieusement.
- Affiche par projet : git status réel (branche, fichiers modifiés, ahead), marqueurs trouvés à la racine (git, package.json, CLAUDE.md, tasks/todo.md), tokens cumulés, dernière session.
⚡ Prompt 6 · Agents et skills, plugins compris
Branche les écrans Agents et Skills sur les vrais fichiers.
- Agents : ~/.claude/agents/*.md + les .claude/agents des projets. Le frontmatter donne name, description, model, tools ; le corps donne les instructions.
- Skills : ~/.claude/skills/*/SKILL.md + ceux des projets.
- Les skills et agents des PLUGINS ne sont pas dans ces dossiers : lis ~/.claude/plugins/installed_plugins.json et suis les installPath actifs. Ne balaye pas plugins/cache/ directement, tu ramènerais aussi les vieilles versions installées côte à côte.
- ~/.claude/skills contient des liens symboliques : dirent.isDirectory() renvoie false dessus. Filtre sur isDirectory() OU isSymbolicLink(), sinon la moitié du registre disparaît.
- Applique la même précédence que Claude Code : plugin < utilisateur < projet.
- Ajoute une écriture réelle : PUT /api/agents/:id réécrit le corps du .md sur le disque, frontmatter préservé.
⚡ Prompt 7 · Le kanban branché sur les fichiers de tâches
Branche Mission Control sur les tasks/todo.md de tous les projets du registre.
- Une case à cocher = une carte. Le titre de section du todo.md devient le contexte de la carte.
- Les titres contiennent du markdown : passe-les en texte brut avant affichage, sinon les cartes montrent **Titre**.
- Déduplique les tags d'une même ligne avec un Set : deux tags identiques dans une ligne = deux clés React identiques = warning et rendu cassé.
- Le statut par défaut suit la case à cocher. Un déplacement de carte est persisté à part dans data/task-overrides.json via PATCH /api/tasks/:id : on ne réécrit jamais le todo.md du projet à la place de l'utilisateur.
⚡ Prompt 8 · L'écran Live
Ajoute l'écran Live : les sessions Claude Code en train de travailler, rafraîchi toutes les 3 secondes.
Il ne passe SURTOUT pas par le scan complet. Il relit uniquement les 512 derniers Ko des transcripts modifiés dans les 15 dernières minutes : coût constant, rafraîchissement 3 s possible.
En contrepartie, les compteurs portent sur la fenêtre relue et pas sur la session entière : écris-le dans l'UI, sinon les chiffres du Live et ceux du Dashboard sembleront se contredire.
Affiche par session : état (outil en cours / réfléchit / attend / au repos), outil courant, projet, branche git, modèle, mode de permission, et les sous-agents lancés par l'outil Task.
⚡ Prompt 9 · Supprimer le faux
Supprime complètement src/lib/data/mock.ts et toute référence à des données inventées. Si un écran devient vide, c'est la bonne nouvelle : ça veut dire qu'il affichait du faux.
Puis relis src/lib/data/index.ts et confirme-moi, source par source, d'où vient chaque chiffre affiché dans l'app.
LOT 4 · Le moteur : piloter un vrai claude
🔥 C'est la partie qui transforme un tableau de bord en OS. Et c'est la seule où tu peux te faire mal : on lance de vraies commandes sur ta machine. Le prompt de sécurité juste après n'est pas optionnel.
⚡ Prompt 10 · Le driver
Ajoute un chat par projet qui pilote un VRAI process claude. On n'écrit aucun runtime d'agent : le CLI en est déjà un.
Commande exacte, un process par projet, lancé avec cwd = le dossier du projet :
claude -p --verbose --input-format stream-json --output-format stream-json --permission-mode <mode> --model <modele>
Deux points vérifiés, ne les discute pas :
- --output-format stream-json en mode -p EXIGE --verbose, sinon le CLI refuse de démarrer ;
- --input-format stream-json garde stdin ouvert : chaque ligne JSON poussée est un tour de plus dans la MÊME session.
Écris src/lib/server/claude-driver.ts : start / send / interrupt / stop / subscribe / getState. Il traduit le flux stream-json en événements typés (text, thinking, tool, tool-result, result, system, error), borne le nombre d'événements gardés en mémoire et tronque les textes longs.
Point crucial : les abonnés vivent par PROJET, pas par session. La page s'abonne au chargement, alors qu'aucune session ne tourne encore : si l'abonnement dépendait d'une session existante, le chat resterait muet après le démarrage.
Conséquence à me confirmer : la conversation s'écrit dans un transcript Claude Code normal, donc l'écran Live et les compteurs de tokens la voient sans une seule ligne de synchronisation.
⚡ Prompt 11 · La sécurité (non négociable)
🚨 Écouter sur 127.0.0.1 ne protège de rien. N'importe quelle page web ouverte dans ton navigateur peut poster surlocalhost:3000. Si tes routes lancentclaudesans jeton, tu viens d'ouvrir un shell à distance sur ta machine.
Sécurise les routes qui lancent ou qui écrivent. Écouter sur 127.0.0.1 ne suffit pas : une page web tierce ouverte dans mon navigateur peut poster sur localhost:3000.
- Un jeton partagé, généré aléatoirement au démarrage (surchargeable par variable d'env), exigé en en-tête x-agentic-token, ou en ?token= pour le flux SSE. Sans lui : 401. Comparaison en timing-safe.
- Le jeton est injecté dans la page par un composant serveur : une origine tierce ne peut pas le lire.
- Le cwd ne vient JAMAIS du navigateur. Il est relu du registre disque à chaque appel. Un projet non enregistré ou absent du disque ne peut pas héberger de chat.
- Modèle et mode de permission en whitelist stricte.
- Jamais de --dangerously-skip-permissions.
⚡ Prompt 12 · La page projet et le chat
Crée la page /projects/<id> : git, sessions du projet, fichiers de mémoire (CLAUDE.md et compagnie), tâches, agents et skills visibles depuis ce dossier, activité par jour.
Et le chat, alimenté par le driver via SSE :
- un bouton démarrer / arrêter / interrompre (SIGINT coupe le tour en cours sans tuer la session) ;
- le choix du modèle et du mode de permission dans la whitelist ;
- les appels d'outils affichés en ligne, résumés (commande, fichier, pattern, url), pas en JSON brut ;
- le coût et la durée renvoyés par chaque événement result ;
- un commentaire SSE toutes les 20 s pour que la connexion ne soit pas coupée.
Rends le markdown des réponses correctement : un composant maison suffit, pas de librairie.
⚡ Prompt 13 · Bonus : déléguer à un sous-agent depuis le chat
Ajoute la possibilité de confier le message à un de mes sous-agents. Encore une fois : on n'écrit AUCUN runtime d'agent. On préfixe simplement le message d'une consigne d'appel de l'outil Task avec subagent_type: "<id de l'agent>", et le CLI fait le reste.
La liste des agents proposés vient du registre déjà lu au lot 3.
🧯 Les 12 murs que tu vas te prendre (et le contournement)
📌 Chaque ligne m'a coûté entre dix minutes et deux heures. Colle ce tableau à Claude Code au début de chaque lot, tu en évites la moitié d'avance.
| Symptôme | Cause réelle | Ce qu'il faut faire |
|---|---|---|
| create-next-app refuse de démarrer | Le dossier a un espace ou une majuscule dans son nom | Scaffolder dans un temp, déplacer, corriger le champ name du package.json |
| Les tokens affichés sont doublés | Les sous-agents (isSidechain: true) portent un usage déjà compté dans la session parente | Ignorer ces lignes au parsing |
| Chaque page met 20 secondes à charger | Le scan relit tous les transcripts à chaque requête | Mémo par fichier sur (mtime, taille) + cache stale-while-revalidate |
| Tous les projets fusionnent en une seule carte | Le dossier Projects est lui-même un repo git, la racine git remonte tout en haut | Borner la racine à racine/organisation/projet |
| Le registre de projets se vide d'un coup | Déduplication par préfixe de chemin | Dédupliquer sur égalité exacte uniquement |
| La moitié des skills n'apparaît pas | ~/.claude/skills contient des liens symboliques, isDirectory() renvoie false dessus | Filtrer sur isDirectory() OU isSymbolicLink() |
| Un plugin apparaît en double, deux versions | Le cache de plugins garde les anciennes versions côte à côte | Lire installed_plugins.json et ne suivre que les installPath actifs |
| Le CLI refuse de démarrer en stream-json | --output-format stream-json en mode -p exige --verbose | Ajouter --verbose |
| Le chat reste muet après « démarrer » | L'abonnement était rattaché à une session inexistante au chargement de la page | Faire vivre les abonnés par projet, pas par session |
| « Le design n'a pas changé » | Un npm run start laissé tourner sert un build figé | Vérifier le process parent, travailler en npm run dev |
| Une page déborde horizontalement | Colonne de grille sans minmax(0,1fr), enfant sans min-w-0 | Ajouter les deux, le contenu tronque au lieu de pousser |
| La page s'étire à l'infini, le bas du cockpit disparaît | Listes longues sans conteneur scrollable | h-[calc(100vh-Xrem)] + min-h-0 + overflow-y-auto interne |
✅ Le prompt de fin de lot
🧪 À coller après chaque lot. C'est ce qui fait la différence entre « il m'a dit que c'était fait » et « c'est fait ».
Avant de me dire que c'est terminé :
1. Lance `npm run build`. Il doit être vert.
2. Lance `npx eslint src --max-warnings=0`. Il doit être propre.
3. Lance le serveur de dev et vérifie que chaque route répond en 200.
4. Ouvre l'écran concerné et dis-moi, chiffre par chiffre, d'où vient chaque valeur affichée. Si un chiffre n'a pas de source disque, retire-le.
5. Coche les lignes correspondantes dans tasks/todo.md.
6. Ajoute dans tasks/lessons.md une ligne par mur rencontré : le symptôme, la cause réelle, le contournement. Une ligne, pas un paragraphe.
📓 Le fichier tasks/lessons.md est le vrai trésor du projet. Au bout d'une journée il contient trente lignes de pièges spécifiques à ta machine. Tu le colles au début d'une nouvelle session, et Claude Code ne refait plus les mêmes erreurs. C'est de là que vient le tableau ci-dessus.
🔭 La suite (ce que je n'ai pas encore branché)
- Le vrai quota d'abonnement : le flux stream-json émet des
rate_limit_eventavec la fenêtre de 5 h et la date de reset. C'est la seule source réelle du quota, impossible à dériver des transcripts. C'est ce qui manque à la jauge du dashboard. - Les hooks Claude Code vers une route
/api/events, pour ne plus rien reparser. - Un backend tmux derrière la même interface que le driver headless, pour rattacher une session au terminal.
- Des routines / cron, et un contrôle depuis le mobile.