Skip to main content
CliDeck is a local Node.js application with a browser-based frontend. This page explains the key architectural decisions for anyone who wants to understand what’s happening behind the scenes.

System Overview

┌─────────────────────────────────────────────────┐
│                   Browser                        │
│  ┌─────────────┐  ┌──────────┐  ┌────────────┐  │
│  │  Sidebar     │  │ Terminal  │  │  Settings   │  │
│  │  (sessions,  │  │ (xterm.js)│  │  Panel      │  │
│  │   search)    │  │           │  │             │  │
│  └──────┬───────┘  └─────┬────┘  └──────┬──────┘  │
│         └────────────┬───┘──────────────┘          │
│                      │ WebSocket                    │
└──────────────────────┼─────────────────────────────┘

┌──────────────────────┼─────────────────────────────┐
│              Node.js Server (:4000)                 │
│                      │                              │
│  ┌───────────────────┴────────────────────────┐    │
│  │           WebSocket Handler                 │    │
│  │  (message routing, config, telemetry setup) │    │
│  └──────┬─────────┬──────────┬────────────────┘    │
│         │         │          │                      │
│  ┌──────┴───┐ ┌───┴────┐ ┌──┴──────────┐          │
│  │ Sessions  │ │ Config │ │ Transcript   │          │
│  │ (PTY mgmt)│ │ (CRUD) │ │ (JSONL store)│          │
│  └──────┬───┘ └────────┘ └─────────────┘          │
│         │                                           │
│  ┌──────┴──────────────────────────────────┐       │
│  │         PTY Processes (node-pty)         │       │
│  │  ┌─────────┐ ┌─────────┐ ┌──────────┐  │       │
│  │  │ claude   │ │ codex   │ │ gemini   │  │       │
│  │  └─────────┘ └─────────┘ └──────────┘  │       │
│  └─────────────────────────────────────────┘       │
│                                                     │
│  ┌──────────────┐  ┌────────────────┐              │
│  │  OTLP Receiver│  │ OpenCode Bridge│              │
│  │  POST /v1/logs│  │ POST /opencode │              │
│  └──────────────┘  └────────────────┘              │
│                                                     │
│  ┌──────────────────────────────────────────┐       │
│  │         Plugin System                     │       │
│  │  ~/.clideck/plugins/ (Trim Clip,           │       │
│  │   Voice Input, custom plugins)            │       │
│  └──────────────────────────────────────────┘       │
└─────────────────────────────────────────────────────┘

Key Components

PTY Sessions (node-pty)

Each terminal session spawns a real pseudo-terminal using node-pty. The agent process runs inside this PTY and behaves exactly as if it were in a normal terminal. CliDeck doesn’t inject any middleware between you and the agent. Terminal output is buffered (up to 200KB per session) and sent to the browser over WebSocket. The browser renders it using xterm.js.

WebSocket Communication

A single WebSocket connection carries all messages between the browser and server. Messages are JSON-encoded with a type field for routing. The server broadcasts to all connected clients — you can have multiple browser tabs open.

OTLP Telemetry Receiver

The server runs an HTTP endpoint at POST /v1/logs that accepts OpenTelemetry log data in JSON format. When an agent is launched, CliDeck sets environment variables that point the agent’s OTLP exporter to this local endpoint. The receiver:
  1. Parses the OTLP resource logs
  2. Matches the log to the correct session (via termix.session_id resource attribute)
  3. Extracts the agent’s session ID from log record attributes
  4. Detects if telemetry is properly configured (triggers setup toast if not)

OpenCode Plugin Bridge

OpenCode uses a different integration path. A JavaScript plugin inside OpenCode sends HTTP events to POST /opencode-events. The bridge maps these events to the correct CliDeck session by matching the working directory.

Activity Monitor

A 1-second polling loop measures I/O rates for each session:
  • Bytes in/out since last poll
  • Burst duration — how long the current output burst has been going
  • Resets after 2 seconds of silence
These stats are broadcast to the browser, which uses them to determine working/idle status. This is the source of truth — not telemetry.

Plugin System

Plugins live in ~/.clideck/plugins/. Each plugin is a folder with an index.js (backend) and optional client.js (frontend), clideck-plugin.json (manifest), and public/ (static assets). On startup, CliDeck seeds bundled plugins (Trim Clip, Voice Input) into the plugins directory if they don’t already exist. Then it loads all plugin folders, calls their init() with a sandboxed API, and wires up their hooks (input/output/status). Plugins communicate with the browser through namespaced WebSocket messages. The plugin API provides:
  • Session hooks — observe output, transform input, react to status changes
  • Frontend messaging — bidirectional communication between backend and browser
  • Toolbar actions — add buttons to the terminal toolbar
  • Settings — user-configurable settings with type validation, persisted in config.json
  • Lifecycle — shutdown handlers for cleanup
Plugin errors are isolated — one plugin can’t crash CliDeck or affect other plugins.

Transcript Store

Every session’s I/O is recorded as plain-text JSONL in ~/.clideck/transcripts/. ANSI codes are stripped, short lines filtered out. The transcript is used for sidebar search — a cache is built on startup and updated incrementally.

Data Flow: Agent Status

Agent outputs text → PTY → node-pty onData
                           ├→ WebSocket 'output' → browser renders in xterm.js
                           ├→ Activity tracker counts bytes
                           └→ Transcript records stripped text

Activity poll (1s) → broadcasts stats → browser computes working/idle
                                         ├→ Updates status indicator
                                         └→ Triggers notification (if idle transition)

Data Flow: Session Resume

Agent starts → telemetry logs flow to /v1/logs
             → OTLP receiver extracts session ID
             → stored as session.sessionToken

CliDeck shuts down → SIGTERM/SIGINT handler
                   → saves sessions to sessions.json
                   → kills PTY processes

CliDeck starts → loads sessions.json
              → shows in "Previous Sessions" sidebar
              → user clicks resume
              → spawns new PTY with resume command + saved session ID

Startup Detection

When CliDeck starts, it scans for existing agent configurations on disk:
  • Codex: checks ~/.codex/config.toml for an [otel] section pointing to localhost:4000
  • Gemini CLI: checks ~/.gemini/settings.json for a telemetry object with the local endpoint
  • OpenCode: checks if ~/.config/opencode/plugins/clideck-bridge.js exists
  • Claude Code: always detected (built-in telemetry)
If a configuration is found, the integration is marked as enabled in the UI automatically — no manual setup needed. This means if you’ve already configured an agent in a previous session, CliDeck picks it up on restart.

Local Only

CliDeck runs entirely on your machine. There’s no cloud service, no account, no external API calls. The Node.js server binds to 127.0.0.1:4000 — it’s not accessible from other machines on your network. If the Node.js server stops, all PTY processes stop too. This is intentional — CliDeck manages terminal sessions locally.