# HAPI CLI Codebase Overview ## Project Overview HAPI CLI (`hapi`) is a command-line tool that wraps Claude Code to enable remote control and session sharing via `hapi-server` (Telegram Bot + Mini App). It's part of a two-component system: 1. **hapi** (this project) - CLI wrapper for Claude Code 2. **hapi-server** - Public server (Socket.IO + REST + SQLite) + Telegram Mini App ## Code Style Preferences ### TypeScript Conventions - **Strict typing**: No untyped code ("I despise untyped code") - **Clean function signatures**: Explicit parameter and return types - **As little as possible classes** - **Comprehensive JSDoc comments**: Each file includes header comments explaining responsibilities. - **Import style**: Uses `@/` alias for src imports, e.g., `import { logger } from '@/ui/logger'` - **File extensions**: Uses `.ts` for TypeScript files - **Export style**: Named exports preferred, with occasional default exports for main functions ### DO NOT - Create stupid small functions / getters / setters - Excessive use of `if` statements - especially if you can avoid control flow changes with a better design - **NEVER import modules mid-code** - ALL imports must be at the top of the file ### Error Handling - Graceful error handling with proper error messages - Use of `try-catch` blocks with specific error logging - Abort controllers for cancellable operations - Careful handling of process lifecycle and cleanup ### Testing - Unit tests using Vitest - No mocking - tests make real API calls - Test files colocated with source files (`.test.ts`) - Descriptive test names and proper async handling ### Logging - All debugging through file logs to avoid disturbing Claude sessions - Console output only for user-facing messages - Special handling for large JSON objects with truncation ## Architecture & Key Components ### 1. API Module (`/src/api/`) Handles bot communication (direct connect; no end-to-end encryption). - **`api.ts`**: Main API client class for session management - **`apiSession.ts`**: WebSocket-based real-time session client with RPC support - **`auth.ts`**: Loads `CLI_API_TOKEN` from env - **`encryption.ts`**: Base64 helpers (no encryption) - **`types.ts`**: Zod schemas for type-safe API communication **Key Features:** - Socket.IO for real-time messaging - Optimistic concurrency control for state updates - RPC handler registration for remote procedure calls ### 2. Claude Integration (`/src/claude/`) Core Claude Code integration layer. - **`loop.ts`**: Main control loop managing interactive/remote modes - **`types.ts`**: Claude message type definitions with parsers - **`claudeSdk.ts`**: Direct SDK integration using `@anthropic-ai/claude-code` - **`interactive.ts`**: **LIKELY WILL BE DEPRECATED in favor of running through SDK** PTY-based interactive Claude sessions - **`watcher.ts`**: File system watcher for Claude session files (for interactive mode snooping) - **`mcp/startPermissionServer.ts`**: MCP (Model Context Protocol) permission server **Key Features:** - Dual mode operation: interactive (terminal) and remote (mobile control) - Session persistence and resumption - Real-time message streaming - Permission intercepting via MCP [Permission checking not implemented yet] ### 3. UI Module (`/src/ui/`) User interface components. - **`logger.ts`**: Centralized logging system with file output - **`qrcode.ts`**: QR code generation for mobile authentication - **`start.ts`**: Main application startup and orchestration **Key Features:** - Clean console UI with chalk styling - QR code display for easy mobile connection - Graceful mode switching between interactive and remote ### 4. Core Files - **`index.ts`**: CLI entry point with argument parsing - **`persistence.ts`**: Local storage for settings and keys - **`utils/time.ts`**: Exponential backoff utilities ## Data Flow 1. **Authentication**: - Use `CLI_API_TOKEN` to authenticate to `hapi-server` (REST + Socket.IO) 2. **Session Creation**: - Create/load session via `POST /cli/sessions` → Establish Socket.IO `/cli` connection 3. **Message Flow**: - Local mode: terminal/SDK → hapi CLI → hapi-server → Telegram Mini App 4. **Permission Handling**: - Claude requests permission → hapi CLI exposes RPC handlers → Mini App calls REST → hapi-server relays RPC to hapi CLI ## Key Design Decisions 1. **File-based logging**: Prevents interference with Claude's terminal UI 2. **Dual Claude integration**: Process spawning for interactive, SDK for remote 3. **No E2E encryption**: Use HTTPS/TLS for `hapi-server` deployments 4. **Session persistence**: Allows resuming sessions across restarts 5. **Optimistic concurrency**: Handles distributed state updates gracefully ## Security Considerations - `CLI_API_TOKEN` is a shared secret; treat it like a password. - No end-to-end encryption: use HTTPS/TLS for `hapi-server` deployments. - Session isolation through unique session IDs. ## Dependencies - Core: Node.js, TypeScript - Claude: `@anthropic-ai/claude-code` SDK - Networking: Socket.IO client, Axios - Terminal: node-pty, chalk, qrcode-terminal - Validation: Zod - Testing: Vitest # Running the Runner ## Starting the Runner ```bash # From the hapi CLI directory: hapi runner start # With custom bot URL (for local development): HAPI_API_URL=http://localhost:3006 CLI_API_TOKEN=your_token hapi runner start # Stop the runner: hapi runner stop # Check runner status: hapi runner status ``` ## Runner Logs - Runner logs are stored in `~/.hapi/logs/` (or `$HAPI_HOME/logs/`) - Named with format: `YYYY-MM-DD-HH-MM-SS-runner.log` # Session Forking `claude` and sdk behavior ## Commands Run ### Initial Session ```bash claude --print --output-format stream-json --verbose 'list files in this directory' ``` - Original Session ID: `aada10c6-9299-4c45-abc4-91db9c0f935d` - Created file: `~/.claude/projects/.../aada10c6-9299-4c45-abc4-91db9c0f935d.jsonl` ### Resume with --resume flag ```bash claude --print --output-format stream-json --verbose --resume aada10c6-9299-4c45-abc4-91db9c0f935d 'what file did we just see?' ``` - New Session ID: `1433467f-ff14-4292-b5b2-2aac77a808f0` - Created file: `~/.claude/projects/.../1433467f-ff14-4292-b5b2-2aac77a808f0.jsonl` ## Key Findings for --resume ### 1. Session File Behavior - Creates a NEW session file with NEW session ID - Original session file remains unchanged - Two separate files exist after resumption ### 2. History Preservation - The new session file contains the COMPLETE history from the original session - History is prefixed at the beginning of the new file - Includes a summary line at the very top ### 3. Session ID Rewriting - **CRITICAL FINDING**: All historical messages have their sessionId field UPDATED to the new session ID - Original messages from session `aada10c6-9299-4c45-abc4-91db9c0f935d` now show `sessionId: "1433467f-ff14-4292-b5b2-2aac77a808f0"` - This creates a unified session history under the new ID ### 4. Message Structure in New File ``` Line 1: Summary of previous conversation Lines 2-6: Complete history from original session (with updated session IDs) Lines 7-8: New messages from current interaction ``` ### 5. Context Preservation - Claude successfully maintains full context - Can answer questions about previous interactions - Behaves as if it's a continuous conversation ## Technical Details ### Original Session File Structure - Contains only messages from the original session - All messages have original session ID - Remains untouched after resume ### New Session File Structure After Resume ```json {"type":"summary","summary":"Listing directory files in current location","leafUuid":"..."} {"parentUuid":null,"sessionId":"1433467f-ff14-4292-b5b2-2aac77a808f0","message":{"role":"user","content":[{"type":"text","text":"list files in this directory"}]},...} // ... all historical messages with NEW session ID ... {"parentUuid":"...","sessionId":"1433467f-ff14-4292-b5b2-2aac77a808f0","message":{"role":"user","content":"what file did we just see?"},...} ``` ## Implications for handy-cli When using --resume: 1. Must handle new session ID in responses 2. Original session remains as historical record 3. All context preserved but under new session identity 4. Session ID in stream-json output will be the new one, not the resumed one