@cabane/companion

中文文档
0.6.232发布于 2026/11/09UNLICENSEDES Module近 30 天下载 1.4万

The Cabane Companion (headless): connect an 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.

原始使用文档
文档目录11 节
  1. Install
  2. Commands
  3. Keeping it running
  4. What's on this device
  5. Secrets
  6. Local overrides
  7. Running each turn somewhere else
  8. Keeping provider credentials outside a sandbox
  9. Harness permissions
  10. Debugging a turn
  11. Not yet

cabane

The Cabane Companion runs your Cabane agents on a device you choose. It receives their work from Cabane, starts a harness on the device and streams replies back. The harness calls its configured model provider; agents use the local files, tools and credentials available to their environment.

It uses Claude Code, Codex or OpenCode. The package includes Claude Code and Codex execution binaries, but setup does not run a vendor CLI installer or sign you in. OpenCode needs its own running server.

Start with Cabane Companion, then Mac setup, Windows setup or Server and VPS setup. FAQ and Help covers sign-in and troubleshooting. This README is the device-side reference: commands, configuration and local state.

Pairing supplies a device token, not your Cabane password or a full-account token. The device token is for this device's assignments and liveness; assigned agents receive workspace-bound credentials. The trust guide explains the layers, records and access.

Install

On the device, in a terminal. One of:

curl -fsSL https://cabane.ai/install.sh | bash

The script brings its own Node (checksum-checked, into ~/.cabane) and needs no sudo. Or, with Node 22 or newer already there (the only route on Windows):

npm i -g @cabane/companion && cabane setup

cabane setup on an unpaired device prints a code and a link:

Pair this device
Enter this code in Cabane, or press Return to open the page:

  WXYZ-2345
  https://app.cabane.ai/pair/enter-code

⠋ Waiting for you to confirm in Cabane… (the code is good for 15 minutes)

Enter the code in Cabane, signed in as yourself: from your own Settings → Devices → Add a device, from a workspace's Settings → Devices → Set up a new device, or on the page the link opens. Cabane shows which device asked; confirming pairs it to your account and nothing else. A workspace's dialog then adds the device to that workspace; from anywhere else, add it to a workspace from that workspace's Settings → Devices. Pairing from a workspace you're setting up places its default agent on the device once a harness there is usable.

Without a terminal, setup prints the same lines once and waits, so a container's logs carry the code. An expired code is replaced by a fresh one, up to three; Ctrl-C stops cleanly. The link is on the server the device pairs with: pass --server <url> for your own Cabane.

From an interactive terminal, setup then starts the Companion in the background, waits for its first check-in and reports the device's state. Without an interactive terminal, setup stays attached to the running Companion.

If no harness is usable, interactive setup asks which one you want to set up. Follow the complete CLI installation and sign-in steps for Mac setup, Windows setup or Server and VPS setup, then check cabane status.

✓ Paired as "maya-mbp".
  Cabane is running in the background. This device is ready — checked in 2s ago.

  Harnesses on this device
  ✓ Claude Code   connected — signed in (2.1.289)
    Codex         needs sign-in — run  codex login
    OpenCode      off — not running

  Status  cabane status     Stop  cabane stop     Log  cabane logs
  Start at login and keep Cabane updating itself:  cabane service install

  In Cabane: https://app.cabane.ai/settings/devices

Setup never installs the service. On macOS or Linux, run cabane service install when you want the Companion to start at login and keep itself updated; the closing block names it. Native Windows uses the background process and has no service installer.

If cabane isn't found afterwards, open a new terminal, or run the source line setup printed.

The server guide provides a Dockerfile and Compose example to build locally. Its Dockerfile installs the Companion and all three command-line tools; you sign in inside the container. Follow that guide for pairing and starting the Docker environment.

Commands

CommandWhat it does
setupUnpaired: print a code to enter in Cabane, wait for the confirm, then detect harnesses and start. Paired: a re-runnable checklist that reports harnesses and starts what isn't running.
start [--foreground]Start the Companion without prompts. From an interactive terminal, run in the background; noninteractive use may stay attached. Unpaired, it names setup. --foreground stays attached for your supervisor.
stopStop the Companion cleanly. Ends any turn in progress.
statusVersion and install, the paired device by its Cabane name, its state in Cabane's words and when it last checked in, each harness's state, where agents work, declared secret names, and local overrides.
update [--auto on|off]Install the version Cabane names now; with --auto, set whether the Companion keeps itself up to date.
service install|uninstall|statusRun as a systemd user service or a LaunchAgent that starts at login and restarts if it stops; remove it; show its state.
logs [-n N] [-f]Recent log lines, or follow.
transcript [file] [--last] [-f]Read back a full agent turn, tool calls and results included.
logout [--purge]Remove the device token and cached agent credentials. Doesn't remove the device from Cabane.

