Skip to content

Workspace: add the @libtmux/workspace-cli package - #24

Open
tony wants to merge 136 commits into
masterfrom
workspace-cli
Open

tony wants to merge 136 commits into
masterfrom
workspace-cli

Conversation

@tony

@tony tony commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Adds @libtmux/workspace-cli, a package whose tmux-workspace binary manages tmux workspaces from YAML and JSON on Node.js 22.12+ or Bun 1.3.14+. Parsing follows tmuxp (repo, v1.74.0 release notes, PyPI), and loads run on native document and tmux services built on libtmux.

libtmux keeps its zero-runtime-dependency contract: Commander, YAML and terminal text helpers are bundled into the CLI only.

What the CLI does

  • Discovery deduplicates aliased directories while keeping precedence, and excludes hidden workspace files. Search uses native JavaScript regular expressions.
  • load allocates windows around explicit indexes with native indexed creation, skips readiness polling for panes without commands, and reports owned window and pane IDs through cleanup failures.
  • Appending verifies the inherited endpoint, then resolves the borrowed session and its daemon identity from one snapshot.
  • Human loads attach through the controlling terminal and switch tmux's authenticated current client; interrupted attachment keeps the workspace.
  • Optional Python extensions run the tmuxp 1.74 builder protocol with owned, bounded child streams, report observed effects without claiming ownership, and keep empty plugin lists native.
  • Cancellation reaches capture reads, file saves and blocked output writes; interrupted capture exits without replacing the destination.
  • Child processes drain briefly after exit, then stop the owned process group, and a successful parent status survives forced capture closure.
  • Human load renders progress presets and templates; --log-file appends severity-filtered NDJSON.
  • Bare root and import invocations return structured usage errors in machine modes instead of human help.
  • -8 is refused before file lookup or tmux invocation.
  • The command reference and Bash, Zsh and fish completion are generated from Commander metadata and checked for freshness.
  • Native imports preserve format-specific command groups, pane order, focus, synchronization timing and invocation-relative directories; unsupported source fields fail before preview or replacement.
  • Native load validates execution and readiness fields across every input before scripts or tmux changes.

Core library changes

  • Window creation accepts an exact index, rejected when invalid or combined with direction.
  • Live snapshots expose the captured daemonIdentity, so callers can tie topology to the daemon that answered without a second acquisition.
  • Daemon identity reads and showOptions and showResolvedOptions on every scope accept CommandOptions, so a caller's abort signal reaches them.
  • colors narrows to 256; 88-colour mode is rejected at runtime and no longer forwards -8. A caller passing 88 stops type-checking.
  • Server.hasSession lists sessions and compares the name directly, exact for any name tmux can store, including one holding . or :. It returns false when no exact name exists or no server is running; other failures to list sessions raise.
  • Server.connect({ target }) rejects session-name prefixes; names containing . or : retain tmux target parsing.
  • Server.versionAtLeast ranks a named development build (next-X.Y) below the release it names and above the one before it, and reads a release candidate (X.Y-rc) as that release. An untargeted build (bare master, or <tag>-master) still satisfies any minimum.
  • Server.validateLayouts and Window.selectLayout check a window plan's layout strings — preset names, mirrored variants, and a serialized tree's checksum and grammar — before either workspace builder applies it.
  • packages/libtmux/docs/api.md is regenerated for the new signatures.

Verification

packages/workspace-cli/tests covers parsing, documents, load, progress, completions and terminal behaviour against real tmux. The install check packs the packages and runs every command from offline-installed tarballs on Node and Bun:

$ bun run --cwd packages/workspace-cli test:install

Platform scope

Native lifecycle and terminal tests cover Linux. The macOS lane verifies package installation; native lifecycle behavior there is unverified.

tony added 30 commits September 12, 2026 07:01
why: Workspace builders lack a user-facing CLI.

