@cabane/bridge
v0.4.4
Published
Connect a coding agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.
Readme
cabane-bridge
Connect a coding agent on your machine to your Cabane workspaces as a responder — it replies to messages inside Cabane while running as a full local AI client, so you can drive work against your own codebase, files, and MCP servers without putting any of it in Cabane. The model and tools run on your box; only the reply crosses back.
The bridge runs Claude Code by default, and also supports opencode — a provider-agnostic coding agent — as an alternate runtime you opt into per machine. See Runtimes.
This is v0. It's a small CLI — install it from npm with one command, pair the machine once, and run. It talks to Cabane only over Cabane's public API.
The full story — why you'd want a bridge, how it works, the privacy and tool-policy model, the diagram — lives at app.cabane.ai/docs/bridges. This README is the reference for running the bridge.
The model in one paragraph
A bridge is a device you register that runs agents you assign to it. You register the device once (in Cabane), pair it to this CLI with a one-time pairing string, and start it. From then on it pulls its assignments from Cabane at runtime — every agent you've pointed at this device, across all your workspaces — and runs each one locally. Which agents to run, and how they're configured (mode, MCP servers, model), is owned by Cabane and delivered per turn. The machine only holds the bits that must be local: the device token, each agent's working directory, and your secrets.
No account password or full-account token ever touches your machine. The pairing string carries a device token (
cabdev_…) — good only for pulling this device's assignments and reporting liveness. Each agent the bridge runs gets its own workspace-bound agent token, delivered once when the agent is assigned and scoped to that one agent in that one workspace. Registering, deactivating, and removing a device — and assigning agents to it — all live in the Cabane app; the CLI only runs the agents locally.
Prerequisites
You need Node 22+ (the bridge runtime — check with node --version) and, for the default Claude Code runtime, Claude Code on your PATH (which claude should resolve). The Agent SDK shells out to claude, riding your local Claude Code's auth, so install it with npm i -g @anthropic-ai/claude-code and complete the login flow (claude → follow the prompts) before starting the bridge.
Prefer opencode? It's supported as an alternate runtime — install it alongside (or instead of) Claude Code and follow Runtimes to wire it in.
You also need a Cabane account. A device is registered under your user account; the agents it runs can live in any workspace you have access to.
Install
npm i -g @cabane/bridgeThat puts cabane-bridge on your PATH. Confirm it with cabane-bridge --version. To update later: npm i -g @cabane/bridge@latest.
First run
In the Cabane app, open Settings → Bridges (under your user settings) and Register a device. Give the machine a label; Cabane shows you — once — a pairing string (and the raw device token). Copy the pairing string; it won't be shown again.
Then, on your machine:
cabane-bridge pair # paste the pairing string at the hidden prompt
# ✓ Paired this device "MacBook" with https://app.cabane.ai.
# Run `cabane-bridge start` — it will pull the agents assigned to this device and run them.
cabane-bridge start
#
# Cabane Bridge is running.
# → Dashboard: http://127.0.0.1:7474
#
# Listening for messages…
# [14:32:14] 4f6a93d2 — replied (8.4s)The pairing string carries a live credential, so pair reads it off-argv by default: with no argument you're prompted to paste it (hidden), or pass --file <path>. Passing it positionally still works but leaves the secret in your shell history and process list, so pair warns.
A freshly paired device runs nothing yet — it has no agents. In Workspace settings → Agents, set an agent's executor to this device. The running bridge picks the assignment up within a minute (no restart), and the next time you @mention that agent, the reply comes from your own machine. Assign agents in as many of your workspaces as you like — one running bridge serves them all.
Commands
| Command | What it does |
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| pair [string] [--file <path>] | Pair this device using the one-time string the Cabane app gives you. Decoded + validated locally (no network call), then the device identity is written to ~/.cabane/config.json (mode 600). Re-pairing replaces the device token and keeps your local agents overrides + dashboard prefs. |
| start [--open] [--no-open] [--daemon] [--port <p>] | Pull this device's assigned agents from Cabane and run them, opening one SSE subscription per workspace and serving the localhost dashboard (default http://127.0.0.1:7474; URL printed on start). Re-pulls assignments every ~60s, so adding/removing an agent in Cabane lands without a restart. Does not auto-open a browser by default — pass --open (or set autoOpen: true in config); --no-open / BRIDGE_NO_OPEN=1 force it off. Foreground by default; --daemon runs it detached (see below). |
| stop | Stops a running bridge (foreground or daemon) cleanly. Idempotent — "nothing running" is a success. |
| status | Prints local state — the paired device, the secret names it declares, the log path, and (when running) whether the bridge is background or foreground, its pid, uptime, and dashboard URL. The live list of assigned agents lives in the dashboard while the bridge runs. |
| transcript [file] [--last] [-f] | Show the full agent transcript for a recent dispatch (see Debugging a turn). No args lists recent turns; --last renders the newest; a filename or substring renders a specific one; --follow / -f live-watches new turns as they land (Ctrl-C to stop). |
| logout [-y] [--purge] | Removes the stored device token and the cached per-agent credentials after a confirm, keeping the rest of your bridge config (agents overrides, dashboard prefs). Re-pair with cabane-bridge pair to restore — agent credentials are re-delivered when the agents are re-assigned to this device. --purge does the full wipe (everything under config). Neither removes the device server-side — remove it in the Cabane app if you want it gone from Cabane too. |
| --version / -V | Prints the bridge's version. |
Lifecycle — register / deactivate / remove a device, and assign agents to it — lives in the Cabane app (Settings → Bridges, and each workspace's Agents page), not the CLI.
Run in the background (daemon mode)
cabane-bridge start is foreground — it holds the terminal and stops when you close it. To set it and forget it, start it detached:
cabane-bridge start --daemon
# Cabane Bridge started in the background (pid 51234).
# → Dashboard: http://127.0.0.1:7474
# Logs: ~/.cabane/bridge.log
# Status: cabane-bridge status
# Stop: cabane-bridge stopThe prompt returns immediately and you can close the terminal — replies keep landing. Check on it (cabane-bridge status) or stop it (cabane-bridge stop) from any terminal.
It's not an OS service. The bridge does not survive a reboot or auto-restart on crash — after a restart, re-run cabane-bridge start --daemon. If you want true service semantics, wrap it in your own launchd/systemd unit; it's a plain long-running process that exits cleanly on SIGTERM.
The dashboard
cabane-bridge start serves a small localhost dashboard (default http://127.0.0.1:7474) so the bridge is usable without tailing logs: live per-workspace connection status, the agents currently assigned to this device, a recent-dispatches feed, a log tail, and a few local-only controls (Stop / Restart / Reload-config). It binds to 127.0.0.1 only — same threat model as any localhost dev tool, no auth. It does not auto-open a browser by default — the URL is printed on start; pass --open (or set autoOpen: true in config) to open it automatically, and --no-open / BRIDGE_NO_OPEN=1 force it off. It keeps working when Cabane itself is unreachable, showing a "⚠ Cabane unreachable" banner.
Configuring an agent: what's in Cabane vs. on this machine
There's a deliberate split. What the agent is — its mode, MCP servers, model, reasoning, and charter — is configured in Cabane (Workspace settings → Agents) and delivered to the bridge per turn, so an edit in Cabane lands on the next turn with no bridge restart. The bridge holds only the bits that can't travel from a server: the working directory an agent runs in (a path on this machine) and your secrets.
Agent modes (set in Cabane)
mode names the agent by what it is, not by how locked-down its tools are. You pick it on the workspace Agents page:
| mode | What the agent is |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| assistant | A Cabane assistant — Cabane tools only (read/write workspace files, post messages). Local shell/filesystem tools are off. The cloud-safe default. |
| coding | A full coding agent on your machine: all tools available, with no permission prompts, in the working directory you set below. Bridge-only. ⚠️ |
| custom | An explicit allowlist — Cabane's tools plus exactly the tools you list, and nothing else. |
⚠️
codingmode is yolo — there is no permissioning yet. It runs a coding agent on your machine, with no approval prompts — it can run any shell command and edit any file in its working directory. Only assign it where you trust it to operate, and treat it like Claude Code with permissions disabled. A future release adds real per-tool gating. Until then,assistantis the safe default.
Working directory + prepare-hook (set on this machine)
A coding agent needs a directory to run in — a path that only exists on your machine, so it lives in ~/.cabane/config.json, not in Cabane. Add an agents map keyed by the agent's id, its @username, or workspace-slug/username (all three show up in cabane-bridge status and the dashboard, copy-pasteable):
{
// …device identity, managed by `pair`; don't hand-edit those fields…
// Per-agent machine-local overrides. Only `cwd` and `prepareHook` live here —
// everything else about the agent (mode, MCP servers, model) is set in Cabane.
"agents": {
"my-coding-agent": {
"cwd": "/Users/you/code/project", // working directory the agent runs in
"prepareHook": "git -C /Users/you/code/project pull --ff-only", // optional: runs before each turn
},
},
}A malformed agents block fails cabane-bridge start immediately with a message pointing at the exact field — it won't silently fall back.
Auto-memory (off by default)
Claude Code's auto-memory — where the agent writes notes to a local ~/.claude memory directory and recalls them on later turns — is off by default on every bridge. The stance is that memory-shaped things belong in your Cabane workspace (a shared, versioned ABOUT.md-style file or doc), not a private local directory that no one else can see. If you run your own bridge and want your normal Claude Code memory workflow back, set claudeCode.autoMemory: true on the agent — the bridge then stops overriding it and your own ~/.claude/settings.json governs auto-memory as usual (this bites in coding mode, where your project settings are read):
{
"agents": {
"my-coding-agent": {
"cwd": "/Users/you/code/project",
"claudeCode": { "autoMemory": true }, // opt back into local Claude Code auto-memory
},
},
}Secrets (set on this machine)
When an agent's MCP servers in Cabane need a credential, that credential is written in Cabane as a ${PLACEHOLDER} reference — the real value never leaves your machine. The bridge resolves placeholders at dispatch time from an explicit, operator-declared store at ~/.cabane/secrets.json, a flat { NAME: "value" } map (mode 600):
// ~/.cabane/secrets.json
{
"GITHUB_TOKEN": "ghp_…",
"LINEAR_API_KEY": "lin_api_…",
}A ${VAR} the store doesn't declare fails the turn loudly — it is never read from your shell environment, so a config can't smuggle out an ambient credential. The bridge reports the declared names (never values) to Cabane on its heartbeat, so the settings UI can warn "this agent needs ${GITHUB_TOKEN}, this device doesn't expose it" before a turn ever runs.
Runtimes: Claude Code and opencode
Each turn runs through a runtime — the local AI client that actually executes the agent. Claude Code is the default, and every bridge advertises it; it's what the Prerequisites set up. A bridge can also run opencode — a provider-agnostic coding agent — as a second runtime, opted into per machine.
Which runtime a turn uses follows the agent's model, set in Cabane (Workspace settings → Agents): a Claude model runs on Claude Code, an opencode model runs on opencode. A device only offers opencode models — and only accepts opencode turns — once it's actually running opencode, so set it up first, then pick an opencode model for the agent. A turn is never routed to a runtime the machine can't run.
To add opencode to this machine:
- Install opencode and authenticate a provider through its own flow (
opencode auth login). Provider keys live in opencode's own store — Cabane never sees them. - Start its server:
opencode serve --port 4096(any free port). Run oneopencode serveper bridge process. - Point the bridge at it — add a top-level
opencodeblock to~/.cabane/config.json:{ // …device identity, managed by `pair`; don't hand-edit those fields… "opencode": { "serverUrl": "http://127.0.0.1:4096" }, } - Restart the bridge. Its next heartbeat advertises the
opencoderuntime, and an agent assigned to this device can now be set to an opencode model in Cabane. Without this block the device stays Claude-Code-only, exactly as before.
Everything else about a bridged agent is unchanged — same assignment, same working directory, same secrets, same dashboard. opencode adds a runtime, not a new way to run the bridge.
Files on disk
~/.cabane/
├── config.json # device identity: baseUrl + device token (cabdev_…) + device id/label,
│ # plus optional per-agent `agents` overrides (cwd / prepareHook), an
│ # optional `opencode` block (serverUrl), and dashboard prefs. mode 600.
│ # No account password, no full-account token.
├── credentials.json # agentId → per-agent workspace-bound token, cached on first assignment
│ # pull (delivered once, then never re-sent). mode 600.
├── secrets.json # operator-declared { NAME: "value" } store for ${PLACEHOLDER} resolution. mode 600.
├── cursors/<workspace_id> # last-seen SSE event id per workspace
├── outbox/<workspace_id>/ # durable per-agent commit queue — a reply survives a transient API outage
├── runtime.json # written while `start` runs: dashboard url + pid (swept on exit)
├── transcripts/ # one JSONL file per dispatch — the full agent turn (see below)
└── bridge.log # rolling JSON-lines logNo SQLite, no embedded DB. Cabane is the source of truth for which agents to run and how they're configured.
Debugging a turn: transcripts
The dashboard shows the agent's final reply, but not how it got there. When a turn misbehaves — an agent that "couldn't read the file", a tool that errored, MCP tools that didn't load, a missing secret — the full picture is on disk. Every dispatch writes the complete Claude Agent SDK message stream (the system init with its tool list, every tool call and its result, the assistant text, the outcome) to a JSONL file under ~/.cabane/transcripts/. On a failed turn, the bridge log also prints the exact path.
Read one back in a readable form:
cabane-bridge transcript --last # render the most recent turn
cabane-bridge transcript # list recent turns (newest first)
cabane-bridge transcript <file> # render a specific one (filename or a substring)
cabane-bridge transcript --follow # live-watch turns as they land (-f; Ctrl-C to stop)--follow (-f) is a built-in live watch: it renders the current turn as its lines append and rolls to the next turn when a new dispatch starts — handy for demos or watching the agent work in real time.
It renders the conversation step by step — 🔧 tool(input) → ok/ERROR: result — so you can see exactly where a turn went wrong. The raw JSONL stays on disk for jq. Transcripts can contain whatever the agent read (file contents in tool results), so they're kept local-only in a 700 dir; the newest ~200 are retained.
What v0 doesn't do yet
There's no OS-service install — start --daemon runs detached but doesn't survive a reboot or auto-restart on crash. There's no per-tool permissioning for coding mode yet. And there's no self-update; re-run npm i -g @cabane/bridge@latest to pick up a new version.
