docs: README propre pour visiteurs + .gitignore + sortir CLAUDE.md du suivi

- README réécrit (anglais) : setup, quick start, usage, accès Tailscale,
  flags CLI, sécurité, comparatif, section développement
- .gitignore réorganisé par sections (ajout *.tgz, coverage, .claude, .remember, CLAUDE.md)
- CLAUDE.md retiré du suivi git (reste local, non publié)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 11:19:51 +02:00
parent 4db593f1f3
commit a7da03cf0d
3 changed files with 140 additions and 91 deletions

25
.gitignore vendored
View File

@@ -1,12 +1,31 @@
# Dependencies
node_modules/ node_modules/
# Build output
dist/ dist/
*.log packages/server/public/
*.tgz
# Test / coverage
coverage/
# Local data & secrets
.env .env
.env.* .env.*
*.db *.db
*.db-* *.db-*
.DS_Store *.log
# Editors & OS
.idea/ .idea/
.vscode/
.DS_Store
# Claude Code / agent tooling (local only — do not commit)
CLAUDE.md
.claude/
.remember/
# Spike scratch output
spikes/**/tmp/ spikes/**/tmp/
spikes/**/captures/ spikes/**/captures/
packages/server/public/

View File

@@ -1,83 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Projet
Arboretum : un daemon Node.js unique (`npx git-arboretum`) qui sert un dashboard web pour piloter les worktrees git et les sessions Claude Code qui tournent dessus, depuis n'importe quel appareil. **MVP atteint** : P1 (colonne vertébrale : monorepo, protocole WS, daemon, front, tests, CI), P2 (découverte & reprise des sessions), P3 (worktrees multi-repo, états fins busy/waiting/idle via `claude-adapter`, dashboard worktree-first) et P4 (« supervision mobile & push » : commande WS `answer` pour répondre aux dialogues sans ouvrir le terminal, notifications Web Push via VAPID, PWA installable) sont livrés. Voir les `VERDICT.md` dans `spikes/` pour les décisions techniques actées avant implémentation.
Les commentaires du code et la doc interne sont en **français** ; le README et les messages utilisateur en anglais. Conserver cette convention.
## Commandes
```bash
npm run build # build shared → server → web (l'ordre compte, voir ci-dessous)
npm run typecheck # tsc -b shared + server UNIQUEMENT (le web se typecheck via vue-tsc dans son build)
npm test # vitest run sur packages/*/test/**/*.test.ts
npx vitest run packages/server/test/ring-buffer.test.ts # un seul fichier
npx vitest run -t "flow control" # par nom de test
npm run dev:server # daemon en watch (tsc -b --watch + node --watch)
npm run dev:web # Vite ; proxifie /api et /ws vers le daemon sur :7317 (cf. note Origin)
npm run pack # build + npm pack du tarball git-arboretum
node packages/server/scripts/acceptance-p1.mjs # acceptation E2E P1 (daemon réel + client WS réel)
node packages/server/scripts/acceptance-p2.mjs # acceptation E2E P2 (découverte/reprise : faux ~/.claude + faux binaire claude)
node packages/server/scripts/acceptance-p3.mjs # acceptation E2E P3 (worktrees : repo git tmp + hook + corrélation session)
node packages/server/scripts/acceptance-p4.mjs # acceptation E2E P4 (push : VAPID/subscribe + commande WS answer)
```
L'acceptation exige `npm run build` au préalable (elle lance `dist/index.js`) et utilise la commande `bash` plutôt que `claude` pour ne pas consommer de quota.
**Node >= 22.16 est requis** (pas seulement recommandé) : la persistance utilise `node:sqlite` (`DatabaseSync`), natif et stable seulement à partir de cette version. `.nvmrc` = 22.
## Monorepo (npm workspaces)
- **`packages/shared`** (`@arboretum/shared`) — source unique de vérité du protocole WS (`protocol.ts`) et des types REST (`api.ts`), importée par le serveur ET le front. C'est une **dépendance runtime non publiée** : la CI pack les deux tarballs (`@arboretum/shared` + `git-arboretum`) et les installe ensemble.
- **`packages/server`** (`git-arboretum`) — le daemon Fastify, **le seul paquet publié sur npm** (bin `arboretum`). Embarque la SPA buildée dans `public/` via le hook `prepack` (`scripts/copy-web.mjs`).
- **`packages/web`** (`@arboretum/web`) — SPA Vue 3 (Pinia, vue-router, xterm.js, Tailwind 4), privée.
Ordre de build imposé : `shared` se construit en premier (projets TS `composite` avec references) ; le front doit être buildé avant `prepack` du serveur pour être embarqué.
## Architecture serveur
`buildApp()` (`packages/server/src/app.ts`) câble tout : `AuthService`, `LoginRateLimiter`, `PtyManager`, les routes REST, la gateway WS, et le service statique de la SPA. `index.ts` est le bin (parse config, ouvre la db, écoute, gère le bootstrap token et le drain au SIGTERM/SIGINT).
### Modèle de sécurité (central — ne pas affaiblir)
Un terminal web est de l'exécution de code à distance par conception. Les garde-fous sont structurants :
- Bind `127.0.0.1` par défaut ; `config.ts` **refuse** tout bind non-loopback sans `--i-know-this-exposes-a-terminal`. Accès distant recommandé via Tailscale Serve.
- Un hook `preValidation` global authentifie **toute** requête `/api/**` et `/ws`, et applique un **check Origin strict** quand l'en-tête est présent (anti cross-site WS hijacking — le cookie `SameSite=Strict` ne couvre pas les upgrades). Les routes statiques sont publiques ; les routes publiques explicites portent `config: { public: true }` (ex. login).
- Tokens stockés **hashés** (sha256), comparaison en temps constant. Le bootstrap token n'est affiché qu'une fois au premier démarrage. Le cookie de session est un payload signé HMAC. Le rate-limit du login est **global** (pas par IP : derrière Tailscale tout arrive de 127.0.0.1) avec backoff exponentiel.
### Protocole WebSocket (`packages/shared/src/protocol.ts`)
Une **seule connexion multiplexée par client**, plusieurs canaux (channels) :
- Contrôle = frames **texte JSON** ; sortie terminal = frames **binaires** `[type u8][channel u32le][payload]`. Un chunk PTY peut couper un caractère UTF-8 en frontière de frame → le décodage est délégué à `xterm.write(Uint8Array)`, jamais au transport.
- **Flow control par watermarks** (`FLOW`). Invariant anti-deadlock à préserver absolument : `ACK_EVERY_BYTES <= LOW_WATERMARK`. Le PTY n'est mis en pause que quand **tous** les clients interactifs dépassent `HIGH` ; les observers ne freinent jamais le flux. Un client trop en retard (`LAGGING_BYTES`) est coupé puis resynchronisé.
- Handshake `hello`/`hello_ok` négocie `PROTOCOL_VERSION`. `parseClientMessage` valide/sanitise tout message entrant (bornes sur dims, longueurs) — toute nouvelle commande client doit y être validée.
### PtyManager (`packages/server/src/core/pty-manager.ts`)
Lance `claude` (ou `bash`) dans node-pty. Sessions vivantes en mémoire (`Map`) ; l'historique est persisté dans la table `sessions` (sqlite). Chaque session a un `RingBuffer` (2 Mo) ; à l'attach, le client reçoit un **resync** = reset terminal + replay de la queue du ring (`REPLAY_TAIL_BYTES`). Outil mono-utilisateur : tout client interactif peut écrire, les observers sont read-only. `kill` envoie SIGTERM puis SIGKILL après un délai de grâce.
### claude-launcher (`packages/server/src/core/claude-launcher.ts`)
Couture volontairement abstraite : Arboretum enveloppe le **CLI `claude` interactif** dans un PTY (pas l'Agent SDK, pas le mode headless). La commande `bash` sert aux tests sans quota. C'est ici que se brancherait un éventuel plan B.
### Détails fastify à connaître
- La route `/ws` est enregistrée **après** le plugin `@fastify/websocket` dans un contexte encapsulé (`app.register(async (scoped) => ...)`) ; sinon le handler reçoit la signature REST `(request, reply)` au lieu de `(socket, req)`.
- `notFoundHandler` renvoie l'`index.html` de la SPA pour tout ce qui n'est pas `/api/` ou `/ws` (routing client-side).
### Base de données (`packages/server/src/db/index.ts`)
`node:sqlite` en mode WAL. Migrations idempotentes versionnées dans le tableau `MIGRATIONS` + table `schema_migrations`. Tables : `settings`, `auth_tokens`, `sessions`.
## Architecture web
SPA Vue 3. Un **singleton `wsClient`** (`src/lib/ws-client.ts`) gère l'unique connexion : handshake, reconnexion avec backoff, ré-attache des terminaux ouverts, corrélation FIFO des `attached`, et le **flow control par ACK côté client** (compteur d'octets réellement traités par xterm, remis à zéro à chaque resync, avec un ACK traînant débouncé anti-deadlock). En dev, `vite.config.ts` proxifie `/api` et `/ws` vers le daemon en **réécrivant l'en-tête Origin** vers celle du daemon (sinon le check Origin strict rejette l'origine de Vite). Le garde de routeur vérifie la session via `GET /api/v1/auth/me`.
## Spikes & roadmap
Les `spikes/sN-*/VERDICT.md` documentent des décisions qui contraignent l'implémentation à venir, notamment :
- **`--resume` doit toujours s'exécuter dans le cwd d'origine** de la session (lu dans le JSONL).
- La **vivacité** d'une session se déduit de `pid` + `procStart`, **jamais** de la présence du fichier registre.
- La distinction d'états fins (busy / waiting / idle) du `claude-adapter` (P3-B) nécessite une lecture d'écran via `@xterm/headless` (le strip ANSI naïf mange les espaces) + le registre `~/.claude/sessions`.
Le `SessionRuntimeStatus` du protocole reste `starting`/`running`/`exited` ; les états fins P3-B (`activity`, `waitingFor`, `dialog`) sont des champs **additifs optionnels** de `SessionSummary`, remplis par le `claude-adapter` pour les sessions claude managées (null pour bash / découvertes).
**P4 (supervision mobile)** s'appuie sur ces champs : la commande WS `answer {channel, action, optionN?}` (validée dans `parseClientMessage` ET `pty-manager.answer` contre le dialogue courant) traduit une intention en keystrokes PTY ; `PushService` (clés VAPID en `settings`, abonnements liés au `tokenId` dans `push_subscriptions`) notifie sur le **front montant** vers `waiting` (débouncé). Côté web : `public/sw.js` (service worker push écrit à la main — **pas** de `vite-plugin-pwa`, trop lourd pour un SW push-only) + `public/manifest.webmanifest` (PWA installable). Web Push exige HTTPS (Tailscale Serve) ; sur iOS, la PWA doit être installée.

