Skip to content

Latest commit

 

History

History
453 lines (381 loc) · 20.8 KB

workspace-cli.md

File metadata and controls

453 lines (381 loc) · 20.8 KB

tmux-workspace reference

The rules tmux-workspace follows when it loads, converts, imports and reports. The README has the commands to start with, and libtmux.org has a page for each command.

Contents — Loading · Discovery, conversion and saving · Document rules · Imports · Output · Python · Subprocesses · Generated reference and development

Loading

Variables

  • Loading resolves ~ and defined $VAR references against the environment the CLI runs in, and it does so in every value tmuxp does: names, directories, scripts, option and environment values, and pane commands.
  • An undefined variable is left as written, so the pane's own shell still sees it.
  • Mapping keys are never expanded.

Choosing the server

  • -S names a socket path and -L a socket name. -S wins when both are given.
  • Without either, LIBTMUX_SOCKET_PATH and then LIBTMUX_SOCKET choose, and the path wins when both are set. A blank variable counts as unset.
  • With neither flag nor variable, no socket is passed and tmux picks the server: the one in TMUX from inside a pane, and its default socket otherwise.
  • shell hands tmuxp the -S or -L a variable chose, because tmuxp reads neither variable. With none chosen, tmuxp follows TMUX itself.
  • An attached load and --append from inside tmux still require the selected server to be the one in TMUX, and refuse otherwise.

Creating, reusing and appending

  • Ordinary loading creates or reuses a session.
  • Append authenticates the inherited tmux daemon before resolving the current pane, then keeps that session across input files even if a script moves the pane.
  • Socket aliases are accepted when they reach that same daemon.
  • Failures report retained changes rather than claiming rollback.

load -2 forces 256-color support for tmux clients, attachment included. Without it, tmux detects the terminal's capabilities. -8 fails before workspace lookup: supported tmux releases removed 88-color mode.

Layouts and focus

  • All input layouts are checked before scripts run or sessions change.
  • Named layouts accept unique abbreviations; version-sensitive names use the running daemon, or the selected client when no daemon is listening.
  • Classic saved layouts require a valid checksum, consistent geometry, a nonempty tree, enough pane cells and at most 256 nested splits.
  • This rejects malformed geometry even when tmux could repair or prune it.
  • JSON layouts require tmux 3.8 or later, valid cell structure and enough pane cells, with at most 200 nested objects. tmux can still reject a layout during application.
  • A window naming no layout tiles its panes; tmuxp instead stacks them, halving each split.
  • A window naming no focus keeps the first window active, which tmuxp does not.
  • A pane naming no focus leaves the last pane of its window active, which is what tmuxp leaves: panes are created detached here and selected afterwards, rather than each split taking the cursor with it.

Window indexes

  • Explicit window indexes are reserved before implicit windows receive free indexes from base-index.
  • Append reserves indexes from later input files and rejects collisions across all remaining inputs before running scripts or changing options.
  • Temporary-window removal preserves requested indexes when renumber-windows is enabled, then restores its local or inherited setting.
  • Restoration failures appear in the partial result.

Pane readiness and shells

  • workspace_builder_options.pane_readiness accepts auto (the default), always, never, or boolean aliases.
  • auto and always both wait, whatever the pane's shell is, because the problem is that a shell owns its terminal only once it has drawn: a command sent before that is echoed by the tty and then redrawn by the line editor, so the pane shows it twice.
  • Readiness polling uses a two-second budget for the pane cursor to move from its origin before commands are sent. A probe and the polling pause can extend the elapsed wait beyond that budget.
  • Cursor movement is a heuristic, not a guarantee that the shell is ready.
  • A timeout warns and continues.
  • Blank panes and explicit launch commands never wait.
  • An unrecognised key under workspace_builder_options is a warning, not a refusal.
  • A pane's shell overrides window_shell; pane_shell is an alias.
  • Setting both pane keys is an error.

Command timing

  • Pane-level enter, sleep_before and sleep_after set command defaults.
  • Command mappings can override them; each override carries to later commands in the same pane.
  • A null sleep resets that delay to zero.
  • enter: false types the command without executing it.