what:
- Add tmuxp parsing and native document/load services.
- Report partial effects and supervise bounded child output.
- Check the Python shell runtime and document remaining gaps.
why: Pane IDs repeat across tmux servers.

what:
- Compare the current and selected daemon identities.
- Reject mismatched append targets before creating windows.
why: Append validation must stop when its caller cancels.

what:
- Accept acquisition options for daemon identity reads.
- Pass CLI cancellation through and regenerate the API reference.
why: Node color warnings can corrupt machine stderr.

what:
- Apply color precedence before loading command modules.
- Verify every command from offline-installed tarballs.
- Document the installed check and remaining CLI work.
why: Python matching needs structured input over stdin.

what:
- Write child input with backpressure while draining output.
- Join canceled processes and their pending input streams.
why: Directory aliases caused repeated workspace entries.

what:
- Deduplicate candidate directories while preserving precedence.
- Exclude hidden workspace files from discovery and counts.
why: Compatibility should preserve a fast native workflow.

what:
- Document native regular expression semantics.
- Keep Python-only behavior behind the optional runtime boundary.
why: Human load must enter the final workspace and reject unavailable
terminal or unrelated-server context before creating sessions.

what:
- Attach through the controlling terminal with redirected output
- Authenticate and explicitly switch tmux's selected current client
- Preserve workspace effects when attachment is interrupted
- Verify real terminal behavior on Bun and Node 22
why: Readiness polling delayed blank workspace loads by two seconds even
though no commands needed a shell prompt.

what:
- Skip shell-policy queries when no pane commands need readiness
- Wait only for panes receiving commands
- Prove blank panes finish without a prompt under a one-second bound
why: Keep lifecycle and script diagnostics available without losing
command results when logging fails or cancellation interrupts output.

what:
- Append severity-filtered NDJSON and retain structured machine stdout
- Preserve command outcomes on log failures and close owned handles
- Verify full-file failures, cancellation, and installed Node/Bun logs
why: A blocked output consumer can hold the CLI open after interruption
even after its owned child has exited.

what:
- Propagate cancellation through human and machine output writes
- Bound final interruption results and exit after owned cleanup
- Verify unread pipes and child reaping on Node and Bun
why: Waiting for an escalation timer after the owned process group has
disappeared adds half a second to cancellation without improving cleanup.

what:
- Finish immediately when the owned process group no longer exists
- Retain escalation for descendants after their direct parent exits
- Verify prompt cancellation and the surviving-descendant path
why: Load accepts progress flags but gives no terminal feedback.

what:
- Render presets and templates from native load completion events
- Bound script history and serialize cancellable terminal updates
- Keep machine output clean and preserve raw stream boundaries
- Verify terminal behavior, runtime floors and local loop budgets
Add index to direct and planned window creation so callers can reserve
sparse layouts without creating a window elsewhere and moving it.

Reject invalid indexes and relative direction before transport. Preserve
occupied windows and verify direct and batched creation on tmux 3.2a and
the current server.
why: Implicit windows can occupy indexes requested by later inputs.

what:
- Allocate around explicit indexes using native indexed creation
- Preserve renumbering policy during early bootstrap removal
- Report owned window and pane IDs through cleanup failures
- Validate index scalars before creating workspace state
why: Supported tmux versions reject the removed -8 flag.

what:
- Reject 88-color mode before file lookup or tmux invocation
- Explain the supported -2 alternative in help and the CLI guide
- Verify human and machine diagnostics without starting tmux
why: File-only commands eagerly load the tmux and YAML implementations.

what:
- Import tmux at dispatch and check cancellation before the operation
- Load YAML only when reading or writing that document format
- Serialize output before creating its temporary file
why: Workspace capture cannot stop an option read without a typed
cancellation path through the core API.

what: Forward command options through own, inherited and global option
reads. Keep scope and decoding unchanged, refresh generated signatures,
and cover every read scope with a pre-aborted signal.
why: Interrupted capture kept reading tmux and replaced its destination
before output noticed cancellation.

