Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Status
Pre-implementation. The repo currently contains only the spec — DOCS/MAIN.md (spacesh tech spec v0.1, in Russian). No source code, no Cargo.toml, no git history yet. Read DOCS/MAIN.md first; it is the single source of truth for what to build and why. When writing code, follow the crate layout, protocol, and milestones it fixes.
What spacesh is
A terminal-workspace for running multiple AI agents (Claude Code, Codex, Gemini, opencode, shell) in parallel on macOS. Architecture: a backend daemon (spaceshd) owns the live PTY sessions; the Tauri GUI and the spacesh CLI are thin clients that talk to it over one Unix-domain socket (~/.spacesh/sock). The daemon outliving the GUI is the core design bet — killing/updating the GUI must not kill a running agent.
Architecture invariants
These are load-bearing decisions from the spec — do not violate them when implementing:
- Daemon is the single source of truth. GUI and CLI hold no state; they only send commands and subscribe to events. A click in the GUI and
spacesh focus s_8f3from a script are the samefocuscommand — never duplicate operational logic between GUI and CLI. - One socket, one protocol. Length-prefixed (
u32BE + payload) JSON frames over async UDS (tokio). Envelope has three kinds:req(client→daemon, correlated byid),res(daemon→client, byid),evt(daemon→subscribers, push, no id). Serialization is isolated inspacesh-protoso JSON→MessagePack is a localized swap. - Hybrid terminal parsing. One PTY byte stream fans out to: (1) GUI
outputevent → xterm.js renders; (2)alacritty_terminalgrid in the daemon = authoritative screen model, used for status detection, scrollback search, OSC 133 last-command extraction, and reattach snapshots. The grid IS the snapshot source — no separate ring buffer. Display (xterm.js) and analysis (alacritty grid) are deliberately split. - Reattach via snapshot. On
attach, daemon serializes the current grid → ANSI dump → GUI writes it into a fresh xterm.js instance for instant repaint, then liveoutputfollows. This is how the daemon architecture solves screen restoration. - Status is pushed, not guessed. State arrives as a
set_statecommand (from agent hooks callingspacesh notify, OSC 133 for shells, pattern-matching fallback last). States:work | wait | done | error | idle.$SPACESH_SURFACE_IDis injected into each panel's env at spawn so hooks know which surface they report on. - Extend through the bus. New features = new commands/events/subscribers (e.g. Telegram/MAX notifications are a subscriber inside
spaceshdlistening tostate/exit). The spine does not move.
Planned code structure
Cargo workspace + Tauri app (per spec §10):
crates/
spacesh-proto/ # protocol types, serde, length-prefix framing — shared by all
spacesh-core/ # workspace/split model, alacritty_terminal grid, status engine — NO I/O, unit-testable
spacesh-pty/ # PTY spawn/io (portable-pty), reader-loop, batching, resize, backpressure
spaceshd/ # daemon: owns sessions, hosts socket, event fan-out, layout persist, launchd, notification subscribers
spacesh-cli/ # thin socket client (clap)
app/
src-tauri/ # Rust: socket client + bridge into webview (tauri 2)
src/ # React/TS front (TerminalView, LayoutEngine, Sidebar, EventCenter, Wizard, Settings, socketBridge)
spacesh-core is deliberately I/O-free so the domain model and status engine are unit-testable in isolation.
Build / run (once the workspace exists)
No build files exist yet. After scaffolding, the conventional commands will be:
cargo build # build all crates
cargo test # run all Rust tests
cargo test -p spacesh-core # test one crate
cargo test -p spacesh-core <name># run a single test by name filter
cargo run -p spaceshd # start the daemon
cargo run -p spacesh-cli -- status --json
cd app && npm install && npm run tauri dev # GUI dev (Tauri 2)
Verify the actual scripts against Cargo.toml / app/package.json once they are created — do not assume.
Performance budgets (enforce in PTY/render paths)
- Keypress → echo: < 16 ms under normal load.
- PTY output batching: coalesce ~4–8 ms or ~16 KB, whichever first — never emit
outputper byte (a 50k-line build log must not yield 50k events). - Backpressure: bounded per-subscriber channel; on overflow drop/coalesce frames for that client, but always keep the authoritative daemon grid up to date.
- Reattach repaint: < 100 ms on a typical grid.
Implementation order (spec §12)
M0 live terminal through socket (vertical slice) → M1 persistence + reattach + launchd → M2 layouts/workspaces/presets → M3 statuses (hooks, OSC 133, fallback, notifications UI) → M4 CLI → M5 features (zoom, Telegram/MAX notifications, scrollback search, CodeMirror 6 diff view) → M6 (post-v1) remote via SSH-tunneled socket.
Explicitly out of v1 scope
Token/limit accounting, legal/auth layer, embedded browser, full Monaco-class editor, agent-command guardrails. "Modifications" = external notifications (Telegram + MAX) only. Don't build these unless the scope decision is revisited.
Conventions
Per the user's global rules: English for all code, comments, and docs; camelCase vars/functions, PascalCase types, snake_case files/dirs, UPPER_CASE env vars. No hard-coded values — use env/config (~/.spacesh/config.toml); tokens via env:VAR references, never plaintext. Spec prose and the docs in DOCS/ are Russian; keep that, but code stays English.