Anything else about a device is done in Cabane, on the device's page.

Keeping it running

For a direct installation, a reboot or a crash leaves the Companion stopped until the next cabane start unless you have installed its service. Under the service it restarts if it stops and starts with its user session (at login on macOS; enable linger on Linux for operation after logout), and follows the version Cabane names: it installs the new version under ~/.cabane/versions, checks it starts, and swaps to it, which can restart the Companion during a turn. A version that can't start is rolled back by the boot guard. cabane update --auto off stops updates; without the service, cabane update installs and the next cabane start runs it.

On native Windows, check the background process with cabane status and run cabane start after a Windows restart. If you installed OpenCode outside Docker, keep its server running separately; the Companion's service does not supervise it.

For the Docker example, keep automatic Companion updates off and rebuild the local image to update the Companion and harness tools. Follow the server guide's update steps.

Other supervisors: run cabane start --foreground with CABANE_COMPANION_SUPERVISED=1 set, using the absolute paths ~/.cabane/node/bin/node and ~/.cabane/current/dist/bin.js, which don't move on an update.

For a direct installation, after Rotate token in Cabane, apply the replacement on the device:

  1. Run cabane stop.
  2. In ~/.cabane/config.json, replace deviceToken with the replacement token shown by Cabane.
  3. Run cabane start.

For Docker, follow Rotate a Docker device's token.

What's on this device

All of it in ~/.cabane/, mode 600, no database. Cabane is the source of truth for which agents run and how.

~/.cabane/
├── config.json       # pairing, local overrides
├── work/             # a directory per agent and conversation, for agents with no folder of their own
├── credentials.json  # per-agent workspace-bound credentials, delivered once
├── secrets.json      # { NAME: "value" } for ${NAME} placeholders
├── transcripts/      # one JSONL per turn, newest ~200 kept, local only
├── outbox/           # a reply survives a transient API outage
└── companion.log

Secrets

An MCP server configured in Cabane names its credential as ${NAME}; the value never leaves the device. The Companion resolves it from secrets.json at dispatch, never from your shell environment, and a name that isn't declared fails the turn loudly. It reports the declared names (never values) on its heartbeat, so Cabane can warn before a turn runs.

A device with no keychain or browser, such as a server or a container, can sign its harness in from secrets.json too: CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY for Claude Code, CODEX_API_KEY for Codex. Only the turn's own harness gets its sign-in. GH_TOKEN there reaches every turn, for gh and git over HTTPS.

Local overrides

config.json can carry, per agent (keyed by id, @username or workspace-slug/username) or for the whole device:

  • cwd: wins over the agent's Starts in folder set in Cabane.
  • prepareHook: a command that resolves the working directory before each turn; wins over both. A device-level hook applies to every agent that doesn't name its own. The hook gets CABANE_DEFAULT_CWD, the directory below, for a hook that only adds to it.
  • root: where agents work when nothing else says. Every agent gets a directory per conversation under it, <root>/<workspace>/<agent>/<conversation>/, made on the first turn and never removed by the Companion. Default ~/.cabane/work; absolute, or ~/…. An agent with no folder in Cabane, no cwd and no hook stands there; a folder or cwd that isn't an existing directory is passed over with a line in the log.
  • harnesses: an allowlist such as ["claude-code"]; absent means all three.
  • claudeCode.autoMemory: Claude Code's local memory directory is off by default in the Companion, because notes belong in the workspace where others can read them. true hands it back.

A malformed block fails cabane start with the field named. A runners block, a row's sandbox, or a prepareHook's validateCached or failureReporter from an older version of the Companion is ignored, with one line in the log naming it.

Every turn's shell gets CABANE_CONVERSATION_ID and CABANE_AGENT_ID, so a script an agent runs can tell which conversation and agent it's working for.

Running each turn somewhere else

runner.command puts a command in front of every turn: "runner": { "command": ["/usr/local/bin/enter-sandbox", "--fresh"] } starts each turn as enter-sandbox --fresh <the Companion's own run-turn command>. Use it to run each turn in a container or VM of your own while the Companion stays outside. Your command must run the rest of its arguments, with the environment and stdin it was given.

