Self-hosted web UI for autonomous AI coding agents.
Run multiple sessions simultaneously, stream tool execution in real time, schedule agent runs with cron, manage services, and monitor iTerm2 terminals and WeChat — all from one browser tab, on any device.
Works with Anthropic and any OpenAI-compatible API (custom base URL + secret key).
Quick Start · Features · Architecture · Configuration · API Reference
AI coding agents are powerful — but a single CLI terminal is a bottleneck. CtrlNect gives you the full control plane in a browser, from any device, with everything wired together:
| Multi-session agents | Run many concurrent agent sessions, each with its own working dir, model, and history |
| Real-time streaming | Token-by-token output — thinking blocks, tool calls, sub-agent events, all live |
| Full agentic toolset | Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch, Agent (sub-agents), NotebookEdit |
| Sub-agent management | Spawn and observe parallel sub-agents from the UI in real time |
| iTerm2 integration | View and control all live iTerm2 terminal sessions directly from the sidebar |
| WeChat Web panel | Embedded WeChat Web — no popup, preserves login, soft-reload support |
| Feishu / Lark bridge | DM integration: send prompts and receive agent replies inside Feishu chat |
| Cron scheduler | Schedule AI prompt runs or shell commands with standard cron expressions |
| Service manager | Start, stop, restart and monitor background services from the sidebar |
| Dual API support | Anthropic (native) and any OpenAI-compatible API with custom base URL + secret key |
| Persistent history | Sessions, messages and logs survive page refreshes and server restarts |
| Remote access | Control your agents from any device on your network |
Prerequisites: Bun v1.1+
# Clone
git clone https://github.com/JaceHo/ctrlnect.git
cd ctrlnect
# Install
bun install
# Set your API key (or use existing ANTHROPIC_AUTH_TOKEN from ~/.zshrc)
export ANTHROPIC_API_KEY=sk-ant-...
# Run
bun run devOpen http://localhost:5173 — create a session and start chatting.
Corporate VPN / proxy: The dev server automatically sets
NODE_TLS_REJECT_UNAUTHORIZED=0to handle self-signed certificates.
Create, switch between, and manage multiple concurrent Claude agent sessions. Each session maintains its own conversation history, working directory, and model selection. Sessions persist across server restarts.
Shared agent memory across sessions: Use AgentMem — a persistent, cross-session memory layer that lets your agents remember context, decisions, and knowledge across different sessions and restarts.
Every session has access to the complete Claude Code toolset. The Agent tool spawns parallel sub-agents — CtrlNect streams their events to the UI in real time so you can observe every sub-task as it executes.
| Tool | Description |
|---|---|
Read |
Read files from the filesystem |
Write |
Create new files |
Edit |
Make targeted edits to existing files |
Bash |
Execute shell commands |
Grep |
Search file contents with regex |
Glob |
Find files by pattern |
WebSearch |
Search the web |
WebFetch |
Fetch and process web pages |
Agent |
Spawn sub-agents for parallel work (streamed live) |
NotebookEdit |
Edit Jupyter notebooks |
All live iTerm2 terminal sessions appear in the sidebar with AI-generated titles and working directory. Click any session to view its output and send commands — without leaving the browser.
Embedded WeChat Web (wx.qq.com) in the right panel — no popup windows. Supports soft reload (preserves login) and opens in a standalone browser tab as fallback.
Messages stream token-by-token as Claude generates them. Thinking blocks, tool invocations, and tool results all render in real time.
Schedule agent runs with standard cron expressions. The built-in scheduler:
- Supports both prompt-type crons (send a message to a session) and command-type crons (run a shell command)
- Auto-imports entries from the system crontab on startup (idempotent)
- Shows next scheduled run time for each job
- Can be triggered manually from the UI
Connect Claude sessions to Feishu DMs. Configure in ~/.openclaw/config.json:
{
"feishu": {
"app_id": "cli_...",
"app_secret": "...",
"poll_interval_ms": 5000,
"sessions": [
{ "feishu_user_id": "ou_...", "session_id": "session-name" }
]
}
}Once configured, messages sent to the bot in Feishu DM are forwarded to the mapped Claude session. Agent replies stream back to the Feishu chat in real time.
Monitor and control background services from the sidebar. View status, start/stop services, and see live output — without leaving the browser.
Switch between Claude models per session:
- Sonnet 4.6 — Fast and capable (default)
- Opus 4.6 — Most capable
- Haiku 4.5 — Fastest
- Markdown with GitHub-flavored extensions
- Syntax-highlighted code blocks (150+ languages)
- Collapsible thinking blocks
- Tool invocation & result visualization
- Inline image display
- Cost tracking per session
Paste images from clipboard, drag-and-drop files, or use the file picker. Images are sent to Claude for visual analysis and coding tasks.
If the Claude Code subprocess crashes, CtrlNect automatically retries with exponential backoff. Each retry uses a fresh SDK session. Non-retryable errors (auth, permissions) fail immediately. Sessions auto-recover to idle after errors.
The sidebar has two sections — sessions (top) and panels (bottom: crons, services). The divider is draggable; the bottom panel overlays the session list when expanded. Height is persisted across refreshes.
Browser (React + Vite) Server (Bun + Hono) Claude Agent SDK
┌──────────────────────┐ WS ┌───────────────────┐ stdio ┌──────────────┐
│ Session Manager │◄──────►│ WS Handler │ │ Claude Code │
│ Chat UI (streaming) │ │ Connection Mgr │◄──────►│ Subprocess │
│ Tool Visualization │ REST │ Agent Runner │ │ (per query) │
│ Cron Scheduler UI │◄──────►│ Session Store │ └──────────────┘
│ Service Manager UI │ │ Message Store │ │
│ Feishu Status │ │ Cron Store │ ▼ ANTHROPIC_BASE_URL
│ WeChat Embed │ │ Cron Scheduler │ Anthropic API
└──────────────────────┘ │ Service Store │ ── or ──
│ Feishu Bridge │◄──────► Feishu API
│ iTerm2 Bridge │◄──────► iTerm2
│ OpenAI Proxy │◄──────► OpenAI-compatible API
└───────────────────┘ (gpt-4o, Groq, Azure…)
Monorepo structure:
ctrlnect/
├── client/ # React 19 + Vite 6 + Tailwind CSS 4
├── server/ # Bun + Hono + Claude Agent SDK
├── packages/
│ └── shared/ # TypeScript types shared between client & server
└── data/ # Session & message persistence (gitignored)
├── sessions.json
├── crons.json
├── services.json
├── feishu-state.json
└── messages/ # Per-session message files
| Layer | Technology |
|---|---|
| Runtime | Bun |
| Frontend | React 19, Vite 6, Tailwind CSS 4, TypeScript |
| Backend | Hono (REST + WebSocket) |
| Agent | @anthropic-ai/claude-agent-sdk |
| Rendering | react-markdown, remark-gfm, highlight.js |
| Storage | JSON files (no database required) |
- User sends a message via the browser
- WebSocket carries it to the Hono server
- Agent Runner calls
query()from the Claude Agent SDK, spawning a Claude Code subprocess - SDK events stream back through WebSocket to the browser in real-time
- Messages are persisted to JSON files on disk
- Session state is broadcast to all connected clients
- Cron Scheduler independently triggers sessions on schedule, reusing the same WS handler
- Feishu Bridge polls for DMs and injects them as chat messages, forwarding replies back
The Agent SDK manages the full Claude Code subprocess lifecycle — tool execution, multi-turn conversation, permission handling — so CtrlNect just needs to relay events.
CtrlNect supports both Anthropic and any OpenAI-compatible API, auto-detected from your environment:
Priority chain:
- Anthropic (default) — if
ANTHROPIC_API_KEYorANTHROPIC_AUTH_TOKENis set - OpenAI-compatible (fallback) — if only
OPENAI_API_KEYis set
CtrlNect includes a built-in inline proxy that transparently translates between the Anthropic Messages API format (used by the Claude Agent SDK internally) and the OpenAI Chat Completions format. No external proxy (LiteLLM, etc.) needed.
# Use OpenAI directly
export OPENAI_API_KEY="sk-..."
export OPENAI_MODEL="gpt-4o" # default: gpt-4o
# Use any OpenAI-compatible endpoint (Azure, Groq, Together, Ollama, etc.)
export OPENAI_API_KEY="..."
export OPENAI_BASE_URL="https://api.groq.com/openai/v1"
export OPENAI_MODEL="llama-3.3-70b-versatile"When OPENAI_API_KEY is set (and no Anthropic key), CtrlNect starts a local translation proxy on 127.0.0.1:19876 that:
- Receives Anthropic-format requests from the Claude Agent SDK
- Translates messages, tools, and streaming SSE format to OpenAI format
- Forwards to your configured OpenAI endpoint
- Translates the response (including streaming tool calls) back to Anthropic format
# Option 1: Anthropic (default)
export ANTHROPIC_API_KEY="sk-ant-..."
export ANTHROPIC_BASE_URL="https://api.anthropic.com" # optional
# Option 2: OpenAI-compatible API
export OPENAI_API_KEY="sk-..."
export OPENAI_BASE_URL="https://api.openai.com/v1" # optional, defaults to OpenAI
export OPENAI_MODEL="gpt-4o" # optional, defaults to gpt-4o
# Server port
export PORT=3001| Variable | Required | Description |
|---|---|---|
ANTHROPIC_API_KEY |
Yes* | Anthropic API key (sk-ant-...) |
ANTHROPIC_AUTH_TOKEN |
Yes* | Or Claude subscription token (cr_...) |
ANTHROPIC_BASE_URL |
No | Custom Anthropic-compatible endpoint |
OPENAI_API_KEY |
Yes* | OpenAI or compatible API key |
OPENAI_BASE_URL |
No | OpenAI-compatible base URL (default: https://api.openai.com/v1) |
OPENAI_MODEL |
No | Model to use in OpenAI mode (default: gpt-4o) |
PORT |
No | Server port (default: 3001) |
*One of ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, or OPENAI_API_KEY is required.
The header shows the active provider as a badge (Anthropic / OpenAI). Click it to switch at runtime — takes effect for new agent runs. Green = API key detected, red = missing.
New sessions default to the project directory. Override per session in the create dialog.
CtrlNect runs with bypassPermissions mode — Claude has full tool access without interactive confirmation prompts. This is by design for a self-hosted tool where you control the environment. Do not expose CtrlNect to untrusted networks.
If you start the server from within a Claude Code session, CtrlNect automatically strips the CLAUDECODE environment variable so the Agent SDK subprocess doesn't conflict with the parent session.
# Start both servers (backend + Vite dev server)
bun run dev
# Or start individually:
bun run dev:server # Backend on :3001 (auto-restart on changes)
bun run dev:client # Vite on :5173 (HMR, proxies to :3001)
# Type check
bun run typecheck
# Build for production
bun run buildThe dev.sh script adds quality-of-life for local development:
- Keyboard shortcuts:
r= restart Vite,R= restart server,q= quit - Control file:
echo r > .dev.sh.control(useful when no TTY) - Watchdog: auto-restarts the server if port 3001 goes down unexpectedly
REST endpoints:
GET /api/sessions List sessions
POST /api/sessions Create session
PATCH /api/sessions/:id Update session
DELETE /api/sessions/:id Delete session + messages
GET /api/sessions/:id/messages Get message history
GET /api/crons List cron jobs
POST /api/crons Create cron job
PATCH /api/crons/:id Update cron job
DELETE /api/crons/:id Delete cron job
POST /api/crons/:id/trigger Manually trigger cron job
POST /api/crons/import-system Import system crontab entries
GET /api/services List services
POST /api/services Create service
PATCH /api/services/:id Update service
DELETE /api/services/:id Delete service
GET /api/models Available models
GET /api/feishu/status Feishu bridge status
GET /health Health check
WebSocket protocol (/ws):
→ { type: "subscribe", sessionId }
→ { type: "chat", sessionId, text, images? }
→ { type: "interrupt", sessionId }
→ { type: "cron_trigger", cronId }
← { type: "stream_start", sessionId }
← { type: "agent_event", sessionId, event } // SDK events
← { type: "stream_end", sessionId, cost? }
← { type: "session_update", session }
← { type: "error", sessionId, message }
Built with the Claude Agent SDK