# 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](#overview)
- [Tech Stack](#tech-stack)
- [Installation & Usage](#installation--usage)
- [Architecture](#architecture)
- [Startup Sequence](#startup-sequence)
- [Authentication](#authentication)
- [Relay Connection](#relay-connection)
- [Message Handling](#message-handling)
- [Features](#features)
  - [Filesystem Operations](#filesystem-operations)
  - [File Watcher](#file-watcher)
  - [Command Runner](#command-runner)
  - [Git Integration](#git-integration)
  - [Claude Code Integration](#claude-code-integration)
- [Control Plane HTTP API](#control-plane-http-api)
- [Database Schema](#database-schema)
- [CLI Commands](#cli-commands)
- [Error Handling](#error-handling)
- [Configuration](#configuration)
- [Testing](#testing)

---

## Overview

```mermaid
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

```bash
# Install globally
npm install -g pocketcoder

# Or run without install
npx pocketcoder start
```

### Basic Usage

```bash
# 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

```mermaid
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:

```mermaid
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

```mermaid
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.

```mermaid
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`                     |

```json
{
  "machine_key": "550e8400-e29b-41d4-a716-446655440000",
  "access_token": "eyJ...",
  "refresh_token": "...",
  "expires_at": 1707666000,
  "user_id": "github|12345"
}
```

### Automatic Token Refresh

```mermaid
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

```mermaid
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

```mermaid
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

```mermaid
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:

```typescript
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

```mermaid
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.

```mermaid
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

```typescript
{
  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.

```mermaid
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

```mermaid
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

```mermaid
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:

```mermaid
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

```json
{
  "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`:

```mermaid
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:

```typescript
{
  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

```bash
# 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](../../LICENSE) file.
