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
- Tech Stack
- Installation & Usage
- Architecture
- Startup Sequence
- Authentication
- Relay Connection
- Message Handling
- Features
- Control Plane HTTP API
- Database Schema
- CLI Commands
- Error Handling
- Configuration
- Testing
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| ClaudeKey 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 startBasic 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| RelayDirectory 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 filesStartup 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 0Authentication
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 completeToken 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.jsonRelay 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_registerwith 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: ReadyMessage 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| RRelayHandler 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_modulesdirectories
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| WATCHEvent 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
seqglobally per project - Client tracks last
seqreceived - On seq gap: client triggers full
fs_listrefresh
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: forwardDesign 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: forwardExecution Model
The daemon does NOT keep Claude Code processes alive permanently:
- Message arrives from client
- Daemon spawns
claude -p --output-format stream-json --verbose ... - Streaming response to client
- Process terminates
- 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 --> DONEMessage 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 -- --coverageTest 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 testsSecurity
Network Security
- Outbound-only: Daemon only makes outbound WebSocket connection
- No public listeners: Control plane bound to
127.0.0.1only - 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_foundon 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.