Web Interface

Browser-based dashboard for clhorde — submit prompts, monitor workers, and interact with Claude sessions from any device.


Quick Start

The web interface is provided by clhorde-web, a thin HTTP/WebSocket bridge that connects to the running daemon. It serves a vanilla JS single-page application with no build step required.

# 1. Start the daemon (if not already running)
$ clhorded &

# 2. Start the web server
$ clhorde-web

# 3. Open in your browser
$ open http://localhost:3120

The web dashboard connects via WebSocket and reflects the same state as the TUI and CLI clients. All three can run simultaneously.

Single binary. All static assets (HTML, CSS, JS, xterm.js) are embedded in the clhorde-web binary at compile time via include_dir!. No external files needed.

Dashboard

The main view shows a split layout: prompt list on the left, detail view on the right.

Prompt List

Submit a Prompt

The submission form at the top of the sidebar provides:

Filter & Search

Use the search input and status filter chips above the prompt list to narrow down prompts. Text search is case-insensitive substring matching. Status chips and text search combine (both must match). Press Esc to clear.

Worker Controls

The footer shows active and max worker counts, plus the default mode. Click +/- to adjust max workers (1–20) or click the mode label to toggle between interactive and one-shot.

Prompt Detail & Terminal

One-shot Prompts

Selecting a one-shot prompt shows an ANSI output viewer with full color support. Live output streams in real time via WebSocket OutputChunk events. Auto-scroll follows new output.

Running one-shot prompts show a follow-up input bar at the bottom — type a message and press Enter to send.

Interactive Prompts (xterm.js)

Selecting an interactive prompt opens a full terminal emulator powered by xterm.js with WebGL rendering. The complete Claude Code TUI renders in the browser — colors, cursor, scrollback, and all.

Action Buttons

The detail header shows context-sensitive action buttons:

StatusAvailable Actions
runningKill, Delete
completed / failedRetry, Resume, Delete
pendingMove Up, Move Down, Delete

Destructive actions (Kill, Delete) require a confirmation dialog.

REST API Reference

All endpoints are prefixed with /api/ and return JSON responses with Content-Type: application/json.

Health & State

MethodPathDescription
GET/api/healthHealth check — returns { "status": "ok" }
GET/api/stateFull daemon state snapshot (prompts, workers, config)

Prompts

MethodPathDescription
GET/api/promptsList all prompts
GET/api/prompts/:idGet single prompt (404 if not found)
GET/api/prompts/:id/outputFull output text for a prompt
POST/api/promptsSubmit a new prompt
POST/api/prompts/:id/inputSend follow-up input to running worker
POST/api/prompts/:id/killKill running worker
POST/api/prompts/:id/retryRetry completed/failed prompt
POST/api/prompts/:id/resumeResume with --resume
DELETE/api/prompts/:idDelete a prompt (returns 204)
POST/api/prompts/:id/move-upMove pending prompt up in queue
POST/api/prompts/:id/move-downMove pending prompt down in queue

Submit Prompt Body

{
  "text": "Review the auth module",      // required
  "mode": "interactive",                // default: "interactive"
  "worktree": false,                    // default: false
  "cwd": "/path/to/project",             // optional
  "tags": ["review", "auth"]            // optional
}

Valid modes: "interactive", "one-shot", "one_shot", "oneshot"

Send Input Body

{
  "text": "Continue with the refactor"    // required, non-empty
}

Configuration

MethodPathDescription
PUT/api/config/max-workersSet max worker count (1–20)
PUT/api/config/default-modeSet default prompt mode
PUT/api/prompts/:id/modeSet mode for a specific prompt

Set Max Workers Body

{ "count": 5 }     // range: 1–20

Set Mode Body

{ "mode": "one-shot" }

Store

MethodPathDescription
GET/api/storeList persisted prompts
GET/api/store/countCounts by status
GET/api/store/pathStorage directory path
POST/api/store/dropDrop prompts by filter
POST/api/store/keepKeep prompts by filter, drop the rest
POST/api/store/clean-worktreesRemove lingering git worktrees

Store Filter Body

// For /api/store/drop:
{ "filter": "all" }          // "all", "completed", "failed", "pending"

// For /api/store/keep:
{ "filter": "completed" }    // "completed", "failed", "pending" (no "all")

Error Responses

Errors return JSON with an error field:

StatusMeaning
400Invalid request body (validation error)
401Authentication required (when token is configured)
404Prompt not found
502Daemon unavailable
{ "error": "prompt 99 not found" }

WebSocket API

Connect to ws://localhost:3120/api/ws for real-time event streaming. On connection, the server sends a StateSnapshot event to bootstrap the client.

Server → Client

Daemon Events

{
  "type": "DaemonEvent",
  "event": {
    "type": "WorkerStarted",
    "prompt_id": 1
  }
}

Event types: StateSnapshot, PromptAdded, PromptUpdated, PromptRemoved, WorkerStarted, WorkerFinished, WorkerError, OutputChunk, MaxWorkersChanged, and more.

PTY Bytes

{
  "type": "PtyBytes",
  "prompt_id": 1,
  "data": "G1szMW0="            // base64-encoded raw PTY bytes
}

Client → Server

Client Requests

{
  "type": "ClientRequest",
  "request": {
    "type": "Ping"
  }
}

PTY Subscription

// Subscribe to PTY bytes for a specific prompt
{ "type": "SubscribePty", "prompt_id": 1 }

// Unsubscribe
{ "type": "UnsubscribePty", "prompt_id": 1 }

Store Management

Click the Store tab in the navigation to access the store management view. This mirrors the clhorde-cli store functionality:

Authentication

By default, clhorde-web binds to 127.0.0.1 (localhost only) and requires no authentication. When exposing on a network interface, you should configure a token:

# Via CLI flag
$ clhorde-web --host 0.0.0.0 --auth-token my-secret-token

# Or via environment variable
$ CLHORDE_WEB_AUTH_TOKEN=my-secret-token clhorde-web --host 0.0.0.0

When a token is configured:

Warning on startup. If you bind to a non-localhost address without setting a token, clhorde-web prints a warning: "Warning: serving without authentication on a network interface".

CLI Options

$ clhorde-web --help
FlagDefaultDescription
--port3120Port to listen on
--host127.0.0.1Host address to bind to
--static-dir(embedded)Override static file directory (for development)
--daemon-socket(auto)Override path to daemon Unix socket
--auth-token(none)Require this token for API access
--cors-origin(disabled)Allow CORS requests from this origin
-v / -vvwarnVerbosity: -v = info, -vv = debug

Environment variables: CLHORDE_WEB_AUTH_TOKEN, CLHORDE_WEB_CORS_ORIGIN

Systemd Service

Run clhorde-web as a systemd user service alongside the daemon:

[Unit]
Description=clhorde web interface
After=clhorded.service
Requires=clhorded.service

[Service]
ExecStart=/path/to/clhorde-web
Restart=on-failure
RestartSec=3

[Install]
WantedBy=default.target
$ systemctl --user enable clhorde-web.service
$ systemctl --user start clhorde-web.service

To expose on the network with authentication, override the command:

[Service]
ExecStart=/path/to/clhorde-web --host 0.0.0.0 --auth-token %H/.config/clhorde/web-token
Environment=CLHORDE_WEB_AUTH_TOKEN=your-token-here