Devince
Development,  AI,  DevOps

codehelm — lokalny command center dla Claude Code

Date Published

Mechaniczna klawiatura z bursztynowym podświetleniem na ciemnym biurku, monitor w tle z rozmytym terminalem

Claude Code zapisuje każdą sesję jako append-only JSONL w ~/.claude/projects/<slug>/<sessionId>.jsonl. Brzmi niewinnie. Po miesiącu intensywnego użycia masz pięćdziesiąt projektów, setki sesji i 800 MB plików, których nigdy nie otworzysz ręcznie. Znalezienie "tej jednej sesji, w której debugowałem race'a w PTY"? grep -r przez całe drzewo katalogów.

CLI jest świetny przy jednej sesji. Przestaje być świetny przy dwudziestu.

Stąd codehelm. Jedno okno Chromium, wszystkie projekty, wszystkie sesje, prawdziwy shell w każdej zakładce. Lokalnie, bez chmury, bez dodatkowych kosztów API.

Co jest w środku

Stack jest świeży i bez fajerwerków. Next.js 15 (App Router), custom server.ts zamiast next start, node-pty dla terminali, chokidar do watchowania JSONL-i, xterm.js w przeglądarce, CodeMirror 6 do edycji CLAUDE.md. Całość binduje się na 127.0.0.1, losowy port z zakresu 49152–65535.

Główne widoki:

  • Sidebar z auto-discovery projektów, aliasami, favorites, grupowaniem po prefiksie ścieżki.
  • Session explorer: preview, rozmiar, liczba wiadomości, estymata kosztu per sesja.
  • Viewer ze streamingiem JSONL przez virtuoso, 9 typów eventów, diff rendering dla Edit/Write, outline i minimapa.
  • Terminal z limitem 16 PTY, persystentnych między reloadami, z badgem git brancha i quick-actions row.
  • Editor do CLAUDE.md z atomowym zapisem i diff-before-save.
  • Command palette pod Ctrl+K i overlay skrótów pod ?.

Ostatni kwartał dołożył scheduled prompts. Cron, który pisze prompt do długo żyjącej zakładki Claude Code bezpośrednio w PTY, tak jakbyś sam wcisnął Enter. Scheduler (croner) tyka, hybrid ready-check patrzy czy Claude wygląda na idle (marker regex na ring bufferze albo > 3 s od ostatniego stdoutu), executor bierze per-tab mutex i wpisuje ESC[200~ <prompt> ESC[201~ \r. Bracketed paste, żeby multiline prompt nie rozpadł się na osobne Entery.

Security model, bo inaczej się nie da

Ta aplikacja odpala shell z pełnymi uprawnieniami usera. Jak ktoś dojdzie do socketu, ma twoje pudełko. Co z tym robić? Defense-in-depth jako konieczność, nie buzzword:

  • Reachability127.0.0.1 only, losowy port, 32-bajtowy token z crypto.randomBytes, rotowany przy każdym starcie.
  • Auth — HttpOnly cookie, CSRF double-submit, timingSafeEqual wszędzie.
  • Host allowlist — tylko 127.0.0.1:PORT i localhost:PORT. Zabija DNS rebinding nawet jeśli cookie wycieknie.
  • CSP — per-request nonce, strict-dynamic, zero unsafe-inline w produkcji.
  • Path guardfs.realpath + prefix equality, fuzz-testowany 100 payloadami.
  • PTY — cap 16, rate limit 10/min, backpressure po 1 MB unacked.
  • Audit log — whitelist pól, mode 0600, nigdy content, nigdy token.

564 unit + integration testów, 7 specyfikacji e2e, pnpm audit --prod --audit-level=high zwraca zero.

Czego to nie robi

Terminal pozostaje jedyną powierzchnią, która gada z Claude. Cron wpisuje tekst do istniejącego PTY, jakby user wcisnął Enter, i tyle. Nie parsuje odpowiedzi, nie chainuje jobów, nie próbuje być nowym chat UI. Co tu dużo mówić, CLI zostaje źródłem prawdy.

Gdzie to jest

github.com/bartek-filipiuk/codehelm

Jedna instancja na hoście, jeden user, zaufana maszyna. 127.0.0.1 to granica zaufania. Potrzebujesz remote access? Tailscale przed tym albo wcale.