A self-hosted web application (distributed as npm package) that provides a browser-based interface for Claude Code CLI sessions. Create projects, each with a persistent terminal running Claude Code, and interact with them through a real-time terminal UI.
Current version: v1.5.73 | GitHub | MIT License
- Project Management: Create, open, start, stop, and delete projects from a dashboard
- Real-time Terminal: Full xterm.js terminal in the browser, connected to Claude Code via WebSocket
- Persistent Sessions: Conversation history stored in each project's
.ccweb/folder — survives uninstall/reinstall (max 20 sessions per project, auto-pruned) - Permission Modes: Run Claude in limited mode (asks before acting) or unlimited mode (
--dangerously-skip-permissions) - Shortcuts Panel: Define reusable prompt commands at project or global level with inheritance
- Session History: Browse past conversations with full message history
- Graph Visualization: Topology graph from
.notebook/graph.yamlwith zoom/pan (layered DAG layout) - File Browser: Browse directories and preview/edit files with zoom-level memory per file
- Auto-restart: Terminals automatically recover from crashes
- Usage Tracking: Monitor Claude Code plan usage directly from the dashboard
- CLI Update:
ccweb updatestops the server and updates to the latest npm version - Localhost Auto-auth: Local access skips login entirely; JWT only required for remote access
- Auto Port Switching: Backend tries ports 3001–3020 and reports the actual port
- Network Access Modes: Local only (127.0.0.1), LAN (private IPs), or public — selectable at startup
- Cloud Backup: Incremental backup to Google Drive, OneDrive, or Dropbox (multi-provider parallel upload, scheduled or manual)
- SkillHub: Browse, search, and download community-shared shortcut commands from GitHub; share your own with one click
- Ambient Sound: Background sounds (singing bowl, rain, wind, stream, etc.) that play when LLM is active, with custom upload support
- Dark/Light Theme: Toggle between themes
- Real-time Activity Push: Dashboard receives project activity updates via WebSocket (no polling), with semantic status badges (Thinking/Writing/Tool)
- Code Splitting: Lazy-loaded routes and heavy components (Office preview, Graph, Chat) — only loaded on demand
- Global State (Zustand): Auth and project state shared across pages without prop drilling
- Error Boundaries: Graceful error handling with toast notifications instead of alerts
- Node.js >= 18
- Claude Code CLI installed and authenticated (
claudecommand available in PATH)
The fastest way to get running:
# Try without installing (one-time)
npx @tom2012/cc-web
# Or install globally
npm install -g @tom2012/cc-web
ccwebOn first launch you'll be prompted to set a username and password. The server auto-selects an available port (starting from 3001) and opens your browser automatically.
ccweb # start (interactive prompts)
ccweb start --daemon # start in background, no prompts
ccweb start --foreground # start in foreground, no prompts
ccweb start --local # local only (default, most secure)
ccweb start --lan # allow LAN access
ccweb start --public # allow public access
ccweb stop # stop background server
ccweb status # show PID, port, data location
ccweb open # open browser to running server
ccweb setup # reconfigure admin username / password
ccweb register # register a new user (interactive)
ccweb update # stop server & update to latest version
ccweb enable-autostart # start automatically on login
ccweb disable-autostart # remove auto-start
ccweb logs # tail background log fileAll user data (credentials, projects, sessions) is stored in ~/.ccweb/ and survives package updates.
Auto-start on login: ccweb enable-autostart registers a launchd agent (macOS) or systemd user service (Linux) so the server starts automatically when you log in.
# 1. Clone the repository
git clone https://github.com/zbc0315/cc-web.git
cd cc-web
# 2. Install dependencies
npm run install:all
# 3. First-time setup (creates login credentials)
npm run setup
# 4. Start backend (Terminal 1)
npm run dev:backend
# 5. Start frontend (Terminal 2)
npm run dev:frontendOpen http://localhost:5173 in your browser.
Browser (React/Vite :5173 dev | Express :3001 prod)
│
├── REST API ──────────► Express (:3001, auto-switches port if busy)
│ │
└── WebSocket ─────────► ws server (same port)
│
TerminalManager
│
node-pty (PTY, user's $SHELL -ilc "claude")
│
claude / claude --dangerously-skip-permissions
| File | Purpose |
|---|---|
index.ts |
Express + WS server, route mounting, static frontend serving, auto port switching, project config migration |
auth.ts |
JWT middleware, localhost auto-auth (isLocalRequest), generateLocalToken() |
config.ts |
File-based JSON store, .ccweb/ per-project config helpers |
terminal-manager.ts |
PTY lifecycle ($SHELL -ilc "claude"), scrollback buffer (5 MB), auto-restart, activity tracking |
session-manager.ts |
Tails Claude's JSONL files, stores sessions in .ccweb/sessions/, prunes to latest 20 per project |
usage-terminal.ts |
Claude Code OAuth usage stats |
routes/auth.ts |
POST /login, GET /local-token (localhost only) |
routes/projects.ts |
CRUD + start/stop + POST /open (restore from .ccweb/) |
routes/update.ts |
GET /check-running, POST /prepare (save memory → wait idle → stop all, used by in-app update flow) |
routes/filesystem.ts |
Directory browser, file read/write |
routes/shortcuts.ts |
Global shortcut CRUD with inheritance |
routes/backup.ts |
Cloud backup API (providers, OAuth, backup trigger, schedule) |
routes/sounds.ts |
Sound file API: presets, download, upload, streaming |
routes/skillhub.ts |
SkillHub API: fetch skills index, submit via GitHub Issue, download to global shortcuts |
backup/ |
CloudProvider implementations (Google Drive, OneDrive, Dropbox), engine, scheduler |
| File/Dir | Purpose |
|---|---|
App.tsx |
Router with auto-auth PrivateRoute (local token for localhost) |
pages/LoginPage.tsx |
Login form, auto-login on localhost |
pages/DashboardPage.tsx |
Project grid, new/open project, fullscreen toggle, update button |
pages/ProjectPage.tsx |
Three-panel layout: FileTree | WebTerminal | RightPanel |
pages/SettingsPage.tsx |
Settings: cloud accounts, backup strategy, backup history |
components/SoundPlayer.tsx |
Audio playback engine (fade in/out, loop/interval modes) |
components/SoundSelector.tsx |
Sound selection and configuration UI popover |
components/WebTerminal.tsx |
xterm.js terminal with fit addon |
components/RightPanel.tsx |
Three tabs: Shortcuts / History / Graph |
components/ShortcutPanel.tsx |
Project + global shortcuts, dialog editor for add/edit |
components/GraphPreview.tsx |
SVG topology graph of .notebook/graph.yaml (layered DAG, zoom/pan) |
components/FileTree.tsx |
Expandable directory tree |
components/FilePreviewDialog.tsx |
File viewer with plain/rendered/edit modes, zoom memory per file |
components/UpdateButton.tsx |
Version display and update check |
pages/SkillHubPage.tsx |
SkillHub browse, search, tag filter, download page |
components/OpenProjectDialog.tsx |
Open existing project from .ccweb/ folder |
components/NewProjectDialog.tsx |
3-step wizard: name → folder → permissions |
lib/api.ts |
Typed REST client, dynamic base URL (relative in prod, localhost:3001 in dev) |
lib/websocket.ts |
useProjectWebSocket hook, dynamic WS URL |
components/ui/ |
shadcn/ui components (zinc theme) |
Application data (~/.ccweb/ for npm install, data/ for dev):
data/
├── config.json ← credentials & JWT secret
├── projects.json ← registered project list
└── global-shortcuts.json ← shared shortcut commands
Per-project data (inside each project folder, portable):
your-project/
├── .ccweb/
│ ├── project.json ← project metadata (id, name, mode, created)
│ └── sessions/ ← conversation history (max 20, auto-pruned)
│ └── {timestamp}-{uuid}.json
└── .notebook/ ← structured notes
├── pages/
└── graph.yaml
The .ccweb/ folder travels with the project. If you reinstall CC Web later, use Open Project to point at the folder and all history is restored.
Client → Server:
| Type | Payload | Purpose |
|---|---|---|
auth |
{ token } |
Authenticate (skipped for localhost) |
terminal_subscribe |
{ cols, rows } |
Subscribe + replay scrollback |
terminal_input |
{ data } |
Keystrokes to PTY |
terminal_resize |
{ cols, rows } |
Resize PTY |
Server → Client:
| Type | Payload | Purpose |
|---|---|---|
connected |
{ projectId } |
Ready |
status |
{ status } |
running/stopped/restarting |
terminal_data |
{ data } |
PTY output |
terminal_subscribed |
{} |
Subscription confirmed |
error |
{ message } |
Error |
Localhost WebSocket connections are pre-authenticated — no auth message needed.
| Method | Endpoint | Purpose |
|---|---|---|
POST |
/api/auth/login |
Login, returns JWT |
GET |
/api/auth/local-token |
Get local token (localhost only) |
GET |
/api/projects |
List all projects |
POST |
/api/projects |
Create new project |
POST |
/api/projects/open |
Open existing project by folder path |
DELETE |
/api/projects/:id |
Delete project |
PATCH |
/api/projects/:id/start |
Start project terminal |
PATCH |
/api/projects/:id/stop |
Stop project terminal |
GET |
/api/projects/:id/sessions |
List sessions |
GET |
/api/projects/:id/sessions/:sid |
Get session with messages |
GET |
/api/projects/activity |
Terminal activity timestamps |
GET |
/api/projects/usage |
Claude Code usage stats |
GET/POST/PUT/DELETE |
/api/shortcuts |
Global shortcut CRUD |
GET |
/api/filesystem |
Browse directories |
POST |
/api/filesystem/mkdir |
Create folder |
GET/PUT |
/api/filesystem/file |
Read/write files |
GET |
/api/update/check-running |
Check if processes are running |
POST |
/api/update/prepare |
Save memory, wait idle, stop all |
GET/POST/DELETE |
/api/backup/providers |
Cloud backup provider CRUD |
GET |
/api/backup/auth/:id/url |
Get OAuth2 authorization URL |
GET |
/api/backup/auth/callback |
OAuth2 redirect callback |
POST |
/api/backup/run/:projectId |
Trigger manual backup |
GET/PUT |
/api/backup/schedule |
Backup schedule config |
GET/PUT |
/api/backup/excludes |
Exclude patterns |
GET |
/api/backup/history |
Backup history |
GET |
/api/skillhub/skills |
Fetch SkillHub index (cached 5 min) |
POST |
/api/skillhub/submit |
Submit skill via GitHub Issue |
POST |
/api/skillhub/download/:id |
Download skill as global shortcut |
# Build everything
npm run build
# Run backend (serves built frontend statically)
cd backend
npm start
# Or use pm2
pm2 start backend/dist/index.js --name cc-web
pm2 save
pm2 startupEnvironment variables:
| Variable | Purpose | Default |
|---|---|---|
CCWEB_DATA_DIR |
Override data directory | data/ relative to backend |
CCWEB_PORT |
Preferred server port | 3001 |
CCWEB_ACCESS_MODE |
Network access mode (local/lan/public) |
local |
# Full build (frontend + backend)
npm run build
# Release checklist:
# 1. Bump version in package.json, UpdateButton.tsx, README.md, CLAUDE.md
# 2. Update docs with new features
# 3. npm run build
# 4. git add -A && git commit && git push
# 5. npm publish --registry https://registry.npmjs.org --access=publiccc-web/
├── package.json ← Root scripts + npm package config
├── bin/ccweb.js ← CLI entry point (ccweb command)
├── setup.js ← Interactive credential setup
├── backend/
│ ├── package.json
│ ├── tsconfig.json
│ └── src/ ← TypeScript source
└── frontend/
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.js
└── src/ ← React + TypeScript source
- PTY-first: Spawns the real
claudeCLI vianode-ptyusing the user's$SHELL -ilc. All Claude Code features (slash commands, MCP, hooks, etc.) work natively. - No database: Pure JSON files, in-memory CRUD. Simple to understand, back up, and debug.
- Per-project
.ccweb/: Data travels with the project folder, survives app reinstall. - Session tailing: Reads Claude Code's native JSONL (
~/.claude/projects/) rather than parsing PTY output. - Scrollback buffer: 5 MB per terminal for client reconnect replay.
- Session pruning: Keeps latest 20 sessions per project, deletes oldest on new session start.
- Zoom memory:
FilePreviewDialogpersists zoom level per file path inlocalStorage. - Auto port switching: Backend tries ports 3001–3020, reports actual port via IPC.
- Localhost auto-auth: Local requests skip JWT verification entirely.
- Add the route handler in
backend/src/routes/*.ts - Auth is already applied — routes are mounted under
authMiddleware - Add the typed call in
frontend/src/lib/api.ts - Call it from your component
- Create
frontend/src/pages/YourPage.tsx - Add a route in
frontend/src/App.tsx - Use existing UI components from
frontend/src/components/ui/
Backend: Node.js, Express, WebSocket (ws), node-pty, TypeScript Frontend: React 18, Vite, Tailwind CSS, shadcn/ui, xterm.js, TypeScript Auth: JWT (bcryptjs for password hashing)
MIT