Rapport d'enquête · 2026-07-04 · siteboon/claudecodeui v1.36.0 analysé sur clone local

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 tab uniquement), locale fr/ 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.

FrontendReact 18 + TypeScript + Vite, Tailwind, xterm.js (WebGL), CodeMirror, react-i18next (10 langues)
BackendNode + Express, WebSocket natif (ws) multiplexé (/ws chat, /shell PTY, notifications, plugins)
DonnéesSQLite (better-sqlite3) comme simple index — la source de vérité reste les JSONL de ~/.claude/projects
AuthJWT, mono-utilisateur strict (« This is a single-user system »)
Desktop / mobileApp Electron, PWA + notifications Web Push, UI mobile soignée
CLIs supportésClaude 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èmePilotCLI (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

3. Inventaire des fonctionnalités CloudCLI

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é PilotCLINaturePortabilité 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 + scaffoldingPILOTCLI.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 :

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 ?

Plan proposé

PhaseContenuEffort
V2.0 — POCInstaller 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-servicesPlugin 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/planOnglet 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émentsLocale 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

RisqueMitigation
API plugin jeune (slot unique, breaking changes possibles)Pinner la version CloudCLI ; garder les plugins minces ; contribuer upstream les slots manquants si besoin
Mono-utilisateurAcceptable aujourd'hui (usage solo) ; si besoin multi-user un jour : instances séparées ou contribution upstream
AGPL si exposition à des tiersRester 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 agentsHors 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.