JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 331
  • Score
    100M100P100Q117826F
  • License Elastic-2.0

PocketCoder host daemon - connects your local machine to PocketCoder cloud

Package Exports

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@pocketcoder/host) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    PocketCoder Daemon

    The PocketCoder Daemon is a local Node.js process that acts as a bridge between Claude Code and the mobile PWA client. It manages Claude Code execution, file synchronization, and real-time communication with the cloud relay server.

    Table of Contents


    Overview

    flowchart LR
        subgraph Internet
            Client["Client<br/>(PWA)"]
            Relay["Relay<br/>(Fly.io)"]
        end
    
        subgraph Local
            Daemon["Daemon<br/>(Local)"]
            Claude["Claude<br/>Code"]
        end
    
        subgraph Cloud
            Supabase["Supabase<br/>(Auth/DB)"]
        end
    
        Client <-->|WebSocket| Relay
        Relay <-->|WebSocket| Daemon
        Relay -->|Auth| Supabase
        Daemon -->|Spawn| Claude

    Key Responsibilities:

    • Claude Code Management: Spawning and managing Claude Code CLI processes
    • Filesystem Operations: Reading, writing, and watching project files
    • Shell Commands: Executing shell commands with streaming output
    • Git Operations: Status, branching, staging, committing, and pushing
    • Relay Communication: Maintaining WebSocket connection with the cloud relay
    • Local Storage: Persisting projects, command history, and auth tokens in SQLite

    Tech Stack

    Component Library Purpose
    Runtime Node.js 24 LTS JavaScript runtime
    Language TypeScript (strict) Type-safe development
    WebSocket ws Relay connection
    Database better-sqlite3 Local persistence
    File Watcher chokidar Filesystem change detection
    CLI Framework commander Command-line interface
    Process Spawning execa Claude Code & shell commands
    HTTP Server fastify Control plane API
    Testing vitest Unit and integration tests
    Build tsc TypeScript compilation

    Installation & Usage

    Prerequisites

    • Node.js 24 LTS or later
    • Claude Code CLI installed and configured
    • Git (for git operations)

    Installation

    # Install globally
    npm install -g pocketcoder
    
    # Or run without install
    npx pocketcoder start

    Basic Usage

    # Login with GitHub (opens browser)
    pocketcoder login
    
    # Start the daemon (runs in background)
    pocketcoder start
    
    # Start in foreground (for debugging)
    pocketcoder start --foreground
    
    # Register a project
    pocketcoder add /path/to/project
    pocketcoder add .  # Current directory
    
    # List registered projects
    pocketcoder projects
    
    # Check daemon status
    pocketcoder status
    
    # View logs
    pocketcoder logs --tail 100 -f
    
    # Stop the daemon
    pocketcoder stop
    
    # Remove a project
    pocketcoder remove <projectId>
    
    # Set machine label
    pocketcoder machine label "Work Laptop"

    Architecture

    graph TB
        subgraph "Local Machine"
            CLI["CLI Commands<br/>(pocketcoder)"]
            CP["Control Plane HTTP<br/>:17333"]
            Daemon["Daemon Core"]
    
            subgraph "Services"
                CR["Claude Runner"]
                CMD["Command Runner"]
                FW["File Watcher"]
                GIT["Git Service"]
                MQ["Message Queue"]
            end
    
            subgraph "Data Layer"
                DB["SQLite Database<br/>(daemon.db)"]
                AUTH["Auth Storage<br/>(auth.json)"]
            end
    
            CLI -->|HTTP| CP
            CP -->|Control| Daemon
            Daemon -->|Spawn| CR
            Daemon -->|Execute| CMD
            Daemon -->|Watch| FW
            Daemon -->|Exec| GIT
            Daemon -->|Queue| MQ
            CR -->|Stream Events| MQ
            Daemon -->|Read/Write| DB
            Daemon -->|Load/Save| AUTH
        end
    
        subgraph "Cloud"
            Relay["Relay Server<br/>WebSocket"]
        end
    
        Daemon <-->|WebSocket| Relay

    Directory Structure

    apps/host/
    ├── src/
    │   ├── index.ts              # Entry point
    │   ├── daemon.ts             # Main startup orchestration
    │   ├── cli/                  # CLI command handlers
    │   │   ├── index.ts
    │   │   ├── start.ts
    │   │   ├── stop.ts
    │   │   ├── add.ts
    │   │   └── ...
    │   ├── relay/                # Relay communication
    │   │   ├── websocket-client.ts
    │   │   ├── message-router.ts
    │   │   ├── relay-request.ts
    │   │   └── messages/
    │   │       ├── daemon-register.ts
    │   │       └── ...
    │   ├── handlers/             # Message handlers
    │   │   ├── fs-list.ts
    │   │   ├── fs-read.ts
    │   │   ├── fs-write.ts
    │   │   ├── git-status.ts
    │   │   ├── command-exec.ts
    │   │   ├── agent-session-new.ts
    │   │   └── ...
    │   ├── services/             # Core services
    │   │   ├── claude-runner.ts
    │   │   ├── command-runner.ts
    │   │   ├── file-watcher.ts
    │   │   ├── git.ts
    │   │   ├── message-queue.ts
    │   │   ├── stream-json-parser.ts
    │   │   └── shutdown.ts
    │   ├── auth/                 # Authentication
    │   │   ├── login.ts
    │   │   ├── relay-auth.ts
    │   │   ├── token-storage.ts
    │   │   └── token-refresh.ts
    │   ├── db/                   # Database layer
    │   │   ├── database.ts
    │   │   └── repositories/
    │   │       ├── projects.ts
    │   │       ├── command-runs.ts
    │   │       └── fs-state.ts
    │   ├── control-plane/        # HTTP control plane
    │   │   ├── server.ts
    │   │   └── routes/
    │   └── utils/                # Utilities
    │       ├── logger.ts
    │       ├── paths.ts
    │       ├── machine-info.ts
    │       └── config.ts
    └── tests/                    # Test files

    Startup Sequence

    The daemon startup follows a 3-phase process:

    sequenceDiagram
        participant CLI as CLI
        participant Daemon as Daemon
        participant DB as SQLite
        participant Auth as Auth
        participant Relay as Relay
    
        Note over Daemon: Phase 1 - Local Init
        Daemon->>Daemon: Create config dirs
        Daemon->>DB: Open/Migrate database
        Daemon->>Daemon: Clean old command runs
        Daemon->>Daemon: Start Control Plane HTTP :17333
        Daemon->>Daemon: Write PID file
    
        Note over Daemon: Phase 2 - Authentication
        Daemon->>Auth: Load existing tokens
        alt Has valid tokens
            Daemon->>Relay: token_refresh (if expired)
            Relay-->>Daemon: New tokens
        else No tokens / invalid
            Daemon->>Daemon: Start OAuth server
            Daemon->>Daemon: Open browser
            Note over Daemon: User completes OAuth
            Daemon->>Relay: auth_exchange {code}
            Relay-->>Daemon: tokens + user info
        end
        Daemon->>Auth: Save tokens
    
        Note over Daemon: Phase 3 - Relay Connection
        Daemon->>Daemon: Setup message router
        Daemon->>Relay: Connect WebSocket
        Relay-->>Daemon: connected
        Daemon->>Relay: daemon_register
        Daemon->>Daemon: Start file watchers
        Daemon->>Daemon: Register signal handlers
    
        Note over Daemon: Ready ✓

    Shutdown Sequence

    sequenceDiagram
        participant Signal as SIGTERM/SIGINT
        participant Daemon as Daemon
        participant Services as Services
        participant DB as Database
    
        Signal->>Daemon: Shutdown signal
        Daemon->>Daemon: Stop accepting requests
    
        Note over Daemon,Services: LIFO cleanup order
        Daemon->>Services: Stop file watchers
        Daemon->>Services: Clear message queues
        Daemon->>Services: Kill Claude processes
        Daemon->>Services: Kill running commands
        Daemon->>Services: Close relay connection
    
        Daemon->>DB: Close database
        Daemon->>Daemon: Remove PID file
        Daemon->>Signal: Exit 0

    Authentication

    The daemon uses GitHub OAuth via Supabase Auth. Important: The daemon does NOT interact with Supabase directly - all token exchange goes through the Relay.

    sequenceDiagram
        participant User as User
        participant Daemon as Daemon
        participant OAuth as OAuth Server
        participant Supabase as Supabase
        participant Relay as Relay
    
        User->>Daemon: pocketcoder login
        Daemon->>Daemon: Start local callback server :random
        Daemon->>OAuth: Open browser to auth URL
        User->>OAuth: Sign in with GitHub
        OAuth->>Daemon: Callback with code
        Daemon->>Relay: auth_exchange { code }
        Relay->>Supabase: Exchange code for tokens
        Supabase-->>Relay: access_token, refresh_token
        Relay-->>Daemon: auth_exchange_response
        Daemon->>Daemon: Generate machineKey (UUID v4)
        Daemon->>Daemon: Save tokens to auth.json
        Daemon-->>User: Login complete

    Token Storage

    Tokens are stored with restrictive permissions (0600):

    OS Path
    macOS ~/Library/Application Support/pocketcoder/auth.json
    Linux ~/.config/pocketcoder/auth.json
    Windows %APPDATA%\pocketcoder\auth.json
    {
      "machine_key": "550e8400-e29b-41d4-a716-446655440000",
      "access_token": "eyJ...",
      "refresh_token": "...",
      "expires_at": 1707666000,
      "user_id": "github|12345"
    }

    Automatic Token Refresh

    sequenceDiagram
        participant Daemon as Daemon
        participant Relay as Relay
        participant Supabase as Supabase
    
        Note over Daemon: Token expired
        Daemon->>Relay: token_refresh {refresh_token}
        Relay->>Supabase: POST /auth/v1/token
        Supabase-->>Relay: New tokens
        Relay-->>Daemon: token_refresh_response
        Daemon->>Daemon: Update auth.json

    Relay Connection

    Connection State Machine

    stateDiagram-v2
        [*] --> disconnected
    
        disconnected --> connecting: connect()
        connecting --> connected: WebSocket opens
        connecting --> disconnected: Error/Close
    
        connected --> connected: Heartbeat ping/pong
        connected --> reconnecting: Unexpected disconnect
    
        reconnecting --> connected: Reconnect success
        reconnecting --> reconnecting: Retry with backoff
    
        reconnecting --> disconnected: Manual disconnect()
        connected --> disconnected: Manual disconnect()
        disconnected --> [*]

    Reconnection Strategy

    • Exponential backoff: 1s, 2s, 4s, 8s... maximum 60s
    • On reconnect: Re-sends daemon_register with full state
    • File watchers: Continue running (local state preserved)
    • Message queue: Maintained, processed on reconnect

    Daemon Registration

    sequenceDiagram
        participant Daemon as Daemon
        participant Relay as Relay
        participant Client as Client
    
        Daemon->>Relay: Connect WebSocket
        Daemon->>Relay: daemon_register
        Note right of Daemon: machineKey, token,<br/>machine info, projects
    
        Note over Relay: Validate JWT<br/>Store in map
    
        Relay--)Client: daemon_status {connected: true}
        Relay-->>Daemon: Ready

    Message Handling

    Message Flow

    graph TB
        subgraph "Relay Server"
            RRelay["Relay WebSocket"]
        end
    
        subgraph "Daemon"
            WS["WebSocket Client"]
            Router["Message Router"]
    
            subgraph "Handlers"
                H1["fs_* Handlers"]
                H2["git_* Handlers"]
                H3["command_exec"]
                H4["agent_* Handlers"]
            end
    
            subgraph "Services"
                CR["Claude Runner"]
                CMD["Command Runner"]
                FW["File Watcher"]
            end
        end
    
        RRelay -->|Message| WS
        WS -->|message event| Router
        Router -->|Dispatch| H1
        Router -->|Dispatch| H2
        Router -->|Dispatch| H3
        Router -->|Dispatch| H4
    
        H3 -->|Execute| CMD
        H4 -->|Spawn| CR
    
        CR -->|Events| Router
        CMD -->|Output| Router
        FW -->|fs_changed| WS
    
        Router -->|send| WS
        WS -->|JSON| RRelay

    Handler Pattern

    All message handlers follow this signature:

    export async function handleMessageType(
      message: RequestMessage,
      send: (response: Message) => void
    ): Promise<void> {
      // 1. Extract and validate payload
      // 2. Query database / filesystem
      // 3. Perform operation
      // 4. Send response via send()
      // 5. Stream events (if applicable)
    }

    Supported Message Types

    Category Message Type Description
    Filesystem fs_list List directory contents
    fs_read Read file content
    fs_write Write file content
    Git git_status Get git status
    git_branches List branches
    git_branch_create Create new branch
    git_checkout Checkout branch/ref
    git_stage Stage files
    git_unstage Unstage files
    git_commit Create commit
    git_push Push to remote
    git_file_base Get file at HEAD
    Commands command_exec Execute shell command
    command_cancel Cancel running command
    Agent agent_session_new Start new Claude session
    agent_message Send message to session
    agent_sessions_list List existing sessions
    agent_cancel Cancel running session
    agent_prompt_response Respond to Claude prompt
    System machines_list List user's machines
    projects_list List projects

    Features

    Filesystem Operations

    sequenceDiagram
        participant Client as Client
        participant Relay as Relay
        participant Daemon as Daemon
    
        Note over Client,Daemon: READ OPERATIONS
        Client->>Relay: fs_list {path: "src"}
        Relay->>Daemon: forward
        Note over Daemon: Read directory
        Daemon-->>Relay: fs_list_response
        Relay-->>Client: {children: [...]}
    
        Client->>Relay: fs_read {path: "src/app.ts"}
        Relay->>Daemon: forward
        Daemon-->>Relay: fs_read_response
        Relay-->>Client: {content: "..."}
    
        Note over Client,Daemon: WRITE OPERATIONS
        Client->>Relay: fs_write {path, content}
        Relay->>Daemon: forward
        Note over Daemon: Write to disk
        Daemon-->>Relay: fs_write_response
        Relay-->>Client: {success: true}

    Lazy Tree Loading

    The filesystem is loaded lazily for performance:

    • Root: fs_list(path="") returns only root level
    • Expand folder: fs_list(path="src") loads that directory
    • Prevents sending large node_modules directories

    Limits

    Limit Value
    Maximum file read size 10 MB
    Maximum file write size 1 MB
    Maximum tree depth 20 levels
    Maximum entries per listing 10,000

    File Watcher

    The daemon uses chokidar to monitor filesystem changes in real-time.

    graph TB
        subgraph "Chokidar Watch"
            WATCH["watch(projectPath)"]
            IGNORE["Ignored Patterns:<br/>node_modules, .git,<br/>dist, build, .next"]
        end
    
        subgraph "Change Detection"
            ADD["add/addDir → create"]
            MOD["change → modify"]
            DEL["unlink/unlinkDir → delete"]
        end
    
        subgraph "Accumulation"
            PEND["Pending Changes"]
            DEBOUNCE["Debounce Timer<br/>(250ms)"]
        end
    
        subgraph "Event Generation"
            SEQ["Increment sequence"]
            TRUNC["Truncate if > 500"]
            EVENT["fs_changed event"]
        end
    
        WATCH -->|Event| ADD
        WATCH -->|Event| MOD
        WATCH -->|Event| DEL
    
        ADD --> PEND
        MOD --> PEND
        DEL --> PEND
    
        PEND -->|Schedule| DEBOUNCE
        DEBOUNCE -->|Fire| SEQ
        SEQ --> TRUNC
        TRUNC --> EVENT
    
        IGNORE -.->|Filters| WATCH

    Event Message

    {
      type: 'fs_changed',
      sessionId: 'proj_abc',
      payload: {
        seq: 43,                    // Incremental sequence
        changes: [
          { kind: 'create', path: 'src/new.ts' },
          { kind: 'modify', path: 'src/app.ts' },
          { kind: 'delete', path: 'src/old.ts' }
        ],
        changesTruncated?: boolean  // true if >500 changes
      }
    }

    Synchronization

    • Each change increments seq globally per project
    • Client tracks last seq received
    • On seq gap: client triggers full fs_list refresh

    Command Runner

    Non-interactive shell command execution with streaming output.

    sequenceDiagram
        participant Client as Client
        participant Relay as Relay
        participant Daemon as Daemon
    
        Client->>Relay: command_exec {command: "npm test"}
        Relay->>Daemon: forward
        Note over Daemon: Spawn process
    
        loop Streaming output
            Daemon--)Relay: command_output {stdoutChunk}
            Relay--)Client: forward
            Daemon--)Relay: command_output {stderrChunk}
            Relay--)Client: forward
        end
    
        Daemon-->>Relay: command_exit {exitCode: 0}
        Relay-->>Client: forward
    
        Note over Client,Daemon: CANCELLATION
        Client->>Relay: command_cancel {requestId}
        Relay->>Daemon: forward
        Note over Daemon: kill (SIGTERM)
        Daemon-->>Relay: command_exit {exitCode: null, signal: "SIGTERM"}
        Relay-->>Client: forward

    Design Principles

    • One-shot execution: Command must terminate on its own
    • No interactive TTY: stdout/stderr captured and streamed
    • Cancellation support: Kill process on request
    • Timeout protection: 120 second default timeout

    Limits

    Limit Value
    Default timeout 120 seconds
    Maximum output 300 KB (truncated)
    Working directory Fixed to project path

    Git Integration

    sequenceDiagram
        participant Client as Client
        participant Relay as Relay
        participant Daemon as Daemon
    
        Note over Client,Daemon: STATUS & BRANCHES
        Client->>Relay: git_status
        Relay->>Daemon: forward
        Note over Daemon: git status --porcelain
        Daemon-->>Relay: git_status_response
        Relay-->>Client: {branch, staged, unstaged}
    
        Note over Client,Daemon: STAGE & COMMIT
        Client->>Relay: git_stage {paths}
        Relay->>Daemon: forward
        Note over Daemon: git add
        Daemon-->>Relay: git_stage_response
        Relay-->>Client: success
    
        Client->>Relay: git_commit {message}
        Relay->>Daemon: forward
        Note over Daemon: git commit -m
        Daemon-->>Relay: git_commit_response
        Relay-->>Client: {sha: "abc123"}
    
        Note over Client,Daemon: PUSH
        Client->>Relay: git_push
        Relay->>Daemon: forward
        Note over Daemon: git push
        Daemon-->>Relay: git_push_response
        Relay-->>Client: success
    
        Note over Client,Daemon: DIFF (via file base)
        Client->>Relay: git_file_base {path}
        Relay->>Daemon: forward
        Note over Daemon: git show HEAD:path
        Daemon-->>Relay: git_file_base_response
        Relay-->>Client: {content}

    Git Operations

    Operation Git Command Description
    git_status git status --porcelain=v1 Branch, staged, unstaged files
    git_branches git for-each-ref List all branches
    git_branch_create git branch <name> Create new branch
    git_checkout git checkout <ref> Switch branch/commit
    git_stage git add <paths> Stage files
    git_unstage git reset HEAD <paths> Unstage files
    git_commit git commit -m Create commit
    git_push git push Push to remote
    git_file_base git show HEAD:<path> Get file at HEAD

    Claude Code Integration

    sequenceDiagram
        participant Client as Client
        participant Relay as Relay
        participant Daemon as Daemon
        participant Claude as Claude CLI
    
        Note over Client,Claude: NEW SESSION
        Client->>Relay: agent_session_new {content}
        Relay->>Daemon: forward
        Note over Daemon: Generate agentSessionId
        Daemon->>Claude: claude -p --output-format stream-json --session-id uuid "prompt"
        Daemon-->>Relay: agent_session_new_response {agentSessionId}
        Relay-->>Client: forward
    
        loop Streaming events
            Claude-->>Daemon: JSON line
            Daemon--)Relay: agent_event {type: "text"}
            Relay--)Client: forward
            Daemon--)Relay: agent_event {type: "tool_use"}
            Relay--)Client: forward
        end
    
        Claude-->>Daemon: result line
        Daemon--)Relay: agent_event {type: "done"}
        Relay--)Client: forward
    
        Note over Client,Claude: CONTINUE SESSION
        Client->>Relay: agent_message {agentSessionId, content}
        Relay->>Daemon: forward
        Daemon->>Claude: claude -p --resume sessionId "prompt"
        loop Streaming
            Daemon--)Relay: agent_event
            Relay--)Client: forward
        end
    
        Note over Client,Claude: CANCEL
        Client->>Relay: agent_cancel
        Relay->>Daemon: forward
        Note over Daemon: kill (SIGTERM)
        Daemon--)Relay: agent_event {type: "done", cancelled: true}
        Relay--)Client: forward

    Execution Model

    The daemon does NOT keep Claude Code processes alive permanently:

    1. Message arrives from client
    2. Daemon spawns claude -p --output-format stream-json --verbose ...
    3. Streaming response to client
    4. Process terminates
    5. Next message resumes with --resume <agentSessionId>

    Claude CLI Flags

    Flag Description
    -p, --print Non-interactive mode
    --output-format stream-json Structured JSON output
    --verbose Required with stream-json
    --session-id <uuid> Create specific session
    -r, --resume <uuid> Resume existing session

    Stream JSON Parser

    Claude Code emits line-delimited JSON. The daemon parses and transforms:

    graph LR
        subgraph "Claude stdout"
            SYS["system init"]
            ASS["assistant"]
            USER["user"]
            RES["result"]
        end
    
        subgraph "StreamJsonBuffer"
            BUF["Parse lines"]
        end
    
        subgraph "AgentEvents"
            INIT["init"]
            TEXT["text"]
            FC["file_create"]
            FE["file_edit"]
            TU["tool_use"]
            PROMPT["prompt"]
            DONE["done"]
        end
    
        SYS --> BUF
        ASS --> BUF
        USER --> BUF
        RES --> BUF
    
        BUF --> INIT
        BUF --> TEXT
        BUF --> FC
        BUF --> FE
        BUF --> TU
        BUF --> PROMPT
        BUF --> DONE

    Message Queue

    • FIFO queue per session: Max 50 messages
    • One Claude process per agentSessionId
    • Queue drains after process completes
    • If queue full: Reject with DAEMON_BUSY

    Control Plane HTTP API

    Local HTTP server on 127.0.0.1:17333 for daemon management.

    Route Method Description
    /health GET Health check (empty 200)
    /status GET Detailed daemon status
    /stop POST Graceful shutdown
    /machine-label GET Get machine label
    /machine-label POST Set machine label
    /re-register POST Re-register with relay

    Status Response

    {
      "version": "1.0.0",
      "machineKey": "550e8400-e29b-41d4-a716-446655440000",
      "loggedIn": true,
      "relay": {
        "connected": true,
        "lastConnectedAt": "2026-02-12T10:00:00Z",
        "lastHeartbeatAt": "2026-02-12T10:15:00Z"
      },
      "capabilities": {
        "hasGit": true,
        "hasClaude": true,
        "claudeStoreFound": true,
        "warnings": []
      },
      "projects": [
        {
          "projectId": "550e8400-...",
          "name": "my-app",
          "path": "/Users/dev/my-app",
          "watcherRunning": true,
          "lastFsSeq": 123
        }
      ]
    }

    Database Schema

    SQLite database at ~/.pocketcoder/daemon.db:

    erDiagram
        PROJECTS ||--o{ FS_STATE : "1:1"
        PROJECTS ||--o{ COMMAND_RUNS : "1:N"
        PROJECTS ||--o{ CLAUDE_PROJECT_DIRS : "1:N"
    
        PROJECTS {
            text project_id PK
            text name
            text path UK
            datetime created_at
            datetime last_opened_at
        }
    
        FS_STATE {
            text project_id PK,FK
            integer last_seq
        }
    
        COMMAND_RUNS {
            text request_id PK
            text project_id FK
            text command
            datetime started_at
            datetime ended_at
            integer exit_code
            boolean truncated
            text output_preview
        }
    
        CLAUDE_PROJECT_DIRS {
            text project_id PK,FK
            text claude_dir_path PK
            datetime last_verified_at
        }

    Retention Policy

    Table Policy
    projects No limit
    fs_state No limit (1 row per project)
    claude_project_dirs No limit
    command_runs 7 days or max 10,000 rows

    Cleanup runs on startup and every 6 hours.


    CLI Commands

    Command Description
    pocketcoder login Authenticate with GitHub
    pocketcoder logout Delete stored credentials
    pocketcoder start Start daemon (background)
    pocketcoder start --foreground Start in foreground
    pocketcoder stop Stop daemon gracefully
    pocketcoder status Show daemon status
    pocketcoder add <path> Register project
    pocketcoder remove <projectId> Unregister project
    pocketcoder projects List registered projects
    pocketcoder logs [--tail N] [-f] View daemon logs
    pocketcoder machine label <text> Set machine label
    pocketcoder enable-autostart Enable autostart
    pocketcoder disable-autostart Disable autostart

    Error Handling

    All errors are caught at router level and converted to error responses:

    {
      type: "error",
      requestId: "req_123",
      error: {
        code: "FILE_NOT_FOUND",
        message: "File does not exist",
        details?: { path: "src/missing.ts" }
      }
    }

    Error Codes

    Code Description
    UNKNOWN_ERROR Generic error
    INVALID_REQUEST Missing/invalid payload
    UNAUTHORIZED Authentication failed
    PROJECT_NOT_FOUND Project doesn't exist
    FILE_NOT_FOUND File doesn't exist
    FILE_TOO_LARGE File exceeds 10MB
    OUTSIDE_PROJECT_ROOT Path escape attempt
    GIT_NOT_INITIALIZED Not a git repo
    COMMAND_TIMEOUT Command exceeded timeout
    DAEMON_BUSY Queue full (50 max)

    Configuration

    Environment Variables

    Variable Description Default
    GATEWAY_URL Gateway WebSocket URL wss://gateway.pocketcoder.dev
    LOG_LEVEL Logging level info

    Config Directory

    OS Path
    macOS ~/Library/Application Support/pocketcoder/
    Linux ~/.config/pocketcoder/
    Windows %APPDATA%\pocketcoder\

    Files

    File Purpose
    daemon.db SQLite database
    auth.json Authentication tokens
    machine-label.txt User-set machine name
    daemon.pid PID file (running indicator)
    logs/daemon.log Log file

    Testing

    # Run all tests
    pnpm test
    
    # Run tests in watch mode
    pnpm test -- --watch
    
    # Run specific test file
    pnpm test -- --run src/handlers/fs-list.test.ts
    
    # Run with coverage
    pnpm test -- --coverage

    Test Structure

    tests/
    ├── handlers/           # Message handler tests
    ├── services/           # Service tests
    ├── auth/               # Auth flow tests
    ├── relay/              # Relay connection tests
    ├── db/                 # Database tests
    ├── cli/                # CLI command tests
    └── utils/              # Utility tests

    Security

    Network Security

    • Outbound-only: Daemon only makes outbound WebSocket connection
    • No public listeners: Control plane bound to 127.0.0.1 only
    • JWT authentication: All relay communication authenticated

    File Security

    • Path validation: All paths validated to be within project root
    • Symlink protection: No escape via symlinks
    • Token storage: Restrictive permissions (0600)

    Threat Model

    • Without stolen JWT: Access probability = 0
    • With stolen JWT: Full access (focus on secure storage)
    • Anti-enumeration: Always respond not_found on auth errors

    Performance Limits

    Aspect Limit Rationale
    Message Queue 50 per session Prevent unbounded memory
    File Changes 500 per event Batch large fs events
    Command Output 300 KB Prevent huge payloads
    Command Timeout 120s Prevent stuck processes
    File Size 10 MB Binary detection
    Debounce Delay 250ms Batch file events
    Relay Request Timeout 15s Prevent blocking

    License

    See the root LICENSE file.