Skip to content

Releases: cyberuni/cyber-mux

cyber-mux@0.4.0

Choose a tag to compare

@unional unional released this 04 Aug 08:43
8f53520

Minor Changes

  • 59865fd: Add the agent-lifecycle capability, normalized by truthful refusal rather than emulation.

    • cyber-mux/agent subpath — the new AgentLifecycle capability seam, its deriveAgentWait
      orchestrator, and the AgentLifecycleUnsupportedError refusal. The AgentStatus type
      (idle | working | blocked | done | unknown) rides out on the core . barrel via LivePane.
    • LivePane.agentStatus — herdr 0.7.5's per-pane agent_status feed, reported on the live pane
      listing exactly like the herdr-only harness field: filled where the backend can answer, OMITTED
      (never a false unknown) where it cannot.
    • agent status <pane> — a snapshot that degrades truthfully: it prints the resolved pane's
      agentStatus on 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 with backend-unsupported (exit 1) naming the
      herdr-only constraint, the exact mirror of how template save refuses a geometry-incapable backend.
    • agentApi(env, deps?) — the exec-bound cyber-mux/agent facade paralleling
      worktreeApi/templateApi: it resolves the backend from env once and exposes
      supported() / status(target) / wait(target, opts?) with the seams bound. It adds no logic of
      its own — supported reads the same capability presence, status the same LivePane.agentStatus,
      and wait routes through deriveAgentWait, 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 new missing-pane code — the same rendering and code family
    as ambiguous-pane — so the caller's next move (cyber-mux list) is folded into the error. This
    covers read, focus, close, submit, send text, send keys, exists, and agent status.
    With no multiplexer the deeper no-mux failure (exit 1) still surfaces first, and agent wait on a
    backend without the agent-lifecycle capability is still refused with backend-unsupported (exit 1)
    ahead of any missing-pane check.

    cyber-mux list also 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 waitForOutput to the seam, and a wait verb 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 native pane 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 its read returns 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-found instead of quietly waiting out the deadline, and so does a wait that never ran (a
    herdr older than 0.7.5, which has no pane wait-output, fails loudly rather than reporting an instant
    false timeout). Resolves #97.

  • 2b04288: Add the cyber-mux worktree provision CLI verb — the command-line surface over the
    provisionWorktree seam. It reuses a free worktree (the set worktree list marks (removable) and
    prune removes) or creates a fresh checkout at the sibling path, and reports whether it reused or
    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 calls WorktreeApi.provision directly.

    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 under cli/worktree/, while the surface-independent library contract (the seam, git-owns-
    facts, removal ordering, and the injectable predicate) stays in mux/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.provision recycles 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 --hardclean -fdx). The result reports whether it reused or created, and carries the
    recycled worktree in full.

Patch Changes

  • bad1d3f: Validate MuxOpenOptions.ratio at the seam: a sizing adapter (tmux, herdr, wezterm) now rejects
    a ratio outside 0 < ratio < 1 with 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, and template'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

Choose a tag to compare

@unional unional released this 21 Jul 10:18
c37b207

