Change your dev environment as fast as you change your code. Deep Steve is a hackable web UI for your Claude Code and Codex agents: it runs on your machine, and your agents build what you ask for right into it.
Run agent sessions side-by-side in browser tabs, each a real PTY with full terminal capabilities and persistent conversation history. OpenCode, Pi, and Hermes also run, as experimental integrations — docs/agents.md is the per-agent breakdown of what each one actually gets.
Runs on macOS and Linux. The daemon is managed by a LaunchAgent on macOS and a systemd user unit on Linux; both are driven through one interface (service.sh). On Linux tmux is a required dependency, not optional — sessions live inside it so they survive the restarts systemd performs on every crash and upgrade.
Security notice: DeepSteve is localhost-first with token authentication. Every surface (web UI WebSocket, MCP endpoint, REST APIs) enforces a Host allowlist, an Origin allowlist, and a per-install bearer token — closing the drive-by-webpage / DNS-rebinding hole. The browser is authenticated transparently via an HttpOnly cookie (no login screen). Auth is always on; do not expose it to an untrusted network regardless.
deepsteve runs agent sessions (see docs/agents.md) inside tmux panes, connected to the browser via WebSocket. This is the default whenever tmux is installed, and it is what makes sessions durable: the agent belongs to the tmux server rather than to deepsteve, so it keeps working through a daemon crash, a ./restart.sh, an auto-update, and closing the browser. Reconnecting reattaches the same live pane.
deepsteve runs its own tmux server, on the socket ~/.deepsteve/tmux.sock, so its panes never mix with the ones you start yourself — tmux ls shows yours, tmux -S ~/.deepsteve/tmux.sock ls shows deepsteve's. (Set tmuxSocket in ~/.deepsteve/settings.json if that path won't work, e.g. a home directory on a network filesystem.) One consequence worth knowing: uninstalling ends deepsteve's sessions, since removing ~/.deepsteve would otherwise leave panes running that nothing could reach again.
Without tmux, deepsteve falls back to node-pty, which runs each session as a child process of the Node server. Everything works the same except durability — those sessions die with the server. The fallback is deliberately visible: the ⚙ button carries an orange dot and Settings → Terminal says which engine you are on and, if tmux wasn't found, where it looked. Install tmux to get durable sessions; if yours lives somewhere the search can't see, point the tmuxBinary setting at it.
- Multiple sessions - Open as many Claude Code or Codex instances as you need in separate tabs
- Real terminal emulation - PTY support via xterm.js.
- Session persistence - Conversations survive server restarts and page refreshes
- GitHub issue picker - Pick an issue from any repo and launch a session with a worktree and prompt
- Agent coordination - MCP tools let sessions chat, share tasks, and coordinate work
- Runs as a daemon - Always available in the background on your Mac
- Themes - Customize the UI with CSS files in
~/.deepsteve/themes/ - Mods - Extend deepsteve with mods for task tracking, agent chat, browser reflection (yes, deepsteve can see itself), or create your own
npm install -g deepsteve
deepsteve startOr without npm — this one bundles its own Node if you don't have one:
curl -fsSL deepsteve.com/install.sh | bashBoth land the same tree in ~/.deepsteve and register the same LaunchAgent (macOS) or systemd user unit (Linux); they differ only in where the files come from. deepsteve start is also the upgrade step — see Installation (npm) for the rest of the CLI.
- macOS, or Linux with a systemd user instance
- Node.js — needed for
npm install -g; the curl installer downloads its own if you have none - tmux — required on Linux (both installers refuse without it), optional on macOS, where node-pty is a supported fallback
- Claude Code or Codex CLI installed — these are the two supported agents. OpenCode, Pi, and Hermes work too, but get no deepsteve MCP tools and no skills; see docs/agents.md.
If you've cloned the repo:
./release.sh # generates install.sh from source files
./install.sh # installs to ~/.deepsteve/install.sh is gitignored — it's generated by release.sh, which embeds all source files as heredocs. You must run release.sh first.
npm install -g deepsteve puts the package in npm's global prefix; deepsteve start is what turns it into a running install. It does what install.sh does: deploys the runtime into ~/.deepsteve, registers the LaunchAgent or systemd user unit, starts the daemon, and prints the URL. The daemon runs from ~/.deepsteve rather than from the npm package, because that directory is also where it writes — mods you install, themes, sessions and settings all live there.
deepsteve start |
deploy, register the service, start the daemon (also the upgrade step after npm install -g deepsteve@latest) |
deepsteve stop |
stop the daemon — sessions live in tmux and survive |
deepsteve restart |
restart, after confirming in the browser (--force moves the confirmation to your terminal) |
deepsteve status |
service state, port, health, log locations — read-only |
deepsteve uninstall |
remove the service, ~/.deepsteve, and deepsteve's own tmux server |
Updating is npm install -g deepsteve@latest && deepsteve start. An npm install deliberately has no in-app auto-update: the package lives in a prefix deepsteve may not own, so Settings → Updates tells you the command instead of offering a button.
Open http://deepsteve.localhost:3000 in your browser (*.localhost resolves to loopback; http://localhost:3000 also works and redirects there).
- Click + to create a new Claude Code or Codex session in any directory
- Click the GitHub icon to pick an issue — deepsteve creates a worktree and starts a session with the issue as a prompt
- On a first run, Show me around starts a guided tour — an agent session opens and builds you a tour page in a second tab. Re-run it any time from Settings → Tips
- Right-click a tab to rename it
- Use Shift+Enter for multi-line input
Press ⌘K to open the command palette for quick keyboard-driven access to tabs, settings, and custom commands.
- Type to filter, arrow keys to navigate, Enter to execute
- Built-in commands: New Tab, Close Tab, Settings, Mods, Next/Previous Tab
- Switch between open tabs by name
- Customize the shortcut in Settings → Keyboard
Drop executable scripts into ~/.deepsteve/commands/ and they appear automatically in the palette:
cat > ~/.deepsteve/commands/hello.sh << 'EOF'
#!/bin/bash
echo "Hello from DeepSteve!"
EOF
chmod +x ~/.deepsteve/commands/hello.shThe command name is derived from the filename (hyphens → spaces, title-cased). For custom names, add a JSON sidecar file alongside the script:
// ~/.deepsteve/commands/hello.json
{ "name": "Say Hello", "description": "Prints a friendly greeting" }Scripts receive environment variables: DEEPSTEVE_SESSION_ID (active tab's shell ID), DEEPSTEVE_CWD (that tab's working directory), DEEPSTEVE_TAB_NAME, DEEPSTEVE_WORKTREE, DEEPSTEVE_WINDOW_ID, DEEPSTEVE_API_URL, and DEEPSTEVE_API_TOKEN (send as Authorization: Bearer $DEEPSTEVE_API_TOKEN when calling the REST API).
Customize the UI with CSS theme files. See the Themes Guide for details.
Extend deepsteve with visual mods — alternative views, panels, and MCP tools for your sessions. See the Mods Guide for details.
Skills enabled in the Mods panel are installed for Claude Code and Codex from the canonical prompts in skills/*.md. The experimental agents (OpenCode, Pi, Hermes) get no skills at all — enabling one in the Mods panel does nothing for those sessions.
- Claude Code: invoke
chat.mdas/deepsteve:chat, with any arguments after the slash command. - Codex: mention the generated skill as
$deepsteve-chat, with any arguments after the mention.
DeepSteve keeps Claude command copies under ~/.claude/commands/deepsteve and exposes Codex-compatible SKILL.md folders through managed symlinks under ~/.agents/skills. Existing non-symlink paths there are left untouched.
Claude Code sessions can run against any Anthropic-compatible API endpoint — OpenRouter, a corporate gateway, a local proxy — using a custom Claude config profile. No mod or proxy needed; each profile is just a separate CLAUDE_CONFIG_DIR with its own provider env, and tabs opt into it per session.
Example: running GLM 5.2 through OpenRouter.
1. Create a config directory with a settings.json that points Claude Code at the provider:
mkdir ~/.claude-open-router
cat > ~/.claude-open-router/settings.json <<'EOF'
{
"env": {
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api",
"ANTHROPIC_AUTH_TOKEN": "sk-or-v1-YOUR-OPENROUTER-KEY",
"ANTHROPIC_API_KEY": "sk-or-v1-YOUR-OPENROUTER-KEY",
"ANTHROPIC_MODEL": "z-ai/glm-5.2",
"ANTHROPIC_SMALL_FAST_MODEL": "anthropic/claude-haiku-4.5",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "anthropic/claude-opus-4.8",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "anthropic/claude-sonnet-5",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "anthropic/claude-haiku-4.5"
},
"model": "z-ai/glm-5.2"
}
EOFANTHROPIC_MODEL/model pick the main model by the provider's id; the ANTHROPIC_DEFAULT_*/SMALL_FAST entries map Claude Code's internal model tiers to provider ids so background/fast paths keep working.
2. Register it in deepsteve: Settings → Custom Claude configs → add a row with a display name (e.g. OpenRouter) and the config dir (~/.claude-open-router).
3. Open a tab with it: the profile appears alongside the agents in the new-tab menu. Sessions started with it spawn Claude Code with CLAUDE_CONFIG_DIR pointing at that directory — provider, model, auth, and conversation history all stay isolated per profile, and regular Claude tabs are unaffected.
Note: plain base-URL routing covers normal use. OpenRouter-specific request-body fields (e.g. pinning a backend provider) aren't expressible this way — that was proposed as a transform-proxy mod in #499 and closed as not needed in practice.
./restart.sh # restart (browser tabs silently reconnect)
./restart.sh --refresh # restart + force browser reload
./status.sh # is it running, on what port, where are the logs?./status.sh is read-only and safe to allowlist; ./restart.sh is not. Both work the
same on either platform — service.sh is the single interface over launchd and the
systemd user unit, so nothing above needs a platform-specific variant.
Logs:
| macOS | ~/Library/Logs/deepsteve.log |
| Linux | ~/.local/share/deepsteve/logs/deepsteve.log |
(./status.sh prints the right one for this machine, along with the service state and,
on Linux, whether user lingering is enabled — without it the daemon stops when your last
login session ends and does not start at boot.)
- Serves
http://deepsteve.localhost:3000on loopback only by default — not accessible from the network.--bind <address>widens that, and logs a warning banner when it does. - Token-authenticated on every surface (WebSocket, MCP, REST): a Host allowlist + Origin allowlist + a per-install secret at
~/.deepsteve/auth-token(0600) close the cross-origin / DNS-rebinding hole. The browser gets the token as an HttpOnly cookie; other clients send it asAuthorization: Bearer <token>. Widen access with--allow-origin/--allow-host. - The UI lives on
http://deepsteve.localhost:3000— still loopback-only (RFC 6761), but with its own browser cookie jar so the auth cookie can't be evicted when other local dev apps fill the sharedlocalhostjar. Browser navigations tolocalhost:3000get a 302 there; disable with--no-canonical-redirect(orDEEPSTEVE_NO_CANONICAL_REDIRECT=1) if*.localhostdoesn't resolve on your system. - Each agent session runs with the permissions of the user who installed deepsteve
WebXR requires a secure context (HTTPS). DeepSteve has built-in HTTPS support for this:
- Start the server with HTTPS enabled, bound to all interfaces:
cd ~/.deepsteve && node server.js --bind 0.0.0.0 --https-
Navigate to
https://<your-laptop-ip>:3443on the Quest browser. The server logs will show the exact URL to use. -
On first connect you'll see a certificate warning — accept it to proceed. (Install mkcert on your laptop for locally-trusted certificates with no warnings.)
Certificates are auto-generated at startup and stored in ~/.deepsteve/certs/. They regenerate automatically when your LAN IP changes.
Bug reports and feature requests are welcome on GitHub Issues. Pull requests are welcome.
~/.deepsteve/uninstall.sh

