P1 spine: monorepo, shared WS protocol, server daemon

- npm workspaces (shared / server / web), TS strict, project refs
- @arboretum/shared: multiplexed WS protocol (JSON control + binary
  output frames: 1B type + u32le channel), flow-control constants
  (ACK 256K, HIGH 384K, LOW 128K, lagging 2M), REST types
- git-arboretum server: Fastify 5 + node:sqlite (single native dep:
  node-pty prebuilt), token auth (sha256 at rest, HMAC cookie, global
  login rate limit + backoff), strict Origin check on /api and /ws,
  PtyManager (2MiB ring with monotonic offset, resync replay = reset +
  256KiB tail, pause/resume only when ALL interactive clients exceed
  HIGH, observers never throttle, lagging clients resync), WS gateway
  (attach/stdin/resize/ack, heartbeat 30s), SIGTERM→SIGKILL 5s grace
- CLI: arboretum [--port 7317] [--bind 127.0.0.1] — non-loopback bind
  requires an explicit safety flag
- Smoke-tested: login/401/403-origin/spawn bash/kill/grace-SIGKILL all
  green
This commit is contained in:
2026-06-11 22:04:09 +02:00
parent 903d5deeb5
commit 4768b606e4
25 changed files with 4467 additions and 474 deletions

View File

@@ -0,0 +1,34 @@
import { execFileSync } from 'node:child_process';
export interface SpawnSpec {
file: string;
args: string[];
env: NodeJS.ProcessEnv;
}
let cachedClaudeBin: string | null = null;
export function resolveClaudeBin(): string {
if (cachedClaudeBin) return cachedClaudeBin;
try {
cachedClaudeBin = execFileSync('which', ['claude'], { encoding: 'utf8' }).trim();
} catch {
throw new Error(
'Claude Code CLI not found in PATH. Install it first: https://code.claude.com/docs/en/quickstart',
);
}
return cachedClaudeBin;
}
/** Module volontairement abstrait : le plan B « BYO API key / Agent SDK » se brancherait ici. */
export function buildSpawnSpec(command: 'claude' | 'bash'): SpawnSpec {
const env: NodeJS.ProcessEnv = {
...process.env,
TERM: 'xterm-256color',
COLORTERM: 'truecolor',
};
if (command === 'bash') {
return { file: 'bash', args: ['--norc'], env };
}
return { file: resolveClaudeBin(), args: [], env };
}