# CLI Architecture This document describes the Happy CLI (`packages/happy-cli`) and its daemon. The CLI is both an interactive tool and a background session manager that keeps machine state in sync with the server. ## System overview ```mermaid graph TB subgraph "Happy CLI" Entry[src/index.ts] API[API Client] Daemon[Daemon Process] Agents[Agent Runners] Persist[Persistence] end subgraph "~/.happy" Settings[settings.json] AccessKey[access.key] DaemonState[daemon.state.json] Logs[logs/] end subgraph Server HTTP[HTTP API] Socket[Socket.IO] end Entry --> API Entry --> Daemon Entry --> Agents Entry --> Persist Persist --> Settings & AccessKey & DaemonState & Logs API --> HTTP & Socket Daemon --> API Agents --> API ``` ## High-level layout - **Entry point:** `src/index.ts` parses subcommands and routes execution. - **API client:** `src/api` handles HTTP + Socket.IO, encryption, and RPC. - **Daemon:** `src/daemon` runs in the background, spawns sessions, and maintains machine state. - **Persistence/config:** `src/persistence.ts` + `src/configuration.ts` manage local state in `~/.happy`. - **Agents:** `src/claude`, `src/codex`, `src/gemini` provide provider-specific runners. ## CLI entry flow ```mermaid flowchart TD Start([happy ...]) --> Parse[Parse subcommand] Parse --> Doctor{doctor?} Parse --> Auth{auth?} Parse --> Connect{connect?} Parse --> Agent{codex/gemini?} Parse --> Default{default} Doctor --> RunDoctor[Run diagnostics] Auth --> RunAuth[Auth flow] Connect --> RunConnect[Connect machine] Agent --> Setup[authAndSetupMachineIfNeeded] Default --> Setup Setup --> Context{Background?} Context --> |Yes| StartDaemon[Start daemon] Context --> |No| RunAgent[Run agent directly] StartDaemon --> SpawnSession[Spawn session] ``` `src/index.ts` is the CLI router. It: - Parses subcommands (`doctor`, `auth`, `connect`, `codex`, `gemini`, and default run flows). - Ensures auth and machine setup when needed (`authAndSetupMachineIfNeeded`). - Starts the daemon or runs an agent directly based on subcommand/context. ## Local state and configuration ```mermaid graph LR subgraph "~/.happy" direction TB settings["settings.json
profile, onboarding"] access["access.key
encryption keys"] daemon["daemon.state.json
PID, port, version"] logs["logs/
CLI/daemon logs"] end subgraph "Environment Overrides" direction TB E1[HAPPY_HOME_DIR] E2[HAPPY_SERVER_URL] E3[HAPPY_WEBAPP_URL] E4[HAPPY_VARIANT] E5[HAPPY_EXPERIMENTAL] E6[HAPPY_DISABLE_CAFFEINATE] end E1 -.-> settings & access & daemon & logs ``` Local state lives under `~/.happy` (or `HAPPY_HOME_DIR`): - `settings.json`: onboarding and profile settings (validated/migrated). - `access.key`: local key material for encryption/auth. - `daemon.state.json`: daemon PID + control port + version. - `logs/`: CLI/daemon logs. Configuration lives in `src/configuration.ts`: - `HAPPY_SERVER_URL` and `HAPPY_WEBAPP_URL` override defaults. - `HAPPY_VARIANT`, `HAPPY_EXPERIMENTAL`, `HAPPY_DISABLE_CAFFEINATE` control behavior. ## API client architecture ```mermaid graph TB subgraph "API Clients" Base[ApiClient] Session[ApiSessionClient] Machine[ApiMachineClient] Encrypt[encryption.ts] end subgraph "Server" HTTP[HTTP API] Socket[Socket.IO] end Base --> |POST /v1/sessions| HTTP Base --> |POST /v1/machines| HTTP Session --> |session-scoped| Socket Machine --> |machine-scoped| Socket Encrypt --> Base & Session & Machine ``` ### HTTP `ApiClient` (`src/api/api.ts`) handles: - Session creation (`POST /v1/sessions`) with encrypted metadata/state. - Machine registration (`POST /v1/machines`) with encrypted metadata/daemon state. - Other CRUD actions through `ApiSessionClient` and `ApiMachineClient`. ### WebSocket ```mermaid graph LR subgraph "ApiSessionClient" S_In[Receive: update] S_Out[Emit: message, update-metadata,
update-state, session-alive, usage-report] end subgraph "ApiMachineClient" M_In[Receive: machine updates] M_Out[Emit: machine-alive,
update metadata/state] end Server((Socket.IO)) --> S_In & M_In S_Out & M_Out --> Server ``` `ApiSessionClient` (`src/api/apiSession.ts`) connects to Socket.IO as a **session-scoped** client: - Receives `update` events and decrypts message content. - Emits `message`, `update-metadata`, `update-state`, `session-alive`, and `usage-report`. `ApiMachineClient` (`src/api/apiMachine.ts`) connects as a **machine-scoped** client: - Sends `machine-alive` heartbeats. - Updates machine metadata/daemon state with optimistic concurrency. - Receives machine updates and merges them locally. ### Encryption ```mermaid flowchart LR subgraph "Client-side" Plain[Plaintext Data] Encrypt[encryption.ts] B64[Base64 Encoded] end Plain --> |encrypt| Encrypt --> B64 --> |send| Server[(Server)] Server --> |receive| B64 --> |decrypt| Encrypt --> Plain style Plain fill:#e8f5e9 style B64 fill:#fff3e0 ``` The CLI encrypts client content before it leaves the machine using `src/api/encryption.ts`. - Session metadata, agent state, messages, machine state, artifacts, and KV values are encrypted client-side. - On-wire encoding is base64; see `encryption.md`. ## Daemon architecture ```mermaid graph TB subgraph "Daemon Process" Control[Control Server
127.0.0.1:port] Sessions[Session Map] MachineClient[ApiMachineClient] end subgraph "Child Processes" S1[Session 1] S2[Session 2] S3[Session N] end CLI[CLI] --> |IPC| Control Control --> Sessions Sessions --> S1 & S2 & S3 MachineClient --> |heartbeat| Server[(Server)] MachineClient --> |state sync| Server ``` The daemon is a long-lived process responsible for running sessions in the background and maintaining machine presence. ### Lifecycle ```mermaid flowchart TD Start([startDaemon]) --> Validate[Validate version] Validate --> Lock[Acquire lock file] Lock --> Auth[Authenticate] Auth --> Register[Register machine with server] Register --> Control[Start control server] Control --> Track[Track child sessions] Track --> Sync[Sync daemon state to server] Sync --> Running([Running]) Running --> |SIGTERM| Shutdown[Cleanup & exit] ``` 1. `startDaemon()` validates the running version and acquires a lock file. 2. It authenticates and registers the machine with the server. 3. It starts a local **control server** for IPC. 4. It keeps a map of tracked child sessions and updates daemon state on the server. ### Control server (local IPC) ```mermaid sequenceDiagram participant CLI participant State as daemon.state.json participant Control as Control Server participant Daemon CLI->>State: Read port State-->>CLI: port: 12345 CLI->>Control: GET /list Control-->>CLI: [sessions...] CLI->>Control: POST /spawn-session Control->>Daemon: Spawn child process Daemon-->>Control: Session started Control-->>CLI: OK CLI->>Control: POST /stop Control->>Daemon: Shutdown ``` `startDaemonControlServer()` (`src/daemon/controlServer.ts`) runs an HTTP server on `127.0.0.1` and exposes: - `/list` (list active sessions) - `/stop-session` - `/spawn-session` - `/stop` (shutdown daemon) - `/session-started` (session self-report) The CLI talks to this server via `controlClient.ts`, using a port stored in `daemon.state.json`. ### Session spawning ```mermaid flowchart LR subgraph "Session Sources" CLI[CLI
foreground] Daemon[Daemon
background] Remote[Mobile/Web
via RPC] end subgraph "Session Process" Session[Agent Session] Handlers[RPC Handlers] end CLI --> Session Daemon --> Session Remote --> |spawn-session| Daemon --> Session Session --> Handlers subgraph "RPC Surface" Handlers --> Bash[bash] Handlers --> Files[file read/write] Handlers --> Search[ripgrep] Handlers --> Diff[difftastic] end ``` Sessions can be started by: - The CLI directly (foreground). - The daemon (background). - Remote requests over RPC (from mobile/web via machine connection). Daemon session spawning uses `registerCommonHandlers` to expose a controlled RPC surface (shell commands, file operations, search/diff helpers). ### Machine state ```mermaid graph TB subgraph "Machine Metadata (static)" M1[host] M2[platform] M3[CLI version] M4[paths] end subgraph "Daemon State (dynamic)" D1[pid] D2[httpPort] D3[startedAt] D4[shutdown info] end subgraph "Sync Targets" Server[(Server)] Local[daemon.state.json] end ApiMachine[ApiMachineClient] M1 & M2 & M3 & M4 --> ApiMachine D1 & D2 & D3 & D4 --> ApiMachine D1 & D2 & D3 & D4 --> Local ApiMachine --> Server ``` - **Machine metadata** is static info (host, platform, CLI version, paths). - **Daemon state** is dynamic (pid, httpPort, startedAt, shutdown info). The daemon updates these via `ApiMachineClient` and mirrors local state into `daemon.state.json` for control/diagnostics. ## RPC and tool bridge ```mermaid sequenceDiagram participant Mobile participant Server participant Daemon participant Session Mobile->>Server: RPC: spawn-session Server->>Daemon: Forward via Socket.IO Daemon->>Session: Spawn process Session-->>Daemon: Running Mobile->>Server: RPC: bash "ls -la" Server->>Session: Forward via Socket.IO Session->>Session: Execute command Session-->>Server: Result Server-->>Mobile: Result Note over Mobile,Session: All RPC flows through Socket.IO
No direct REST exposure ``` RPC is used to send commands over the Socket.IO connection: - Sessions register RPC handlers (e.g., `bash`, file read/write, `ripgrep`, `difftastic`). - The daemon registers a spawn-session handler so the server/mobile client can ask it to start a local session. This mechanism allows the server and mobile clients to drive local actions without exposing a broad REST surface. ## Implementation references - CLI entry: `packages/happy-cli/src/index.ts` - Daemon: `packages/happy-cli/src/daemon` - Control server/client: `packages/happy-cli/src/daemon/controlServer.ts`, `packages/happy-cli/src/daemon/controlClient.ts` - API clients: `packages/happy-cli/src/api` - Persistence: `packages/happy-cli/src/persistence.ts` - Config: `packages/happy-cli/src/configuration.ts`