what: Pass the invocation signal through capture reads and file saves.
Check cancellation before atomic publication, retain completed writes,
and prove interrupted backend clients exit without replacing the file.
why: Keep completion and offline references aligned with native
options without starting a runtime on each Tab.

what:
- Export Commander metadata and validate native shell choices
- Generate Bash, Zsh and fish scripts with reference artifacts
- Check freshness, real shell insertion and installed script parity
why: Bare root and import groups must not mix human help into
machine output or report success without an operation.

what:
- Return structured usage diagnostics and status 2 in machine modes
- Preserve human help and explicit help/version handling
- Cover both groups with JSON and NDJSON regressions
why: Callers need to associate topology with the daemon that answered
without a second acquisition that may observe a replacement.

what:
- Return the immutable captured identity with each live snapshot
- Verify empty-server identity, cancellation and unchanged query count
- Update generated API examples and the published type contract
why: Resolving the current pane in a second acquisition can observe a
replacement daemon after the selected endpoint has been checked.

what:
- Compare the selected snapshot identity with the inherited endpoint
- Resolve the borrowed session from that same snapshot
- Remove one complete graph acquisition from append preflight
why: Supported tmux versions reject 88-color mode, so accepting it in
the core defers a configuration error until execution.

what:
- Restrict public color overrides to 256 with runtime validation
- Remove legacy client flags and update docs and type contracts
- Verify rejection and 256-color acquisition on both tmux lanes
why: An empty plugin array selects no extension, but JavaScript array
truthiness incorrectly rejected it as requiring the Python bridge.

what:
- Require an actual plugin selection before leaving native loading
- Verify blank panes load with empty plugins and unavailable Python
why: A completed child can leave descendants holding captured pipes open.
Cancellation callbacks also discarded output already captured by the CLI.

what:
- Allow 100ms to drain after exit, then stop the owned process group
- Close remaining capture streams after process-group cleanup
- Retain captured output when a callback raises the active abort reason
- Prove normal-exit and cancelled descendant cleanup in the runner tests
why: Node reports premature closure when cleanup ends inherited pipes,
replacing a successful parent status with an unrelated stream error.

what:
- Recognize only deliberate capture closure during owned cleanup
- Mark interrupted capture as truncated and preserve the parent status
- Exercise the emitted runner on Node 22 with a TERM-ignoring child
why: Python plugins and custom builders need an explicit boundary while
ordinary workspace loading remains native and independent of Python.

what:
- Validate all native plans and extension selections before execution
- Run the tmuxp 1.74 builder protocol with owned bounded child streams
- Retain the authenticated append target across mixed input files
- Report observed effects without claiming ownership or rolling them back
- Reconcile surviving topology only within the same daemon lifetime
- Verify plugins, cancellation, custom builders and installed Node/Bun use
why: Chunk boundaries inserted blank panel lines into CRLF output.

what:
- Track a trailing carriage return separately for each script stream.
- Verify empty chunks, interleaving and workspace resets.
why: Avoid restarting Bun for each command behavior check while retaining
executable coverage for help and ordinary nonzero exit statuses.

what:
- Call the injected command entry point with fresh captured streams.
- Share isolated environment setup with the executable checks.
- Keep usage and editor failures at the process boundary.
tony added 12 commits September 18, 2026 22:07
…ument asks for that is not there

why: Readiness was conditional on the server's default-shell being zsh, so
under bash every pane showed its command twice: the text arrived before the
line editor owned the terminal, the tty echoed it, and readline redrew it. The
rule was never about zsh. Two quieter silences went with it: a builder option
this port does not implement refused the whole document, and a start_directory
that does not exist built happily with every pane in $HOME and no word about
the typo.

what:
- Wait for a pane's prompt whatever the pane's shell is, unless the document
  turns readiness off
