npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pocketcoder/host

v0.0.19

Published

PocketCoder host daemon - connects your local machine to PocketCoder cloud

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.