Discovery, conversion and saving

  • Use --help on any command for its arguments.
  • Discovery checks local project files and the first existing global directory from TMUXP_CONFIGDIR, XDG and the legacy directory.
  • Conversion preserves extension fields and never executes workspace commands.
  • Explicit file saves protect existing files unless --force is supplied.
  • convert asks for confirmation before writing unless --yes is given; freeze --save-to names its own destination and needs neither --yes nor a terminal to write it.

Document rules

  • Native loading rejects a key it does not implement before contacting tmux.
  • A key starting with x-, at any level, is the exception: a vendor extension, accepted, ignored, and preserved by convert.
  • YAML anchors and merge keys expand into ordinary values, and date-like scalars stay text.
  • A document holds one mapping; duplicate keys, cyclic aliases, nonfinite numbers and nonstring mapping keys are rejected.
  • A file ending in .json must be JSON.
  • YAML expansion stops at 100 levels and 100,000 values.

Imports

import tmuxinator and import teamocil translate supported source shapes and validate the resulting native workspace before writing stdout or a destination. convert remains lossless document conversion and does not validate loading.

  • Tmuxinator window command arrays stay sequential commands in one pane; explicit panes create separate panes.

  • Project pre_window arrays form one ; -joined command, and window pre arrays form one &&-joined command before each explicit pane.

  • pre_tab is accepted as an alias for pre_window.

  • Teamocil commands arrays form one ; -joined command; legacy cmd and splits are accepted.

  • Window options and the first requested window/pane focus are preserved; absent focus selects the first item.

  • Layouts and source order remain part of the translated workspace.

  • Imports record an absolute project directory from the invocation directory, including when root is omitted.

  • Window roots resolve against that project directory, so moving the saved file does not change its working directories.

  • Directories need not exist during import; native loading still checks them.

  • Missing session names use the source filename without its extension.

  • Conflicting non-null aliases and malformed scalar or command shapes are rejected.

  • Lifecycle hooks, project pre/post, endpoint/runtime overrides, named pane titles, Teamocil clear/filters and other unsupported fields are rejected before output.

  • Tmuxinator expands ERB templates through Ruby before parsing, so unexpanded <% markup is refused before output or overwrite; Teamocil evaluates no templates, so the same markup in a Teamocil source is ordinary text and is preserved.

  • Java creates all panes before sending commands, so before-command synchronization cannot preserve the source delivery order and is rejected.

  • Tmuxinator synchronize: after uses native options_after instead.

  • Window pre requires explicit nonempty panes; otherwise Tmuxinator would omit that command.

  • Move unsupported behavior into an explicit native workspace or keep using its source tool.

Output

  • --json returns one document; --ndjson takes precedence when both are present.
  • Load and child-process output stream as escaped, flushed events.
  • Machine output never includes ANSI styling or implicit prompts.
  • Interactive editors and Python shells use the controlling terminal while their result remains on stdout.

The machine contract

  • Every machine-readable record carries schema_version and command.
  • A --json result is one document; a --ndjson stream is one record per line, each with an event and a sequence counting from 1.
  • A committed transcript of a load's stream lives beside the tests, so a record that gains, loses or renames a field is a diff rather than a surprise.

A failure names one code. This is the whole set, shared with the other ports of this tool, so the same condition answers with the same name whichever one a script calls:

  • workspace_not_found — the named workspace is not where discovery looked

  • invalid_workspace — the document parsed, and does not describe a workspace

  • unsupported_key — the document uses a key native loading does not implement

  • session_not_found — the session the command names is not on the server

  • session_mismatch — the named session exists and is not what the document describes

  • tmux_unavailable — tmux could not be found or run

  • tmux_unsupported — tmux is older than 3.3a; the message says to upgrade tmux or use the previous release

  • tmux_failed — tmux ran and refused, or the server changed under the command

  • script_failed — a child program could not be run, or ended badly

  • destination_exists — the destination a capture was told to write is taken

  • usage — the command was invoked in a way that cannot be carried out

  • A layout name is invalid_workspace when no tmux accepts it, and tmux_failed when this daemon will not take one another would: main-horizontal-mirrored arrived in 3.5, and main-h stopped being a unique abbreviation there.

  • The document is the same either way; the daemon is not.