- Warn, rather than refuse, for an unknown key inside
  workspace_builder_options; the document still loads
- Warn once per declared start_directory tmux cannot change into, naming the
  path and the fallback
why: pane-base-index and its like are window options in tmux. Setting one
through a session target lands it on whatever window is current at the time,
which here is the bootstrap window the load is about to remove, so a document
asking for pane-base-index got panes numbered from zero and no word about it.

what:
- Split a session's options: by tmux's own window-option catalog, applying the
  window-scoped ones to every window the load creates and the rest to the
  session
- Order them before a window's own options:, so a window that names the same
  option again still wins
why: With no pane declaring focus, a load left the first pane of a window
active, because every split is created detached. tmuxp leaves the last pane
created. Every other layout behaviour here is held to tmuxp; this one was not,
so the cursor came up somewhere else after an otherwise identical load.

what:
- Select the last pane created in each window when nothing declares focus; an
  explicit focus still wins
- Update the README: the reuse comparison, the session removed on a failed
  create, the append that keeps and names its windows, and readiness for any
  shell
…where it differs

why: applyWindow batched every split off the window's active pane with no
layout pass between them, so halving one pane in turn ran out of rows and tmux
answered "size or position no space for a new pane". A six-pane window built by
the CLI failed through the published API on the same host and the same tmux.
The CLI has carried the fix, and the comment explaining it, since this branch
started; the library never got it, and its real-tmux suite never tried more
than two panes in a window.

why also: the two builders answer differently -- one converges a session that
exists, the other compares it against the document -- and accept different
documents, and nothing said so.

what:
- Split one pane at a time and rebalance between splits; a declared layout is
  still applied last and still has the final say
- Test a six-pane window at 80x24 against real tmux
- State in the README and at the entry point that the CLI and this package are
  separate implementations, naming the converge-versus-compare difference and
  the fields the strict schema rejects
…port focuses

The import writes an explicit focus on the first pane unless the source names
one, so that case is about explicit focus winning, not about the default the
last-pane change moved.
…he CLI

The library's README and entry point already say it; the command's README said
nothing, so a reader arriving from either side now learns the same thing: two
builders, different answers for a session that exists, different accepted
documents.
why: `partial` reports retained effects. A reused session that does not
satisfy the document has none -- reuse compares, it never rebuilds -- so
`status: "partial"` beside `completed_stages: []` claimed progress that never
happened. `invalid_workspace` was also the wrong side: the document parsed
fine, the session was the one that disagreed.

what:
- Add `session_mismatch` to the closed `MachineCode` set: the named session
  exists and does not satisfy the document.
- Report the reuse-mismatch load with `code: "session_mismatch"` and
  `status: "error"` instead of `invalid_workspace`/`partial`; nothing built,
  nothing changed, session left exactly as it was.
- Move the existing reuse-mismatch test onto the new code and status.
- Log both changed lines in the still-unreleased changelog entry.
why: The previous commit forced status to error whenever the thrown error was
a session mismatch, regardless of what else load had already built. A load of
two documents where the first completes and the second finds a mismatched
session then reported error for a session it had just built and kept --
moving the same lie to the other direction.

what:
- Fold the exemption into the `changed` predicate: a result with
  `missing_windows` set contributes no retained effect, but a `completed`
  or otherwise-touched result from another input in the same load still
  does. Drop the blanket status override.
- Add a two-input regression: first document builds and keeps a session,
  second mismatches; expect `status: "partial"`, the first result at
  `stage: "completed"`, and `session_mismatch` on the error.
why: The "<name> is already running. Attach?" prompt's answer was orthogonal
to the reuse comparison: whichever way the user answered, load ran the same
compare against the session and reported session_mismatch for anything the
document declared that the session did not hold. Declining means the tool
never went to reuse that session -- nothing was built, nothing was switched,
the client stayed put -- so there is nothing to compare and no mismatch to
report. The visible symptom was the wrong exit code for declining the
prompt; the real defect was running the comparison at all after a decline,
independent of what the exit code was mapped to.