123
README.md
View File

@@ -4,6 +4,8 @@
**Status: MVP.** The worktree-first dashboard, session discovery & resume, multi-repo worktree lifecycle, live session states, the web terminal, and mobile supervision (installable PWA, Web Push when a session needs you, approve/deny without opening a terminal) are implemented and tested. **Status: MVP.** The worktree-first dashboard, session discovery & resume, multi-repo worktree lifecycle, live session states, the web terminal, and mobile supervision (installable PWA, Web Push when a session needs you, approve/deny without opening a terminal) are implemented and tested.
---
## The problem ## The problem
Working with AI coding agents changed how we use git: one feature = one worktree = one Claude Code session, several of them in parallel. But the tooling didn't follow: Working with AI coding agents changed how we use git: one feature = one worktree = one Claude Code session, several of them in parallel. But the tooling didn't follow:
@@ -20,7 +22,101 @@ A single Node.js daemon you run on your dev machine (`npx git-arboretum`), servi
- **Full worktree lifecycle** — create (with per-repo post-create hooks: `npm ci`, copy `.env`…), adopt worktrees created by hand, delete with guardrails, prune orphans. - **Full worktree lifecycle** — create (with per-repo post-create hooks: `npm ci`, copy `.env`…), adopt worktrees created by hand, delete with guardrails, prune orphans.
- **Session discovery & resume** — sessions you launched in your own terminal show up automatically; resume dead ones, observe or fork live ones. Never corrupts a live session. - **Session discovery & resume** — sessions you launched in your own terminal show up automatically; resume dead ones, observe or fork live ones. Never corrupts a live session.
- **Web terminal** — full xterm.js terminal to every managed session, surviving browser disconnects. - **Web terminal** — full xterm.js terminal to every managed session, surviving browser disconnects.
- **Supervision from your phone** — installable PWA with push notifications when a session needs you; approve/deny a prompt without opening a terminal. *(Web Push needs HTTPS — use [Tailscale Serve](docs/tailscale.md); on iOS, install the app to your home screen first.)* - **Supervision from your phone** — installable PWA with push notifications when a session needs you; approve/deny a prompt without opening a terminal.
---
## Requirements
- **Node.js ≥ 22.16** — required, not just recommended. Arboretum persists state with `node:sqlite` (`DatabaseSync`), which is native and stable only from this version. (`.nvmrc` pins `22`.)
- **The `claude` CLI** on your `PATH` if you want Arboretum to launch and manage Claude Code sessions. Arboretum wraps the interactive CLI you already use — install and authenticate it as usual.
- A **git** repository (or several) you want to manage.
## Quick start
### Run it (recommended)
```bash
npx git-arboretum
```
On first start, Arboretum prints a one-time **access token** and the URL to open:
```
┌──────────────────────────────────────────────────────────────────┐
│ First start — your access token (shown once, store it safely): │
└──────────────────────────────────────────────────────────────────┘
<your-token-here>
Login at: http://127.0.0.1:7317/
```
Open the URL, paste the token to log in, and you're in. The token is stored **hashed** — it is shown only once, so save it somewhere safe (a password manager). You can manage tokens later from **Settings**.
### Run from source
```bash
git clone https://git.lidge.fr/johanleroy/arboretum.git
cd arboretum
nvm use # or ensure Node ≥ 22.16
npm install
npm run build # builds shared → server → web (order matters)
node packages/server/dist/index.js
```
## Using Arboretum
1. **Add a repository.** From the dashboard, register a local git repo by its path. Optionally configure **post-create hooks** (e.g. `npm ci`, `cp ../.env .env`) that run automatically every time you create a new worktree for that repo.
2. **Create or adopt worktrees.** Spin up a new worktree + branch in one click (hooks run for you), or adopt a worktree you created by hand. Each worktree shows its branch, ahead/behind, and dirty-file count.
3. **Start or resume a session.** Launch a Claude Code session on a worktree, or resume one that was started in your terminal — Arboretum discovers existing sessions automatically and always resumes them in their original working directory.
4. **Watch the live states.** Each session reports whether it's *busy*, *waiting for your input*, or *idle*. Open the **web terminal** to interact directly; it survives browser disconnects (closing the tab does not kill the session).
5. **Supervise from your phone.** Install the PWA, and when a session flips to *waiting* you get a push notification. Approve or deny the prompt straight from the notification UI — no terminal required.
## Remote access from your phone
Arboretum binds to `127.0.0.1` by default and **refuses** to bind to a non-loopback address without an explicit override. The recommended (and safe) way to reach it from other devices is **[Tailscale Serve](https://tailscale.com/kb/1242/tailscale-serve)** — valid HTTPS, tailnet identity, no open ports:
```bash
# Expose the local daemon over HTTPS inside your tailnet
tailscale serve --bg 7317
```
Then start Arboretum allowing your tailnet origin (the strict Origin check needs to know about it):
```bash
npx git-arboretum --allow-origin https://<machine>.<tailnet>.ts.net
```
Open `https://<machine>.<tailnet>.ts.net` from any device on your tailnet. **Web Push requires HTTPS**, so Tailscale Serve (or another HTTPS front) is also what enables mobile notifications. On **iOS**, install the app to your home screen first, then allow notifications.
> ⚠️ A web terminal is remote code execution **by design**. Never expose Arboretum directly to the public internet.
## Configuration
All options are CLI flags:
| Flag | Default | Description |
|---|---|---|
| `--port <n>` | `7317` | Port to listen on. |
| `--bind <addr>` | `127.0.0.1` | Bind address. Non-loopback is refused unless `--i-know-this-exposes-a-terminal` is set. |
| `--allow-origin <url>` | — | Additional allowed `Origin` (repeatable). Needed for Tailscale/HTTPS access. |
| `--db <path>` | `<data>/arboretum.db` | SQLite database path. |
| `--vapid-contact <mailto/url>` | `mailto:arboretum@localhost` | VAPID contact subject for Web Push. |
| `--print-token` | `false` | Hint about token re-printing (tokens are hashed and cannot be re-shown). |
| `--i-know-this-exposes-a-terminal` | `false` | Acknowledge binding to a non-loopback address. **Avoid** — prefer Tailscale Serve. |
State (the SQLite database) lives in `$XDG_DATA_HOME/arboretum` (default `~/.local/share/arboretum`).
## Security model
A web terminal is remote code execution *by design*. Arboretum's guardrails are structural:
- Binds to `127.0.0.1` by default; refuses non-loopback binds without an explicit flag.
- Authenticates **every** `/api/**` request **and** every `/ws` upgrade with revocable tokens, and applies a **strict `Origin` check** (the `SameSite=Strict` cookie does not cover WebSocket upgrades — this is the anti cross-site hijacking guard).
- Tokens are stored **hashed** (sha256) and compared in constant time; the bootstrap token is shown only once. The session cookie is an HMAC-signed payload. Login is rate-limited with exponential backoff.
The recommended way to reach Arboretum from other devices is Tailscale Serve (valid HTTPS, tailnet identity, no open ports). Never expose it directly to the internet.
## What makes it different ## What makes it different
@@ -35,14 +131,31 @@ A single Node.js daemon you run on your dev machine (`npx git-arboretum`), servi
Anthropic's Remote Control is great at piloting *one* session from your phone. Arboretum is the layer it doesn't provide: the consolidated, self-hosted board of all your worktrees and sessions across all your repos. Anthropic's Remote Control is great at piloting *one* session from your phone. Arboretum is the layer it doesn't provide: the consolidated, self-hosted board of all your worktrees and sessions across all your repos.
## Security model
A web terminal is remote code execution *by design*. Arboretum binds to `127.0.0.1` by default, authenticates every request **and** every WebSocket upgrade with revocable tokens, and strictly checks the `Origin` header. The recommended way to reach it from other devices is [Tailscale Serve](docs/tailscale.md) (valid HTTPS, tailnet identity, no open ports). Never expose it directly to the internet.
## A note on Claude usage ## A note on Claude usage
Arboretum wraps the **interactive** Claude Code CLI in a PTY — the same thing you run in your terminal, displayed in your browser. It does not use the Agent SDK or headless mode. Anthropic's usage policies around programmatic use may evolve; Arboretum will track CLI releases and document any impact transparently. Arboretum wraps the **interactive** Claude Code CLI in a PTY — the same thing you run in your terminal, displayed in your browser. It does not use the Agent SDK or headless mode. Anthropic's usage policies around programmatic use may evolve; Arboretum will track CLI releases and document any impact transparently.
## Development
Arboretum is an npm-workspaces monorepo: `@arboretum/shared` (WS/REST protocol, source of truth), `git-arboretum` (the Fastify daemon, the published package), and `@arboretum/web` (the Vue 3 SPA).
```bash
npm run build # build shared → server → web (order matters)
npm run typecheck # tsc -b shared + server
npm test # vitest across packages
npm run dev:server # daemon in watch mode
npm run dev:web # Vite dev server (proxies /api and /ws to the daemon on :7317)
```
End-to-end acceptance scripts (run `npm run build` first):
```bash
node packages/server/scripts/acceptance-p1.mjs # core: daemon + real WS client
node packages/server/scripts/acceptance-p2.mjs # session discovery & resume
node packages/server/scripts/acceptance-p3.mjs # worktrees & session correlation
node packages/server/scripts/acceptance-p4.mjs # Web Push + WS `answer` command
```
## License ## License
MIT — see [LICENSE](LICENSE). MIT — see [LICENSE](LICENSE).