@band-app/worker

中文文档
0.64.0发布于 2026/11/07MITES Module近 30 天下载 5700

Serves a machine to a Band hub: files, git, terminals and coding agents over an outbound link.

原始使用文档
文档目录11 节
  1. Install
  2. Run as a service
  3. Container image
  4. Run it
  5. Path policy
  6. State and tokens
  7. The band CLI
  8. Path policy
  9. Calls
  10. Ephemeral mode
  11. Tests

@band-app/worker

band-worker is the process that runs on a machine and serves it to a Band hub. It dials the hub over @band-app/link, completes the handshake, and answers the hub's calls with @band-app/host-local: files, git, processes, terminals, language servers, agent processes and the agent environment. The hub reaches it through RemoteHost (packages/host-remote) and its /api/workers/connect endpoint. The tests here use a link server of their own.

Install

The package is @band-app/worker and its binary is band-worker. It needs Node 22.5 or newer, and git on the machine for worktrees. node-pty compiles on Linux during the install, so a C++ toolchain, Python and make have to be present there (macOS and the container image need none).

npm install -g @band-app/worker
band-worker --hub https://hub.example.com --token "$BAND_WORKER_TOKEN" --root ~/code

pnpm --filter @band-app/worker build bundles the package into dist/band-worker.mjs. The @band-app/* packages are inlined and every third-party package stays a dependency, so the native modules (node-pty, @vscode/ripgrep) install for the machine the worker runs on. scripts/pack-smoke.sh packs the worker, installs the tarball into an empty directory, runs band-worker --help and starts a PTY from that install. CI runs it.

A checkout runs src/ through tsx. Set BAND_WORKER_USE_DIST=1 to run the bundle from a checkout.

The release workflow publishes this package with the same version as the desktop app, and the container image below as ghcr.io/band-app/band-worker.

Run as a service

band-worker install-service --hub <url> --token <bootstrap token> [--root <dir> --name <name> --labels k=v --worker-id <id> --state-dir <dir>] registers the worker so it starts at login and restarts on failure (src/service.ts):

  • Linux: the systemd user unit ~/.config/systemd/user/band-worker.service reads ~/.band/worker-service/worker.env, then systemctl --user enable --now and loginctl enable-linger. When linger is refused, the command prints what to run.
  • macOS: the launchd agent ~/Library/LaunchAgents/app.band.worker.plist carries the environment and logs to ~/.band/worker-service/worker.log.

~/.band/worker-service is mode 0700 and the file holding the token is 0600. Neither the command nor the unit file prints or contains the token outside that file. band-worker uninstall-service removes the service files and keeps the worker's state. band-worker status exits 0 when the service runs, 3 when it is installed but stopped and 4 when it is not installed.

Container image

docker/worker.Dockerfile builds a node:22-bookworm-slim image with the packed worker, git, ssh, curl, jq and bash. It runs as uid 10001 (worker) and keeps worktrees in the /work volume and the worker id and session token in /home/worker/.band/worker.

docker run -d -v band-work:/work -v band-worker-state:/home/worker/.band/worker \
  -e BAND_HUB_URL=https://hub.example.com \
  -e BAND_WORKER_TOKEN=bwb_... ghcr.io/band-app/band-worker:latest

# or from a checkout:
docker build -f docker/worker.Dockerfile -t band-worker .

The worker takes plain http only for a loopback hub. For a hub on the same Docker host, add --network host and use http://127.0.0.1:<port>; otherwise put the hub behind HTTPS.

Run it

pnpm --filter @band-app/worker start -- \
  --hub https://hub.example.com \
  --token "$BAND_WORKER_TOKEN" \
  --root ~/code --root ~/work \
  --labels gpu=1,zone=home --name laptop
FlagEnvironmentMeaning
--hub <url>BAND_HUB_URLHub URL. http and ws are accepted only for loopback, because the hello carries the token.
--token <token>BAND_WORKER_TOKEN, BAND_BOOTSTRAP_TOKENA session token, or a bootstrap token (prefix bwb_).
--worker-id <id>BAND_WORKER_IDThe id the hub issued with the bootstrap token. Without it the hub names the worker when it trades the token.
--root <dir>BAND_WORKER_ROOTSA directory the worker may serve. Repeat for more. With none, <state dir>/worktrees.
--name <name>BAND_WORKER_NAMEReported as the name label.
--labels k=v,...BAND_WORKER_LABELSPlacement labels.
--state-dir <dir>BAND_WORKER_STATE_DIRDefault $BAND_HOME/worker, or ~/.band/worker.
--ephemeralBAND_WORKER_EPHEMERAL=1Exit when idle.
--idle-exit <dur>BAND_WORKER_IDLE_EXITIdle time before an ephemeral worker exits (90s, 10m, default 10m).

Exit codes: 0 after a signal or an idle exit, 1 when the hub rejects the worker or a bootstrap fails, 2 for a bad command line.

Path policy

A hub call may name a path only when it resolves, with .. folded and symlinks followed, to one of these:

  • A root (--root, a root added through the repo picker, or the default <state dir>/worktrees).
  • A folder the worker manages itself: the clone directory (--repos-dir, default ~/band/repos). It is served from startup, whatever the roots, because the worker creates it. The managed directory itself cannot be removed or moved.
  • A git worktree registered by a repo that sits inside a root.

Anything else is refused with "is outside the worker's roots". The message adds "It is a worktree of , which is not inside a root" only for a linked worktree whose main repo is outside the roots. A plain checkout never gets the hint. A symlink inside a managed folder that leads elsewhere is refused like any other.

State and tokens

The state directory (mode 0700) holds worker-id, created once and reused so the hub sees the same worker after a restart, and session-token (mode 0600). A session token passed with --token is used as given. A bootstrap token is traded once for a session token by POST /api/workers/exchange with { token, workerId?, name? }, answered by { sessionToken, workerId }. The hub binds the token to the worker id it issued it for, and the worker adopts that id and saves it as worker-id. A session token lives until it is revoked. Restarting with the same bootstrap token reuses the saved one. Tokens are never logged.

The band CLI

The worker carries its own band CLI of the same release and never fetches it from the hub. It looks for the binary in this order:

  1. BAND_CLI_PATH.
  2. The package @band-app/worker-cli-<platform>-<arch> (darwin-arm64, linux-x64, linux-arm64), an optional dependency of @band-app/worker. npm installs only the one that matches the machine.
  3. Resources/binaries/band beside the desktop app's bundled worker.
  4. apps/cli/target/{release,debug}/band in a checkout.

At start the worker runs band skills install with it and puts its directory first on the PATH of every agent and terminal it starts. Those processes call the hub through the relay with their own token. With no binary found the worker logs one line naming the package and runs without band. The Docker image builds the CLI and sets BAND_CLI_PATH.

A worker older than this change still asks a newer hub for the CLI with cli.fetch. The hub answers method-not-found, the old worker logs a warning and keeps running with any copy it cached earlier. This worker deletes that old cached copy at start and never puts its directory on the PATH.

Path policy

Every path in a call must be absolute and, once .. and symlinks are resolved, inside a declared root. The check follows a symlink at the end of the path only when the call does (readFile, writeFile, list), so rm and rename of a link act on the link. A write through a dangling link is checked against where it would land. A root itself can't be removed or renamed. A rejected call fails with code -32010 and data.path.

A path outside every root is also allowed when, once resolved, it is or lies inside a git worktree that a repo on this worker lists in git worktree list, and that repo is itself inside a root. A repo is one with a URL mapping on the worker, or a git folder that is a root or sits directly in one. This covers worktrees another tool made outside the repo folder. Only the resolved path counts, so a symlink in a root that points at a non-worktree folder is refused, and one that points at a registered worktree is allowed. The worker reads each repo's worktree list at most every 5 seconds, and worktree.create and worktree.remove clear it at once. A worktree removed with plain git outside the worker is refused again within 5 seconds. When a refused path is a worktree of a repo that is not inside a root, the error names that repo and says to add its folder as a root.

The policy covers the paths the hub names. It does not limit what a shell, command or agent does once it runs, and a local process that swaps a directory for a symlink between the check and the use can still escape.

Calls

Methods are named after the Host interface: host.info, exec, git.exec, git.gh, worktree.*, fs.*, search.*, lsp.*, acp.*, scripts.*, pty.*, agentEnv.*. Params are objects with the interface's argument names. src/methods-basic.ts and src/methods-streams.ts list them.

A result is one of { json }, { bytes } (base64) or { chan, as }. A result over 256 KiB goes down a channel the worker opened before replying. The hub reads it to the end, then ends its own side to release the channel. Data over the message limit going to the worker works the same way: fs.writeFile takes data: { chan } for a channel the hub opened.

Calls that stream open a channel and reply with its id, so the hub sees link.open before the reply:

  • fs.readStream, fs.watch, search.stream: bytes or newline-delimited JSON, worker to hub. The hub ends or resets the channel to stop.
  • lsp.connect: language server stdio, both directions.
  • acp.spawn: agent stdout and stdin on one channel, stderr on another, plus acp.exit as a notification. The hub ending its side closes stdin, and resetting the channel kills the agent.
  • pty.attach: terminal output down, keystrokes up. Closing the channel detaches the viewer, and with killOnClose it kills the terminal. The reply carries the screen snapshot. A terminal that exits ends its channels and sends a pty.exit notification.

Terminals run in a terminal daemon, a detached process the worker launches on the first spawn (src/terminal-daemon.ts, bundled to dist/terminal-daemon.mjs, the same code as the hub's daemon). It owns the PTYs and a scrollback mirror, so a shell survives a worker restart, an upgrade or a crash. The daemon's files live in <state dir>/run/ (mode 0700): the pid record, the token and the log, and the socket (mode 0600, moved to a private dir in /tmp when the path would exceed the Unix socket limit). Scrollback checkpoints are in <state dir>/terminal-history/.

After a restart the worker reconnects to the daemon when the hub connects. pty.listAll lists the same terminal ids, and pty.attach replays the screen. A shell that exited while the worker was down left a record in <state dir>/run/exits/, and the worker sends its pty.exit (exit code, killed: false) on the next connect, once. The daemon exits when it has no shell and no worker connected. Closing a terminal kills its shell. band-worker uninstall-service ends the daemon and every shell. The systemd unit has KillMode=process and the launchd plist AbandonProcessGroup, so stopping or restarting the service leaves the shells running.

BAND_TERMINAL_DAEMON=0 keeps terminals in the worker process, where they end with it. A worker started in a test process (Worker.start without persistentTerminals) does the same.

Ephemeral mode

An ephemeral worker asks the hub whether it may exit once nothing has run for --idle-exit (the hub can replace that time with lifecycle.policy). It exits with code 0 only when the hub answers that every worktree on it is stored. A refusal, such as a running terminal or a failed upload, keeps the worker up, and it asks again after another idle time. Against a hub with no lifecycle.idle handler it exits at once, as before.

A call in progress and every open channel count as activity, including channels the hub opened. The reads the hub makes on its own schedule (host.info, worktree.list, git.exec, git.gh, and the read-only fs.* and search.listFiles calls) hold the worker while they run but do not restart the idle clock, so a status poller does not keep a machine awake. A worker that has lost the hub for an hour (or its idle time, if longer) also exits, because it cannot store anything without the hub.

Two methods serve the hub's sleep and wake handshake: lifecycle.exportSessions reads the agent session files for the given session ids from ~/.claude/projects, ~/.codex/sessions and the directories in BAND_AGENT_SESSION_DIRS, and lifecycle.importSessions moves staged files back there. See Ephemeral workers.

Tests

pnpm --filter @band-app/worker test runs node:test suites against a real link server on a random port. The ephemeral and token suites start the real bin/band-worker.mjs. Every test uses a temporary BAND_HOME and state dir.

版本与发布记录

@band-app/worker 共发布 33 个版本,首次发布于 2026/10/06;过去 12 个月发布了 33 个版本。

版本发布日期距今
0.64.0latest2026/11/07今天
0.63.02026/11/07今天
0.62.02026/11/071 天前
0.61.02026/11/061 天前
0.60.12026/11/062 天前
0.60.02026/11/052 天前

关于 @band-app/worker 的常见问题

@band-app/worker 是什么?

Serves a machine to a Band hub: files, git, terminals and coding agents over an outbound link.

如何安装 @band-app/worker?

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

@band-app/worker 支持 TypeScript 吗?

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

@band-app/worker 支持 ESM 还是 CommonJS?

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

@band-app/worker 需要什么版本的 Node.js?

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

@band-app/worker 可以商用吗?

@band-app/worker 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@band-app/worker 的下载量有多少?

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

@band-app/worker 还在维护吗?

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