what:
- Track the decline on the existing-session branch of the attach prompt
  (the only prompt this applies to; it names inputs.at(-1) alone) and skip
  the missingWindows compare for that one input when set. Accepting the
  prompt (or the non-interactive default) still compares, as does every
  other reuse -- mirrors rs's `Disposition::Decline` guard.
- The skip is per-input, not a return before the loop: a multi-document
  load still builds every earlier document that does not yet exist: only
  the last input's own comparison is what a decline excuses.
- Add a pty-driven terminal test: a bare session missing a declared window,
  attach prompt declined, asserts exit 0 and the session untouched. The
  existing (n) test at this prompt used a session matching the document
  exactly, so it could assert an exit code without ever reaching the
  compare -- proved by reverting the fix and watching this new test's exit
  assertion fail (0 vs 1).
why: The pty test asserting a declined attach skips the reuse comparison
named the decision by shorthand in its own comment rather than describing
the behavior, which reads like a ticket reference embedded in the test.

what:
- Reword the comment to state what the assertion checks without the
  shorthand names.
why: An interrupt during session creation was handled the same as a build
failure: the before-script catch and the outer session-create catch each
killed a session this load had just started, whatever raised the error.
Stopping the tool is not the same as it refusing or failing something it can
name, and it does not always know which of its own mutations before the
interrupt landed; destroying what it built on the way out is a second
unbounded operation begun at the moment the user asked to stop.

what:
- Guard both catches on the abort signal's state: a new session is only
  removed for a diagnosable failure, never for a cancellation.
- Add a regression for an interrupt raised while building a window with no
  before_script involved: the session and the window already created are
  retained and reported partial, not removed. Reverting the guard reproduces
  the old removal.
- Rework the existing before-script cancellation test the same way: the
  session it was building is retained rather than torn down. Reverting that
  guard reproduces the old failure too.
- Log the changed behavior.
why: `applyWorkspace` ran to completion once begun. A caller that had
already given up -- a request abandoned, a watcher restarting -- still
paid for every tmux call, and there was no way to say so. The README also
said nothing about two calls racing on one session name, which tmux
cannot arbitrate: a session that does not exist yet has no lock, so both
callers can observe none and each start one.

what:
- `applyWorkspace` and `planWorkspace` take a `signal`, threaded through
  every tmux call they make, plus a check at the loop boundaries where no
  call is in flight
- Say in both READMEs that concurrent calls on one name are uncoordinated,
  and that callers should serialize them
- Test: an already-fired signal rejects before any session exists, and it
  is the preflight that rejects, not an unknown-option guard
- Changelog entries

tests: workspace package typechecks; 43 tests pass. Dropping the signal in
normalization fails the new test.
tony added a commit that referenced this pull request Sep 19, 2026
why: `@libtmux/workspace-cli` is new on this branch, so most of its
`Unreleased` entries described the `tmux-workspace` command's own prior
behaviour -- a state no user of a published release ever saw. WRITING.md's
slop-prevention rule bars mentioning an intermediate state or "no longer"
behaviour unless a published release's users actually experienced the old
one, and files this kind of entry as a plain description of what the
command does instead.

what:
- Fold the `@libtmux/workspace-cli` entries (`load`, `freeze`, `convert`,
  `import`, the machine `code` taxonomy, `--ndjson` events, terminal
  sizing, and packaging) into a few grouped paragraphs describing the
  command's behaviour, dropping "no longer"/"now"/"previously"/"was"
  contrast and the `**Breaking.**` marker, since nothing published broke
- Keep every entry under `@libtmux/workspace` (`applyWorkspace`'s split
  rebalancing, the `signal` parameter, the two builders' documented
  differences), `libtmux` (layout validation, `ServerOptions.colors`,
  `ServerSnapshot.daemonIdentity`, cancellable acquisition) and
  `@libtmux/mcp` (`select_layout`, stdio shutdown) unchanged: each
  describes a real change to an already-published package
