CloudCLI (claudecodeui) & la V2 de PilotCLI
Enquête sur l'implémentation du chat Claude Code dans CloudCLI, inventaire de ce qui fait l'originalité de PilotCLI, et stratégie recommandée pour construire PilotCLI V2 sur cette base.
TL;DR
- Pourquoi leur chat marche et pas le nôtre : CloudCLI utilise le
Claude Agent SDK officiel (
query()de@anthropic-ai/claude-agent-sdk) qui renvoie des messages structurés (texte, tool use, thinking, permissions, resume, interrupt). PilotCLI scrape le buffer PTY avec des regex + résumés LLM : ~12 000 lignes fragiles dont ~1 000 uniquement pour déboguer le pipeline. Ce n'est pas un écart d'exécution, c'est un écart d'architecture. - Recommandation : ne pas rattraper — adopter. Stratégie hybride « upstream + plugins » : CloudCLI quasi-stock (fork de configuration minimal), fonctionnalités PilotCLI portées en plugins (web services + Caddy, docs hub), et le workflow markdown conservé tel quel côté projets (conventions + skills Claude Code, indépendant de l'UI).
- Points de vigilance : licence AGPL-3.0 (OK en usage privé),
système mono-utilisateur, plugin API jeune (slot
tabuniquement), localefr/déclarée mais vide.
1. CloudCLI en bref
Le repo siteboon/claudecodeui a été rebrandé CloudCLI
(@cloudcli-ai/cloudcli, Siteboon AI B.V.) avec une offre cloud commerciale à côté.
Projet très actif : v1.36.0, 36 releases, dernier commit la veille de cette enquête.
Licence AGPL-3.0-or-later.
| Frontend | React 18 + TypeScript + Vite, Tailwind, xterm.js (WebGL), CodeMirror, react-i18next (10 langues) |
|---|---|
| Backend | Node + Express, WebSocket natif (ws) multiplexé (/ws chat, /shell PTY, notifications, plugins) |
| Données | SQLite (better-sqlite3) comme simple index — la source de vérité reste les JSONL de ~/.claude/projects |
| Auth | JWT, mono-utilisateur strict (« This is a single-user system ») |
| Desktop / mobile | App Electron, PWA + notifications Web Push, UI mobile soignée |
| CLIs supportés | Claude Code (SDK), Codex (SDK), Cursor CLI, Gemini CLI, OpenCode (spawn + stream-json) |
2. La question clé : comment leur chat fonctionne
2.1 SDK structuré, pas de scraping
CloudCLI ne lance pas claude dans un terminal qu'il essaierait de lire. Le module
server/claude-sdk.js (~900 lignes) appelle query() du
Claude Agent SDK et itère sur un générateur asynchrone de messages typés :
import { query } from '@anthropic-ai/claude-agent-sdk';
const run = query({ prompt, options: { resume: sessionId, permissionMode, mcpServers, ... } });
for await (const message of run) { /* messages structurés */ }
Chaque événement est normalisé vers un format unique NormalizedMessage avec un champ
kind (text, tool_use, tool_result,
thinking, stream_delta, permission_request,
interactive_prompt, complete…). Le frontend fait un simple
switch sur kind et rend chaque tool avec un renderer dédié
(diff pour Edit/Write, todo list, plan, panneau de question interactive).
2.2 Les problèmes que nous n'avons jamais vraiment résolus… déjà résolus
| Problème | PilotCLI (Chat Mode) | CloudCLI |
|---|---|---|
| Savoir où en est le CLI | Regex sur le buffer (spinners braille, prompts hardcodés claude>) + fallback LLM, faux positifs chroniques (« blocked » monté à 120 s, détection des menus numérotés désactivée en commentaire : « too many false positives ») | Événements typés du SDK — l'état est un fait, pas une devinette |
| Contenu des messages | xterm.js caché hors écran dans le navigateur pour re-rendre le flux ANSI, extraction des 50 dernières lignes, résumé LLM (coût + latence à chaque changement) | Texte assistant natif, streaming token par token (stream_delta), thinking séparé |
| Envoyer une réponse / un choix | Mode « machine à écrire » (10 ms/caractère) + 5 fins de ligne forçables (\r, \n, \r\n, ESC O M, typewriter) exposées jusque dans l'UI — preuve qu'aucune ne marchait partout | canUseTool callback : la question arrive en JSON, la réponse repart en JSON (panneau AskUserQuestion, approbation d'outils avec « Allow & remember ») |
| Reprendre une session | Recréation de PTY + replay de buffer, sessions zombies après restart backend | resume: sessionId natif + historique lu des JSONL + re-attach WebSocket avec rejeu des événements manqués (seq) |
| Interrompre | Kill du PTY | queryInstance.interrupt() propre |
| Volume de code | ~12 000 lignes (dont debug logger 421 l. + DebugPanel 518 l. juste pour comprendre pourquoi ça se déclenche — ou pas) | ~900 lignes (claude-sdk.js) + providers/renderers, testés |
Notre propre code avait déjà tiré la conclusion : dev-cli-service (dev3/dev4,
sortie JSON structurée pollée) est né précisément pour contourner le scraping. CloudCLI est
cette même idée, poussée au bout avec le SDK officiel.
2.3 Et aussi
- Sessions gateway : ID applicatif stable en DB, mappé sur l'ID natif Claude au premier message — le front ne manipule jamais l'ID provider. Watcher chokidar sur
~/.claude/projects(+ équivalents Cursor/Codex/Gemini) pour la découverte en continu. - Terminal brut conservé : onglet Shell node-pty (c'est le seul endroit où le CLI est spawné directement, ex.
claude --resume <id>), PTY cachés 30 min avec replay pour la reconnexion. - Images : collées/déposées en base64, écrites dans
.tmp/images/du projet, chemins injectés dans le prompt. - Slash commands & skills : scan de
.claude/commands/projet + user, autocomplete avec ranking d'usage. - Multi-CLI par registry de providers : ajouter un 6ᵉ CLI est un pattern établi (sessions, synchronizer, models, auth, mcp, skills par provider).
3. Inventaire des fonctionnalités CloudCLI
- Chat multi-provider streaming (thinking, diffs, plan mode, subagents, budget tokens)
- Terminal shell intégré (xterm + node-pty, reconnexion)
- Explorateur de fichiers + éditeur CodeMirror (diff/merge, minimap), recherche ripgrep
- Panneau Git complet (20 endpoints : status, diff, commit, branches, push, message de commit généré par IA)
- Gestion MCP (CRUD par provider, scopes user/projet)
- TaskMaster (kanban, éditeur de PRD, parse-prd → tâches)
- Voice STT/TTS via proxy compatible OpenAI (Whisper, LocalAI, Groq…)
- PWA mobile + notifications Web Push, app desktop Electron
- Command palette Cmd+K (fichiers, sessions, commits, actions)
- Browser-use (automatisation Playwright exposée en MCP)
- API agent headless (API key + SSE) pour CI/automatisation
- Onboarding providers (login CLI in-app, détection URL OAuth), auto-update
- Système de plugins (voir §5)
4. Ce que PilotCLI a et que CloudCLI n'a pas
L'intuition de départ se confirme : la valeur originale de PilotCLI est dans le workflow et l'infrastructure de projet, pas dans le code de l'UI.
| Fonctionnalité PilotCLI | Nature | Portabilité vers une V2 CloudCLI |
|---|---|---|
Workflow markdown structuré — plan/ (phases, frontmatter Status), specs/, archive, génération de la page Progress | Conventions + scripts + skills Claude Code | Indépendant de l'UI — survit tel quel. Un plugin « tab » peut afficher plan/progress dans l'UI (gray-matter est déjà une dépendance de CloudCLI) |
Structure imposée + scaffolding — PILOTCLI.md généré, .pilotcli.json, bootstrap plan/specs/web + git | Service backend + conventions | Plugin ou CLI de scaffolding ; les conventions restent dans les projets |
| Web services managés — modèle dev|build, presets (vite, next, flutter-web, astro, node-api, docs), allocation ports/sous-domaines, exposition publique Caddy (*.kwe.to), builds persistés | Cœur backend (web-service-manager/runner + caddy-service) | Le vrai chantier V2 : plugin « tab » avec serveur dédié (l'API plugin supporte un process backend par plugin) ; le code TS actuel est largement réutilisable |
Docs par projet + hub — site Astro web/ par projet, service docs auto, hub multi-projets | Convention + service docs | Les sites Astro restent tels quels ; l'onglet Docs devient un plugin (iframe des sous-domaines docs) |
Pipeline screenshots — manifest typé, Playwright multi-device × profil, diff vs baseline, /validate-feature | Outillage projet (tests + skills) | Indépendant de l'UI — survit tel quel |
| WhatsApp — pilotage à distance (webhook Business API, boutons de choix, notifications) | Service backend | À réévaluer : le Web Push mobile de CloudCLI couvre une partie du besoin (notifications) ; le pilotage complet serait un plugin serveur. À décider en V2, pas bloquant |
| Multi-utilisateur + rôles, admin users | Backend + UI | Perdu — CloudCLI est mono-user câblé partout (DB sans user_id, état en Maps globales). Refonte structurelle si nécessaire un jour |
| Remote agents (terminaux sur machines distantes) | Backend + agent | Exclu de la V2 par décision — CloudCLI suppose tout en local ; alternative simple : une instance CloudCLI par machine |
| i18n hébreu (RTL), tags projets, timestamps anti-cache | Divers | Mineur ; CloudCLI a fr déclaré mais dossier fr/ vide — à fournir (ou contribuer upstream) |
Dans l'autre sens, CloudCLI apporte immédiatement ce que PilotCLI n'a pas ou a en fragile : chat SDK fiable, sessions/resume natifs, multi-CLI (Cursor, Codex, Gemini), panneau Git riche, gestion MCP, mobile/PWA + push, Electron, command palette, API headless.
5. Le système de plugins CloudCLI
Réel mais jeune. À connaître avant de tout miser dessus :
- Plugins installés depuis un repo git dans
~/.claude-code-ui/plugins/(clone,npm install --ignore-scripts, build, activation dans Settings). - Manifest :
name,displayName,entry, typereact|module,permissions[], et un seul slot :tab(un onglet du contenu principal). - L'entry exporte
mount(container, api)/unmount(); l'API fournit le contexte (thème, projet, session),onContextChange()etrpc(). - Serveur par plugin : un process Node isolé, proxifié en HTTP (
/api/plugins/:name/rpc/*) et WebSocket (/plugin-ws/:name) — c'est ce qui rend un plugin « web services + Caddy » réaliste. - Limites : pas de hooks sur le pipeline de chat, pas de routes arbitraires, pas d'événements custom. Starter officiel :
cloudcli-plugin-starter.
6. Recommandation : stratégie V2
Adopter CloudCLI comme socle, en « upstream + plugins » plutôt qu'en fork lourd. Réécrire un chat SDK de cette qualité dans PilotCLI coûterait plusieurs semaines pour rattraper un projet qui avance plus vite que nous ; forker lourdement nous ferait perdre le bénéfice de cette vélocité à chaque merge. La quasi-totalité de la valeur PilotCLI se transplante en plugins + conventions projet.
Pourquoi pas un fork profond ?
- Vélocité upstream : plusieurs releases/mois ; un fork divergent transforme chaque update en séance de merge douloureuse.
- AGPL-3.0 : usage privé self-hosted sans obligation ; mais si un jour la V2 est exposée à des tiers (beta-testeurs sur le réseau), l'obligation de publier les sources modifiées s'applique. Moins on modifie le cœur, moins c'est un sujet.
- Les frictions réelles d'un fork (mono-user câblé partout, branding dispersé, migrations DB impératives) ne valent la peine que si on a besoin de toucher le cœur — or nos besoins sont périphériques (onglets + services).
Plan proposé
| Phase | Contenu | Effort |
|---|---|---|
| V2.0 — POC | Installer CloudCLI stock sur le serveur, pointé sur ~/pilotcli-projects. Valider : chat Claude, terminal, git, mobile/PWA, plugin starter. Décision go/no-go. | 0.5–1 j |
| V2.1 — Plugin web-services | Plugin tab + serveur : porter web-service-manager/runner/caddy-service (modèle dev|build, presets, ports 30xxx, exposition kwe.to). C'est le seul gros morceau. | 3–5 j |
| V2.2 — Plugin docs/plan | Onglet Docs (iframe des sites web/ par projet + hub) et vue Plan/Progress (lecture plan/*.md via gray-matter). Le scaffolding devient un script/skill. | 2–3 j |
| V2.3 — Compléments | Locale fr/*.json (contribution upstream idéale), WhatsApp si toujours voulu (plugin serveur), purge PilotCLI v1 des fonctions migrées. | 2–4 j |
Pendant la transition, PilotCLI v1 continue de tourner (il gère les web services et les
sous-domaines) ; on le décommissionne fonction par fonction. Le workflow markdown
(plan/specs/web, screenshots, /validate-feature) ne bouge pas d'un pouce :
il vit dans les projets et dans les skills Claude Code, pas dans l'UI.
Risques
| Risque | Mitigation |
|---|---|
| API plugin jeune (slot unique, breaking changes possibles) | Pinner la version CloudCLI ; garder les plugins minces ; contribuer upstream les slots manquants si besoin |
| Mono-utilisateur | Acceptable aujourd'hui (usage solo) ; si besoin multi-user un jour : instances séparées ou contribution upstream |
| AGPL si exposition à des tiers | Rester proche du stock ; publier les plugins (ils sont à nous, licence au choix si non dérivés du cœur — à valider selon le couplage) |
| Pas de remote agents | Hors scope V2 (décision) ; fallback : une instance par machine |
Annexe — incident docs 502 (résolu)
Au passage, le site docs était bien en 502. Cause : au démarrage, le backend re-déclare les
routes Caddy des services publics (intention en DB) mais ne relance pas leurs process
serve (autoStart=false) → upstream mort → 502. Corrections :
docs kwenta (31100) redémarré ; le projet mvp n'avait aucun service docs
→ créé et démarré (ce site, 30603.kwe.to, autoStart activé) ; « Snake Game »
(31200) pointe sur un projet de test effacé (/tmp/snake-game), à purger.
Amélioration suggérée : au boot, ne register une route Caddy qu'après health-check de l'upstream,
ou auto-démarrer les services publics.