Tango
No description available
Ask AI about Tango
Powered by Claude Β· Grounded in docs
I know everything about Tango. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
tango
A split-pane web app for collaborating with terminal-based coding agents. The left pane is an Excalidraw canvas (and other UI surfaces, over time); the right pane is a real interactive terminal running claude --dangerously-skip-permissions in your chosen workspace. An optional further-right sidebar mirrors a booted iOS Simulator via serve-sim for tight feedback while building mobile UI. An in-process MCP server gives Claude tools to read and write the canvas, and a small gpt-5.5 controller agent in the toolbar can drive the visible UI on your behalf β moving the cursor, clicking buttons, and dispatching work to terminal-Claude.
The repo is tango itself. The directory tango operates on (where Claude's shell runs, where snapshots get written) is a separate workspace you pick from the in-app picker on first launch.
Stack
- Next.js 16 (App Router) on a custom Node server β
next devis not used; the WebSocket bridge and MCP transport live in-process. - React 19 + Tailwind v4 + a shadcn-style primitive layer in src/components/ui/.
- node-pty for the terminal, bridged over
/ws/terminal. - Excalidraw for the canvas, bridged over
/ws/canvas. @modelcontextprotocol/sdkin-process MCP server at/mcp, exposing canvas tools and agent UI tools.- Vercel AI SDK v6 +
@ai-sdk/mcpcontroller agent at/api/agent, driving anAgentCursorOverlayover/ws/agent-cursor.
Getting started
Prereqs: Node 20+, a working C/C++ toolchain for node-pty's prebuilds, and an OPENAI_API_KEY in .env.local if you want to use the controller agent.
npm install # postinstall fixes node-pty perms + symlinks .env.local
npm run dev # tsx server.ts on :3000
Open http://localhost:3000. On first launch a blocking dialog asks you to pick a workspace directory β pick (or create) the folder you want Claude to operate in. After that the right pane spawns a terminal there and runs claude.
Other scripts:
npm run build # next build (server stays the same)
npm start # production server
npm test # vitest run
npm run test:watch
If a fresh clone errors with posix_spawnp failed, run node scripts/fix-node-pty.js (the postinstall hook should already handle this).
How it works
browser ββHTTPβββΊ Next request handler β
browser ββHTTPβββΊ /mcp StreamableHTTPServerTransport β
browser ββHTTPβββΊ /api/agent streamText + @ai-sdk/mcp client βββ server.ts
browser ββHTTPβββΊ /api/sim/status β getSimStatus() β
browser ββWSβββββΊ /ws/terminal β attachPty β
browser ββWSβββββΊ /ws/canvas β attachCanvas β
browser ββWSβββββΊ /ws/agent-cursor β attachAgentCursor β
β β β
node-pty.spawn($SHELL) scene cache McpServer + tools
cwd: WORKSPACE_DIR (canvasBridge) (canvas + agent UI tools)
auto-runs `claude`
Plus, on darwin, server boot also spawns npx serve-sim once and the right-most panel iframes its preview UI.
- Custom server (server.ts) hosts Next, three
WebSocketServers, and the MCP transport in one process. Required fornode-ptyand for the SDK's Node-typed transport β don't migrate tonext devor edge runtimes. - MCP tools (src/server/mcp.ts) split into canvas (
get_canvas_state,set_canvas_state,add_elements,clear_canvas,screenshot_canvas) and agent UI (dom_inspect,cursor_move,cursor_click,cursor_type,terminal_type). Terminal-Claude sees all of them; the controller agent sees only the agent UI subset (it's a controller, not the brain β it delegates creative work back into the terminal viaterminal_type). - Designer canvas is duplex: Excalidraw changes snapshot to the server every 500ms, and MCP tool writes broadcast back to all connected browsers as scene patches.
- Simulator sidebar (src/server/sim.ts, darwin only) mirrors a booted iOS Simulator. Server boot spawns
npx serve-sim, parses the preview URL from its stdout, and the right-most aside iframes that URL inside a sandboxed frame. Best-effort: ifxcrunor the package isn't available, the panel surfaces the error and the rest of tango is unaffected.
Workspace
Resolution order at boot (src/server/workspace.ts):
TANGO_WORKSPACEenv var β pinned, picker is read-only.~/.tango/state.json#lastWorkspaceβ last-picked, if it still exists.- Unset β picker blocks the UI; PTY and snapshot routes refuse work until a workspace is chosen.
Picking a workspace is non-destructive. Five things land in the chosen directory:
.claude/tango.mdβ generated docs about thetango-canvasMCP tools (overwritten).CLAUDE.mdβ a 3-line sentinel block (<!-- tango:start β¦ -->) is managed; everything outside is preserved byte-for-byte..mcp.jsonβ merged undermcpServers['tango-canvas']; refuses on malformed JSON..claude/settings.jsonβ merged to addenv.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1.design-scratch/βmkdir -p. PNGs from the canvas's "Send to Claude" button land here.
You can switch workspace mid-session via the pill in the top bar; the terminal and canvas tear down and reopen against the new cwd.
Going deeper
CLAUDE.md is the canonical architecture reference β it documents the wire protocols, the subtle invariants (why terminal_type writes text and \r as separate writes, why transport.onclose must not call mcpServer.close(), why appState needs sanitizing on every JSON boundary, etc.), the design system, and the load-bearing test surfaces. Read it before changing anything in src/server/ or src/components/Terminal.tsx, SketchPanel.tsx, DesignerCanvas.tsx, or AgentCursorOverlay.tsx.
docs/test-coverage-proposal.md tracks the test gaps and a tiered plan for closing them.