tony added a commit that referenced this pull request Sep 19, 2026
why: `@libtmux/workspace-cli` ships whole in this pull request, so what a
caller gets belongs in one set of entries rather than the running record
kept while it was written.

what:
- Describe the package under its own heading: loading and its prompts,
  document handling, machine codes, events, `freeze` and imports
- Leave the `libtmux`, `@libtmux/workspace` and `@libtmux/mcp` entries as
  the published-package changes they already record
- Carry the pull request number on every entry
why: The cancellation commit left builder.test.ts with a trailing blank
line; oxfmt --check failed the Gates job on it.

what:
- Run oxfmt over builder.test.ts; no behavior change.
…the lookup

why: load refuses a session_name or window_name holding "." or ":" before it
ever runs tmux, since those are target separators; freeze's own check ran
only against a session it had already resolved. freezeSession matched an
explicit name by plain string equality against a full snapshot, not through
tmux's own "-t" parsing, so on 3.7a and later it did find a session actually
named "my.proj" and refuse it correctly -- but every version at or below 3.6b
rewrites that name to "my_proj" at creation, and 3.7 itself refuses to create
it, so the lookup answered session_not_found before the addressability check
ever ran. The regression test asserting the real contract inherited a
session-existence precondition that cannot hold on any of those releases, and
building its own fixture failed outright on 3.7, which also refuses a window
named with a dot.

what:
- Check an explicit session_name argument's own syntax before asking tmux
  whether anything runs under it, sharing freeze's existing addressability
  check with the one already run against a resolved session and window name
- Rewrite the freeze test to assert the refusal directly, independent of
  whether a session exists under the literal name; add a case addressing an
  existing dotted session by ID, gated on the one era that can hold one
- Tighten the CHANGELOG line the earlier freeze fix landed under
… target

why: no `-t` target spelling is both exact and correct for every name.
`=name` matches exactly but tmux reads a "." or ":" in the name as a target
separator first, so `has-session -t =my.proj` answered no even when a
session was named exactly that; `name:` survives those characters but
resolves a unique prefix the same way a bare name does, so it would
silently reintroduce the ambiguity `hasSession` exists to avoid. Sending
the same request twice (freeze's `session_not_found` on a name it should
have refused on syntax alone) is the same class of bug found on this
branch's own freeze fix, in a different command.

what:
- hasSession lists every session and compares the requested name against
  `#{session_name}` in JavaScript, exact for any name tmux can store
- Update the docstrings on both the internal helper and Server.hasSession
- Unit test: the mocked request now matches list-sessions, plus a case a
  session named "alphabet" must not satisfy hasSession("alpha")
- Integration test: a session actually named "my.proj", spawned outside
  the library since newSession refuses the name itself, must satisfy
  hasSession("my.proj") -- gated on the one tmux era that stores such a
  name verbatim, verified against every tagged release from 3.2a to 3.7c
- Reword the names.ts doc comment: a name holding "." or ":" needs a
  target spelling ordinary lookups do not use, not "unaddressable"
- Regenerate docs/api.md; changelog entry under `libtmux` > Server
why: tmux 3.8's candidate reports its version as `3.8-rc`, and older
candidates as `3.0-rc3`. The parser refused both as invalid, so any
call that checks the running version threw against a candidate build.

what:
- Read a trailing `-rc` or `-rcN` as a build of the release it names,
  classified the same way as `next-X.Y`
- Move `3.7-rc1` out of the rejected spellings and pin `3.8-rc`, its
  fields, and its ordering beside `next-3.8`; keep `-rc0` and trailing
  junk rejected
- Record the fix in the changelog
why: A control connection sent its target to attach-session as given,
and tmux matches a bare -t name as a prefix: asking to attach to `doom`
when only `doomsday` existed attached to `doomsday` and streamed a
session nobody named.