A wrapped turn gets the device's secrets on its stdin rather than reading secrets.json, inherits no sign-in from the Companion's own environment, and is never signalled by process group from outside: your command ends whatever the turn leaves behind.

What your command can rely on, and can set:

Variable
CABANE_DISPATCHRead: the dispatch as JSON, including conversationId and agentId.
CABANE_RUNNER_SPECRead: the turn's settings as JSON, including agentUsername.
CABANE_TURN_IDRead: the turn's id.
CABANE_COMPANION_HOMERead, when the Companion was started with one: the home its .cabane directory is under.
CABANE_WORK_ROOTSet: the root the turn's working directory goes under, in place of root.
CABANE_TRANSCRIPT_DIRSet: where the turn writes its transcript. A directory one level under ~/.cabane/transcripts/ lists in cabane transcript.
CABANE_WORK_ROOT_DEDICATEDSet to 1 when the root is already this one conversation and agent's: the turn stands in it directly, with no per-conversation directory under it.

These names are a contract: changing one would break your command, so none changes without a note in the release.

Keeping provider credentials outside a sandbox

An operator-managed sandbox can use the host-side @cabane/companion/credential-broker module. This is an embedding API for a trusted host adapter, not a proxy that becomes safe by listening on localhost. The adapter must authenticate the accepted socket as one live sandbox instance and revoke its grants when that instance is stopped. A shared UID, a copied placeholder, a socket filename or an HTTP header is not an instance identity. An unresolved, ambiguous or changed identity must return null from resolveIdentity(socket).

Run the broker and its credential files under a host identity outside every sandbox UID mapping. Hold an exclusive process lock for the entire service lifetime, including shutdown: there must be exactly one renewal owner for each account file. The parent of an account file must be owned by that identity with mode 0700; the file must be a regular, single-link 0600 file. Stop the service before importing or changing login state. fileCredentialStore(path) and openBrokerCredentials(store) read that state; share the resulting owner between every grant using the account. Renewal writes and fsyncs a replacement before releasing it. A failed persistence write refuses further use until the operator recovers the stored account.

The account format is { mode, accessToken, refreshToken?, idToken?, expiresAt?, accountId? }. mode is claude-setup, claude-oauth, codex-oauth or github. OAuth modes require a refresh token and an expiry in epoch milliseconds. A setup token never renews. Normal Claude OAuth renews inside the broker before expiry, or on one upstream 401; Codex renewal returns fresh placeholders to its client. After a failed exchange the owner refuses renewal for a backoff window, 30 seconds doubling to 15 minutes, rather than retrying upstream on every request; a successful renewal clears it. Never place the real account file, a credential-store bind, or an alternate harness login inside the sandbox. Real OAuth compatibility must be verified with the installed harness and login mode; a successful setup-token turn proves nothing about OAuth renewal.

Create the broker with createCredentialBroker({ resolveIdentity, audit? }). Its server handles model and git routes; bind it only to host loopback. Git's HTTP proxy requests are accepted only for the exact http://github.com/ authority, then forwarded to fixed HTTPS GitHub upstreams. Arbitrary proxy destinations and CONNECT tunnels are refused. Its githubServer handles gh's http_unix_socket transport; authenticate that socket's peer too. Neither exposes a management endpoint. Call grant({ identity, credentials, expiresAt }) only from the trusted host adapter. credentials maps claude, codex and github to their corresponding credential owners. The returned token is disposable; revoke() aborts active requests and removes its aliases. Teardown calls revokeIdentity(identity); service shutdown calls revokeAll() and closes both servers. A restart creates fresh grants, so old placeholders stop working.

Scope the GitHub token on GitHub's side; the broker imposes no repository, branch, PR-action or GraphQL-shape policy.

