A web GUI for managing your Claude Code setup — sessions, memory, skills, projects, MCP servers, hooks, cron jobs, and more — all from a browser, reachable from any device.
Claude Code stores everything on disk under ~/.claude/ (session transcripts, memory files, skills, settings). This console reads and manages that data through a clean web interface instead of the CLI and a text editor.
A sidebar-navigated single-page app backed by a small aiohttp server. Each page maps to a slice of your Claude Code environment:
| Page | What it shows |
|---|---|
| Dashboard | At-a-glance counts (sessions, live sessions, memory files, skills, MCP servers, cron jobs, projects) + recent projects with quick links |
| Projects | Every project in your workspace with its session/memory counts, CLAUDE.md, memory files, and SOP/skill docs — plus configurable live/deployed app URLs |
| Sessions | All Claude Code sessions across projects — browse, view transcripts, rename, resume, or delete |
| Usage | Token/cost analytics across all transcripts — totals, daily trend, a GitHub-style activity heatmap with week drill-down, and breakdowns by model, project, and agent (all day boundaries in Pacific time) |
| Chat | Send a prompt to claude and stream the response (SSE), with per-request working-directory selection |
| Slack | Triage queue for unread Slack messages — auto-classifies and drafts replies, lets you edit/approve, and posts the approved reply (background scan/classify/draft workers) |
| Triage queue for unread Outlook email — auto-classifies and drafts reply-all responses you can edit/approve; approving saves an Outlook draft (never sends), with the same background workers as Slack | |
| Memory | Browse and edit ~/.claude memory files (typed: feedback / user / project / reference) with rendered markdown |
| Agents | Browse global and per-project role agents (.claude/agents/*.md) and their accumulated context |
| Skills | List installed skills and read their SKILL.md content |
| MCP Servers | View configured MCP servers |
| Hooks | Inspect configured Claude Code hooks |
| Cron Jobs | View scheduled jobs |
| Workers | Live status of background workers (scan/classify/draft, session reapers) — running / stopped / errored, with last-run times |
| Tasks | Track tasks |
| Plugins | View installed plugins |
| Settings | Inspect Claude Code settings |
The server runs locally on 127.0.0.1:9000 by default and serves a pre-built React frontend — no Node.js required at runtime.
- Python ≥ 3.10 and Node.js (for building the frontend).
- Claude Code installed and used at least once. The console is a viewer and manager for the data Claude Code stores in
~/.claude/; if you've never runclaude, the pages will simply be empty (and the Chat page needs theclaudebinary on yourPATH).
# 1. Clone
git clone https://github.com/caoxulai/claude-code-console.git claude-web
cd claude-web
# 2. Build the frontend (produces frontend/dist/, then copies it to server/static/)
cd frontend && npm install && npm run build && cd ..
# 3. Install the package
python3 -m venv .venv
.venv/bin/pip install -e .Step 2 is what ships the UI: npm run build writes frontend/dist/, and its
postbuild hook (scripts/copy-dist.mjs) copies that into server/static/,
which pip install picks up as package data. Build before installing, or the
installed server has no UI to serve.
For frontend hot-reload during development, run the Vite dev server (proxies
/api to the backend) alongside the backend:
.venv/bin/claude-web start --no-browser # backend on :9000
cd frontend && npm run dev # frontend on :9001 (proxies /api to :9000).venv/bin/claude-webThe server starts on http://127.0.0.1:9000 and your browser opens to it. Leave
the terminal running — closing it (or pressing Ctrl-C) stops the server.
Other useful commands:
claude-web start --port 8888 # use a different port
claude-web start --no-browser # don't auto-open a browser (headless/remote host)
claude-web setup # create the optional config file (see Configuration)| Symptom | Fix |
|---|---|
claude-web: command not found |
Activate the venv (source .venv/bin/activate) or run .venv/bin/claude-web. |
| Browser doesn't open (remote/headless) | Use claude-web start --no-browser, then open http://127.0.0.1:9000 yourself (tunnel/port-forward if remote — see Accessing from other devices). |
| Pages are empty | You haven't used Claude Code yet, or your projects live somewhere other than ~/workspace/projects — set CLAUDE_WEB_WORKSPACE (see Configuration). |
| Chat page errors | The claude binary isn't on your PATH. Install Claude Code (Prerequisite 2). |
Optional config at ~/.claude-web/config.json (or set CLAUDE_WEB_CONFIG to point elsewhere). Run claude-web setup to scaffold it.
{
"projectUrls": {
"my-project": [
{ "url": "http://127.0.0.1:8080", "label": "Local", "type": "local" },
{ "url": "https://my-app.example.com", "label": "Deployed", "type": "deployed" }
]
}
}projectUrls keys are matched as substrings against project names; matched projects show clickable URL pills on the Dashboard and Projects pages.
The web console has no interactive approval channel, so console-driven sessions
run without the per-edit confirmation prompt you'd see in the CLI — Claude
executes tool calls, including file edits and writes, without asking. The mode
is configurable via the permissionMode config key or the
CLAUDE_WEB_PERMISSION_MODE environment variable (the env var takes
precedence):
{
"permissionMode": "bypassPermissions"
}Allowed values are acceptEdits, auto, bypassPermissions, default,
dontAsk, and plan. The default is bypassPermissions; a missing or
unrecognized value falls back to bypassPermissions rather than being passed
through to the CLI.
Safety caveat: with the default
bypassPermissions, the console makes changes to your filesystem (and runs commands) without prompting. Only run goals you trust, and prefer a more restrictive mode (e.g.planoracceptEdits) if you want a tighter blast radius.
| Variable | Default | Purpose |
|---|---|---|
CLAUDE_WEB_PORT |
9000 |
Server port |
CLAUDE_WEB_WORKSPACE |
~/workspace/projects |
Where to look for projects (symlink-resolved) |
CLAUDE_WEB_CONFIG |
~/.claude-web/config.json |
Config file location |
CLAUDE_WEB_CWD |
$HOME |
Default working directory for chat |
CLAUDE_WEB_PERMISSION_MODE |
bypassPermissions |
Tool-call approval mode for console runs (see Permission mode) |
The server binds to 127.0.0.1 and has no authentication — it can read/write
your ~/.claude files and run shell commands. The recommended way to reach it
from a phone or laptop is to keep it on loopback and forward the port over a
trusted channel: an SSH port-forward from the remote host running the console,
then open the forwarded URL.
This is the common setup: the console runs on a remote host (e.g. a dev server),
but you want to use it in your laptop's browser. Because the server stays on
127.0.0.1 of the remote host, you reach it with an SSH port-forward — a
secure tunnel that maps a port on your laptop to 127.0.0.1:9000 on the host.
Nothing is exposed to the network.
-
On the remote host, start the console.
--no-browserskips the browser-open attempt (which no-ops on a headless host anyway — plainclaude-webworks the same for tunneling):claude-web start --no-browser
Leave this terminal running.
-
On your laptop, open a second terminal and forward the port. Use the same host you normally SSH to:
ssh -N -L 9000:127.0.0.1:9000 <your-remote-host>
<your-remote-host>is whatever you use today, e.g. a hostname or an alias from your~/.ssh/config.-Nmeans "just forward, don't open a shell." Leave it running while you use the console; pressCtrl-Cto disconnect.- If port
9000is already taken on your laptop, map a different local port:-L 9100:127.0.0.1:9000, then use:9100in step 3.
-
On your laptop, open http://127.0.0.1:9000 (or
:9100if you remapped). You're now using the console running on the remote host.
Tip: the forward adds no new auth of its own — anyone who can SSH to your host can already reach the port. Make sure your SSH session is established before step 2.
Binding directly to a routable interface (--host 0.0.0.0) is refused by
default, because an unauthenticated server that can execute shell commands is
remote code execution for anyone who can reach the port. If you genuinely
control the network and accept that risk, pass --allow-remote (or set
CLAUDE_WEB_ALLOW_REMOTE=1); the server then prints a warning and binds anyway.
.venv/bin/python -m pytest tests/ -q # run tests
cd frontend && npm run build # rebuild the UIThe build is a two-step hybrid: npm run build produces the static frontend
under frontend/dist/, then its postbuild hook runs scripts/copy-dist.mjs,
which wipes and repopulates server/static/ from it. server/static/ is
gitignored (build output) but shipped as package data, so the installed server
serves the UI at runtime with no Node present. In dev mode the server prefers
frontend/dist/, and on every start it prints one line naming the directory it
actually serves, so a stale or absent build is visible:
[claude-web] INFO server.app: Frontend dist: /path/to/server/static (packaged server/static)
[claude-web] INFO server.app: Frontend dist: /path/to/frontend/dist (missing — UI not served)
Packaging metadata lives entirely in pyproject.toml — version, dependencies,
the claude-web console script, and package data. There is no setup.py;
bump the version in pyproject.toml only.
Before opening a change, run the repo check gate:
bash scripts/check.shIt runs, in order, and exits non-zero on the first failure:
- Backend tests —
.venv/bin/python -m pytest tests/ -q(fatal) - Frontend tests —
cd frontend && npm test(fatal) - Frontend build —
cd frontend && npm run build(fatal) - Lint —
npx eslint .(non-fatal, reported only)
The lint step is intentionally non-fatal: the repo carries ~50 pre-existing lint errors scheduled for a later clean-up batch, so its result is printed but does not fail the gate. Once that batch lands, an in-script comment marks exactly where to flip lint to fatal. The script resolves the repo root from its own location, so it works from any working directory.
server/ aiohttp backend
app.py application factory + SPA serving
cli.py claude-web CLI (start / setup)
routes/ one module per API area (sessions, memory, skills, …)
frontend/ React + Vite single-page app
src/pages/ one component per console page
tests/ pytest (server + routes)