what:
- Pass a session id through as given and anchor a name with `=`
- Cover it: a prefix name is refused while the exact name still attaches
- Record the fix in the changelog
why: compareTmuxVersions checked development-ness before major and
minor, so next-X.Y and a release candidate both ranked above every
tagged release. server.versionAtLeast("99.0") answered true on
next-3.9, passing a feature gate for a release that build had not
shipped.

what:
- Split development builds into named-next (next-X.Y, ranks below
  the release it has not shipped and above the one before it) and
  untargeted (bare master, <tag>-master, which names no release and
  stays unbounded)
- Rank a release candidate as the release it names, not below it: it
  is feature-frozen there, unlike a next-X.Y build
- Strip each development marker independently so parseTmuxVersion
  and the new classifier agree on every shape
- Update versionAtLeast's TSDoc, the README, and the changelog;
  regenerate docs/api.md
why: build ran notices (write mode) before the Gates lane's test
step, so notices.test.ts compared freshly regenerated content to
itself and could never catch a stale THIRD-PARTY-NOTICES.md.

what:
- Call notices:check in build, matching how docs:check already
  gates staleness there
why: hasSession answered false for any list-sessions failure, not
only a cold socket. A permission error or a daemon that refused the
command read the same as "no such session", which a caller deciding
whether to create one cannot tell apart from an actual absence.

what:
- Share isColdEndpoint (moved from layout.ts) so hasSession can
  answer false only for a socket with no daemon listening
- Raise every other failure instead of folding it into "no session"
- Update the TSDoc and changelog; regenerate docs/api.md
- Unit test both directions: a cold socket and a non-cold failure
why: `@libtmux/workspace-cli` ships whole in this pull request, so what a
caller gets belongs in one set of entries rather than the running record
kept while it was written.

what:
- Describe the package under its own heading: loading and its prompts,
  document handling, machine codes, events, `freeze` and imports
- Leave the `libtmux`, `@libtmux/workspace` and `@libtmux/mcp` entries as
  the published-package changes they already record
- Carry the pull request number on every entry
- Rank a named development build and a release candidate by the release
  they name, and raise a real `hasSession` failure instead of reading it
  as "no such session"
- Record that `Session.newWindow` accepts an exact `index`, and that the
  option readers and `daemonIdentity` accept `CommandOptions`
tony added a commit that referenced this pull request Sep 19, 2026
why: `@libtmux/workspace-cli` ships whole in this pull request, so what a
caller gets belongs in one set of entries rather than the running record
kept while it was written.

what:
- Describe the package under its own heading: loading and its prompts,
  document handling, machine codes, events, `freeze` and imports
- Leave the `libtmux`, `@libtmux/workspace` and `@libtmux/mcp` entries as
  the published-package changes they already record
- Carry the pull request number on every entry
- Rank a named development build and a release candidate by the release
  they name, and raise a real `hasSession` failure instead of reading it
  as "no such session"
tony added a commit that referenced this pull request Sep 19, 2026
why: `@libtmux/workspace-cli` ships whole in this pull request, so what a
caller gets belongs in one set of entries rather than the running record
kept while it was written.

what:
- Describe the package under its own heading: loading and its prompts,
  document handling, machine codes, events, `freeze` and imports
- Leave the `libtmux`, `@libtmux/workspace` and `@libtmux/mcp` entries as
  the published-package changes they already record
- Carry the pull request number on every entry
- Rank a named development build and a release candidate by the release
  they name, and raise a real `hasSession` failure instead of reading it
  as "no such session"
- Record that `Session.newWindow` accepts an exact `index`, and that the
  option readers and `daemonIdentity` accept `CommandOptions`
@tony
tony force-pushed the workspace-cli branch 2 times, most recently from 4d7d81a to 3d55c6e Compare September 19, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants