Library API
Everything the CLI does is a thin shell over a pure core, and that core ships as a
library. Import it when you are building a tool on top of cyber-mux — a dispatcher, an agent
harness, a workspace manager — rather than shelling out to the cyber-mux binary and parsing its
output.
The library is the seam and nothing else: it contains no console.log and no process.exit.
Those live only in the CLI modules, which the library entries deliberately do not re-export, so the
process-owning surface is structurally unreachable from every import path below.
Install
Section titled “Install”npm install cyber-muxcyber-mux is ESM-only and ships its own types (.d.mts). It has one runtime dependency
(commander, used by the CLI); the library entries pull in none of it.
Entry points
Section titled “Entry points”Three subpaths, each a different slice of the core. Import from the narrowest one that covers your need.
| Import | What it is | Page |
|---|---|---|
cyber-mux |
The mux core — resolveMux and the MuxSession it returns, the raw MuxAdapter contract underneath, the three adapters, nudge, the probe, and the Exec/NewId seams. |
MuxAdapter |
cyber-mux/worktree |
worktreeApi — the git-worktree helpers (list, resolve, remove safely) with Exec/fs bound — over the raw exec-first functions. |
Worktree |
cyber-mux/template |
templateApi — bound resolution — plus the template schema, validator, and flat-N desugarer. |
Template |
The seam pattern
Section titled “The seam pattern”Every core function takes its side effects as parameters rather than reaching for node:child_process
or node:fs directly. The two you will meet most often:
Exec— the synchronous command runner every adapter uses. The real one isnodeExec. Rather than threading it through every call,resolveMuxbinds it once when it resolves the session — a test binds a fake instead (resolveMux(env, { exec: fake })), so a session is drivable with no real multiplexer. A single call can still override the bound runner with a trailingdepsargument (mux.open(opts, { exec })).NewId— the id-minting seam. The real one isnodeNewId(a v4 UUID); a test passes a deterministic counter.
Bind the real implementations at the edge of your program — that is exactly what the CLI does.
Quick start
Section titled “Quick start”Resolve the session for whatever multiplexer this process is inside, open a pane, hand it its turn, and read the result back:
import { resolveMux } from 'cyber-mux'
// Resolve the session for whatever multiplexer this process is inside — binds nodeExec once.// Throws if the process is in no supported multiplexer.const mux = resolveMux(process.env)
// Open a pane to the right of THIS one, running a command.const pane = mux.open({ cwd: process.cwd(), at: 'pane:right', from: mux.callerPane(), // split the caller, not the user's focused pane launch: 'claude',})
// Submit a message and verify the peer actually took its turn.const { taken, resubmits } = await mux.nudge(pane, 'run the tests')console.log({ taken, resubmits })
// Read the pane's current output.console.log(mux.read(pane, { lines: 40 }))resolveMux throws if the process is in no supported multiplexer. To run with-or-without one, gate
first with the non-throwing probe:
import { probeMultiplexer, resolveMux, nodeExec } from 'cyber-mux'
if (probeMultiplexer(nodeExec, process.env).mux !== 'none') { const mux = resolveMux(process.env) // ...}A caller threading its own runner per call instead of binding one reaches for the raw, exec-first
resolveMuxAdapter seam underneath.