@azad-ai/workbenchd
v0.1.12
Published
Multi-root workbench daemon: filesystem, search, git, terminal, and LSP over one multiplexed JSON-RPC WebSocket.
Downloads
1,513
Maintainers
Readme
@azad-ai/workbenchd
A multi-root workbench daemon. One process serves N workspace roots to N clients over a single multiplexed JSON-RPC 2.0 WebSocket: filesystem, ripgrep search, git, terminals, and LSP.
It is a standalone daemon with no knowledge of any particular agent runtime or UI. A supervisor starts it, hands it a port and a token, and points clients at it.
bun add -g @azad-ai/workbenchd
workbenchd serve --port 8790 --token "$(openssl rand -base64 24)" --allowed-root /workspaceBun is required. The pty is built on
Bun.spawn({ terminal }), which has no Node equivalent that avoids a native module. Everything else deliberately usesnode:child_process, but terminals will not work under Node.bash is required for command tracking. Shell integration is injected with bash's
--init-file; a POSIXsh(busybox ash, dash) rejects that flag, so those shells run plainly and emit no OSC 633 markers — exit codes, cwd tracking, and busy detection stay inert. The daemon warns when it lands in that state. Linux, macOS, and both glibc and musl are verified; Windows is not supported.
Why one socket
Roots are attached explicitly — roots.attach {path} returns an opaque rootId — and every
method carries that id. The connection is never bound to a root, so switching workspaces is a
parameter change rather than a reconnect, and a second client attaching to the same root
shares its watcher, shells, git cache, and language servers immediately.
Terminal output and file bytes travel as binary frames with a 24-byte aligned header on the same socket, so pty traffic never pays for UTF-8 re-encoding or JSON escaping.
Surface
| Namespace | Methods |
|---|---|
| workbench | hello, unsubscribe |
| roots | attach, list, detach, describe |
| fs | stat, read, write, mkdir, remove, rename, list, watch |
| search | paths, text |
| git | snapshot, log, commit, stageFiles, fetch, push, blame/diff/timeline, … |
| term | create, list, close, rename, sendInput, resize, attach, watchAll |
| lsp | attach, send, detach, status, getPreferences, setDocumentLanguageEnabled |
workbench.hello is mandatory before anything else and negotiates the protocol version. The
full machine-readable contract is emitted by workbenchd emit-openrpc --out openrpc.json.
Requests are cancellable with $/cancelRequest, which really does kill the underlying git
or rg subprocess rather than merely abandoning the promise.
Terminals
Shells run with VS Code's shell-integration scripts injected, so OSC 633 markers drive
cwd, lastCommand, lastCommandExitCode, and a live isRunningProcess flag. Output is
retained in a ring buffer with absolute byte offsets, so a reconnecting client resumes
exactly where it left off — or gets a rendered screen snapshot when it has fallen too far
behind.
Language servers
None are bundled. Servers resolve from the workspace's own node_modules/.bin first, then
PATH; WORKBENCHD_LSP_CMD_<LANG> overrides a specific one. Presets exist for typescript,
eslint, json, python, rust, and go. Anything not installed is reported as unsupported by
lsp.status and is never fatal.
Two transports reach the same session: the hub tunnel (lsp.attach / lsp.send) and a raw
/lsp WebSocket, because monaco-languageclient insists on dialling its own socket. N
clients multiplex onto one server process via request-id rewriting and document-ownership
refcounting.
Options
workbenchd [serve] [options] Start the workbench server
workbenchd emit-openrpc --out FILE Write the OpenRPC contract document
--host HOST Bind address (default 127.0.0.1)
--port PORT Port; 0 picks a free one (default 0)
--token TOKEN Require this token in workbench.hello
--no-auth Serve without a token (required to start unauthenticated)
--allowed-root PATH Confine roots.attach (comma-separated)
--allow-all-roots Serve any path the daemon's user can read; the right choice
when the daemon already runs inside the sandbox it would be
confined to
--trusted-origin ORIG Browser Origins allowed to connect (comma-separated); required
when clients reach the daemon through a tunnel
--dev Loopback dev mode: generate a token, default roots to cwd,
relax the Origin check, and write .azad/workbenchd.json
--idle-eviction-ms MS Idle grace before a root's engines are torn downThe daemon exits 1 rather than starting unauthenticated. Pass --no-auth to do that on
purpose.
Running as a service
See packaging/ for systemd units, the environment contract, and the
auto-update timer.
