Releases: cyberuni/cyber-mux
Release list
cyber-mux@0.4.0
Minor Changes
-
59865fd: Add the agent-lifecycle capability, normalized by truthful refusal rather than emulation.
cyber-mux/agentsubpath — the newAgentLifecyclecapability seam, itsderiveAgentWait
orchestrator, and theAgentLifecycleUnsupportedErrorrefusal. TheAgentStatustype
(idle | working | blocked | done | unknown) rides out on the core.barrel viaLivePane.LivePane.agentStatus— herdr 0.7.5's per-paneagent_statusfeed, reported on the live pane
listing exactly like the herdr-onlyharnessfield: filled where the backend can answer, OMITTED
(never a falseunknown) where it cannot.agent status <pane>— a snapshot that degrades truthfully: it prints the resolved pane's
agentStatuson herdr and, on a backend with no agent-state feed, still prints the pane with no
status and exits 0 rather than refusing.agent wait <pane> [--until <s...>] [--timeout <ms>]— a blocking drive of herdr's native
agent wait, reporting the reached state. On tmux, wezterm and zellij — which have no native
per-pane agent-state primitive — it is refused withbackend-unsupported(exit 1) naming the
herdr-only constraint, the exact mirror of howtemplate saverefuses a geometry-incapable backend.agentApi(env, deps?)— the exec-boundcyber-mux/agentfacade paralleling
worktreeApi/templateApi: it resolves the backend fromenvonce and exposes
supported()/status(target)/wait(target, opts?)with the seams bound. It adds no logic of
its own —supportedreads the same capability presence,statusthe sameLivePane.agentStatus,
andwaitroutes throughderiveAgentWait, so the refusal stays specified and enforced once.
The refuse-not-emulate normalization is deliberate: a lookalike wait built from output polling would
silently disagree with herdr's own state derivation, so a backend without the primitive is refused
rather than guessed. -
fa91591: A missing
<pane>on a pane verb now lists the live panes as candidates. It stays a usage error
(exit 2), but instead of merely naming the missing argument it queries the backend and reports each
live pane's id, label, and cwd under the newmissing-panecode — the same rendering and code family
asambiguous-pane— so the caller's next move (cyber-mux list) is folded into the error. This
coversread,focus,close,submit,send text,send keys,exists, andagent status.
With no multiplexer the deeperno-muxfailure (exit 1) still surfaces first, andagent waiton a
backend without the agent-lifecycle capability is still refused withbackend-unsupported(exit 1)
ahead of any missing-pane check.cyber-mux listalso gains a herdr-only agent-status column, shown only when the backend feeds a
per-pane agent state (omitted entirely on tmux, wezterm, and zellij). -
1914eb9: Add a portable
waitForOutputto the seam, and awaitverb to the CLI: block until a pane's output
matches a literal (--match) or a regex (--regex), or until a timeout elapses. Real support on
every backend rather than a one-backend capability — herdr drives its nativepane wait-output
(0.7.5), while tmux, WezTerm and Zellij poll their existing read through one shared loop, so every
backend searches exactly the snapshot itsreadreturns and existing output counts as a match. The
CLI puts the verdict in the exit code (0 matched, 1 timed out) and prints the pane's own output on a
timeout, so a caller that guessed the wrong pattern keeps the evidence; a pane that is GONE fails with
pane-not-foundinstead of quietly waiting out the deadline, and so does a wait that never ran (a
herdr older than 0.7.5, which has nopane wait-output, fails loudly rather than reporting an instant
false timeout). Resolves #97. -
2b04288: Add the
cyber-mux worktree provisionCLI verb — the command-line surface over the
provisionWorktreeseam. It reuses a free worktree (the setworktree listmarks(removable)and
pruneremoves) or creates a fresh checkout at the sibling path, and reports whether itreusedor
created, the worktree, and on reuse the recycled entry. Flags:--branch(required),--base,
--path,--format.The verb uses the default availability gate only and offers no flag to inject a host predicate —
that is the deliberate surface divergence from the library seam, which takes an injectable one. A
host that must exclude, say, a live-session worktree callsWorktreeApi.provisiondirectly.The worktree spec is now split by public surface to make that divergence first-class: the
cyber-mux worktree <verb>surface (verbs, flag defaults, table rendering, and this new verb) is
specified undercli/worktree/, while the surface-independent library contract (the seam, git-owns-
facts, removal ordering, and the injectable predicate) stays inmux/worktree/. -
7df7b93: Add
worktree provision— reuse a free worktree instead of always creating a fresh one. The twin of
prune: prune removes disposable worktrees,provisionWorktree/WorktreeApi.provisionrecycles one
through the same default gate (isWorktreeRemovable), else creates. Availability is an injected
predicate so a host can add its own "no live session bound" check without leaking that concept into
the worktree seam. A reused worktree is reset to a pristine tree on a fresh branch (switch -c→
reset --hard→clean -fdx). The result reports whether it reused or created, and carries the
recycled worktree in full.
Patch Changes
- bad1d3f: Validate
MuxOpenOptions.ratioat the seam: a sizing adapter (tmux,herdr,wezterm) now rejects
a ratio outside0 < ratio < 1with a named error instead of rendering it into a silently broken split
(above 1 produced a negative length; 0 or 1 gave a whole-region split). The check lives with the size
render (assertRatioInRange), so a backend that cannot size a split (zellij, which drops the ratio)
is unaffected, andtemplate's schema still refuses a degenerate ratio earlier per node. The range was
already documented as a contract precondition; it is now enforced. Resolves #18.
cyber-mux@0.3.0
Minor Changes
-
cd74775: Expose a library API.
cyber-muxnow publishes real entry points beside the CLI:cyber-mux— the multiplexer core:resolveMux, which returns aMuxSessionwithExecbound
(mux.open(opts), no runner threaded per call), over the raw exec-injectedMuxAdaptercontract
and its types reached viaresolveMuxAdapter; the mux probe (probeMultiplexer,currentPane);
callerPane; the tmux/herdr/wezterm adapters;nudge; and theExec/NewIdseams (each a type
plus its real implementation).cyber-mux/worktree— the git-worktree adapter (resolvePrimaryRoot,assertDistinctFromPrimary,
gitWorktreeAdapter,listWorktreesFromGit,removeWorktreeSafely, and theWorktreeFsseam),
plusworktreeApi(deps?)— the same helpers withExec/WorktreeFsbound.cyber-mux/template— template resolution and theTemplateStoreseam, plustemplateApi(env, deps?)
— resolution withenv/Exec/TemplateStorebound.
Every entry ships type declarations, and the core is pure: it takes its effects (
Exec,NewId,
WorktreeFs,TemplateStore) as parameters, with the real implementations exported as separate
named values, so a host binds them once and tests drive fakes.probeMultiplexergains an
envPrefixoption so a host embedding cyber-mux under its own namespace adopts the env fast-path
without forking detection. The CLI bin is unchanged.The package also ships its TypeScript source (tests excluded) alongside declaration maps, so
go-to-definition on any exported symbol lands in real source rather than a generated.d.ts.Pre-1.0, depend on this with a caret range (
^0.2.0); a 0.x minor may still carry breaking changes. -
90daa48:
cyber-mux template edit [<name>]shows a template's panes and fills them in — the other half of
template save, which captures geometry but lands with nocommandon any pane.The bare form lists and mutates nothing: a table of every pane with its position, label, dir and
current value, plushelp[N]suggestions for what to do next. Itspanecolumn is verbatim what
--settakes, so acting on the listing is a paste rather than a derivation. Panes are addressed by
ordinal (3, or2.3for tab 2 pane 3) and never by label, since two panes may share a label by
design. Aposition(top-left,right) is shown because apply order is a tree walk rather than a
reading order — pane 2 of a 2x2 is the pane below pane 1, not the one beside it.--set <pane>=<value>writes without a terminal, is repeatable, splits on the first=only so a
value may contain one, and clears the field when the value is empty. Re-running the same--setis a
no-op that exits 0 and leaves the file's mtime alone, so a checked-in template is never dirtied by an
edit that changes nothing. A batch naming one pane that does not exist writes none of them, and the
error lists every identifier that would have worked.--interactiveasks one question per pane instead, in apply order, with the current value pre-filled
into the editable line: Enter keeps,-clears,'-'is a literal dash, Ctrl-D abandons the edit and
leaves the file untouched. It refuses when stdin is not a tty or when--format json|agentwas asked
for, and points at--setinstead.--field command|labelpicks what both modes write;--dry-runprints the result instead of writing
it. A template's spelling survives either way — one written with the flatpanes/arrangesugar
comes back out flat rather than re-spelled as a tree. -
ff91915:
worktree listnow answers whether a worktree is still needed, not only whether it is occupied.Entries carry two new booleans —
merged(the branch's tip is an ancestor of the repo's default
branch) anddirty(the checkout has uncommitted changes) — read from git on every backend, exactly
aslinkedandprunableare. The default branch is resolved fromorigin/HEAD, falling back to the
primary checkout's branch;mainis never hardcoded.The table compresses those two, plus the workspace binding, into a single
(removable)marker onBRANCH
— merged and clean and unoccupied, i.e. safe to remove. It rides onBRANCHbecause the
branch is what carries the work that landed, and it is mutually exclusive with(*), so no row ever
shows two markers.--format jsonis unmarked as always: consumers read the rawmergedanddirty
booleans and compose their own policy.A squash or rebase merge rewrites the commits, so such a branch reads
merged: falseand goes
unmarked — the signal errs toward "still needed" deliberately. Any signal git cannot determine (a
detached HEAD, aprunableentry, no default branch) is an absent field and an unmarked row, never
a guess and never a failure.This reports only. Removal gating and pruning are unchanged: nothing consults
(removable)before deleting
anything. -
6a36ad6: Add
worktree pruneto remove every disposable worktree in one call — the same gateworktree listmarks(removable)with. The bare form previews the candidates; pass--forceto actually remove them. -
20da54f: Add a Zellij backend — the fourth multiplexer cyber-mux drives, after tmux, herdr, and WezTerm.
Detected via
$ZELLIJ(fast-path overrideCYBER_MUX=zellij), with self-identity from
$ZELLIJ_PANE_ID. Driven throughzellij action …and gated on Zellij ≥ 0.44.1, the release
that added per-pane CLI addressing (--pane-idacross the action verbs,focus-pane-id,
list-panes --json, and ids returned fromnew-pane/new-tab) — the stable per-pane handle the
seam requires.Capability shape: it names panes (
new-pane --name/rename-pane) and reports the focused pane
(is_focused), unlike WezTerm. Aworkspaceplacement opens a new tab in the ambient session —
Zellij pane ids are session-scoped and the seam's pane target carries no session — but the occupied
workspace is still reported as the session name, unlike tmux. Tiled splits are always even (no
ratio), env rides in as a command prefix (no--envflag), and pane-geometry introspection
(template save) is not yet supported.
Patch Changes
-
c4f2293:
template savenow explains the command limit in terms of portability rather than availability,
and its help text changes accordingly.The old wording — "no multiplexer can report the command a pane was launched with" — was true as
literally phrased and misleading in effect. Probed against live binaries: herdr 0.7.4'spane process-inforeturns full argv for a pane's whole foreground tree, and/procreaches the same from
a pid on any backend, so "there is nothing to be had here" was false.The real reason a capture writes no
commandis that what a backend reports is the resolved
command line, not the one that was typed:nr web devcomes back as
node /run/user/1000/fnm_multishells/4223_1784479278417/bin/nr web dev, a path carrying a uid, a pid
and a timestamp that is dead on the next machine. A template is meant to be checked in and run
elsewhere, and applying one submits whatevercommandsays — so a wrong one fails by executing
something. Absent beats wrong.Behavior is unchanged: a capture still records no
commandon any pane.template save --helpnow
also names the twotemplate editcalls that fill them in. -
1fa102d: Place every tab of a multi-tab template in the workspace the apply opened. Previously only the first tab landed there — each later tab was created beside the pane the command was run from, because a
tabplacement with no anchor is resolved against the workspace the user is looking at.SessionOpenOptionsgainswithin, the workspace atabplacement opens inside, honored by the herdr and WezTerm backends and ignored by tmux, which has no workspace tier. -
9af5af2:
CYBER_MUX=screenis now rejected with a named error instead of the generic "run inside a
multiplexer" throw. GNU Screen is detected — an override pinning it, or a realscreenancestor, is
reported truthfully — but it is not a drivable backend, and pinning it now says so plainly.The
CYBER_MUXcontract used to namescreenas an accepted override value alongside
tmux/herdr/wezterm, but no adapter ever stood behind it, so settingCYBER_MUX=screenproduced
cyber-mux requires a session backend — run inside tmux, herdr, or wezterm— a lie, since the caller
had declared a real multiplexer. The value looked supported and was not.Probed live (GNU Screen 5.0.2): the blocker is identity, which is load-bearing across the whole
contract (SessionTarget.id,currentPane,LivePane.id). Screen addresses its split regions
positionally — no per-region id to send to or read from — and leaves$WINDOWunset in windows
opened viascreen -X, exactly the panes a driver creates, so a pane cannot even self-identify. Every
supported backend ships a stable per-pane id ($TMUX_PANE/$HERDR_PANE_ID/$WEZTERM_PANE);
screen has no equivalent for driven panes.Rather than ship a half-faithful adapter with unstable pane identity,
cyber-muxkeepsscreen
recognized-but-rejected: the value is still honored as an override (so it is never silently ignored
and fallen through to discovery) and now fails with the reason. Detection of a realscreensession
is unchanged; only the drive step rejects it. Full probe and decision: the45-screen-adapterADR. -
9c06f45:
worktree listdrops the LINKED column from the table. The primary checkout is marked(*)after
its branch instead, so the one bit that column carried costs no width.--format jsonis unchanged:
every entry still carries thelinkedboolean. -
68c28a1:
worktree listmarks a prunable worktree — one whose checkout no longer exists on disk — with
(gone)after it...