@cabane/companion 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +218 -0
- package/dist/cli.js +9936 -0
- package/dist/pairing-config.js +410 -0
- package/dist/runtime.js +8928 -0
- package/dist/static/app.js +585 -0
- package/dist/static/index.html +116 -0
- package/dist/static/styles.css +537 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# cabane-companion
|
|
2
|
+
|
|
3
|
+
Connect a **coding agent on your machine** to your [Cabane](https://cabane.ai) 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.
|
|
4
|
+
|
|
5
|
+
The Companion runs **[Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code)** by default, and also supports **[opencode](https://opencode.ai)** — a provider-agnostic coding agent — as an alternate runtime you opt into per machine. See [Runtimes](#runtimes-claude-code-and-opencode).
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
**The full story** — why you'd want the Companion, how it works, the privacy and tool-policy model, the diagram — lives at **[app.cabane.ai/docs/reference/connectors](https://app.cabane.ai/docs/reference/connectors)**. This README is the reference for _running_ the Companion.
|
|
10
|
+
|
|
11
|
+
## The model in one paragraph
|
|
12
|
+
|
|
13
|
+
The Companion runs on a **device you register**, running **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.
|
|
14
|
+
|
|
15
|
+
> **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 Companion 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.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
You need **Node 22+** (the Companion 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 Companion.
|
|
20
|
+
|
|
21
|
+
Prefer **opencode**? It's supported as an alternate runtime — install it alongside (or instead of) Claude Code and follow [Runtimes](#runtimes-claude-code-and-opencode) to wire it in.
|
|
22
|
+
|
|
23
|
+
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.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm i -g @cabane/companion
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That puts `cabane-companion` on your PATH. Confirm it with `cabane-companion --version`. To **update** later: `npm i -g @cabane/companion@latest`.
|
|
32
|
+
|
|
33
|
+
## First run
|
|
34
|
+
|
|
35
|
+
In the Cabane app, open **Settings → Devices** (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.
|
|
36
|
+
|
|
37
|
+
Then, on your machine:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
cabane-companion pair # paste the pairing string at the hidden prompt
|
|
41
|
+
# ✓ Paired this device "MacBook" with https://app.cabane.ai.
|
|
42
|
+
# Run `cabane-companion start` — it will pull the agents assigned to this device and run them.
|
|
43
|
+
|
|
44
|
+
cabane-companion start
|
|
45
|
+
#
|
|
46
|
+
# Cabane Companion is running.
|
|
47
|
+
# → Dashboard: http://127.0.0.1:7474
|
|
48
|
+
#
|
|
49
|
+
# Listening for messages…
|
|
50
|
+
# [14:32:14] 4f6a93d2 — replied (8.4s)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
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 Companion 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 Companion serves them all.
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
|
|
59
|
+
| Command | What it does |
|
|
60
|
+
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| `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. |
|
|
62
|
+
| `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). |
|
|
63
|
+
| `stop` | Stops a running Companion (foreground or daemon) cleanly. Idempotent — "nothing running" is a success. |
|
|
64
|
+
| `status` | Prints local state — the paired device, the secret names it declares, the log path, and (when running) whether the Companion is background or foreground, its pid, uptime, and dashboard URL. The live list of assigned agents lives in the dashboard while the Companion runs. |
|
|
65
|
+
| `transcript [file] [--last] [-f]` | Show the full agent transcript for a recent dispatch (see [Debugging a turn](#debugging-a-turn-transcripts)). 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). |
|
|
66
|
+
| `logout [-y] [--purge]` | Removes the stored **device token** and the cached per-agent credentials after a confirm, keeping the rest of your Companion config (`agents` overrides, dashboard prefs). Re-pair with `cabane-companion 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. |
|
|
67
|
+
| `--version` / `-V` | Prints the Companion's version. |
|
|
68
|
+
|
|
69
|
+
Lifecycle — **register / deactivate / remove** a device, and **assign agents** to it — lives in the Cabane app (Settings → Devices, and each workspace's Agents page), not the CLI.
|
|
70
|
+
|
|
71
|
+
## Run in the background (daemon mode)
|
|
72
|
+
|
|
73
|
+
`cabane-companion start` is foreground — it holds the terminal and stops when you close it. To set it and forget it, start it detached:
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
cabane-companion start --daemon
|
|
77
|
+
# Cabane Companion started in the background (pid 51234).
|
|
78
|
+
# → Dashboard: http://127.0.0.1:7474
|
|
79
|
+
# Logs: ~/.cabane/bridge.log
|
|
80
|
+
# Status: cabane-companion status
|
|
81
|
+
# Stop: cabane-companion stop
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The prompt returns immediately and you can close the terminal — replies keep landing. Check on it (`cabane-companion status`) or stop it (`cabane-companion stop`) from any terminal.
|
|
85
|
+
|
|
86
|
+
**It's not an OS service.** The Companion does **not** survive a reboot or auto-restart on crash — after a restart, re-run `cabane-companion 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`.
|
|
87
|
+
|
|
88
|
+
## The dashboard
|
|
89
|
+
|
|
90
|
+
`cabane-companion start` serves a small localhost dashboard (default `http://127.0.0.1:7474`) so the Companion 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.
|
|
91
|
+
|
|
92
|
+
## Configuring an agent: what's in Cabane vs. on this machine
|
|
93
|
+
|
|
94
|
+
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 Companion per turn, so an edit in Cabane lands on the next turn with **no restart**. The Companion 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**.
|
|
95
|
+
|
|
96
|
+
### Agent modes (set in Cabane)
|
|
97
|
+
|
|
98
|
+
`mode` names the agent by what it _is_, not by how locked-down its tools are. You pick it on the workspace Agents page:
|
|
99
|
+
|
|
100
|
+
| `mode` | What the agent is |
|
|
101
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `assistant` | A Cabane assistant — Cabane tools only (read/write workspace files, post messages). Local shell/filesystem tools are off. The cloud-safe default. |
|
|
103
|
+
| `coding` | A full coding agent on your machine: all tools available, **with no permission prompts**, in the working directory you set below. **Companion-only.** ⚠️ |
|
|
104
|
+
| `custom` | An explicit **allowlist** — Cabane's tools plus exactly the tools you list, and nothing else. |
|
|
105
|
+
|
|
106
|
+
> ⚠️ **`coding` mode 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, `assistant` is the safe default.
|
|
107
|
+
|
|
108
|
+
### Working directory + prepare-hook (set on this machine)
|
|
109
|
+
|
|
110
|
+
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-companion status` and the dashboard, copy-pasteable):
|
|
111
|
+
|
|
112
|
+
```jsonc
|
|
113
|
+
{
|
|
114
|
+
// …device identity, managed by `pair`; don't hand-edit those fields…
|
|
115
|
+
|
|
116
|
+
// Per-agent machine-local overrides. Only `cwd` and `prepareHook` live here —
|
|
117
|
+
// everything else about the agent (mode, MCP servers, model) is set in Cabane.
|
|
118
|
+
"agents": {
|
|
119
|
+
"my-coding-agent": {
|
|
120
|
+
"cwd": "/Users/you/code/project", // working directory the agent runs in
|
|
121
|
+
"prepareHook": "git -C /Users/you/code/project pull --ff-only", // optional: runs before each turn
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A malformed `agents` block fails `cabane-companion start` immediately with a message pointing at the exact field — it won't silently fall back.
|
|
128
|
+
|
|
129
|
+
### Auto-memory (off by default)
|
|
130
|
+
|
|
131
|
+
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 Companion. 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 Companion and want your normal Claude Code memory workflow back, set `claudeCode.autoMemory: true` on the agent — the Companion 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):
|
|
132
|
+
|
|
133
|
+
```jsonc
|
|
134
|
+
{
|
|
135
|
+
"agents": {
|
|
136
|
+
"my-coding-agent": {
|
|
137
|
+
"cwd": "/Users/you/code/project",
|
|
138
|
+
"claudeCode": { "autoMemory": true }, // opt back into local Claude Code auto-memory
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Secrets (set on this machine)
|
|
145
|
+
|
|
146
|
+
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 Companion resolves placeholders at dispatch time from an explicit, operator-declared store at `~/.cabane/secrets.json`, a flat `{ NAME: "value" }` map (mode 600):
|
|
147
|
+
|
|
148
|
+
```jsonc
|
|
149
|
+
// ~/.cabane/secrets.json
|
|
150
|
+
{
|
|
151
|
+
"GITHUB_TOKEN": "ghp_…",
|
|
152
|
+
"LINEAR_API_KEY": "lin_api_…",
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
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 Companion 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.
|
|
157
|
+
|
|
158
|
+
## Runtimes: Claude Code and opencode
|
|
159
|
+
|
|
160
|
+
Each turn runs through a **runtime** — the local AI client that actually executes the agent. **Claude Code is the default**, and every Companion advertises it; it's what the [Prerequisites](#prerequisites) set up. The Companion can _also_ run **[opencode](https://opencode.ai)** — a provider-agnostic coding agent — as a second runtime, opted into per machine.
|
|
161
|
+
|
|
162
|
+
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.
|
|
163
|
+
|
|
164
|
+
To add opencode to this machine:
|
|
165
|
+
|
|
166
|
+
1. **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**.
|
|
167
|
+
2. **Start its server:** `opencode serve --port 4096` (any free port). Run one `opencode serve` per Companion process.
|
|
168
|
+
3. **Point the Companion at it** — add a top-level `opencode` block to `~/.cabane/config.json`:
|
|
169
|
+
```jsonc
|
|
170
|
+
{
|
|
171
|
+
// …device identity, managed by `pair`; don't hand-edit those fields…
|
|
172
|
+
"opencode": { "serverUrl": "http://127.0.0.1:4096" },
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
4. **Restart the Companion.** Its next heartbeat advertises the `opencode` runtime, 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.
|
|
176
|
+
|
|
177
|
+
Everything else about a Companion-run agent is unchanged — same assignment, same working directory, same secrets, same dashboard. opencode adds a runtime, not a new way to run the Companion.
|
|
178
|
+
|
|
179
|
+
## Files on disk
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
~/.cabane/
|
|
183
|
+
├── config.json # device identity: baseUrl + device token (cabdev_…) + device id/label,
|
|
184
|
+
│ # plus optional per-agent `agents` overrides (cwd / prepareHook), an
|
|
185
|
+
│ # optional `opencode` block (serverUrl), and dashboard prefs. mode 600.
|
|
186
|
+
│ # No account password, no full-account token.
|
|
187
|
+
├── credentials.json # agentId → per-agent workspace-bound token, cached on first assignment
|
|
188
|
+
│ # pull (delivered once, then never re-sent). mode 600.
|
|
189
|
+
├── secrets.json # operator-declared { NAME: "value" } store for ${PLACEHOLDER} resolution. mode 600.
|
|
190
|
+
├── cursors/<workspace_id> # last-seen SSE event id per workspace
|
|
191
|
+
├── outbox/<workspace_id>/ # durable per-agent commit queue — a reply survives a transient API outage
|
|
192
|
+
├── runtime.json # written while `start` runs: dashboard url + pid (swept on exit)
|
|
193
|
+
├── transcripts/ # one JSONL file per dispatch — the full agent turn (see below)
|
|
194
|
+
└── bridge.log # rolling JSON-lines log
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
No SQLite, no embedded DB. Cabane is the source of truth for which agents to run and how they're configured.
|
|
198
|
+
|
|
199
|
+
## Debugging a turn: transcripts
|
|
200
|
+
|
|
201
|
+
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 Companion log also prints the exact path.
|
|
202
|
+
|
|
203
|
+
Read one back in a readable form:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
cabane-companion transcript --last # render the most recent turn
|
|
207
|
+
cabane-companion transcript # list recent turns (newest first)
|
|
208
|
+
cabane-companion transcript <file> # render a specific one (filename or a substring)
|
|
209
|
+
cabane-companion transcript --follow # live-watch turns as they land (-f; Ctrl-C to stop)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`--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.
|
|
213
|
+
|
|
214
|
+
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.
|
|
215
|
+
|
|
216
|
+
## What v0 doesn't do yet
|
|
217
|
+
|
|
218
|
+
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/companion@latest` to pick up a new version.
|