Set runner.brokeredCredentials: true beside runner.command in the Companion config. This removes provider and GitHub credential variables from the launcher's environment, secret-store payload and device-grant payload before the wrapper receives them. The wrapper supplies brokerClientConfiguration({ baseUrl, placeholder, githubSocket, codexConfig })'s environment and generated configuration to the isolated home, and writes await grant.codexAuth() as its placeholder-only Codex auth.json. The helper preserves the supplied Codex settings while selecting the broker provider and HTTP Responses transport. Include its git configuration in the sandbox's global config and use its gh configuration there. For a GitHub-enabled box, provide githubSocket. The helper sets GH_TOKEN and preserves github.com in git's expanded remote so gh can resolve a tracked branch, without setting GH_REPO. The generated git config rewrites HTTPS URLs to an HTTP GitHub URL carrying only a placeholder, and sends that origin through the loopback broker using Git's per-origin http.proxy; the broker's upstream hop remains HTTPS. Install the entire generated config, replacing the previous generated file, rather than retaining an older rewrite. Do not bypass this proxy with NO_PROXY or a per-remote proxy override. For a model-only box, omit githubSocket or pass null; no GitHub environment is emitted and both git/gh config strings are empty, so an adapter can clear any previous generated routing. The wrapper must remove old credential mounts and alternate authentication helpers; this switch cannot inspect or secure an operator's filesystem mounts.

The fixed upstream hosts are api.anthropic.com, chatgpt.com, api.github.com and github.com. Paths and request bodies pass through without a workflow policy, except for known credential-minting, credential-registering or disclosure endpoints: Claude CLI API-key creation and OAuth token exchange, ChatGPT session/token disclosure, and GitHub app-manifest conversion, installation/application token endpoints, legacy authorizations, SSH user/deploy-key registration, runner registration/removal/JIT credentials and OAuth/device token endpoints. These return new secrets the echo filter cannot know in advance, or register box-chosen credentials that would outlive the grant. This deny-list must be revisited when providers add credential-returning or credential-registering routes; it is not a guarantee against unknown endpoints or arbitrary encodings. Redirects are refused and responses are filtered for known credential echoes. Credential-free injection is restricted to Codex's designated /backend-api/ps/mcp route. Each upstream exchange, streams included, is capped at ten minutes by default (requestTimeoutMs on createCredentialBroker), and request bodies at 64 MiB.

Arbitrary room secrets and other MCP credentials still follow their existing delivery contract. This API does not protect SSH keys or offer general secret injection into arbitrary HTTP destinations. Claude's feature flags fall back to defaults when their direct requests use placeholders. Before enabling a sandbox deployment, verify actual socket ownership, PID/namespace reuse, service UID separation, grant revocation, concurrent renewal and restart, required client operations, and a canary extraction attempt from inside the real sandbox. A successful unit test or package installation does not establish that host boundary.

Harness permissions

The Companion approves Claude Code's tools through a hook and approves OpenCode permission requests; no turn waits for a tool-approval dialog. For Codex it disables approvals and, when your configuration sets a top-level sandbox_mode, passes no sandbox flag; without that setting it uses danger-full-access. Choose where agents run using Agent access, and configure the tools through Harness sandboxes.

Debugging a turn

Cabane shows the conversation; the device also keeps a detailed local record. Every turn writes its full stream to ~/.cabane/transcripts/: the tool list, every tool call and its result, the text, the outcome. A failed turn's log line names its transcript.

cabane transcript --last   # render the most recent turn
cabane transcript          # list recent turns
cabane transcript -f       # live-watch turns as they land
cabane logs -f             # follow the log

cabane start --log-level debug or "logLevel": "debug" in config.json for more; --log-format json for machine-readable lines.

Not yet

Native Windows has no service install and no self-update. There is no per-tool permissioning in Cabane. Control agents' access through their environment and the harness's own settings.

版本与发布记录

@cabane/companion 共发布 226 个版本,首次发布于 2026/08/02;过去 12 个月发布了 226 个版本。

版本发布日期距今
0.6.232latest2026/11/09今天
0.6.2302026/11/072 天前
0.6.2292026/11/062 天前
0.6.2282026/11/053 天前
0.6.2272026/11/035 天前
0.6.2262026/11/035 天前

关于 @cabane/companion 的常见问题

@cabane/companion 是什么?

The Cabane Companion (headless): connect an 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.

如何安装 @cabane/companion?

在项目目录运行 npm install @cabane/companion,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 cabane、cabane-companion,可以用 npx cabane 直接运行。

@cabane/companion 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/cabane__companion 是否存在。

@cabane/companion 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cabane/companion 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cabane/companion 可以商用吗?

@cabane/companion 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

@cabane/companion 的下载量有多少?

根据 npm 官方下载统计,@cabane/companion 在 2026/10/09 – 2026/11/07 这 30 天内被下载 14,274 次,日均约 476 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cabane/companion 还在维护吗?

最新版本 0.6.232 发布于 2026/11/09(今天);至今共发布 226 个版本,其中过去 12 个月发布了 226 个;首次发布于 2026/08/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。