Minor Changes

  • cd74775: Expose a library API. cyber-mux now publishes real entry points beside the CLI:

    • cyber-mux — the multiplexer core: resolveMux, which returns a MuxSession with Exec bound
      (mux.open(opts), no runner threaded per call), over the raw exec-injected MuxAdapter contract
      and its types reached via resolveMuxAdapter; the mux probe (probeMultiplexer, currentPane);
      callerPane; the tmux/herdr/wezterm adapters; nudge; and the Exec/NewId seams (each a type
      plus its real implementation).
    • cyber-mux/worktree — the git-worktree adapter (resolvePrimaryRoot, assertDistinctFromPrimary,
      gitWorktreeAdapter, listWorktreesFromGit, removeWorktreeSafely, and the WorktreeFs seam),
      plus worktreeApi(deps?) — the same helpers with Exec/WorktreeFs bound.
    • cyber-mux/template — template resolution and the TemplateStore seam, plus templateApi(env, deps?)
      — resolution with env/Exec/TemplateStore bound.

    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. probeMultiplexer gains an
    envPrefix option 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 no command on any pane.

    The bare form lists and mutates nothing: a table of every pane with its position, label, dir and
    current value, plus help[N] suggestions for what to do next. Its pane column is verbatim what
    --set takes, so acting on the listing is a paste rather than a derivation. Panes are addressed by
    ordinal (3, or 2.3 for tab 2 pane 3) and never by label, since two panes may share a label by
    design. A position (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 --set is 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.

    --interactive asks 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|agent was asked
    for, and points at --set instead.

    --field command|label picks what both modes write; --dry-run prints the result instead of writing
    it. A template's spelling survives either way — one written with the flat panes/arrange sugar
    comes back out flat rather than re-spelled as a tree.

  • ff91915: worktree list now 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) and dirty (the checkout has uncommitted changes) — read from git on every backend, exactly
    as linked and prunable are. The default branch is resolved from origin/HEAD, falling back to the
    primary checkout's branch; main is never hardcoded.

    The table compresses those two, plus the workspace binding, into a single (removable) marker on BRANCH
    — merged and clean and unoccupied, i.e. safe to remove. It rides on BRANCH because the
    branch is what carries the work that landed, and it is mutually exclusive with (*), so no row ever
    shows two markers. --format json is unmarked as always: consumers read the raw merged and dirty
    booleans and compose their own policy.

    A squash or rebase merge rewrites the commits, so such a branch reads merged: false and goes
    unmarked — the signal errs toward "still needed" deliberately. Any signal git cannot determine (a
    detached HEAD, a prunable entry, 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 prune to remove every disposable worktree in one call — the same gate worktree list marks (removable) with. The bare form previews the candidates; pass --force to actually remove them.

  • 20da54f: Add a Zellij backend — the fourth multiplexer cyber-mux drives, after tmux, herdr, and WezTerm.

    Detected via $ZELLIJ (fast-path override CYBER_MUX=zellij), with self-identity from
    $ZELLIJ_PANE_ID. Driven through zellij action … and gated on Zellij ≥ 0.44.1, the release
    that added per-pane CLI addressing (--pane-id across the action verbs, focus-pane-id,
    list-panes --json, and ids returned from new-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. A workspace placement 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 --env flag), and pane-geometry introspection
    (template save) is not yet supported.

Patch Changes

  • c4f2293: template save now 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's pane process-info returns full argv for a pane's whole foreground tree, and /proc reaches 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 command is that what a backend reports is the resolved
    command line, not the one that was typed: nr web dev comes 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 whatever command says — so a wrong one fails by executing
    something. Absent beats wrong.

    Behavior is unchanged: a capture still records no command on any pane. template save --help now
    also names the two template edit calls 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 tab placement with no anchor is resolved against the workspace the user is looking at. SessionOpenOptions gains within, the workspace a tab placement opens inside, honored by the herdr and WezTerm backends and ignored by tmux, which has no workspace tier.

  • 9af5af2: CYBER_MUX=screen is 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 real screen ancestor, is
    reported truthfully — but it is not a drivable backend, and pinning it now says so plainly.

    The CYBER_MUX contract used to name screen as an accepted override value alongside
    tmux/herdr/wezterm, but no adapter ever stood behind it, so setting CYBER_MUX=screen produced
    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 $WINDOW unset in windows
    opened via screen -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-mux keeps screen
    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 real screen session
    is unchanged; only the drive step rejects it. Full probe and decision: the 45-screen-adapter ADR.

  • 9c06f45: worktree list drops 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 json is unchanged:
    every entry still carries the linked boolean.

  • 68c28a1: worktree list marks a prunable worktree — one whose checkout no longer exists on disk — with
    (gone) after it...

Read more