Declining a prompt writes nothing and is not an error; the command says what it did not do. A prompt that cannot be asked for want of a terminal is usage.

  • Names outside that set are not a verdict on the request.

  • interrupted says the process was signalled and reports where it stopped, with exit 130.

  • log_unavailable says a --log-file sink that opened stopped accepting records; it is reported separately and leaves the command's own result and exit status alone.

  • A --log-file that cannot be opened in the first place is usage, like any other invocation problem.

  • A command's status is decided by what each of its results retained, not by which codes its errors carry.

  • Loading two files where the first builds a session and the second does not match its own is partial: the first session is still there.

An --ndjson stream carries these events:

  • started — the command's work began, with inputs

  • workspace-started — one input began, with that input's effects so far

  • session-created — a session this load owns now exists

  • window-created — a window exists, with pane_total still to come

  • pane-created — a pane exists; its commands have not been sent

  • pane-completed — a pane has had its commands sent

  • window-completed — a window and every pane in it are done

  • workspace-completed — one input finished, with its effects

  • script-started — a child program is about to run

  • script-output — a fragment of a child's output, naming its stream

  • script-completed — a child ended, with child_status and truncated

  • warning — something asked for that will not happen, with its own code

  • completed — the command finished, carrying its whole result

  • failed — the command stopped, carrying the result up to the failure

  • A load's effects record describes one input: input, input_index, session_id, session_name, reused, owned_session, changed, window_total, session_pane_total, window_ids, pane_ids, and stage — the point the load reached, one of resolve, windows_preflight, before_script, options, readiness, windows, commands, finalize, completed or reused.

  • A load that removed the session it created adds session_removed.

  • A Python-bridge load adds engine, effects_scope and effects_unknown, because what it reports is observed topology rather than operations it performed.

The envelope's status is ok, partial or error. partial means effects were retained; error means none were. The exit status carries the same verdict:

  • 0 — the command finished, and status is ok.
  • 1 — it failed, and status is error or partial.
  • 2 — a usage failure: the invocation cannot be carried out.
  • 130 — the process was interrupted.

Terminal output

Human output uses semantic colors. NO_COLOR disables styling, followed by the explicit --color policy. Automatic color also recognizes FORCE_COLOR, CLICOLOR_FORCE and CLICOLOR.

Terminal stderr displays event-driven load progress. --progress-format accepts the default, minimal, window, pane and verbose presets, or a template. TMUXP_PROGRESS_FORMAT supplies its default. Templates accept these fields:

  • Identity: session, workspace_path, window.

  • Position: window_index, window_total, window_progress, pane_index, pane_total, pane_progress, progress.

  • Completion: windows_done, windows_remaining, window_progress_rel, pane_done, pane_remaining, pane_progress_rel, session_pane_total, session_panes_done, session_panes_remaining, session_pane_progress, overall_percent, summary.

  • Bars: bar, pane_bar, window_bar. status_icon is empty.

  • Unknown fields stay literal; {{ and }} escape braces.

  • The script panel shows the latest output while preserving its original stdout/stderr destination.

  • --progress-lines or TMUXP_PROGRESS_LINES sets its rows: 3 by default, 0 hides the panel, and -1 uses the available initial terminal height.

  • Retained panel text is bounded to 65,536 characters.

  • Terminal dimensions are sampled once; the display does not track resizing, and Unicode clipping is conservative.

  • --no-progress, TMUXP_PROGRESS=0, TERM=dumb, redirected stderr and machine output disable the display.

  • Completion and interruption clear its frame.

Logging

  • --log-level filters diagnostics from debug through critical, defaulting to warning.
  • Load accepts --log-file for appended JSON log records.
  • New files use private permissions on POSIX filesystems; existing permissions are preserved.
  • Info-level logs include both captured bootstrap streams.
  • Diagnostics use stderr and preserve machine stdout.
  • Invalid log destinations fail before tmux changes.
  • Runtime append or close failures produce a separate logging diagnostic and preserve the workspace result and exit status; a failed logger stops recording.

Interruption

  • Interrupting a command returns exit 130.
  • Writes retain their order within each stream.
  • Final output has a bounded delivery allowance after interruption; an undrained pipe can leave that output incomplete.
  • Caller-supplied output streams remain open.
  • If a stream ignores interruption, its pending write can finish later when the caller drains it; cancellation does not reap that write.

