@tsconfigdotjson/vibe-os
v0.7.0
Published
A coding desktop in the browser, on a box you own. Git worktrees per piece of work, terminals that survive a reload, and SSH that runs in the tab.
Maintainers
Readme
A coding desktop in the browser, on a box you own.
Switch between the git repos on a machine, spin up a worktree per piece of work, and open terminals into it, from anything with a browser.
Open the desktop and you get a project picker, a sidebar of workspaces, and windows you can drag around a snap grid. Every window is a dtach session in its workspace's worktree, so closing the tab and coming back tomorrow finds whatever was running still running.
Down the right is a rail of profiles, the roles you work as. QA Engineer, Backend Manager, whatever your work divides into. Each carries a colour, a harness with its settings, and a standing prompt. Click one and you get a window tinted in its colour with Claude already running in the worktree.
There is nothing to configure. No key to copy and paste, no authorized_keys to
edit, no sshd_config change, and no root.
Contents
Quick start
Try it locally
docker run --rm -p 127.0.0.1:8080:80 -e VIBE_OS_NO_TOKEN=1 ghcr.io/tsconfigdotjson/vibe-os
open http://localhost:8080A blank Debian box with sshd, dtach and git, the same shape as a fresh VPS. The
token is off, so the port is on loopback only: a vibe-os with no token is a
shell for anyone who can reach it. The image has no harness, so profiles open a
shell; from a clone, docker compose up --build adds Claude Code.
Or set up a VPS
On a fresh Debian or Ubuntu box, as any user with sudo (or as root, which creates one):
curl -fsSL https://raw.githubusercontent.com/tsconfigdotjson/vibe-os/main/install.sh | sh
vibe-os setup # private: reached over your tailnet
vibe-os setup --domain box.example.com # or public: HTTPS on a name pointing at the boxsetup runs the same checks as doctor and offers to fix each one, printing
every command before it runs it. Private puts it behind
Tailscale, so only your tailnet can reach
it. Public serves it to the internet with its own Let's Encrypt certificate and
the token as the gate (On a public address).
See Setup for each step.
For an agent with SSH access to the box, the prompt is one line:
Install vibe-os with
curl -fsSL https://raw.githubusercontent.com/tsconfigdotjson/vibe-os/main/install.sh | sh, then runvibe-os setup --yesand show me its output.
Drive it from an agent (MCP)
vibe-os is also an MCP server, at /mcp on the same URL as the desktop. An agent
in Claude Code, Codex, Cursor or any other MCP client can create workspaces,
open windows as profiles, hand them prompts, wait for them to finish and read
their answers.
It needs the URL and the server's token. On the box, the token is in
~/.vibe-os/config.json:
export VIBE_OS_TOKEN=$(sed -n 's/.*"token": *"\([^"]*\)".*/\1/p' ~/.vibe-os/config.json)Then, for Claude Code:
claude mcp add --transport http vibe-os https://your-box.example.com/mcp \
--header "Authorization: Bearer $VIBE_OS_TOKEN"MCP has the setup for Codex, Cursor, VS Code, Windsurf, Gemini CLI and Claude Desktop, and the tools it offers. The token is a shell on the box: keep it out of anything that is shared or logged.
How it works
┌─────────────────┐ ┌──────────────────┐ ┌──────┐
│ browser │ wss://…/websocket │ vibe-os (bun) │ TCP │ sshd │
│ ssh.wasm │ ─────────────────────► │ :80 / :443 │ ─────► │ :22 │
│ private key │ │ byte pipe │ │ │
│ in IndexedDB │ POST /api/ssh/… │ + SSH CA │ │dtach │
└─────────────────┘ ─────────────────────► └──────────────────┘ └───▲──┘
│
┌─────────────────┐ │
│ your terminal │ ssh -t … vibe-os attach quiet-amber-otter-1 │
│ ssh + dtach │ ──────────────────────────────────────────────────────►─┘
└─────────────────┘The SSH protocol runs inside the browser. Key exchange, authentication and channel multiplexing happen in a Go/WASM sandbox in the tab. The server is a byte pipe: it never sees plaintext and holds no credentials for your session.
vibe-os is its own short-lived certificate authority. On first start it
generates an ed25519 CA and appends one cert-authority line to
~/.ssh/authorized_keys. Each window generates a keypair in the browser, keeps
the private half in IndexedDB, and posts only the public half to be signed.
Certificates last 12 hours. Revoking every browser that ever connected is
deleting one line.
Windows survive because dtach owns them, not vibe-os. Each certificate
carries a force-command that attaches to the window's dtach socket, creating
it in the workspace's worktree if it is not there yet. Restarting the server,
reloading the page and closing the laptop all leave the session running.
The bottom route in that diagram is the same session by a different door: your own ssh client, your own key, straight to sshd. See popping out.
Documentation
| | | | --- | --- | | Using it | projects, workspaces, profiles, prompts, popping a terminal out, the desktop and its shortcuts | | Harnesses | Hermes, Cursor, Codex and Pi, and adding your own | | Deploying on a VPS | what the box needs, setup, Tailscale or a public address, the firewall, memory, an always-on browser, updating | | Security | the token, what it gates, and what to put in front of it | | CLI reference | every command and flag | | API | driving workspaces and windows over HTTP | | MCP | the same, as MCP tools for Claude Code, Codex and Cursor | | Development | building, testing and releasing |
vibe-os hands out shell access to the machine it runs on. Treat access to it as equivalent to SSH access to the box, and read Security before exposing it anywhere.
Contributing
Issues and pull requests welcome. Before opening one:
bun run check # lint, typecheck and tests, the same three CI runsTests live in test/ for the server and beside the code for the browser.
Anything with logic worth trusting should arrive with some.
docs/gotchas.md lists the traps in this codebase that cost someone an afternoon. Worth a skim before changing the terminal or the session plumbing.
Comments in this repository explain why rather than what. If you undo a documented decision, update the reasoning with it.
License
MIT, see LICENSE. Bundles c2FmZQ/sshterm
(MIT) and the packages listed in
THIRD_PARTY_NOTICES.md. After changing dependencies,
regenerate that file with bun run notices.