Python

  • Python shell commands and extension workspaces use TMUX_WORKSPACE_PYTHON (default python3) with tmuxp 1.74.0 installed.

  • The interpreter's libtmux must also declare TMUX_OPTIONAL_FEATURE_POLICY_VERSION >= 2. No released libtmux does yet; it comes with tmux-python/libtmux#780. Until then, install libtmux from that source, or skip shell and extensions.

  • The version is checked before any input workspace changes.

  • Nonempty plugins, workspace_builder, or workspace_builder_paths select the bridge.

  • Builder paths resolve relative to the workspace file; custom builders receive their own configuration keys.

  • Plugin import or version failures stop the load instead of prompting to skip the plugin.

  • Ordinary native loading and read commands do not require Python.

  • This CLI also permits an explicit custom builder to omit windows.

  • Common fields still receive tmuxp expansion, and present windows receive inherited defaults.

  • The builder supplies its own topology when it omits that field.

  • Search uses Java regex syntax; it does not launch Python or promise identical Python re semantics.

  • Extension append receives the originally authenticated session ID, even if an earlier input moves the invoking pane.

  • Combining extension append with before_script fails before Python or tmux starts: the pinned Python builder can delete a borrowed session when that script fails.

  • Native scripted append and extension append without a script remain available.

  • Bridge results identify engine: python, effects_scope: observed, and effects_unknown: true.

  • The bridge keeps owned_session: false: observing a session does not establish ownership.

  • Reported window and pane IDs were absent from the selected daemon's pre-child snapshot and appear in the target session after the child exits or is interrupted.

  • They do not describe arbitrary plugin actions, transient objects, or changes to other sessions.

  • Child output remains separate from native build events; the bridge does not produce native pane progress.

  • Java retains observed sessions after extension failure.

  • Python builders and plugins remain executable user code with their own mutation and cleanup behavior.

The Python bridge requires tmuxp 1.74.0 and a Python libtmux whose minimum is tmux 3.3a or newer and whose integer optional-feature policy marker is at least 2. Both the runtime probe and the child check it before extension or shell code runs; see the migration notes. Native loading retains the core floor and does not acquire control.

Subprocesses

  • Captured subprocesses retain bounded output and report truncation.
  • Linux uses an owned setsid session to terminate descendants retaining captured output; other platforms use known process handles.
  • Successfully detached services with closed output streams are retained.

Generated reference and development

Generate metadata from the same graph that parses commands:

$ tmux-workspace --generate schema

Generate Bash completion:

$ tmux-workspace --generate bash

--generate zsh and --generate fish write the same completion for those shells. It suggests commands, option names and accepted choice values; it does not query running sessions or discover saved workspace names.

  • Add --json to receive a schema_version: 1 artifact with command: generate, format: bash, the exact completion text in script, and status: ok.
  • --ndjson returns that artifact as one completed event with sequence: 1; it takes precedence when both machine flags are present.
  • Without either flag, stdout contains the Bash script.
  • Schema generation keeps its metadata document in every output mode.

For repeated CLI execution while editing, keep Gradle watching the sources:

$ ./gradlew :libtmux-workspace-cli:runDevelopment \
    --args='--help' \
    --continuous \
    --configuration-cache \
    --max-workers=2 \
    --no-parallel \
    -Dorg.gradle.continuous.quietperiod=50
  • Replace --help with the command to exercise.
  • This task compiles separate development classes with javac and uses the JVM's first compilation tier for startup.
  • It skips Error Prone and NullAway for the CLI classes and does not run tests.
  • Published libraries, normal compilation, installation and check keep their full checks and never consume these development classes.
  • The initial build includes setup; later edits reuse Gradle and the compiler.
  • This JVM setting also limits later optimization in long-running commands.
  • Use normal run or installation tasks for representative runtime benchmarks.

Run the CLI checks with cached Gradle configuration:

$ ./gradlew :libtmux-workspace-cli:check \
    --configuration-cache \
    --max-workers=2 \
    --no-parallel
  • The tests use isolated sockets under the Java test root.
  • PTY regressions use Python 3 standard-library helpers; native application commands do not require Python.
  • Set TMUX_WORKSPACE_TEST_PYTHON to an interpreter with tmuxp 1.74.0 to enable the extension fixtures.
  • Tests replace HOME; user-installed Python packages also need an explicit PYTHONUSERBASE.
  • The repository-wide check remains the full gate; a focused CLI check does not replace it.