# libtmux for TypeScript > The TypeScript port of libtmux (libtmux). Every code sample below is TypeScript; the same pages exist for the other nine ports under their own prefix. - [TypeScript API reference](https://libtmux.org/en/ts/latest/reference/): every public symbol, generated from the source. Hosted on libtmux.org. --- # tmuxp load Source: https://libtmux.org/en/ts/latest/workspace/cli/load/ > Load a workspace file, saved workspace name, or project directory. Multiple inputs build in order; without `-d`, the final session is attached or selected through the current-client flow. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Load a workspace file, saved workspace name, or project directory. Multiple inputs build in order; without `-d`, the final session is attached or selected through the current-client flow. ## Loading and attachment Create [`workspace.yaml`](../../guides/installation/#create-the-input) using the [installation walkthrough](../../guides/installation/), then load it on that walkthrough's dedicated socket: ```console $ tmuxp load \ -S "$WORKSPACE_TMP/tmux.sock" \ -d \ "$WORKSPACE_TMP/workspace.yaml" ``` `-d` avoids attachment. Inside an existing tmux client, the normal interactive flow can switch to the new session, append windows, or stay detached. `--append` explicitly selects the append flow and needs a current target session. An existing session is handled through tmuxp's load policy; loading is not a declarative reconciliation operation that removes surplus windows. Put flags before the complete group of filenames. The reference accepts flags before or after that group, but a flag between two filenames can cause an argument error. `-s` overrides the final input's session name when several files are loaded. tmuxp parses `-2` and `-8` as mutually exclusive flags, separate from the CLI text's `--color` setting. Legacy `-8` is unsupported; [tmux removed 88-color support](https://raw.githubusercontent.com/tmux/tmux/3.2a/CHANGES). ## Progress and script output `--progress-format` accepts `default`, `minimal`, `"window"`, `"pane"`, `verbose`, or a custom format. Available tokens include `{session}`, `{window}`, `{window_index}`, `{window_total}`, `{window_progress}`, `{window_progress_rel}`, `{windows_done}`, `{windows_remaining}`, `{pane_index}`, `{pane_total}`, `{pane_progress}`, `{progress}`, `{session_pane_progress}`, `{overall_percent}`, `{bar}`, `{pane_bar}`, `{window_bar}`, and `{status_icon}`. The output panel defaults to three lines. `--progress-lines 0` hides the panel and sends script output to stdout; `-1` permits all available lines up to terminal height. `--no-progress` disables animation. See [environment settings](../../configuration/environment/) for environment bindings and [command ordering](../../configuration/commands/) for what is executed. The local TypeScript CLI uses an optional Python bridge for explicit plugins or custom builders. Extension progress shows the workspace label and script output without native pane counters. See [hooks and builders](../../configuration/hooks/) for runtime requirements and append restrictions, and [output](../../reference/output/) for observed effects. ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `"workspace_files"` | one or more | filepath to session or filename of session in tmuxp workspace directory | | `-L` | value; None | passthru to tmux(1) -L | | `-S` | value; None | passthru to tmux(1) -S | | `-f` | value; None | passthru to tmux(1) -f | | `-s` | value; None | start new session with new session name | | `--yes`, `-y` | flag; False | always answer yes | | `-d` | flag; False | load the session without attaching it | | `-a`, `--append` | flag; False | load workspace, appending windows to the current session | | `-2` | flag; None | force tmux to assume the terminal supports 256 colours. | | `-8` | flag; None | legacy 88-colour flag; unsupported by tmux 3.2a+ | | `--log-file` | value; None | file to log errors/output to | | `--progress-format` | value; None | Spinner line format: preset name (default, minimal, window, pane, verbose) or a format string with tokens {session}, {window}, {progress}, {window_progress}, {pane_progress}, etc. Env: TMUXP_PROGRESS_FORMAT | | `--progress-lines` | value; None | Number of script-output lines shown in the spinner panel (default: 3). 0 hides the panel entirely (script output goes to stdout). -1 shows unlimited lines (capped to terminal height). Env: TMUXP_PROGRESS_LINES | | `--no-progress` | flag; False | Disable the animated progress spinner. Env: TMUXP_PROGRESS=0 | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/load.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp freeze Source: https://libtmux.org/en/ts/latest/workspace/cli/freeze/ > Capture a live session as a starting workspace file. A capture records observable session state; it cannot recover the original scripts, plugin intent, comments, or every application state. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Capture a live session as a starting workspace file. A capture records observable session state; it cannot recover the original scripts, plugin intent, comments, or every application state. ## Export a named session After the [installation walkthrough](../../guides/installation/) creates `workspace-guide`, export it to a new destination: ```console $ tmuxp freeze \ -L workspace-guide \ --workspace-format yaml \ --save-to captured-workspace.yaml \ --yes \ workspace-guide ``` Without a session argument, tmuxp resolves or asks for a live session. Without a format or destination, it can ask for those choices. `--yes` answers yes/no questions; it does not supply every missing selection. `--quiet` suppresses explanatory status, but prompts can still occur. Successful export writes the workspace document to a file, not stdout. Declining a confirmation can return without saving. In this reference, an explicit `--save-to` path bypasses the overwrite confirmation used by the prompted path. Select a new path deliberately. The native contract applies the same protection to explicit and prompted destinations and keeps `--force` distinct from `--yes`. Inspect the capture before reloading. See the [export and reload workflow](../../guides/export-session/) and the [native machine output](../../reference/output/) for the distinction between a saved file's format and a CLI result stream. ## What the captured document holds The seven native ports agree on three things a capture records, and on one it does not. **Window options are written under `options_after`.** tmux applies them after the panes exist, and options like `automatic-rename: off` do not hold if they are applied before. `load` accepts either spelling. **A pane sitting at a shell gets no `shell_command`.** Naming the shell would start a shell inside a shell when the document is reloaded. The shell is recognised by name as well as by the session's `default-shell`, because macOS runs bash for `/bin/sh` and the pane reports `bash`. **Nothing about the machine it was captured on.** No session environment, so a document does not carry the capturing host's `SSH_AUTH_SOCK` or `DISPLAY`, and no `default-size`, which would otherwise pin a reloaded session to the size of the terminal that captured it. ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `"session_name"` | optional | | | `-S` | value; None | pass-through for tmux -S | | `-L` | value; None | pass-through for tmux -L | | `-f`, `--workspace-format` | value; None | yaml, json | | `-o`, `--save-to` | value; None | file to save to | | `--yes`, `-y` | flag; False | always answer yes | | `--quiet`, `-q` | flag; False | suppress explanatory/status text; prompts still occur despite the parser help claiming otherwise | | `--force` | flag; False | overwrite the workspace file | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/freeze.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp convert Source: https://libtmux.org/en/ts/latest/workspace/cli/convert/ > Convert a workspace document between YAML and JSON while retaining its mapping keys. Conversion does not prove that a native builder supports every retained field. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Convert a workspace document between YAML and JSON while retaining its mapping keys. Conversion does not prove that a native builder supports every retained field. ## Change the file format Given the [`workspace.yaml`](../../guides/installation/#create-the-input) from the [installation walkthrough](../../guides/installation/), convert it to [`workspace.json`](./): ```console $ tmuxp convert \ --yes \ workspace.yaml ``` The destination has the same stem and opposite extension. In the reference, `--yes` permits replacement of an existing destination without an additional existence check. The original input remains. YAML comments and textual formatting do not round-trip through JSON. Conversion uses the complete document mapping. A serializer for a reduced native workspace struct can silently discard extension keys and is insufficient for this command. The native machine mode returns a document without writing a guessed destination; its extra save and overwrite controls are described in [output](../../reference/output/). ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `workspace_file` | required | checks tmuxp and current directory for workspace files. | | `--yes`, `-y` | flag; False | always answer yes | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/convert.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp edit Source: https://libtmux.org/en/ts/latest/workspace/cli/edit/ > Resolve a saved workspace or file and open it in the configured editor. The lookup rules are shared with loading. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Resolve a saved workspace or file and open it in the configured editor. The lookup rules are shared with loading. ## Open a workspace With [`workspace.yaml`](../../guides/installation/#create-the-input) already saved and `$EDITOR` set to a single executable: ```console $ tmuxp edit workspace.yaml ``` The reference passes the entire `$EDITOR` value as one executable string followed by the file path. An editor command containing arguments is not tokenized. It waits for the child process but does not propagate that child's exit status. Use an editor wrapper executable when arguments are needed. The native contract defines argument parsing and propagates child failure. These are deliberate behavior changes, not existing Python guarantees. See [discovery](../../guides/discovery/) and [environment](../../configuration/environment/). ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `workspace_file` | required | checks current tmuxp and current directory for workspace files. | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/edit.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp debug-info Source: https://libtmux.org/en/ts/latest/workspace/cli/debug-info/ > Collect tmuxp, Python, tmux, configuration, and environment diagnostics for troubleshooting. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Collect tmuxp, Python, tmux, configuration, and environment diagnostics for troubleshooting. ## Inspect structured diagnostics ```console $ tmuxp debug-info --json ``` The command writes one JSON object. Named path fields mask the home directory, but raw tmux output arrays are preserved. Review diagnostics before sharing them because names, commands, and raw tmux values may identify your environment. `--ndjson` is a native extension. The native result should name the port and runtime and define redaction for raw values. See [troubleshooting](../../guides/troubleshooting/) and [output](../../reference/output/). ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `--json` | flag; False | output as JSON | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/debug_info.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp ls Source: https://libtmux.org/en/ts/latest/workspace/cli/ls/ > List discovered project and saved workspace files, with optional grouping and configuration content. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. List discovered project and saved workspace files, with optional grouping and configuration content. ## List workspace records ```console $ tmuxp ls --json ``` JSON output is an object with `workspaces` and `global_workspace_dirs`; no workspaces still produces the object with an empty array. `--ndjson` emits one workspace record per line and emits zero lines for no records. When both format flags are supplied, NDJSON wins. `--full` includes full configuration content. `--tree` groups the human display by directory. Discovery includes nearest project configurations and configured global directories. It is not a recursive scan of every descendant directory. See [discovery rules](../../guides/discovery/) for precedence and candidate names. ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `--tree` | flag; False | display workspaces grouped by directory | | `--json` | flag; False | output as JSON | | `--ndjson` | flag; False | output as NDJSON (one JSON per line) | | `--full` | flag; False | include full config content in output | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/ls.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp search Source: https://libtmux.org/en/ts/latest/workspace/cli/search/ > Search discovered workspace fields using regular expressions or literal strings. Queries combine with AND unless `--any` selects OR. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Search discovered workspace fields using regular expressions or literal strings. Queries combine with AND unless `--any` selects OR. ## Find workspaces by session name ```console $ tmuxp search \ --json \ --fixed-strings \ --field session \ workspace ``` Field prefixes in query terms and repeated `--field` restrictions select name, session (`s`), path (`p`), window (`w`), or pane data. `--ignore-case` ignores case; `--smart-case` does so only when a pattern has no uppercase. `--word-regexp` requires whole words and `--invert-match` selects nonmatches. JSON results contain `"name"`, `"path"`, `"session_name"`, `"source"`, `matched_fields`, and `"matches"`. The pinned reference emits an empty byte stream for no matches, even with `--json`. With no query, machine search can print human help and return normally. An invalid regular expression can also yield no machine output. The native contract instead uses `[]` for an empty JSON result and usage status 2 for a missing or invalid pattern. Python regular-expression behavior is part of compatibility. Native regex libraries differ in lookaround, backreferences, Unicode, flags, and word boundaries. Matching the options alone does not establish expression equivalence. See [compatibility](../../reference/compatibility/) and [output](../../reference/output/). ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `"query_terms"` | zero or more | search patterns (prefix with field: for field-scoped search) | | `-f`, `--field` | value; None | restrict search to field(s): name, session/s, path/p, window/w, pane | | `-i`, `--ignore-case` | flag; False | case-insensitive matching | | `-S`, `--smart-case` | flag; False | case-insensitive unless pattern has uppercase | | `-F`, `--fixed-strings` | flag; False | treat patterns as literal strings, not regex | | `-w`, `--word-regexp` | flag; False | match whole words only | | `-v`, `--invert-match` | flag; False | show workspaces that do NOT match | | `--any` | flag; False | match ANY pattern (OR logic); default is ALL (AND logic) | | `--json` | flag; False | output as JSON | | `--ndjson` | flag; False | output as NDJSON (one JSON per line) | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/search.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp shell Source: https://libtmux.org/en/ts/latest/workspace/cli/shell/ > Open a Python shell with tmux objects available, or evaluate Python using `-c`. This command remains Python-specific even when reached through a native command. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Open a Python shell with tmux objects available, or evaluate Python using `-c`. This command remains Python-specific even when reached through a native command. ## Evaluate with a selected server Continue the [installation walkthrough](../../guides/installation/) through its detached load, leaving `workspace-guide` running in the same shell: ```console $ tmuxp shell \ -S "$WORKSPACE_TMP/tmux.sock" \ -c 'print(server.sessions)' \ workspace-guide editor ``` Use `-c`; the reference does not define `--command`. Optional session and window arguments select context. `--best` chooses the best available shell backend; the explicit selectors require their corresponding Python packages. The `--pdb` path uses a debugger rather than a normal REPL. The paired `--use-pythonrc` / `--no-startup` and `--use-vi-mode` / `--no-vi-mode` options share destinations. The last occurrence wins. The negative options disable the corresponding setting. See [environment](../../configuration/environment/) for startup and backend settings. A native REPL is not an equivalent implementation of Python `-c`, IPython, or PTPython. Native ports that support this command use an optional version-checked Python bridge and report an unsupported-runtime error if it is absent. Check [port coverage](../../reference/compatibility/) before relying on that bridge. Interactive machine output needs a separate terminal; an interactive transcript cannot share JSON stdout. Backend selectors are mutually exclusive. ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `"session_name"` | optional | | | `"window_name"` | optional | | | `-S` | value; None | pass-through for tmux -S | | `-L` | value; None | pass-through for tmux -L | | `-c` | value; None | instead of opening shell, execute python code in libtmux and exit | | `--best` | flag; best | use best shell available in site packages | | `--pdb` | flag; None | use plain pdb | | `--code` | flag; None | use stdlib's code.interact() | | `--ptipython` | flag; None | use ptpython + ipython | | `--ptpython` | flag; None | use ptpython | | `--ipython` | flag; None | use ipython | | `--bpython` | flag; None | use bpython | | `--use-pythonrc` | flag; False | load PYTHONSTARTUP env var and ~/.pythonrc.py script in --code | | `--no-startup` | flag; False | disable Python startup loading; shares a destination with `--use-pythonrc`, last occurrence wins | | `--use-vi-mode` | flag; False | use vi-mode in ptpython/ptipython | | `--no-vi-mode` | flag; False | disable vi mode; shares a destination with `--use-vi-mode`, last occurrence wins | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/shell.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp import Source: https://libtmux.org/en/ts/latest/workspace/cli/import/ > Import a workspace from a supported external configuration format. Select one of the two child commands. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Import a workspace from a supported external configuration format. Select one of the two child commands. ## Choose the source format - [Teamocil](../import-teamocil/) converts a Teamocil workspace. - [tmuxinator](../import-tmuxinator/) converts a tmuxinator workspace. The parent command groups importers and has no workspace file argument of its own. Each child requires a source at the parser boundary, despite optional-looking help. Conversion is schema translation; it does not make tmuxinator Ruby or ERB evaluation available in a native YAML reader. ## Arguments and flags The parent accepts `-h` / `--help` and selects a child command. All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/import_config.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp import teamocil Source: https://libtmux.org/en/ts/latest/workspace/cli/import-teamocil/ > Translate a Teamocil workspace into tmuxp configuration, review the result, and select its saved representation. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Translate a Teamocil workspace into tmuxp configuration, review the result, and select its saved representation. ## Select an existing source With [`project.yml`](../../guides/discovery/) already present in the Teamocil configuration directory: ```console $ tmuxp import teamocil project ``` The source lookup uses [`~/.teamocil`](./). A source argument is effectively required: although its positional action has optional arity, it belongs to a required exclusive group, and omission exits 2. The reference previews and saves the transformed document interactively. Native automation needs explicit destination, encoding, and overwrite policy instead of implicit prompts. See [output](../../reference/output/). Inspect translated commands and directories before loading the result. An extensionless name searches the configured source directory. A filename with an extension is resolved relative to the current directory unless you provide an explicit path. ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `workspace_file` | effectively required | `nargs="?"` belongs to a required exclusive group; omission exits 2. Source lookup uses [`~/.teamocil`](./). | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/import_config.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp import tmuxinator Source: https://libtmux.org/en/ts/latest/workspace/cli/import-tmuxinator/ > Translate a tmuxinator workspace into tmuxp configuration, preserving an explicit boundary around dynamic Ruby configuration. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Translate a tmuxinator workspace into tmuxp configuration, preserving an explicit boundary around dynamic Ruby configuration. ## Select an existing source With [`project.yml`](../../guides/discovery/) already present in the configured tmuxinator directory: ```console $ tmuxp import tmuxinator project ``` `TMUXINATOR_CONFIG` overrides the source directory and expands a leading tilde. A source argument is effectively required, and omission exits 2 despite optional-looking help. The reference previews and saves the transformed document interactively. Do not interpret successful YAML parsing as support for ERB templates or arbitrary Ruby execution. Native importers must either implement a documented bridge or reject unsupported dynamic input. Inspect the resulting commands and directories before loading. See [environment](../../configuration/environment/) and [output](../../reference/output/). An extensionless name searches the configured source directory. A filename with an extension is resolved relative to the current directory unless you provide an explicit path. ## Arguments and flags | Argument or flags | Arity / default | Choices or meaning | | --- | --- | --- | | `workspace_file` | effectively required | `nargs="?"` belongs to a required exclusive group; omission exits 2. Source lookup honors `TMUXINATOR_CONFIG`. | All commands accept `-h` / `--help`. Root options precede the command; see the [CLI overview](../). The [output reference](../../reference/output/) distinguishes current Python flags from native all-command JSON and NDJSON. [Parser and implementation source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/import_config.py). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Workspace command reference Source: https://libtmux.org/en/ts/latest/workspace/cli/ > The tmuxp command tree, root options, and native compatibility scope. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../reference/compatibility/) describes this port's implemented coverage. The installed Python command is `tmuxp`. Native command names and installation artifacts are not established by this documentation prototype. The pages below document the Python reference grammar and identify native extensions. ## Commands - [load](./load/) builds sessions from files or saved workspace names. - [freeze](./freeze/) captures a live session to a workspace file. - [convert](./convert/) changes YAML and JSON representation. - [edit](./edit/) opens a resolved workspace in an editor. - [ls](./ls/) lists discovered configurations. - [search](./search/) searches their names and content. - [debug-info](./debug-info/) reports runtime and tmux diagnostics. - [shell](./shell/) evaluates Python with tmux context. - [import](./import/) selects [Teamocil](./import-teamocil/) or [tmuxinator](./import-tmuxinator/) conversion. ## Root options `-h` / `--help` shows help; `-V` / `--version` prints the version. `--log-level` selects `"debug"`, `"info"`, `warning`, `"error"`, or `critical`, with parser default `warning`. `--color` accepts `auto`, `always`, or `never`, defaulting to `auto`. Place root flags before the subcommand: ```console $ tmuxp --color never ls ``` Short flags belong to their command: `search -S` is smart case, while `load -S` selects a tmux socket path. Similarly, `-f` selects a tmux configuration for load, a field for search, and an output format for freeze. ## Machine mode and automation Current Python machine output exists on `ls`, `search`, and `debug-info` as described by their own flags. The native command tree adds `--json` and `--ndjson` to every leaf and allows these long options before or after the command. These additions are not installed Python features. Read [output and color](../reference/output/), [exit behavior](../reference/exit-codes/), [completion](./completion/), and [automation](../guides/automation/) before building a script around a command. [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Shell completion Source: https://libtmux.org/en/ts/latest/workspace/cli/completion/ > Generate completion from command definitions and check native availability. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. tmuxp uses the separately installed `shtab` package for experimental completion. The parser entry point is [`tmuxp.cli.create_parser`](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). Generate a script in an environment that can import both packages, then use the shell-specific installation procedure from the [upstream completion guide](https://tmuxp.git-pull.com/cli/completion/). ## Native completion Commander defines the native command graph. The CLI package generates Markdown, a JSON command catalog, and Bash, Zsh, and Fish completion from those definitions. `docs:check` compares generated files in CI. Keep option event order when paired flags share a destination. Completion is derived from actual command metadata. It must include nested import commands, local short flags, positional arity, mutually exclusive choices, and all-command machine options. Verify generated scripts with the installed executable and target shell. See the [command tree](../) and [compatibility reference](../../reference/compatibility/). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Concepts Source: https://libtmux.org/en/ts/latest/concepts/ > tmux objects, command transports, queries, and workspaces across the libtmux language ports. libtmux lets you create sessions, arrange windows and panes, send commands, and read output from tmux. Start with the object hierarchy, then read about the transport, query, or workspace behavior your program needs. These pages explain shared concepts and differences between language ports: - **[Server, session, window, pane](server-session-window-pane/)**: tmux's object hierarchy and attached clients. - **[Control mode vs one-shot](transports/)**: subprocess commands, persistent connections, and batching. - **[Filtering and queries](queries/)**: find objects and handle absent or ambiguous matches. - **[Workspaces](workspaces/)**: build pane layouts from code or configuration files. Use the header's port links to open documentation for your language, including its API reference. --- # Server, session, window, pane Source: https://libtmux.org/en/ts/latest/concepts/server-session-window-pane/ > The object hierarchy every libtmux port mirrors from tmux itself, and the client that sits outside it. libtmux models tmux's server, session, window, and pane objects: ``` Server ├── Session │ └── Window │ └── Pane └── Client (attached view) ``` A `Server` contains sessions. Each `Session` contains links to windows, and each `Window` contains panes. Commands run inside a `Pane`, where you send input and capture output. A window can be linked to more than one session. ## Stable identity, not name or index tmux assigns a unique ID to each session, window, and pane at creation. The ID remains stable for that object's lifetime even if its name or index changes: | Object | ID prefix | Example | |--------|-----------|---------| | Session | `$` | `$13` | | Window | `@` | `@3243` | | Pane | `%` | `%5433` | | Server | - | identified by socket name or path instead | Python handles use the object ID to refresh their fields. Immutable snapshots, such as those in TypeScript, Swift, and Go, use IDs to identify the same tmux object across reads. Walking the whole tree, in each port: ```typescript for (const session of await server.sessions()) { console.log(session.name); for (const window of session.windows) { console.log(" ", window.index, window.name); for (const pane of window.panes) { console.log(" ", pane.id, pane.currentCommand); } } } ``` ## Client: a view, not a child A `Client` represents a terminal attached to a session. Several clients can view the same server, and each can switch sessions or windows independently. Client fields describe the view at the time of the read. A [control-mode connection](../transports/) is also a client. It appears in `list-clients`, counts toward `session_attached`, and affects attachment-dependent behavior such as `destroy-unattached`. Closing the last attached client can therefore destroy a session configured with that option. ## What differs between ports Ports differ in how they read state and report failures: - **Refreshing state.** Python objects reflect their last read until you call `.refresh()`. TypeScript, Swift, and Go also provide snapshots whose relationships can be queried without another tmux command. - **Blocking and async calls.** Python and Java use blocking calls. Rust, TypeScript, .NET, and Swift provide async APIs. Go uses ordinary calls with contexts for cancellation and deadlines. - **Failure handling.** Python and .NET raise exceptions. C++ returns `expected`. Some commands have an expected negative answer, such as `has-session` when a session is absent; check the method's result contract before treating that answer as a failure. See [Control mode vs one-shot](../transports/) for command costs and connection behavior. --- # Control mode vs one-shot Source: https://libtmux.org/en/ts/latest/concepts/transports/ > How a call in your program actually reaches the tmux server, and why a port might give you a choice. libtmux sends commands to tmux through subprocesses or persistent control-mode connections. Some ports also batch commands into one invocation: 1. **One-shot subprocess.** Each command spawns a fresh `tmux` process, which parses argv, executes the command, prints its output, and exits. This is the default in every port, and the *only* lane in Python: every `.cmd()` call underneath the object API is a `subprocess.Popen` around a `tmux` invocation. 2. **A persistent control-mode client.** `tmux -C attach-session` starts one long-lived tmux process that stays attached and speaks a line-oriented protocol over its stdout: commands go in, replies and asynchronous notifications (`%window-add`, `%output`, ...) come out, without starting a process per call. 3. **One invocation, several commands.** tmux accepts more than one command per invocation (`;`-joined, or one `-F`-tagged `list-*` per line). A port can fold several logical operations into a single process start without opening a control-mode connection at all. ## Where each port draws the line | Port | One-shot | Folded invocation | Persistent control client | |------|----------|--------------------|-----------------------------| | Python | every call | - | test-only (`ControlMode`, `libtmux._internal`) | | TypeScript | default | `pipeline()`, `batch()` | `connect()` / `watch()`: notifications only, commands stay per-process | | Go | `process` path | `Plan.Run` | `connection` (`Session.OpenControl`), `streaming` (`OpenNotifications`) | | Rust | `plan` feature, sequential | `plan`, folded | `control-mode` feature | | C# | "One-shot" mode | "Chained" mode (`server.Chain()`) | "Control" mode (`EnterControlModeAsync`) | | C++ | bounded subprocess (default) | `Chain` | `Server::control()` → `Connection` | | Java | every call | `Batch` | `ControlClient` (`attach`, `send`, `subscribeEvents`) | | Swift | default | - | `server.connect()` / `.watch()` (notifications; see below) | Choose based on whether you need command results, notifications, or a batch of changes. ## Notifications and commands are separable TypeScript's `connect()` adds an event observer while commands such as `session.newWindow(...)` and `pane.sendKeys(...)` continue to run as separate tmux processes. A dedicated process provides a completion boundary for output from alias-expanded or waiting commands. Go's `Session.OpenControl`, .NET's `EnterControlModeAsync`, Java's `ControlClient.send`, and Rust's `control-mode` feature can send commands through the persistent connection. Check your port's transport API before assuming that subscribing to events also changes how commands run. ## A control client is a real client A persistent control connection attaches a tmux client. It appears in `list-clients`, increments `session_attached`, and affects `destroy-unattached`, client hooks, and idle-client accounting. Each connection counts separately. Python's internal `ControlMode` test helper uses this behavior for commands that require an attached client, such as `display-popup` and `detach-client`. ## Why fold several commands into one invocation Creating an object can require a second command to read its resulting state. Batching reduces those repeated reads and process starts. TypeScript's `batch()` resolves planned mutations from one final snapshot. Go's `Plan`, .NET's `Chain`, and C++'s `Chain` also group operations without attaching a control client. ## What this costs in practice The Rust `matrix` example and .NET README compare process counts and timings. Their results describe specific workloads and environments: - **Rust's** `matrix` example runs the same create-and-query workload five ways. Blocking sequential and async sequential both cost 6 processes for 6 dispatches; folding the same 6 into async batches costs 3 processes; routing them over a control-mode connection costs exactly 1. - **C#'s** README reports the *marginal* cost of one more command in each mode, as medians against tmux 3.7b: roughly 2.3 ms for another one-shot process, roughly 0.2 ms for another command over an already-open control client, and roughly 0.02 ms for another command folded into one chained invocation. A persistent connection avoids starting a client for each command. A chain groups a known sequence into one invocation. For occasional commands, use the default subprocess transport; measure your workload before changing transports for performance. Use a notification stream when your program needs tmux events. ## Choosing a lane These examples use each port's subprocess API. See the port reference for batching and control-mode setup. ```typescript import { Server } from "libtmux"; // Each awaited command uses a tmux subprocess. const server = new Server(); const session = await server.newSession({ name: "work" }); const editor = await session.newWindow({ name: "editor" }); await editor.panes.at(0)?.sendKeys("echo hello"); ``` To inspect tmux's control protocol, attach a control client: ```console $ tmux -C attach-session -t work ``` --- # Filtering and queries Source: https://libtmux.org/en/ts/latest/concepts/queries/ > How you get from every session on the server to the one pane you mean, and what happens when zero or several match. Use a collection filter to find matching sessions, windows, or panes. Use an exactly-one lookup when your next operation requires a single target. - **Filtering returns a collection; exactly-one lookup checks the result count.** A `.filter()` or `.where()` call returns zero or more matches. Methods such as `.get()`, `.one()`, and `Selections.exactlyOne()` return one object or report a missing or ambiguous match. - **Choose where to filter.** Filter a snapshot in your program when you need several queries over the same data. A tmux format filter can reduce the rows returned by a live read. The cost depends on the data and queries you need. ## Python: `.filter()` and `.get()`, Django-style `server.sessions`, `session.windows`, and `window.panes` are `QueryList` collections. Call `.filter()` with field names and optional lookup suffixes: Lookups include `exact`, `contains`, `startswith`, `endswith`, `regex`, and their case-insensitive `i`-prefixed variants. Multiple keywords and chained `.filter()` calls combine with AND. `.get()` requires exactly one match. Its `default` argument handles an absent result; multiple matches still raise `MultipleObjectsReturned`. Server-wide collections (`server.windows`, `server.panes`) enumerate window links. A window linked to two sessions appears once per session, so a lookup can be ambiguous even when the window ID is unique. Use `Window.linked_sessions` to find its sessions. For a known ID, use `Pane.from_pane_id()` or `Window.from_window_id()` to resolve the object directly. For servers with hundreds or thousands of panes, `.filter()` still builds every object before you discard the ones that don't match. `search_sessions`, `search_windows`, and `search_panes` push a tmux `-f` filter expression down to the server instead, so libtmux builds objects only for the matches: Python-side lookups work with the library's supported tmux versions. The tmux filter grammar requires tmux 3.2 or newer. An unknown format token expands to an empty value, so a malformed filter can look like a valid filter with no matches. If `search_*()` unexpectedly returns no results, try `#{m:*,#{session_name}}` to check that the session data is available. ## TypeScript: criteria as data TypeScript's `Selection.where()` accepts structured, serializable criteria that can be stored in a configuration file or sent through MCP: ```ts snapshot.sessions.where({ AND: [ { name: { startsWith: "prod" } }, { windows: { some: { name: { regex: { pattern: "^log", flags: "" } } } } }, ], }); ``` `some`, `every`, and `none` test related objects. `{ mode: "insensitive" }` enables case-insensitive comparison. Use `.where()` for criteria that can be encoded with `encodeWhereDocument` and decoded with `decodeWhereDocument`; use `.filter()` for a predicate function. `.one()` throws `NoMatchError` or `MultipleMatchesError`. `.oneOrUndefined()` permits an absent result. ## Go, Rust, Java, C++: typed fields that fail queries at compile time These ports use typed fields to reject invalid comparisons at compile time: - **Go** offers both `tmux.PaneFilter{Active: tmux.Ptr(true), ...}` structs that push down into `SearchPanes` (one tmux command, only matches returned), and a `snapshot()` read followed by `tmuxq.Where(panes, predicate)` when you want several answers from one read. - **Rust** uses typed fields: `fields.pane_active.eq(true)` is valid, but `.gt(...)` on that boolean field is not. Expressions compose with `.and()`. With the `serde` feature, a query can be encoded as a versioned JSON document for configuration or MCP. - **Java** exposes each field as a typed accessor (`Pane_.index()`, `Session_.name()`) that plugs straight into an ordinary `Stream.filter()`; `Pane_.index().startsWith("2")` doesn't compile because the index is a number, not a string. `Selections.exactlyOne(...)` is the `.get()`-shaped call, throwing `NoMatchException` or `MultipleMatchesException`. - **C++** composes `FilterExpr` values with `&&`, `||`, and `!`, as in `pane::command.starts_with("nv") && pane::active`. Invalid field operations such as `pane::active.starts_with("x")` fail to compile. Examples of typed and local filters: ## The cardinality contract, side by side | Port | Collection filter | Exactly-one | Empty | Several | |------|--------------------|--------------|-------|---------| | Python | `.filter()` | `.get()` | `ObjectDoesNotExist` (or `default=`) | `MultipleObjectsReturned` | | TypeScript | `.where()` / `.filter()` | `.one()` | `NoMatchError` (or `.oneOrUndefined()`) | `MultipleMatchesError` | | Java | `Stream.filter()` | `Selections.exactlyOne()` | `NoMatchException` | `MultipleMatchesException` | See the Go, Rust, C++, .NET, and Swift references for their exactly-one result types and failure handling. --- # Workspaces Source: https://libtmux.org/en/ts/latest/concepts/workspaces/ > Build pane layouts with the object API or a workspace configuration file. A workspace arranges windows and panes for a task, such as editing code, running a development server, and following logs. Build it with the object API when the layout depends on program logic. Use a declarative builder when you want to store the layout in a configuration file, such as [tmuxp](https://tmuxp.git-pull.com/) YAML or JSON. ## Building one imperatively Create a window, split it into panes, apply a layout, and send each pane its command: `Window.split()` or `Pane.split()` adds a pane. Direction and size control its placement. `select_layout()` rearranges the panes while their processes continue running. tmux provides `even-horizontal`, `even-vertical`, `main-horizontal`, `main-vertical`, and `tiled` layouts. Python's `attach=False` and C++'s detached creation keep new windows in the background. Check the creation defaults for your port if focus matters. Splits and resizes require tmux commands; [Control mode vs one-shot](../transports/) covers their transport costs and batching. ## Building one declaratively These packages read or build workspace configurations based on tmuxp: | Port | Package | Shape | |------|---------|-------| | Python | tmuxp itself | the format this whole idea is named after | | TypeScript | `@libtmux/workspace` | `applyWorkspace(server, { session_name, windows: [...] })` | | Go | `workspace` | tmuxp-shaped, per the port's own module layout | | Rust | `tmux-workspace` | tmuxp-shaped | | Java | `libtmux-workspace` | "enough of tmuxp's format to describe a workspace" | | C# | `LibTmux.Workspace` | reads tmuxp YAML directly | | Swift | `TmuxWorkspace` | Swift, JSON, or YAML (YAML needs the `YAMLWorkspaces` trait) | TypeScript's `applyWorkspace` applies a desired configuration. Applying the same configuration again reuses its existing objects: ```ts await applyWorkspace(server, { session_name: "api", windows: [ { window_name: "editor", panes: ["vim", "git status"] }, { window_name: "server", panes: [{ shell_command: "bun dev", focus: true }] }, ], }); ``` C++ provides a consumer example in `examples/workspace/` that reads tmuxp configuration. The workspace builder is part of that example, rather than a library package: ## Cleaning up For temporary workspaces, Python's `Window` and `Session` context managers kill their objects on block exit, including when the block raises: See [Context managers](/topics/context-managers/) for cleanup support in each port. Use explicit kill methods when the handle does not provide scope-based cleanup. --- # Workspace configuration Source: https://libtmux.org/en/ts/latest/workspace/configuration/ > Tmuxp workspace configuration and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../reference/compatibility/) describes this port's implemented coverage. A workspace file describes one tmux session, its windows and panes, and the commands sent to them. YAML and JSON carry the same field names. Save this complete example as [`workspace.yaml`](../guides/installation/#create-the-input): ```yaml session_name: workspace-example start_directory: ./ windows: - window_name: editor layout: even-horizontal panes: - echo ready - blank ``` The equivalent JSON is: ```json { "session_name": "workspace-example", "start_directory": "./", "windows": [ { "window_name": "editor", "layout": "even-horizontal", "panes": ["echo ready", "blank"] } ] } ``` With Python tmuxp installed, load this file detached on a socket reserved for the example: ```console $ tmuxp load \ -L configuration-example \ -d \ workspace.yaml ``` Inspect the resulting panes: ```console $ tmux -L configuration-example list-panes -t '=workspace-example' ``` Remove the example session when finished: ```console $ tmux -L configuration-example kill-session -t '=workspace-example' ``` ## How configuration becomes a session The tmuxp loader reads a document, expands command shorthand and shell variables, and applies inherited defaults before selecting a workspace builder. The classic builder then creates tmux objects and sends commands. Completion means construction and command delivery finished; it does not establish that a server launched in a pane is ready. `"session_name"` and `"windows"` are needed by normal loading. A window can omit its name and let tmux choose one; an omitted `"panes"` list defaults to one blank pane. Supplying explicit names and pane lists makes a portable example clearer. An empty pane list is not the same input as an omitted list. The internal `validate_schema` helper requires each window_name, but the current CLI/classic builder path does not call it. It is not a complete JSON Schema or an exact description of what load accepts. A YAML parser accepting a key also does not mean a builder implements it. ## Configuration reference - [Session](./session/) covers identity, options, environment, and root keys. - [Windows](./windows/) covers names, indexes, option timing, and focus. - [Panes](./panes/) covers shorthand, blank forms, shell, and overrides. - [Commands](./commands/) covers before commands, Enter, delays, and history. - [Environment](./environment/) separates process settings from pane values. - [Directories](./directories/) explains file discovery and path resolution. - [Layouts](./layouts/) explains pane arrangement and terminal size. - [Hooks and builders](./hooks/) covers scripts and Python extensions. Use the [configuration gallery](../examples/gallery/) for more complete files. Configuration conversion should preserve the source mapping, including extension keys; loading that mapping requires separate support for every execution feature. ## Current TypeScript builder `@libtmux/workspace` validates a strict subset. Its apply operation reconciles a session using ownership and command policies, which differs from tmuxp load. Its YAML helper requires Bun; Node callers need a separate YAML decoder. See the [native builder behavior](../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [loader.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/loader.py); [validation.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/validation.py); [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py). --- # Session configuration Source: https://libtmux.org/en/ts/latest/workspace/configuration/session/ > Tmuxp session configuration and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. The root mapping names the session and supplies defaults inherited by its windows and panes. The file's name is independent of `"session_name"`: loading [`project.yaml`](../../guides/discovery/) can create a session named `development`. ```yaml session_name: development start_directory: ./ suppress_history: true shell_command_before: - echo preparing pane environment: WORKSPACE_ROLE: development options: default-shell: /bin/sh global_options: status: true windows: - window_name: main panes: - echo ready ``` `"global_options"` changes the shared tmux server. Use a dedicated socket while learning these options. The example assumes `/bin/sh` exists. ## Root keys | Key | Meaning | | --- | --- | | `"session_name"` | Session identity; expanded before building | | `"windows"` | Ordered list of window mappings | | `start_directory` | Starting directory and base for inherited window directories | | `"options"` | tmux session options applied during construction | | `"global_options"` | tmux options applied with global scope on the selected server | | `"environment"` | Values placed in the tmux session environment | | `shell_command_before` | Commands prepended to commands in every pane | | `"suppress_history"` | Default history suppression inherited by windows and panes | | `"before_script"` | Process run after initial session creation, before configured windows | | `"plugins"` | List of Python plugin class references | | `workspace_builder` | Classic builder, registered builder name, or Python class reference | | `workspace_builder_paths` | Trusted directories used for Python builder imports | | `workspace_builder_options` | Builder behavior settings such as pane_readiness | Options use tmux option names and values. A workspace key such as `start_directory` is not a tmux option and does not belong in `"options"`. Some tmux settings are window options even when tmux permits them through a session target; consult tmux's option scope when choosing the catalog. ## Construction order The classic builder creates the initial session, runs its initial plugin hook and workspace before_script, then applies root options, global options, and session environment before creating configured windows. The temporary initial window is replaced. Hooks can observe those tmux operations; loading is not an invisible transaction. The same-named session is handled by the [load command](../../cli/load/) and its attach, switch, append, and detached choices. Changing a YAML file does not automatically reconcile an existing running session. An explicit `load -s` value overrides the configured name for that invocation. ## Inherited defaults A window can override its start directory and history policy, and a pane can override them again. Before commands accumulate in session, window, then pane order. Session environment remains distinct from window/pane launch environments. Read [directories](../directories/), [commands](../commands/), and [environment](../environment/) before relying on inheritance. Python scripts, plugins, and custom builder references execute code. Their exact timing and runtime requirements are in [hooks and builders](../hooks/). ## Current TypeScript builder The local CLI supports session environment and before scripts. Explicit plugins and custom builders use the optional Python bridge described in [hooks and builders](../hooks/); custom builders may omit `windows`. The separate `@libtmux/workspace` library represents session name, start directory, options and windows. Environment, plugins, before_script and builder selection remain outside that library's strict schema. See the [native builder behavior](../../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py); [loader.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/loader.py); [load.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/load.py). --- # Window configuration Source: https://libtmux.org/en/ts/latest/workspace/configuration/windows/ > Tmuxp window configuration and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Each item in `"windows"` describes one window and its ordered panes. Set `"window_name"` when the name matters; an omitted name lets tmux choose it. `"window_index"` selects a tmux numeric index independently of the item's position in the list. ```yaml session_name: window-example start_directory: ./ windows: - window_name: tools window_index: 1 start_directory: ./ layout: even-horizontal focus: true options: automatic-rename: false options_after: synchronize-panes: true panes: - echo left - echo right ``` This configuration sends each pane its own initial command before enabling synchronized input. Later typing in one synchronized pane can affect the other pane. ## Window keys | Key | Meaning | | --- | --- | | `"window_name"` | Label used by tmux; shell variables are expanded | | `"window_index"` | Explicit numeric position, otherwise use tmux's available index | | `"panes"` | Ordered pane list; omission supplies one blank pane | | `"layout"` | Named tmux layout or explicit layout description | | `start_directory` | Directory inherited by panes unless a pane overrides it | | `"window_shell"` | Initial shell/application for panes, subject to pane shell override | | `focus` | Select the window after building | | `"options"` | Window options applied during creation | | `"options_after"` | Window options applied after panes and their initial commands | | `"environment"` | Launch environment used when a pane lacks its own map | | `shell_command_before` | Commands prepended after session before commands | | `"suppress_history"` | Window default overriding the session history policy | ## First pane and later panes A new window already has its initial pane. Tmuxp uses the first configured pane's start_directory, shell, and environment when launching that window, then creates splits for later panes. A first-pane override therefore matters during new-window, not only while creating splits. A pane shell overrides window_shell. Window environment is used when the pane has no environment map; providing a pane map selects that map instead. These rules have native-port differences, so use the current builder note on this page. ## Index, layout, and focus The classic builder moves the temporary initial window before creating the configured first window. This permits an explicit first index without reusing the temporary window's content. Do not assume window list position and tmux numeric index are identical. Window options precede layout application. `"options_after"` exists for settings such as synchronize-panes that should take effect after individual setup commands. [Layouts](../layouts/) explains named layouts, dimensions, and focus; [panes](../panes/) describes the pane forms accepted inside a window. ## Current TypeScript builder Names, directories, layout, focus, options, pre-pane commands, and panes are supported. Native apply matches windows by position, not tmux numeric index, and can reuse existing windows. See the [native builder behavior](../../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py); [loader.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/loader.py); [2-pane-synchronized.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/2-pane-synchronized.yaml). --- # Pane configuration Source: https://libtmux.org/en/ts/latest/workspace/configuration/panes/ > Tmuxp pane configuration and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. A pane can be a command string, a list of commands, or a mapping of settings. Each item in the window's `"panes"` list creates one pane; a command list inside that item describes several commands in that same pane. ```yaml session_name: pane-example start_directory: ./ windows: - window_name: main start_directory: ./ panes: - echo one command - [echo first command, echo second command] - shell_command: - echo configured pane start_directory: ./ focus: true - blank ``` ## Blank forms | Form inside `"panes"` | Reference interpretation | | --- | --- | | null, omitted YAML value, `blank`, or `"pane"` | Pane without its own commands | | Empty mapping or empty list | Expands to a pane without its own commands | | Mapping with no shell_command | Keeps the pane's other settings and uses no own commands | | `shell_command: null` or a single null command | No own commands | | Empty string `""` | Sends an empty command, normally pressing Enter | A blank pane can still receive inherited [before commands](../commands/). Blank forms do not disable session/window/pane setup. An omitted window panes key is defaulted to one blank pane; an explicitly empty panes list is a different shape and should not be used as a portable way to request that default. ## Pane keys | Key | Meaning | | --- | --- | | `shell_command` | String, ordered command list, or supported command dictionaries | | `shell_command_before` | Setup prepended after session/window before commands | | `start_directory` | Pane directory override | | `"shell"` | Shell/application launched for this pane | | `focus` | Select this pane in its window | | `"environment"` | Environment map selected for this pane's launch | | `"suppress_history"` | Pane history policy override | | `"enter"` | Default for whether to submit each command | | `sleep_before`, `sleep_after` | Default delays in seconds around each command | ## Launch a shell or type commands `"shell"` chooses the process tmux starts in the pane. `shell_command` sends text into the process already running there. Launching an application through shell can work with tmux's remain-on-exit behavior, while typing that application's name into a shell has different process semantics. A pane shell overrides window_shell, including on the first pane. The first pane also supplies its directory and environment during window creation. Use an installed shell/application path rather than assuming the same executable exists on every host. A successful build confirms command delivery, not application readiness or command exit status. See [commands](../commands/) for Enter, timing, and history, and [environment](../environment/) for launch-map selection. ## Current TypeScript builder A pane is a string or supported mapping. Null, bare command lists, shell, environment, and timed command dictionaries are not the complete tmuxp shapes accepted here. See the [native builder behavior](../../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [loader.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/loader.py); [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py); [blank-panes.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/blank-panes.yaml); [pane-shell.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/pane-shell.yaml). --- # Workspace commands Source: https://libtmux.org/en/ts/latest/workspace/configuration/commands/ > Tmuxp workspace commands and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Tmuxp sends workspace commands into panes in order. A command can be a string or a mapping with `"cmd"` and execution controls. `shell_command_before` adds shared setup without copying it into every pane. ```yaml session_name: command-example shell_command_before: - echo session setup windows: - window_name: main shell_command_before: - echo window setup panes: - shell_command_before: - echo pane setup shell_command: - cmd: echo ready sleep_after: 0.2 - cmd: echo typed but not submitted enter: false sleep_after: 0 ``` This sends the three setup commands, submits echo ready, waits 0.2 seconds, and leaves the final command at the prompt. The delay pauses construction; it does not ask the application whether it is ready. ## Forms and order `shell_command` and `shell_command_before` accept a scalar command or a list. A command dictionary requires `"cmd"` for the text. The loader expands shell variables and tilde expressions in command strings before the builder sends them. Variables already known to the process running tmuxp can therefore be substituted before a pane shell sees the command. For each pane, the loader concatenates session before commands, window before commands, pane before commands, then the pane's own commands. A blank pane still receives inherited setup. Quoting affects the pane shell too; YAML quoting alone does not bypass tmuxp's expansion step. ## Enter and delays | Setting | Default and scope | | --- | --- | | `"enter"` | True; pane default, then command override | | `sleep_before` | No delay; pane default, then command override in seconds | | `sleep_after` | No delay; pane default, then command override in seconds | In the classic builder, a command's enter/delay override carries forward to subsequent commands in that pane. Set `enter: true` or an explicit zero delay to restore that behavior for a later command. Absence means keep the current value; zero is an intentional delay override. Pauses run synchronously during construction. They can let a startup command settle but do not monitor its success. A failed pane command is not necessarily a failed tmux send operation, and starting a web server is not proof it is accepting connections. ## History and prompt readiness History suppression defaults to true. A session value trickles to a window, and a pane can override it. Suppression prefixes command text with a space. Bash needs HISTCONTROL containing ignorespace or ignoreboth; zsh needs HIST_IGNORE_SPACE. Without shell support, the command can still enter history. Builder pane_readiness is separate from command delays. Auto waits for the configured zsh session shell, while custom pane/window launch commands skip prompt waiting. See [hooks and builders](../hooks/) for the policy and its limits. A workspace [before_script](../hooks/) runs as a process outside the panes and checks its exit status. Use it for bootstrap work that must succeed before configured windows are built, rather than treating pane command delivery as a checked process result. ## Current TypeScript builder The default native policy sends commands only to newly created panes. `commands: "always"` also replays commands in reused panes. These library options are not tmuxp YAML keys. See the [native builder behavior](../../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [loader.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/loader.py); [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py); [sleep.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/sleep.yaml); [skip-send.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/skip-send.yaml). --- # Workspace environment Source: https://libtmux.org/en/ts/latest/workspace/configuration/environment/ > Tmuxp workspace environment and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. The environment of the process running tmuxp controls discovery, expansion, and presentation. A workspace's `"environment"` mapping controls the tmux session or the environment passed when launching a pane. These are separate settings. ```yaml session_name: environment-example environment: WORKSPACE_ROLE: shared windows: - window_name: main environment: WINDOW_ROLE: tools panes: - echo window environment - environment: PANE_ROLE: isolated shell_command: env ``` The first pane receives the window launch map. The second selects its own pane map instead of merging it with the window map. It still inherits applicable tmux session/process environment. A pane map does not mean a completely empty environment plus that map. ## Expansion before launch Tmuxp expands tilde and environment-variable expressions in session/window names, paths, before_script, command strings, environment values, and string option values. It uses the environment of the process invoking tmuxp. It does not first populate that process environment from the workspace's environment mapping. For example, a command using an already-set process variable can be substituted before pane creation. Inspect the expanded intent when a variable should instead be read dynamically by a pane shell. Unknown variables and shell-specific expressions follow the loader's expansion and the eventual shell's rules, not a general template language. ## CLI and runtime variables | Variables | Effect | | --- | --- | | `TMUXP_CONFIGDIR`, `XDG_CONFIG_HOME`, `HOME` | Global workspace directory selection and home expansion | | `TMUXINATOR_CONFIG` | Tmuxinator import source directory | | `$EDITOR` | Editor executable; reference default is vim | | `$TMUX`, `$TMUX_PANE` | Current tmux connection and shell object context | | `TMUXP_PROGRESS` | Value 0 disables animated load progress | | `TMUXP_PROGRESS_FORMAT` | Default/minimal/window/pane/verbose preset or custom tokens | | `TMUXP_PROGRESS_LINES` | Script panel lines: default 3, 0 hides, -1 caps to terminal height | | `TMUXP_DETECT_TERMINAL_SIZE` | Value 1 enables size detection; default 1 | | `TMUXP_DEFAULT_COLUMNS`, `TMUXP_DEFAULT_ROWS` | Fallback session dimensions | | `COLUMNS`, `LINES`, `ROWS` | Terminal helper overrides and fallback sizing inputs | | `NO_COLOR`, `FORCE_COLOR` | Color policy; nonempty values are significant | | `PYTHONSTARTUP` | Startup file used by supported shell startup behavior | | `IPYTHON_ARGUMENTS` | Whitespace-split arguments for the IPython backend | | `PYTHONBREAKPOINT` | Can affect debugger selection in tmuxp shell | | `SHELL` | Shell diagnostics and readiness fallback | | `DISABLE_AUTO_TITLE` | Oh My Zsh automatic-title warning | | `PATH` | Executable lookup and diagnostics | | `LIBTMUX_TMUX_FORMAT_SEPARATOR` | Python libtmux format collection override | Explicit progress flags take precedence over their corresponding defaults. Nonempty NO_COLOR disables color even with always; otherwise explicit never/always precede FORCE_COLOR and automatic TTY detection. Machine-output extensions must disable ANSI regardless of forced color. `CLICOLOR` and `CLICOLOR_FORCE` are proposed cross-port presentation extensions, not variables read by this tmuxp reference. Native format codecs also need separate evidence before claiming the Python separator override works. See [directories](../directories/) for existing-directory precedence, [layouts](../layouts/) for size resolution, and [shell](../../cli/shell/) for Python-specific environment effects. ## Current TypeScript builder The strict schema does not implement tmuxp environment maps or full interpolation. Node support in the package does not supply Bun's YAML parser. See the [native builder behavior](../../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [loader.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/loader.py); [finders.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/finders.py); [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py); [load.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/load.py); [shell.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/shell.py); [shell.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/shell.py); [colors.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/_internal/colors.py); [util.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/util.py). --- # Workspace files and directories Source: https://libtmux.org/en/ts/latest/workspace/configuration/directories/ > Tmuxp workspace files and directories and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Tmuxp accepts an explicit workspace file, a saved workspace name, or a project directory. The location of the selected file determines how config-relative paths expand. Use an explicit file while debugging a discovery problem. Load a file on a dedicated server: ```console $ tmuxp load \ -L directory-example \ -d \ ./workspace.yaml ``` Load the current project's workspace: ```console $ tmuxp load . ``` These commands use Python tmuxp and assume the corresponding workspace file already exists. The first command leaves its session detached; use the session name from the file when inspecting or cleaning it up. ## Global and local discovery For the preferred global workspace directory, tmuxp tries TMUXP_CONFIGDIR, XDG_CONFIG_HOME/tmuxp (or the XDG default), then the legacy [`~/.tmuxp`](../../guides/discovery/) directory. It chooses the first existing directory. If none exists, it returns the legacy location. Setting TMUXP_CONFIGDIR to a nonexistent path does not automatically select that path over an existing fallback. Project discovery walks the current directory and its parents, choosing at most one workspace per directory in `.tmuxp.yaml`, [`.tmuxp.yml`](../../guides/discovery/), [`.tmuxp.json`](../../guides/discovery/) order. It stops at home or filesystem root. `ls` can report global directory candidates and locally discovered workspaces; that inventory is not identical to resolving one explicit load argument. The importers use their own source roots: Teamocil uses [`~/.teamocil`](../../cli/import-teamocil/); tmuxinator uses TMUXINATOR_CONFIG, with tilde expansion, or [`~/.tmuxinator`](../../cli/import-tmuxinator/). Their source argument is effectively required, even though the parser's positional arity looks optional. ## Start directories ```yaml session_name: directory-example start_directory: ./ windows: - window_name: root panes: - pwd - window_name: child start_directory: ./src panes: - pwd - start_directory: ./ shell_command: pwd ``` This example assumes the project has a `"src"` directory. The first window inherits the session directory. The child window resolves ./src against the explicit session directory, and its second pane resolves ./ against that window directory. Both child panes start in src. The root start_directory resolves a dot-relative path from the configuration file's directory. At child levels, the pinned loader resolves dot-relative paths against the immediate parent's start_directory before inherited defaults are filled. Define that parent value explicitly when using ./ or ../ in a child. A missing parent start_directory can raise KeyError during expansion. A plain relative window directory such as src is joined to the session directory later, during trickle. A dot-relative pane under that still-relative window can resolve against the invoking process's current directory first. Use an explicit ./src window value as above, or an absolute path, to avoid that inconsistency. Native normalization that resolves every child consistently would correct this reference behavior. When a document names no `start_directory` at any level, panes start in the directory the command was run from, not the directory the workspace file lives in. That is what tmuxp does, and all eight implementations agree on it. An explicit relative value such as `./src` is the other case: it always resolves against the workspace file's directory, at every level, so a workspace stays portable no matter where it is loaded from. Absolute and expanded-home paths retain their explicit location. Quote `~` in YAML to avoid its null spelling. A pane override also applies to the first pane in the Python classic builder. Inspect resolved values and the resulting pane directory when loading a workspace from a different working directory. ## Missing directories and bootstrap A nonexistent path can make tmux start somewhere unexpected, including a home directory, rather than producing a useful configuration error. Inspect the actual pane directory when verifying a workspace. Native ports may reject, retain, report, or pass through the value differently. A relative before_script path is resolved from the workspace file. Its process working directory uses the session start_directory when supplied. A bootstrap process can create required project files, but it runs after the initial tmux session exists. See [hooks](../hooks/) for failure handling and [environment](../environment/) for variable expansion. ## Current TypeScript builder Directories inherit from workspace to window to pane. The package does not implement the tmuxp name/project discovery workflow or its full config-relative expansion. See the [native builder behavior](../../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [finders.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/finders.py); [loader.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/loader.py); [import_config.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/import_config.py); [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py); [start-directory.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/start-directory.yaml). --- # Workspace layouts and focus Source: https://libtmux.org/en/ts/latest/workspace/configuration/layouts/ > Tmuxp workspace layouts and focus and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. A window's `"layout"` chooses how tmux arranges its panes. Named layouts adapt to the window size; explicit layout strings describe geometry more directly. The selected tmux version and terminal dimensions affect the final result. ```yaml session_name: layout-example windows: - window_name: main layout: main-horizontal focus: true options: main-pane-height: 60% panes: - shell_command: echo main pane focus: true - echo first lower pane - echo second lower pane ``` ## Layout names and options Common tmux layout names are even-horizontal, even-vertical, main-horizontal, main-vertical, and tiled. Layout availability belongs to the target tmux version. Options such as main-pane-height and main-pane-width shape applicable main-pane layouts. A row/column count and a percentage are different values; preserve that distinction in YAML/JSON. The classic builder applies window options before selecting a layout and applies options_after after panes and their setup commands. This lets a workspace size its main pane while delaying synchronize-panes until individual setup is complete. An explicit layout string can depend on the current number of panes and window size. A layout captured from one terminal is a starting point to inspect on another, not a portable pixel diagram. ## Terminal dimensions When TMUXP_DETECT_TERMINAL_SIZE is 1 (the default), the classic builder asks Python's terminal-size helper for initial session dimensions. COLUMNS and LINES can influence that helper. Fallback width uses TMUXP_DEFAULT_COLUMNS, then COLUMNS, then 80. Fallback height uses TMUXP_DEFAULT_ROWS, then ROWS, with nominal default 24. The helper can choose terminal dimensions instead of its fallback, so a TMUXP_DEFAULT value alone is not an unconditional size override. Detached sessions also need dimensions for reproducible layouts. Invalid numeric environment values can fail before useful construction. ## Focus and indexes Set window focus to select that window after construction, and pane focus to select the active pane within its window. Prefer one focused window and one focused pane per window; multiple true values depend on build order and are harder to reason about. A window_index selects its numeric tmux position. Pane IDs such as `%3` are runtime identities, not YAML pane list positions. Base-index and pane-base-index settings can make visible indexes differ from zero-based list positions. Inspect a running workspace's geometry and selection: ```console $ tmux -L layout-example list-windows -t '=layout-example' ``` This command assumes the sample workspace was loaded on the dedicated layout-example socket. Use [load](../../cli/load/) to select that socket, then inspect panes as needed. A successful load does not prove every native port honors the same focus/index/options policy. ## Current TypeScript builder Apply handles supported layouts/focus/options, but planWorkspace describes topology changes only. It does not enumerate command, option, layout, or focus changes. See the [native builder behavior](../../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py); [main-pane-height.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/main-pane-height.yaml); [main-pane-height-percentage.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/main-pane-height-percentage.yaml); [focus-window-and-panes.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/focus-window-and-panes.yaml); [window-index.yaml](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/window-index.yaml). --- # Workspace hooks and builders Source: https://libtmux.org/en/ts/latest/workspace/configuration/hooks/ > Tmuxp workspace hooks and builders and current TypeScript builder compatibility. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Workspace scripts, plugins, and custom builders extend how Python tmuxp creates a session. They are execution features, not passive configuration metadata. A workspace naming Python code needs that code installed or importable in tmuxp's environment. ## Bootstrap with before_script ```yaml session_name: bootstrap-example start_directory: ./ before_script: ./bootstrap.sh windows: - window_name: main panes: - echo bootstrap completed ``` This complete configuration assumes bootstrap.sh exists and is executable. Tmuxp resolves the script relative to the workspace file and uses the session start_directory as the process working directory when supplied. A zero exit status permits configured-window construction to continue; a failing script raises an error. The classic builder has already created the initial session when before_script runs. It kills that session when the bootstrap process fails. That does not undo files or other external effects created by the script. Pane shell_command is different: successful text delivery does not check the command's exit status. ## Python plugins `"plugins"` is a list of Python class references, conventionally a class in a package's plugin module. Install that package into the same Python environment as tmuxp. Plugins can declare tmux, libtmux, and tmuxp version requirements. The lifecycle includes these distinct hooks: | Hook | When it applies | | --- | --- | | `before_workspace_builder` | Initial session exists, before configured windows | | `on_window_create` | A window has been created, before its panes finish | | `after_window_finished` | That window's panes and setup have finished | | `"before_script"` | Plugin callback after session construction | | `reattach` | Reattachment to a session that already exists | The plugin callback named before_script is not the workspace before_script process. Their timing and execution mechanism differ. Plugin methods can change live tmux state; choose a plugin only when its behavior is intended for the workspace. ## Select a workspace builder The default is the built-in classic builder. `workspace_builder` can name a registered entry point in the [`tmuxp.workspace_builders`](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/registry.py) group, a `module:attribute` reference, or a dotted Python path. Custom builders receive expanded configuration and a libtmux server. `workspace_builder_paths` lists trusted directories temporarily added to Python's import path. Tilde and environment variables expand, relative entries resolve against the workspace file, and entries must exist as directories. Tmuxp does not use site.addsitedir for these paths. Adding an import path is not permission to treat arbitrary workspace files as inert data. A builder implements the synchronous build/session interface and cooperates with plugin, progress, before-script, script-output, and build-event callbacks. The configuration alone cannot establish that an arbitrary custom builder honors those callbacks or the classic builder's behavior. ## Pane readiness ```yaml session_name: readiness-example workspace_builder: classic workspace_builder_options: pane_readiness: auto windows: - window_name: main panes: - echo ready ``` Auto, the default, waits for a prompt when the configured session shell is zsh. Always requests the wait for default-shell panes; never skips it. Accepted aliases include true/on/yes/1 for always and false/off/no/0 for never, with strings normalized for case and surrounding whitespace. Unknown values are rejected. Custom pane/window launch commands skip prompt waiting. Readiness checks concern a shell prompt, not the eventual application's health, and do not acknowledge that every later command was consumed. Use [commands](../commands/) for explicit delays and Enter behavior. ## Current TypeScript builder The local `tmux-workspace load` CLI runs ordinary workspaces without Python. Explicit `plugins` or `workspace_builder` selections use an interpreter with tmuxp 1.74.0 installed. `TMUX_WORKSPACE_PYTHON` selects it; the default is `python3`. Empty plugin lists and blank builder names stay native. The adapter expands common fields and resolves `workspace_builder_paths` relative to the workspace file. Explicit custom builders may omit `windows` and consume their own configuration. Append keeps the authenticated current session across input files. Python extension append rejects `before_script`; run that script separately. Native append supports before scripts. Extension output is streamed and retained within the CLI's capture limit. Progress shows a workspace label without native pane counters. Results describe observed changes, which may include concurrent work; the CLI does not claim ownership or roll back extension effects. See [output](../../reference/output/) and [compatibility status](../../reference/compatibility/) for the result fields and cancellation behavior. The separate `@libtmux/workspace` library's apply policies cover ownership, pruning and command replay; they do not implement Python hooks. See the [native builder behavior](../../internals/topics/) and [configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts) before using these fields through application code. ## Reference source [classic.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/classic.py); [registry.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/registry.py); [protocol.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/builder/protocol.py); [options.py](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/workspace/options.py); [plugins.md](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/docs/topics/plugins.md). --- # Examples Source: https://libtmux.org/en/ts/latest/examples/ > Programs for sending input, capturing output, and building workspaces in each language. These examples show how to complete a tmux task in each language. Use the tabs to select your port. Each page includes the source file and the checks run by that port's test suite; see those details before adapting an excerpt into a standalone program. Examples marked with `file=` are read from the port source during the build. Other blocks are copied excerpts. The source details identify which mechanism each block uses. - **[Attach and send keys](attach-and-send-keys/)**: get a session, send a command, and read output. Uses the hierarchy described in [Server, session, window, pane](/concepts/server-session-window-pane/). - **[Capture pane output](capture-pane-output/)**: read back what a pane is showing, and wait for output to appear instead of guessing a delay. The companion to [Capturing output](/guides/capturing-output/). - **[Build a workspace from a file](workspace-from-file/)**: the tmuxp-shaped job of describing a multi-window session as data and building it in one call, in each port that has one. ## What "verified" means, per port Port repositories use the following checks for their source examples. This site reads or copies those examples; a successful site build alone does not execute them: | Port | Mechanism | What it checks | |------|-----------|-----------------| | Python | `pytest` `testpaths` includes `README.md` and `src/libtmux` | `>>>` doctest blocks run against a real, isolated tmux session on every test run | | TypeScript | `scripts/check-doc-runnable.ts` | A block tagged `` must appear, line for line, in that file, which the integration suite executes | | Go | `go generate ./tmux` (`internal/generate/docs`) | A `` region in `README.md` is rewritten from the matching `// docs:name` … `// docs:end` region in `examples/`; CI fails on drift | | Rust | `#![doc = include_str!("../README.md")]` | The entire README is a doc comment, so `cargo test --doc` compiles and runs every fenced Rust block in it | | Java | `docs-tests` (`./gradlew :docs-tests:test`) | Every Java fence in READMEs and guides is compiled against the real artifacts, then run against real tmux via `libtmux-junit5` | | .NET | `sync_snippets.py --check` + `ReadmeExampleTests` | A `` region is quoted from a tested `[Example]` method; every `csharp run` block is additionally compiled and executed | | C++ | `tools/docs/check_readme.py` | Each ` ```cpp ` block in `README.md` must appear verbatim as a `#region` in `examples/05-readme.cpp`, which CTest builds and runs | | Swift | `Scripts/check_examples.py` | Each ` ```swift ` block in `README.md` and product READMEs must appear in `Examples/Sources/`, which `swift test --package-path Examples` compiles through its public products | See [Testing with libtmux](/guides/testing-with-libtmux/) for the fixture each of those test suites runs against, and each example page below for the exact file a given snippet was quoted from. --- # Attach and send keys Source: https://libtmux.org/en/ts/latest/examples/attach-and-send-keys/ > Get a session handle, send a command to a pane, and capture output. Get a session handle, send a command to a pane, and capture output. These examples use libtmux from your program; to attach your terminal interactively, see [Attaching to tmux](/guides/attaching-to-tmux/). Select your port below. The examples retain their source's setup, error handling, and cleanup, so the operations shown vary by port. [Where this comes from](#where-this-comes-from) identifies each source and its test coverage. ```typescript file="examples/quickstart/quickstart.ts" import { Server, TmuxCommandError, type ServerSnapshot } from "libtmux"; /** * A runnable tour of the API, driven by the tests so it cannot rot. * * Every step here appears in README.md. */ export async function quickstart(server: Server): Promise { // Nothing is read until you ask. `snapshot()` is the only step that talks to // tmux; everything reachable from it resolves locally. const session = await server.newSession({ name: "quickstart" }); const editor = await session.newWindow({ name: "editor" }); await editor.split(); const snapshot = await server.snapshot(); // Declarative filtering, serializable and stable on the wire. const found = snapshot.windows.where({ name: "editor" }).one(); // Relations are plain properties: no await, no tmux command. const paneCount = found.panes.length; if (paneCount !== 2) throw new Error(`expected two panes, saw ${String(paneCount)}`); // A criterion is spelled like the handle accessor it filters. if (snapshot.panes.count({ currentCommand: { contains: "" } }) === 0) { throw new Error("expected panes to report a current command"); } const first = found.panes.at(0); if (first === undefined) throw new Error("expected a pane"); await first.sendKeys("echo hello-from-libtmux", { literal: true }); // Failures carry their parts rather than a formatted sentence. try { await server.setOption("not-a-real-option", "1"); } catch (error) { if (!(error instanceof TmuxCommandError)) throw error; if (error.args[0] !== "set-option") throw error; } return snapshot; } ``` ## Finding an existing session instead For a script that runs repeatedly, look up a session before creating it. [Attaching to tmux](/guides/attaching-to-tmux/#finding-a-session-instead-of-always-creating-one) shows that pattern, and [Filtering and querying, in practice](/guides/querying-and-filtering/) covers absent and ambiguous matches. ## Where this comes from ### Python **Source:** `src/libtmux/server.py`, `session.py`, `pane.py` docstrings **In this page:** hand-quoted, composed from three separate docstrings **Checked by:** `pytest` runs every `>>>` doctest (`testpaths` includes `src/libtmux`) against a real, isolated tmux session on every test run ### TypeScript **Source:** `examples/quickstart/quickstart.ts` **In this page:** read whole from the file **Checked by:** run against real tmux by `bun test examples`; its first half is also mirrored into README.md under a `` marker, checked line-for-line by `scripts/check-doc-runnable.ts` ### Rust **Source:** `crates/libtmux/examples/scratch.rs` **In this page:** read whole from the file **Checked by:** run to completion against a throwaway tmux by `scripts/run-examples.sh` (`just examples`), which CI runs and which also asserts the example leaves no session behind ### Go **Source:** `examples/quickstart/main.go` **In this page:** read whole from the file **Checked by:** the whole file runs against a real tmux server as `TestQuickstart`; the `docs:quickstart` region inside it is additionally mirrored into README.md by `go generate ./tmux`, and CI fails if the two drift ### Java **Source:** `examples/src/main/java/io/github/libtmux/examples/BuildAWorkspace.java` **In this page:** read whole from the file **Checked by:** run against real tmux by the `examples` module's own `ExamplesRunTest` ### .NET **Source:** `examples/LibTmux.Examples/Snippets/OneShot.cs` **In this page:** read whole from the file **Checked by:** its `ConnectAndBuild` region is mirrored into README.md and checked by `sync_snippets.py --check`; the mirrored `csharp run` block is additionally compiled and run by `ReadmeExampleTests` ### C++ **Source:** `examples/05-readme.cpp`, the `connect` and `build` regions **In this page:** Copied excerpts from the `connect` and `build` regions. **Checked by:** quoted verbatim into README.md, checked for drift by `tools/docs/check_readme.py`, and the whole file is built and run by CTest ### Swift **Source:** `Examples/Sources/ExampleCode/Changing.swift` **In this page:** read whole from the file **Checked by:** matched against the README's "Change what is there" section by `Scripts/check_examples.py`; compiled and run through the package's public products by `swift test --package-path Examples` ### Source inclusion A `file="..."` fence reads the named source during the site build. Hand-quoted excerpts are copies; their source files and regions are listed above. --- # Capture pane output Source: https://libtmux.org/en/ts/latest/examples/capture-pane-output/ > Capture a pane's screen and wait for expected output or a completion signal. Read a pane after [sending input](../attach-and-send-keys/). [Sending keys](/guides/sending-keys/#the-race-you-cant-see-from-the-call-site) explains why an immediate capture can miss output. These examples show screen capture and waiting; [Capturing output](/guides/capturing-output/) explains the choices. See [source details](#where-this-comes-from) for each example's source and validation. ## Read what's on screen ```typescript file="examples/capture/capture.ts" import type { Server } from "libtmux"; /** * Move text through a tmux buffer, and read what a pane is showing. * * The half of orchestration that is not making things happen. A named buffer * is tmux's own clipboard: anything in it can be pasted into any pane on the * server, by this process or by a person at the keyboard, without the text * passing through your program a second time. */ export async function moveTextThroughABuffer( server: Server, text: string, ): Promise<{ named: readonly string[]; roundTripped: readonly string[] }> { // tmux stores nothing for empty text and reports success, so a buffer whose // content is computed has to be checked before it is written — otherwise the // name is absent and the next call to read it is what fails. if (text === "") throw new Error("tmux holds no empty buffer"); await server.setBuffer("report", text); const roundTripped = await server.showBuffer("report"); const named = await server.listBuffers(); // Buffers outlive the program that made them. await server.deleteBuffer("report"); return { named, roundTripped }; } /** * What a pane is showing, scrollback included. * * `start` counts back from the visible top, so -100 asks for the last hundred * lines or as many as exist. A pane that has printed nothing answers with * nothing rather than with blank lines. */ export async function readPane(server: Server): Promise { const session = await server.newSession({ name: "capture" }); const pane = session.activePane; if (pane === undefined) throw new Error("a new session always has one pane"); return pane.capture({ start: -100 }); } ``` The .NET example under "Wait for text instead of guessing a delay" uses `Pane.CaptureAsync` within a wait. Its separate Psmux transport also provides a capture API, shown in `examples/LibTmux.Examples/Snippets/Psmux.cs`. ## Wait for text instead of guessing a delay The Python example uses `wait_for`, tmux's signal channel. It waits for a signal from the command rather than matching pane text. ```typescript file="examples/agent/agent.ts" import type { Server } from "libtmux/server"; /** * Drive tmux the way an agent does: act, then wait for the result. * * One control observer carries notifications while commands use the server's * engine. Keeping the observer open makes event-driven waits persistent; it * does not turn command output into a control-mode protocol. */ export async function runAndWait(server: Server, command: string, marker: string): Promise { // A connection attaches, so the session has to exist first. On a server with // none, `connect()` fails saying exactly that. const session = await server.newSession({ name: "agent" }); await using live = await server.connect({ target: session.id }); const pane = (await live.snapshot()).sessions.one({ id: session.id }).panes.one(); // Subscribe before acting. A marker printed between the command and the // subscription is one nobody is listening for, and the wait never ends. const printed = live .subscribe() .find( (event) => event.kind === "output" && event.paneId === pane.id && event.data.includes(marker), { timeoutMs: 30_000 }, ); await pane.sendKeys(command); const event = await printed; if (event === undefined) throw new Error(`${command} never printed ${marker}`); return event.kind === "output" ? event.data : ""; } /** * Wait for the server to reach a shape, rather than for one event. * * `waitFor` reads the server, then re-reads on each notification, so it returns * at once when the condition already holds and does not miss a change that * lands while it is subscribing. */ export async function buildAndSettle(server: Server, windows: readonly string[]): Promise { const session = await server.newSession({ name: "settling" }); await using live = await server.connect({ target: session.id }); const bound = (await live.snapshot()).sessions.one({ id: session.id }); for (const name of windows) { // eslint-disable-next-line no-await-in-loop -- window order is observable. await bound.newWindow({ name }); } const settled = await live.waitFor( (snapshot) => windows.every((name) => snapshot.windows.exists({ name })), { timeoutMs: 30_000 }, ); return settled.windows.count({ session: { is: { id: session.id } } }); } ``` `Session.OpenNotifications` receives tmux events as a stream. For tests that need to wait for screen text, use `tmuxtest.WaitForText`; see [Testing with libtmux](/guides/testing-with-libtmux/). Rust's `wait_for_text` looks before it sleeps, joins wrapped lines so a needle spanning a wrap still matches, and returns `PaneWait::Dead` rather than hanging forever if the pane's process ends first. Attach the `ControlClient` to receive `%output` notifications. An unattached client receives command replies only. `TmuxWait.UntilAsync` polls a read against a predicate rather than sleeping a fixed amount. C++ has no checked snippet that waits on pane *text*. `Server::wait_for(channel, timeout)`, in `include/libtmux/server.hpp`, uses tmux's own `wait-for` signal instead of scraping output, and its doc comment explains why that is the safer choice when the command you are waiting on can be made to announce itself: "a server that dies under a waiter makes tmux exit zero, which is indistinguishable from being signalled ... this reports that as a failure instead." `waitForOutput` takes patterns for both success and failure, so a process that fails fast is discovered immediately rather than by timing out. ## Where this comes from ### Python **Source:** `src/libtmux/pane.py` (`capture_pane`), `src/libtmux/server.py` (`wait_for`) docstrings **In this page:** hand-quoted **Checked by:** `pytest` runs every `>>>` doctest against a real, isolated tmux session on every test run ### TypeScript **Source:** `examples/capture/capture.ts` (read), `examples/agent/agent.ts` (wait) **In this page:** read whole from each file **Checked by:** both run against real tmux by `bun test examples`; `agent.ts` is additionally mirrored into README.md under a `` marker checked by `scripts/check-doc-runnable.ts` ### Go **Source:** `examples/quickstart/main.go` (read, already shown whole on the previous page), `examples/control-mode-subscribe/main.go` (wait) **In this page:** read: hand-quoted; wait: read whole from the file **Checked by:** both run against real tmux as `TestQuickstart` / `TestControlModeSubscribe`; the wait file's `docs:watching` region is additionally mirrored into README.md by `go generate ./tmux` ### Rust **Source:** `crates/libtmux/examples/scratch.rs`, already shown whole on the previous page **In this page:** hand-quoted excerpts of the same file **Checked by:** run to completion against a throwaway tmux by `scripts/run-examples.sh`, which CI runs ### Java **Source:** root `README.md` Quickstart (read), `examples/src/main/java/io/github/libtmux/examples/WatchPaneOutput.java` (wait) **In this page:** read: hand-quoted; wait: read whole from the file **Checked by:** every README fence is compiled and run against real tmux by `docs-tests`; `WatchPaneOutput` is additionally run by the `examples` module's `ExamplesRunTest` ### .NET **Source:** root `README.md`, "Running something, and reading it back" **In this page:** hand-quoted **Checked by:** one of the `csharp run` blocks compiled and run against real tmux by `ReadmeExampleTests` ### C++ **Source:** `examples/05-readme.cpp` `capture` region (read); `include/libtmux/server.hpp` doc comment (wait, no fence) **In this page:** hand-quoted **Checked by:** the `capture` region is quoted verbatim into README.md and checked by `tools/docs/check_readme.py`; the whole file is built and run by CTest ### Swift **Source:** `Examples/Sources/ExampleCode/Changing.swift` (read, already shown whole on the previous page), `Waiting.swift` (wait) **In this page:** read: hand-quoted excerpt; wait: read whole from the file **Checked by:** both matched against the README by `Scripts/check_examples.py` and run by `swift test --package-path Examples` ### Source inclusion Go, Rust, and Swift each reuse a file already shown in full on [Attach and send keys](../attach-and-send-keys/#where-this-comes-from): rather than dump the same file a second time, this page quotes just the relevant lines by hand, with a comment naming the source, and points back at the full listing there. --- # Build a workspace from a file Source: https://libtmux.org/en/ts/latest/examples/workspace-from-file/ > The tmuxp-shaped job of describing a multi-window session as data and building it in one call, in each port that has a checked way to do it. [tmuxp](https://tmuxp.git-pull.com/) describes sessions, windows, panes, and shell commands in configuration files. Several libtmux ports provide builders for this format. [Source details](#where-this-comes-from) identify the example files and their checks. For Python, use [tmuxp](https://tmuxp.git-pull.com/), a separate application built on libtmux's `Server`, `Session`, `Window`, and `Pane` APIs. ```typescript file="examples/workspace/workspace.ts" import type { Server } from "libtmux/server"; import type { Session } from "libtmux/session"; import { applyWorkspace } from "@libtmux/workspace"; import type { WorkspaceInput } from "@libtmux/workspace/config"; /** * Build the shape most people reach for tmux to get: one session, a window * per concern, each pane already running the thing it is there for. * * `server.batch` plans several windows and resolves them from one final * snapshot. `buildWorkspace`, below, delegates declared topology to the * workspace package instead of maintaining another reconciler here. */ export async function buildSimpleWorkspace(server: Server): Promise { const built = await server.newSession({ name: "work", shellCommand: "sleep 30", windowName: "editor", }); const [logs, shell] = await server.batch([ built.plan.newWindow({ name: "logs", shellCommand: "tail -f /dev/null" }), built.plan.newWindow({ name: "shell" }), ]); await logs.selectLayout("even-horizontal"); void shell.name; // "shell" return built; } export const DEVELOPMENT_WORKSPACE = { session_name: "workspace-example", windows: [ { panes: ["sleep 30", "sleep 30"], window_name: "editor" }, { panes: ["sleep 30"], window_name: "server" }, { window_name: "logs" }, ], } satisfies WorkspaceInput; /** Apply the package's declarative workspace to a server. */ export function buildWorkspace(server: Server): Promise { return applyWorkspace(server, DEVELOPMENT_WORKSPACE); } /** * Tear a workspace down without caring whether it is there. * * Killing a session that has already gone is not a failure worth propagating, * which is the one case worth handling separately from every other tmux error. */ export async function removeWorkspace(server: Server, name: string): Promise { const found = (await server.snapshot()).sessions.first({ name }); if (found === undefined) return false; await found.kill(); return true; } ``` TypeScript's `applyWorkspace` reuses existing objects when the same configuration is applied again. Go's `Example()`, in `workspace/example_test.go`, is a Go `Example` function: `go test` runs it and checks its output against the `// Output:` comment at the end, so this is executed on every test run rather than merely present in a README. `Parse` rejects a field it doesn't recognize rather than dropping it silently, and reports every problem it finds at once with the line it's on. `Build` is not atomic: tmux has no transaction, so a failure partway through leaves whatever was already created in place, identified by the session `Build` still returns. Rust's `freeze(&session).await?` exports an existing session to the workspace format. It recovers windows, panes, and working directories, but cannot recover the shell command originally typed to start a process. Java's `read` and `parse` validate the configuration and return a `Workspace` value. Only `build` changes tmux state. The .NET builder can wait for shell readiness before sending commands; [Sending keys](/guides/sending-keys/#the-race-you-cant-see-from-the-call-site) explains the startup race. It polls `pane_current_command`, `cursor_x`, and `cursor_y` for up to ten seconds by default. `PaneReadiness.Auto` waits for zsh, `Always` waits for every pane running the session's default shell, and `Never` sends immediately. If `BuildAsync` fails partway through, `WorkspaceBuildException.PartialResult` identifies what was created. C++'s `examples/workspace/` implements a consumer of the core API with its own `workspace.hpp` and `tmuxp.hpp` types. Those types are part of the example, not the library package. See [the example's README](https://github.com/libtmux/libtmux-cxx/tree/main/examples/workspace) to adapt it. Swift's `WorkspaceBuilder.build` rejects an existing session with the requested name. `Workspace.decode(yaml:)` reads tmuxp YAML when the `YAMLWorkspaces` trait is enabled. `Workspace.decode(json:)` needs no additional trait. ## Where this comes from ### Python **Source:** Not listed. **In this page:** no fence; the README says tmuxp is a separate project by design **Checked by:** n/a ### TypeScript **Source:** `examples/workspace/workspace.ts` (`@libtmux/workspace`) **In this page:** read whole from the file **Checked by:** run against real tmux by `bun test examples/workspace` ### Go **Source:** `workspace/example_test.go` (`workspace.Parse` / `workspace.Build`) **In this page:** read whole from the file **Checked by:** `Example()` and its siblings run under `go test` and are checked against their own `// Output:` comments ### Rust **Source:** `crates/tmux-workspace/README.md`, "Build it" **In this page:** hand-quoted **Checked by:** the crate's own `crates/tmux-workspace/src/lib.rs` includes the README as a doc comment (`#![doc = include_str!("../README.md")]`), so `cargo test --doc` runs this exact block ### Java **Source:** `libtmux-workspace/README.md`, "What you get back" **In this page:** hand-quoted **Checked by:** every Java fence in the module's README is compiled and run against real tmux by `docs-tests` ### .NET **Source:** `src/LibTmux.Workspace/README.md` **In this page:** hand-quoted **Checked by:** one of the READMEs and docs `ReadmeExampleTests` compiles and runs against real tmux ### C++ **Source:** `examples/workspace/` (a consumer, not a library API) **In this page:** prose only **Checked by:** `examples/workspace/tests/` runs the consumer suite against real tmux; `ctest -R consumer.workspace` selects it. It exercises the example's own types, not a published `libtmux` API ### Swift **Source:** `Examples/Sources/ExampleCode/Workspaces.swift` **In this page:** read whole from the file **Checked by:** matched against the README's "Workspaces, from a file or from Swift" section by `Scripts/check_examples.py`; compiled and run by `swift test --package-path Examples` ### Source inclusion Rust, Java, and .NET use copied excerpts from their README examples. The source details above identify those files and their checks. --- # Workspace example gallery Source: https://libtmux.org/en/ts/latest/workspace/examples/gallery/ > Pinned YAML examples, JSON counterparts, and execution prerequisites. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. The examples below reproduce the pinned tmuxp YAML fixture corpus. Each record links to its source and, where present, its JSON twin. They illustrate configuration features; they are not all self-contained runnable projects. The `minimal` fixture and several other files omit window names despite the validator requiring them. Treat these as normalization and compatibility probes, not a promise that every fixture passes every load path. Where this port's native parser accepts a fixture below, that establishes only that its document parses, not that it fully executes. The [installation walkthrough](../../guides/installation/) supplies a complete runnable starting file. ## 2-pane-synchronized [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/2-pane-synchronized.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/2-pane-synchronized.json). ```yaml session_name: 2-pane-synchronized windows: - window_name: Two synchronized panes panes: - ssh server1 - ssh server2 options_after: synchronize-panes: on ``` ## 2-pane-vertical [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/2-pane-vertical.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/2-pane-vertical.json). ```yaml session_name: 2-pane-vertical windows: - window_name: my test window panes: - echo hello - echo hello ``` ## 3-pane [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/3-pane.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/3-pane.json). ```yaml session_name: 3-panes windows: - window_name: dev window layout: main-vertical shell_command_before: - cd ~/ panes: - shell_command: - cd /var/log - ls -al | grep \.log - echo hello - echo hello ``` ## 4-pane [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/4-pane.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/4-pane.json). ```yaml session_name: 4-pane-split windows: - window_name: dev window layout: tiled shell_command_before: - cd ~/ panes: - shell_command: - cd /var/log - ls -al | grep \.log - echo hello - echo hello - echo hello ``` ## blank-panes [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/blank-panes.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/blank-panes.json). ```yaml session_name: Blank pane test windows: # Emptiness will simply open a blank pane, if no shell_command_before. # All these are equivalent - window_name: Blank pane test panes: - - pane - blank - window_name: More blank panes panes: - null - shell_command: - shell_command: - # an empty string will be treated as a carriage return - window_name: Empty string (return) panes: - "" - shell_command: "" - shell_command: - "" # a pane can have other options but still be blank - window_name: Blank with options panes: - focus: true - start_directory: /tmp ``` ## env-variables [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/env-variables.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/env-variables.json). ```yaml start_directory: "${PWD}/test" shell_command_before: "echo ${PWD}" before_script: "${MY_ENV_VAR}/test3.sh" session_name: session - ${USER} (${MY_ENV_VAR}) windows: - window_name: editor panes: - shell_command: - tail -F /var/log/syslog start_directory: /var/log - window_name: logging for ${USER} options: automatic-rename: true panes: - shell_command: - htop - ls $PWD ``` ## focus-window-and-panes [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/focus-window-and-panes.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/focus-window-and-panes.json). ```yaml session_name: focus windows: - window_name: attached window focus: true panes: - shell_command: - echo hello - echo 'this pane should be selected on load' focus: true - shell_command: - cd /var/log - echo hello - window_name: second window shell_command_before: cd /var/log panes: - pane - shell_command: - echo 'this pane should be focused, when window switched to first time' focus: true - pane ``` ## main-pane-height-percentage [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/main-pane-height-percentage.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/main-pane-height-percentage.json). ```yaml session_name: main-pane-height start_directory: "~" windows: - layout: main-horizontal options: main-pane-height: 67% panes: - shell_command: - top start_directory: "~" - shell_command: - echo "hey" - shell_command: - echo "moo" window_name: my window name ``` ## main-pane-height [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/main-pane-height.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/main-pane-height.json). ```yaml session_name: main-pane-height start_directory: "~" windows: - layout: main-horizontal options: main-pane-height: 30 panes: - shell_command: - top start_directory: "~" - shell_command: - echo "hey" - shell_command: - echo "moo" window_name: my window name ``` ## minimal [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/minimal.yaml). ```yaml session_name: My tmux session windows: - panes: - ``` ## options [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/options.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/options.json). ```yaml session_name: test window options start_directory: "~" global_options: default-shell: /bin/sh default-command: /bin/sh options: main-pane-height: ${MAIN_PANE_HEIGHT} # works with env variables windows: - layout: main-horizontal options: automatic-rename: on panes: - shell_command: - man echo start_directory: "~" - shell_command: - echo "hey" - shell_command: - echo "moo" ``` ## pane-shell [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/pane-shell.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/pane-shell.json). ```yaml session_name: Pane shell example windows: - window_name: first window_shell: /usr/bin/python2 layout: even-vertical suppress_history: false options: remain-on-exit: true panes: - shell: /usr/bin/python3 shell_command: - print('This is python 3') - shell: /usr/bin/vim -u none shell_command: - iAll panes have the `remain-on-exit` setting on. - When you exit out of the shell or application, the panes will remain. - Use tmux command `:kill-pane` to remove the pane. - Use tmux command `:respawn-pane` to restart the shell in the pane. - Use and then `:q!` to get out of this vim window. :-) - shell_command: - print('Hello World 2') - shell: /usr/bin/top ``` ## plugin-system [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/plugin-system.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/plugin-system.json). ```yaml session_name: plugin-system plugins: - "tmuxp_plugin_extended_build.plugin.PluginExtendedBuild" windows: - window_name: editor layout: tiled shell_command_before: - cd ~/ panes: - shell_command: - cd /var/log - ls -al | grep *.log - echo "hello world" ``` ## session-environment [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/session-environment.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/session-environment.json). ```yaml session_name: Environment variables test environment: EDITOR: /usr/bin/vim DJANGO_SETTINGS_MODULE: my_app.settings.local SERVER_PORT: "8009" windows: - window_name: Django project panes: - ./manage.py runserver 0.0.0.0:${SERVER_PORT} - window_name: Another Django project environment: DJANGO_SETTINGS_MODULE: my_app.settings.local SERVER_PORT: "8010" panes: - ./manage.py runserver 0.0.0.0:${SERVER_PORT} - environment: DJANGO_SETTINGS_MODULE: my_app.settings.local-testing SERVER_PORT: "8011" shell_command: ./manage.py runserver 0.0.0.0:${SERVER_PORT} ``` ## shorthands [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/shorthands.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/shorthands.json). ```yaml session_name: shorthands windows: - window_name: long form panes: - shell_command: - echo 'did you know' - echo 'you can inline' - shell_command: echo 'single commands' - echo 'for panes' ``` ## skip-send-pane-level [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/skip-send-pane-level.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/skip-send-pane-level.json). ```yaml session_name: Skip command execution (pane-level) windows: - panes: - shell_command: echo "___$((1 + 3))___" enter: false - shell_command: - echo "___$((1 + 3))___"\; - echo "___$((1 + 3))___" enter: false ``` ## skip-send [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/skip-send.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/skip-send.json). ```yaml session_name: Skip command execution (command-level) windows: - panes: - shell_command: # You can see this - echo "___$((11 + 1))___" # This is skipped - cmd: echo "___$((1 + 3))___" enter: false ``` ## sleep-pane-level [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/sleep-pane-level.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/sleep-pane-level.json). ```yaml session_name: Pause / skip command execution (pane-level) windows: - panes: - # Wait 2 seconds before sending all commands in this pane sleep_before: 2 shell_command: - echo "___$((11 + 1))___" - cmd: echo "___$((1 + 3))___" - cmd: echo "___$((1 + 3))___" - cmd: echo "Stuff rendering here!" - cmd: echo "2 seconds later" ``` ## sleep-virtualenv [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/sleep-virtualenv.yaml). ```yaml session_name: virtualenv shell_command_before: # - cmd: source $(poetry env info --path)/bin/activate # - cmd: source `pipenv --venv`/bin/activate - cmd: source .venv/bin/activate sleep_before: 1 sleep_after: 1 windows: - panes: - shell_command: - ./manage.py runserver ``` ## sleep [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/sleep.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/sleep.json). ```yaml session_name: Pause / skip command execution (command-level) windows: - panes: - shell_command: # Executes immediately - echo "___$((11 + 1))___" # Delays before sending 2 seconds - cmd: echo "___$((1 + 3))___" sleep_before: 2 # Executes immediately - cmd: echo "___$((1 + 3))___" # Pauses 2 seconds after - cmd: echo "Stuff rendering here!" sleep_after: 2 # Executes after earlier commands (after 2 sec) - cmd: echo "2 seconds later" ``` ## start-directory [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/start-directory.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/start-directory.json). ```yaml session_name: start directory start_directory: /var/ windows: - window_name: should be /var/ panes: - shell_command: - echo "\033c - it trickles down from session-level" - echo hello - window_name: should be /var/log start_directory: log panes: - shell_command: - echo '\033c - window start_directory concatenates to session start_directory - if it is not absolute' - echo hello - window_name: should be ~ start_directory: "~" panes: - shell_command: - 'echo \\033c ~ has precedence. note: remember to quote ~ in YAML' - echo hello - window_name: should be /bin start_directory: /bin panes: - echo '\033c absolute paths also have precedence.' - echo hello - window_name: should be workspace file's dir start_directory: ./ panes: - shell_command: - echo '\033c - ./ is relative to workspace file location - ../ will be parent of workspace file - ./test will be \"test\" dir inside dir of workspace file' - shell_command: - echo '\033c - This way you can load up workspaces from projects and maintain - relative paths.' ``` ## suppress-history [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/suppress-history.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/suppress-history.json). ```yaml session_name: suppress suppress_history: false windows: - window_name: appended focus: true suppress_history: false panes: - echo "window in the history!" - window_name: suppressed suppress_history: true panes: - echo "window not in the history!" - window_name: default panes: - echo "session in the history!" - window_name: mixed suppress_history: false panes: - shell_command: - echo "command in the history!" suppress_history: false - shell_command: - echo "command not in the history!" suppress_history: true - shell_command: - echo "window in the history!" ``` ## window-index [YAML source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/window-index.yaml); [JSON source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/examples/window-index.json). ```yaml session_name: Window index example windows: - window_name: zero panes: - echo "this window's index will be zero" - window_name: five panes: - echo "this window's index will be five" window_index: 5 - window_name: one panes: - echo "this window's index will be one" ``` ## Prerequisites and portability SSH examples need the named hosts. Django and virtualenv examples need their project and environment. The plugin example needs its named Python plugin. Shell paths, log directories, top, `htop`, and editors are host-specific. The pane-shell fixture includes an obsolete Python 2 path; it is retained as upstream evidence, not recommended installation guidance. Review [configuration](../../configuration/), [commands](../../configuration/commands/), and [compatibility](../../reference/compatibility/) before adapting these fixtures. [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Guides Source: https://libtmux.org/en/ts/latest/guides/ > Task-oriented walkthroughs that sit between the concepts and each port's own API reference. Use these guides to connect to tmux, send input, capture output, query objects, and test your program. [Concepts](/concepts/) explains the object model and transport choices. - **[Getting started](getting-started/)**: install tmux, choose a port, and run an example. - **[Attaching to tmux](attaching-to-tmux/)**: select a socket and find or create a session. - **[Sending keys](sending-keys/)**: send literal text, named keys, and Enter. - **[Capturing output](capturing-output/)**: read the screen or scrollback and wait for a result. - **[Filtering and querying, in practice](querying-and-filtering/)**: apply the lookup contracts from [Filtering and queries](/concepts/queries/). - **[Testing with libtmux](testing-with-libtmux/)**: use isolated tmux servers and manage test cleanup. [Examples](/examples/) provides source-backed programs for the same tasks, with source and validation details on each page. --- # Getting started Source: https://libtmux.org/en/ts/latest/guides/getting-started/ > Install tmux, pick a port, and run the smallest thing that proves your setup works. ## Install tmux The common tmux baseline documented here is 3.2a. Individual features can require a newer release; check your port's compatibility notes. Confirm your installed version: ```console $ tmux -V ``` If `tmux` is missing or older than 3.2a, install a supported version with your platform's package manager. libtmux uses an installed tmux executable. ## Pick a port Choose the port for your project's language: [Python](/py/), [TypeScript](/ts/), [Rust](/rs/), [Go](/go/), [Java and Kotlin](/java/), [.NET](/dotnet/), [C++](/cxx/), or [Swift](/swift/). [Server, session, window, pane](/concepts/server-session-window-pane/) explains the shared model, and [Control mode vs one-shot](/concepts/transports/) covers transport differences. For a prerelease package, pin an exact version and check its release notes before upgrading. API availability and defaults can differ between ports. ## Run the smallest thing that proves it works Start a tmux session to connect to: in one terminal: ```console $ tmux new-session -s foo -n bar ``` In a second terminal, install your port's package and run its example. The Python example uses the `foo` session above; the other examples create their own sessions. Installation commands appear in comments at the start of each block. [Attach and send keys](/examples/attach-and-send-keys/) provides the full examples, their source files, and their validation details. ```typescript // bun add libtmux import { Server } from "libtmux"; const server = new Server(); const session = await server.newSession({ name: "work" }); const editor = await session.newWindow({ name: "editor" }); await editor.split(); await editor.panes.at(0)?.sendKeys("echo hello"); const lines = await editor.panes.at(0)?.capture(); ``` ## What just happened A server handle targets tmux without taking over your terminal. Creating a session starts the server if needed. Sending keys writes input to a pane; the method's Enter and literal-text options control how tmux interprets it. [Sending keys](../sending-keys/) explains those defaults. Capture methods read the pane's screen or a requested scrollback range. ## Where to go next - [Concepts](/concepts/) for the mental model behind what you just did: the object hierarchy, how commands actually reach tmux, and how filtering works once you have more than one session to choose from. - [Attaching to tmux](../attaching-to-tmux/), [Sending keys](../sending-keys/), and [Capturing output](../capturing-output/) go one level deeper into each half of the round trip you just ran. - [Attach and send keys](/examples/attach-and-send-keys/) for the fully checked version of every block above, and how each one is verified. - Your port's own API reference (via the port switcher) once you're ready to look up method details. --- # Attaching to tmux Source: https://libtmux.org/en/ts/latest/guides/attaching-to-tmux/ > What a plain constructor call actually connects to, and how to find a session that might already exist instead of always creating a new one. Obtain a server and session handle to control tmux from your program. Your process keeps its own stdin and stdout, and tmux continues running independently. [Attach and send keys](/examples/attach-and-send-keys/) demonstrates this workflow. Attaching your terminal is a separate operation. Python's `Session.attach()` runs `tmux attach-session` and hands the terminal to tmux. [tmuxp](https://tmuxp.git-pull.com/) uses it after building a workspace. Check your port's reference if your program needs to hand over the terminal. ## Which socket a bare constructor reaches Use an explicit socket when several tmux servers may be running. The examples below show each constructor's defaults and environment-aware alternatives. To locate the server from inside a pane, use the port's environment lookup API for `TMUX` and `TMUX_PANE`. ```typescript // Behaves like the other ports' bare form. Naming a socket wasn't confirmed // against a checked snippet for this page: check the port's own reference. const server = new Server(); ``` Sources: Python's `Server.from_env()` is doctested in `src/libtmux/server.py` (`pyproject.toml` `testpaths`). .NET's resolution order and `FromEnvironment` are from `src/LibTmux/README.md` ("Where a bare connect lands"), one of the nine documents `ReadmeExampleTests` compiles and runs. Go's is `tmux/server_options.go`'s doc comments. Rust's fallback is `crates/libtmux/examples/find.rs`, run via `cargo run --example find`. C++'s four constructors are the README's own description of `Server`, at the top of "What is libtmux?". Swift's is `README.md`'s top-level quickstart, matched against `Examples/Sources/QuickStart/main.swift` by `Scripts/check_examples.py`. Java's is `README.md`, run by [`docs-tests`](../testing-with-libtmux/#java-docs-tests). ## Finding a session instead of always creating one A script that runs more than once usually wants "attach if a session by this name already exists, create it otherwise," not a fresh session every time. ```typescript const session = snapshot.sessions.where({ name: "demo" }).oneOrUndefined(); ``` Sources: Go uses `examples/filter-query/main.go`, region `docs:query-in-tmux`. Java uses `examples/.../BuildAWorkspace.java`, run by the examples module's tests. For exactly-one lookup semantics, see [Filtering and querying, in practice](../querying-and-filtering/). Swift uses `Examples/Sources/ExampleCode/Querying.swift`, checked against the README by `Scripts/check_examples.py` and exercised by `Examples/Tests/ExampleTests/ModeTests.swift`. .NET exposes `Server.HasSessionAsync(name)` in `src/LibTmux/Server.Lifecycle.cs`. Its `CreateSessionAsync` supports `ReplaceExisting`, which kills and recreates the session. Rust and C++ provide query APIs for session lookup. See [Filtering and querying, in practice](../querying-and-filtering/) and the port references for the call signatures; this page has no tested excerpt for those combinations. ## Where to go next - [Sending keys](../sending-keys/) and [Capturing output](../capturing-output/) pick up once you have a pane handle. - [Attach and send keys](/examples/attach-and-send-keys/) has the full, sourced code for the round trip this guide assumes. - [Testing with libtmux](../testing-with-libtmux/) if the server you want to attach to is one your own test suite should own and tear down. --- # Sending keys Source: https://libtmux.org/en/ts/latest/guides/sending-keys/ > Literal text versus tmux key names, whether Enter is pressed for you, and why a command can outrun the shell about to run it. Send literal text to type characters into a pane, or send tmux key names such as `C-c`, `Enter`, and `Up` to press those keys. Check the method's literal-text and Enter defaults: typing the word `Enter` and pressing Enter are different operations. ## Literal text, key names, and whether Enter follows These examples show each port's text, named-key, and Enter behavior. [Attach and send keys](/examples/attach-and-send-keys/) provides the full source examples and validation details. ```typescript // literal: true reads the text as characters even when it could be read as // a tmux key name. sendKeys presses Enter unless you say otherwise. await pane.sendKeys("q", { enter: false, literal: true }); ``` Check each method's input contract before sending text that could be a key name. Some ports separate text and key-name methods; others use a literal-text flag. [Concepts](/concepts/) introduces the shared tmux model. ## The race you can't see from the call site Completing `send-keys` means tmux accepted the input. The shell may still be starting, and the command may still be running. The port examples provide different ways to wait: - **Rust** uses a `retry_until` loop in the README's query example to wait for the shell. - **Go** provides `tmuxtest.WaitForShellReady` for tests that need a ready shell. - **.NET** demonstrates waiting for command output in the README's "Running something, and reading it back" section. Wait for shell readiness before sending input when startup matters. Then wait for the command's expected output or a completion signal before reading its result. The next guide covers those waiting APIs. ## Where to go next - [Capturing output](../capturing-output/): reading back what you just sent, and waiting for it correctly instead of guessing a delay. - [Attach and send keys](/examples/attach-and-send-keys/): the full sourced round trip this guide picks apart piece by piece. --- # Capturing output Source: https://libtmux.org/en/ts/latest/guides/capturing-output/ > Read a pane's screen or scrollback and wait for output or a completion signal. Capture a pane to read its visible screen or scrollback. After [Sending keys](../sending-keys/), wait for the expected output or a completion signal before reading the result. ## Visible pane vs. scrollback `tmux capture-pane` distinguishes the currently visible screen from the scrollback history above it, and every port exposes that split rather than flattening it: ```typescript // start counts back from the visible top, so -100 asks for the last // hundred lines or as many as exist. const lines = await pane.capture({ start: -100 }); ``` Java's `pane.capture()` and .NET's `pane.CaptureAsync()` return the visible pane as a list of lines; neither's own README shows a scrollback option as of this page, so check the port's reference before assuming one exists. Sources: TypeScript's is `examples/capture/capture.ts`, run by `bun test examples/capture`. Go's is `examples/quickstart/main.go`. Rust's is `crates/libtmux/README.md`'s capability table, doctested via `#![doc = include_str!("../README.md")]`. C++'s is `README.md`'s "Read a pane" section, quoted verbatim from `examples/05-readme.cpp`'s `capture` region and checked by `tools/docs/check_readme.py`. Swift's is `README.md`, "Change what is there." ## Wait for the expected text An immediate capture can race the shell, as [Sending keys](../sending-keys/#the-race-you-cant-see-from-the-call-site) explains. Wait for the expected text with a timeout so your program stops promptly when the output arrives and reports a failure if it never does: ```typescript // No fixed-poll helper: subscribe to the event stream *before* sending, // then wait for the specific event, so a marker printed between the two // calls is never missed. const found = live.subscribe().find( (event) => event.kind === "output" && event.paneId === pane.id && event.data.includes(marker), { timeoutMs: 30_000 }, ); await pane.sendKeys(command); await found; ``` Python's pytest plugin supplies isolated test servers; see [Testing with libtmux](../testing-with-libtmux/). For Python and C++ signal-based waiting, use the `wait-for` APIs below. This page does not include a wait-for-text helper for those ports. Sources: Go's is `tmux/tmuxtest/screen.go`, quoted in `README.md`'s "Testing your own code" section. Rust's is `crates/libtmux/README.md`, doctested. TypeScript's is `examples/agent/agent.ts`, run by the integration suite and quoted in `packages/libtmux/README.md` (``). Java's is `examples/.../WatchPaneOutput.java`. .NET's is `README.md`, one of the `csharp run` blocks `ReadmeExampleTests` runs. Swift's is `Examples/Sources/ExampleCode/Waiting.swift`, matched against `` and the README by `Scripts/check_examples.py`; the same file's `server.capture(pane, since: mark)`, called in a loop with the cursor it returns, is the "watch as it prints" shape for output too large or too open-ended to wait on a single pattern. ## When the pane can announce itself: `wait-for`, not scraping If you control the command, have it signal completion with `tmux wait-for -S done`. Wait on the same channel to avoid matching screen text: The Python example is a doctest in `src/libtmux/server.py`. C++'s `Server::wait_for(channel, timeout)`, declared in `include/libtmux/server.hpp`, also detects a server that dies during the wait. Swift's `server.wait(for:)` example waits for a build to signal completion: Source: `Examples/Sources/ExampleCode/Waiting.swift`. Rust's `crates/libtmux/README.md` documents the same pattern under "tmux keeps a signal nobody is waiting on, so the job finishing first does not lose the race, and nothing polls," runnable as `examples/orchestrate.rs`. ## Where to go next - [Filtering and querying, in practice](../querying-and-filtering/): once you're reading more than one pane, finding the right one to capture. - [Testing with libtmux](../testing-with-libtmux/): the isolated-server fixtures that make waiting on real tmux practical inside a test suite. - [Capture pane output](/examples/capture-pane-output/): the full sourced code for the patterns above. --- # Filtering and querying, in practice Source: https://libtmux.org/en/ts/latest/guides/querying-and-filtering/ > Filter tmux objects, require one match, and choose where a query runs. Find sessions, windows, or panes with collection filters and exactly-one lookups. [Filtering and queries](/concepts/queries/) explains the result-count contracts and the choice between local and tmux-side filtering. This guide adds examples for common queries. ## Filling in the rest of the cardinality table | Port | Collection filter | Exactly-one | Empty | Several | |------|--------------------|--------------|-------|---------| | Go | `tmuxq.Where(values, predicate)` | `tmuxq.ExactlyOne(values, predicate)` | `tmuxq.ErrNoMatch` | `tmuxq.ErrMultipleMatches` | | Rust | `.iter().matching(&expr)` | `.exactly_one()` | prints via the error's `Display` | same, one error type covers both | | C++ | pipe a range into [`libtmux::matching(expr)`](/cxx/latest/reference/libtmux-matching/) | `libtmux::exactly_one(range)` | `.error()` says which way it went wrong | same call, same error type | Go's `ExampleExactlyOne` in `tmuxq/example_test.go` checks the result with `go test` and `// Output:` assertions: Rust's is `examples/find.rs`, run via `cargo run --example find`: C++'s is quoted straight from `examples/05-readme.cpp`'s `cardinality` region into `README.md`, and `tools/docs/check_readme.py` fails the build if the two ever disagree: For .NET and Swift result-count handling, consult the port reference. The examples here demonstrate .NET's `IEnumerable.Matching(expression)` returning an `IReadOnlyList` and Swift's `hasSession(_:)` returning a `Bool`. The latter checks existence; see [Attaching to tmux](../attaching-to-tmux/#finding-a-session-instead-of-always-creating-one). ## Declarative filters that travel, beyond Python and TypeScript [Filtering and queries](/concepts/queries/) covers Python's `.filter()` lookups and TypeScript's `.where()` documents. Two more ports build the same "a query is data, not code" idea, verified against their own README: Sources: .NET's is `src/LibTmux/README.md`, "Filtering." Swift's is `Examples/Sources/ExampleCode/Filtering.swift`, matched against the README by `Scripts/check_examples.py`. ## Case-insensitive matching ```typescript // mode: "insensitive" on the comparison, rather than a separate lookup name. snapshot.sessions.where({ name: { contains: "API", mode: "insensitive" } }); ``` For case-insensitive matching in Java, .NET, Go, Rust, and C++, consult the port reference. Sources for the examples above: Python's lookup is covered in [Filtering and queries](../../concepts/queries/); TypeScript's is in `README.md`, "What querying looks like"; Swift's is in `Examples/Sources/ExampleCode/Filtering.swift`. ## Push the filter into tmux, or read once and filter locally Use a tmux-side filter to reduce the rows returned, or query a snapshot when you need several answers from one read. [Filtering and queries](/concepts/queries/) compares Python's `search_sessions()` with `.filter()`, and Go's `SearchPanes` with a snapshot plus `tmuxq.Where`. Check the required tmux version. Unknown format tokens expand to empty values, so validate an unexpectedly empty search before concluding that no objects match. ## Where to go next - [Attach and send keys](/examples/attach-and-send-keys/): its "Finding an existing session instead" section is this guide's recipes applied to one concrete lookup. - [Testing with libtmux](../testing-with-libtmux/): most of the fixtures there hand you a server with exactly one thing on it, which is precisely when an exactly-one query is the right tool instead of a filter you then index into. --- # Testing with libtmux Source: https://libtmux.org/en/ts/latest/guides/testing-with-libtmux/ > Every port ships a way to give your own tests a real, disposable tmux server. What each one hands you, and what it guarantees about cleanup. Use a private tmux server to test code that creates sessions, sends input, or captures output. The fixtures below allocate a separate socket and manage normal test cleanup. Cleanup after abrupt process termination depends on the fixture; Java's stale-server cleanup is described below. Request the fixture to obtain its server. Python's `session` fixture depends on `server`, so requesting a session also creates an isolated server: That exact block is a doctest in `src/libtmux/pytest_plugin.py`, checked by running it as a nested pytest run and asserting it passes. `session_params` overrides how the fixture builds a session (window size, for instance) without forking it; a temporary `HOME` and tmux config keep window and pane indices stable across machines, so an assertion like `window_name == "test"` doesn't depend on whatever `.tmux.conf` the test runner happens to have. `tmuxtest.NewServer(ctx, t)` captures the environment and working directory, resolves the tmux executable, and creates a server on its own socket. Construction can return an error. Test cleanup kills the server, and wait failures include the last captured screen. Source: `README.md`, "Testing your own code," backed by `tmux/tmuxtest/`. Enable the `test-support` feature in a dev-dependency. The crate README uses these guards in doctests through `#![doc = include_str!("../README.md")]`. `libtmux::test::retry_until(deadline, condition)` polls an arbitrary async condition; `Pane::wait_for_text` waits specifically for pane text. See [Capturing output](../capturing-output/). `libtmux-junit5` supplies each test with a running `Server` containing a session named `libtmux`. Request `TmuxSocketPath` when your code takes a socket path. Fixtures live in JUnit's per-test extension store. A shutdown hook kills servers owned by that JVM, and startup cleanup removes servers left by JVMs that have exited. Source: `libtmux-junit5/README.md`. The port's `docs-tests` module compiles Java fences from READMEs and guides, then runs them against `libtmux-junit5` servers. A `` directive can instead require a named exception, a compile failure, or an explicit skip reason. Source: `docs-tests/README.md`. `LibTmux.Testing` ships as a separate package, under `src/LibTmux.Testing/`. `await using` disposes the scope and kills its server when the block exits. Use `TmuxWait.UntilAsync` to wait for expected state; see [Capturing output](../capturing-output/). Source: `README.md`, "Testing your own code," exercised by `ReadmeExampleTests`. The example comes from `README.md`, "Testing your own tmux tools," and is checked against the `fixture` region in `examples/05-readme.cpp` by `tools/docs/check_readme.py`. Enable the `testing` CMake component with `find_package(libtmux COMPONENTS testing)`. It creates a private socket and temporary directory, sets `TMUX_TMPDIR`, and removes `TMUX` and `TMUX_PANE` from the child environment. `SocketNamespace::consumer(...)` labels sockets with the consumer suite's name. `examples/tests/README.md` shows use from outside the library's build tree. `TmuxFixture` is a separate package product. It starts a server with a bootstrap session and limits concurrent fixtures to reduce process and pseudo-terminal exhaustion. `LIBTMUX_TMUX_BIN` selects the executable; otherwise it checks installed locations. Source: `Tests/TmuxFixture/README.md`. TypeScript's harness at `packages/libtmux/src/_internal/test/testkit.ts` is internal and unpublished. For external tests, create an isolated `Server` and manage its cleanup in your test framework. ## Where to go next - [Capturing output](../capturing-output/): the wait helpers most of these fixtures are meant to be used alongside, instead of a fixed `sleep` in a test. - [Attach and send keys](/examples/attach-and-send-keys/) and [Capture pane output](/examples/capture-pane-output/): the same operations these fixtures give you a server to run, shown as tested examples in their own right. --- # Install and load a workspace Source: https://libtmux.org/en/ts/latest/workspace/guides/installation/ > Install the prerelease TypeScript workspace CLI from npm and load a session on a private tmux socket. Install and run the native TypeScript `tmux-workspace` command from its npm prerelease, `@libtmux/workspace-cli`. **This is a partial, prerelease implementation.** ## Install from npm Use a Unix environment with tmux 3.2a or newer on `PATH` for this walkthrough. The command runs on Node.js 22.12 or newer, or Bun 1.3.14 or newer. Install it globally with npm: ```console $ npm install -g @libtmux/workspace-cli ``` The package puts `tmux-workspace` on `PATH`. To run it without installing, use `npx -y @libtmux/workspace-cli` wherever this walkthrough runs `tmux-workspace`. The [overview](../../) lists the Bun, pnpm and Yarn runners and global installs. Inspect the installed command: ```console $ tmux-workspace --help ``` ## Create the input Keep this shell open for the walkthrough. Create a temporary directory for its configuration and private tmux socket: ```console $ WORKSPACE_TMP="$(mktemp -d)" ``` Write a minimal configuration with two blank shell panes to [`workspace.yaml`](./#create-the-input) inside that directory: ```console $ cat > "$WORKSPACE_TMP/workspace.yaml" <<'YAML' session_name: workspace-guide windows: - window_name: editor layout: even-horizontal panes: [null, null] YAML ``` ## Load and inspect Load detached on the temporary socket. The JSON result describes the load; `-d` prevents terminal attachment: ```console $ tmux-workspace load \ -S "$WORKSPACE_TMP/tmux.sock" \ -d \ --json \ "$WORKSPACE_TMP/workspace.yaml" ``` Inspect the two panes through the same endpoint: ```console $ tmux \ -S "$WORKSPACE_TMP/tmux.sock" \ list-panes \ -t '=workspace-guide:editor' ``` Attach with tmux when ready: ```console $ tmux \ -S "$WORKSPACE_TMP/tmux.sock" \ attach-session \ -t '=workspace-guide' ``` Detach with your configured tmux detach binding. Capture the live session without choosing a file destination: ```console $ tmux-workspace freeze \ -S "$WORKSPACE_TMP/tmux.sock" \ --json \ workspace-guide ``` Capture reports recoverable live state. It cannot reconstruct the original command history, script or plugin definitions. Remove the walkthrough session when finished: ```console $ tmux \ -S "$WORKSPACE_TMP/tmux.sock" \ kill-session \ -t '=workspace-guide' ``` The configuration remains in the temporary directory until you remove it. Every tmux command above addresses that private socket. ## Current limits Interactive prompts and complete configuration/platform acceptance remain unfinished. Capture reports live state rather than recovering the original workspace commands or extension intent. Python shell evaluation and explicit plugin/custom-builder loads require an interpreter with tmuxp 1.74.0 installed. Select it with `TMUX_WORKSPACE_PYTHON`; the default is `python3`. Install extension packages in that interpreter or provide `workspace_builder_paths` relative to the workspace file. See [hooks and builders](../../configuration/hooks/) for append and failure behavior. Ordinary native loading of this example does not require Python. ## Python alternative For the separate released tmuxp application, install its isolated Python tool environment with uv: ```console $ uv tool install tmuxp ``` Follow the [Python installation guide](/py/latest/workspace/guides/installation/) for that workflow. Installing tmuxp does not install the native command. ## Continue [Discovery](../discovery/), [configuration](../../configuration/) and the [load reference](../../cli/load/) explain the tmuxp compatibility model. Compare those references with the local command's help and the limits above. [Export and reload](../export-session/) explains the capture workflow, and the [compatibility reference](../../reference/compatibility/) records builder gaps. Use [Internals](../../internals/) for the library and consumer APIs. [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Find saved workspaces Source: https://libtmux.org/en/ts/latest/workspace/guides/discovery/ > Resolve project files, explicit paths, and global workspace names. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Use an explicit YAML or JSON file path when you need an unambiguous input. A directory resolves its project configuration, and a saved name resolves through the tmuxp configuration roots. ## Global directories An existing `TMUXP_CONFIGDIR` takes precedence, followed by the XDG configuration directory and then the legacy [`~/.tmuxp`](./) directory. A nonexistent explicit directory does not automatically win discovery. See [environment](../../configuration/environment/) for the relevant variables. ## Project files Project discovery walks from the current directory toward its ancestors, stopping at home or the filesystem root. It selects at most one candidate per directory, preferring `.tmuxp.yaml`, then [`.tmuxp.yml`](./), then [`.tmuxp.json`](./). Nearer directories come first. It does not recursively enumerate child projects. Load the current project's configuration: ```console $ tmuxp load . ``` The normal command can attach or prompt. Use `-d` when you want detached execution and select `-L` or `-S` for an isolated server. [ls](../../cli/ls/), [search](../../cli/search/), and [edit](../../cli/edit/) use the same discovery concepts with their own result and error behavior. [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Automate workspace operations Source: https://libtmux.org/en/ts/latest/workspace/guides/automation/ > Python automation and the local native CLI machine protocol. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. For the current Python loader, supply an explicit file, dedicated socket, and `-d` to avoid attachment. `--yes` answers yes/no questions; it is not a universal replacement for missing session, format, or destination choices. ## Read current records ```console $ tmuxp ls --json ``` Validate the JSON object before consuming its `workspaces` array. Python search has different empty-result behavior: no matches can produce no bytes rather than `[]`. See [search](../../cli/search/) before relying on a pipeline. ## Proposed native machine protocol Local native CLIs provide `--json` and `--ndjson`. Implemented services and terminal behavior vary by port. The shared machine contract resolves choices from arguments, avoid implicit stdin prompts, keep diagnostics on stderr, and prevent child output or attachment from corrupting stdout. The operation result must report partial completion instead of claiming rollback. NDJSON load events must be observable while work is still running, followed by exactly one terminal record. A buffered array split into lines after completion does not meet this requirement. Both output flags together select NDJSON. Read [output](../../reference/output/) for stream shapes and [exit behavior](../../reference/exit-codes/) for errors and interruption. [Export and reload](../export-session/) distinguishes document encoding from CLI result encoding. [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Export and reload a session Source: https://libtmux.org/en/ts/latest/workspace/guides/export-session/ > Capture, inspect, and replay a workspace without assuming lossless recovery. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Use the session created by the [installation walkthrough](../installation/). Capture it to a new YAML destination: ```console $ tmuxp freeze \ -L workspace-guide \ --workspace-format yaml \ --save-to captured-workspace.yaml \ --yes \ workspace-guide ``` Review the output file. Capture cannot reconstruct original scripts, shell history, plugin decisions, comments, or every application's state. Compare window and pane topology, directories, layouts, environment, and options explicitly. Replay under a new name on the same dedicated server: ```console $ tmuxp load \ -L workspace-guide \ -d \ -s workspace-replay \ captured-workspace.yaml ``` Inspect the replay: ```console $ tmux -L workspace-guide list-panes -t '=workspace-replay' ``` Remove the replay when finished: ```console $ tmux -L workspace-guide kill-session -t '=workspace-replay' ``` The [freeze reference](../../cli/freeze/) explains prompts and overwrite behavior. [convert](../../cli/convert/) changes document representation without validating native execution support. The [native compatibility page](../../reference/compatibility/) records which ports lack capture or lose fields. [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Troubleshoot workspace loading Source: https://libtmux.org/en/ts/latest/workspace/guides/troubleshooting/ > Diagnose argument, discovery, configuration, shell, and partial-build failures. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../../reference/compatibility/) describes this port's implemented coverage. Start by identifying the failing stage. A parser error happens before a workspace is loaded. A missing file is a discovery problem. An accepted document can still fail during tmux creation, shell startup, command dispatch, or a plugin callback. ## Arguments and files Use the [command reference](../../cli/) for local flag meanings. Place root `--color` and `--log-level` before the command. Keep multiple load filenames together. Both importer children require a source argument. Confirm an explicit file works before investigating saved-name discovery. ## Configuration and commands Check the [configuration reference](../../configuration/) and the [example gallery](../../examples/gallery/). YAML parsing alone does not validate keys or prove builder support. A missing shell executable, directory, plugin package, SSH target, or application can prevent an otherwise valid workspace from behaving as intended. Commands are sent into panes and can require shell readiness. `enter: false` intentionally leaves text unsubmitted. Delays are measured in seconds; explicit zero and omission differ. See [commands](../../configuration/commands/) and [hooks](../../configuration/hooks/). ## Diagnostics and remaining state ```console $ tmuxp debug-info --json ``` Inspect raw tmux values before sharing diagnostics. Use the same `-L` or `-S` endpoint when inspecting a failed load. A partial build can leave sessions, windows, and panes behind; inspect them before cleanup. Do not assume rollback or kill an unrelated default server. Native failures must be evaluated against [port compatibility](../../reference/compatibility/), not inferred from a Python example. The native machine contract reports completed and failed stages explicitly. [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Inspect a workspace through MCP Source: https://libtmux.org/en/ts/latest/workspace/guides/inspect-with-mcp/ > Connect the development TypeScript MCP server to a session loaded by its native workspace CLI. Inspect the session you loaded with the TypeScript workspace CLI by pointing its MCP server at the same tmux socket. The loaded windows and panes are ordinary tmux objects; discovery returns their existing IDs. **This guide uses development `workspace-cli` source.** Continue the [installation walkthrough](../installation/#load-and-inspect) through its detached load, keeping that shell and `WORKSPACE_TMP` available. Leave the `workspace-guide` session running. Build both executables from the same native repository checkout; released package instructions may describe different MCP contracts. ## Build the MCP server Run from the native repository root with the installation walkthrough's toolchain and dependencies. ```console $ bun run --cwd packages/mcp build ``` The installation walkthrough has already installed the frozen dependencies and built the local core package. Keep the MCP output chunks, workspace packages and installed dependencies available to Node. ## Select the same socket Configure an MCP client to launch the following command with the shown environment. The client owns the process's standard input and output for JSON-RPC messages. ```console $ LIBTMUX_TOOLSETS=inspect \ LIBTMUX_SOCKET_PATH="$WORKSPACE_TMP/tmux.sock" \ node packages/mcp/dist/server.js ``` In a client's configuration file, use absolute executable or script paths and expand `WORKSPACE_TMP` to its actual value. Configuration files do not perform shell variable expansion. Retain the environment used to build and run the native executable. For a workspace loaded with `-L NAME`, set `LIBTMUX_SOCKET=NAME` instead of `LIBTMUX_SOCKET_PATH`. Do not set both. If the CLI selected a binary through `TMUX_BIN`, give that same path to MCP through `LIBTMUX_TMUX_BIN`. ## Inspect and wait 1. Discover tools with `tools/list` and read the `tmux://capabilities` resource. Confirm that its resolved endpoint matches the loaded socket. 2. Call [list_sessions][mcp-source], then [list_windows][mcp-source] with `session: "workspace-guide"`, and [list_panes][mcp-source]. Retain the returned session, window and pane IDs. 3. Select one returned pane ID for capture or a bounded text wait. Use the argument names below; discover the full schema before adding options. | Tool | Arguments | | --- | --- | | [capture_pane][mcp-source] | `"paneId"`, `"maxLines"` | | [wait_for_text][mcp-source] | `"paneId"`, `"patterns"`, `"timeoutMs"` in milliseconds | Set `"timeoutMs"` to `10000` for a ten-second wait. A pending wait permits other inspection calls on the same connection. To check that behavior, start a wait for text absent from the pane, then request [list_panes][mcp-source] before its deadline. Client cancellation uses `notifications/cancelled` with the outstanding request ID; the connection remains usable for inspection. Capture omits trailing empty rows. Use [snapshot_pane][mcp-source] for structured pane state and [capture_since][mcp-source] for incremental observation; their budgets and cursor fields are described in the discovered schemas. If discovery does not show `workspace-guide`, compare the resolved socket in capabilities with the CLI's `-S` path. A different socket selects a different daemon even when session names match. ## Close the connection Close the MCP connection's standard input to stop the server and release pending work. This separately loaded workspace remains running. When you finish the walkthrough, remove only its session on the same socket: ```console $ tmux \ -S "$WORKSPACE_TMP/tmux.sock" \ kill-session \ -t '=workspace-guide' ``` The configuration remains in the temporary directory until you remove it. See the verified [native workspace workflow][workspace-source] and [development MCP reference][mcp-source] for this source contract. The site's released MCP pages retain their version-pinned contracts. [workspace-source]: https://github.com/libtmux/libtmux-ts/blob/3db812d1aaca4501e26b1cff7551d28ab1fdf530/packages/workspace-cli/README.md [mcp-source]: https://github.com/libtmux/libtmux-ts/blob/3db812d1aaca4501e26b1cff7551d28ab1fdf530/packages/mcp/README.md --- # TypeScript workspace internals Source: https://libtmux.org/en/ts/latest/workspace/internals/ > Architecture and development interfaces of the TypeScript workspace builder. These pages document the in-development workspace builder for contributors and applications that call its APIs. For workspace loading from a terminal, see [tmuxp](https://tmuxp.git-pull.com/). ## Builder pipeline `parseWorkspace` validates input data. `planWorkspace` inspects the target session and reports proposed topology changes; `applyWorkspace` performs the changes through a caller-owned `Server`. The caller supplies file reading, command-line handling, attachment, and application lifetime. ## Read the implementation - [Guides](./guides/) show builder setup and application code. - [Topics](./topics/) explain configuration, behavior, and failures. - [Examples](./examples/) exercise the builder through the language API. - [API](../reference/) links the configuration and construction interfaces. ## Implementation scope `@libtmux/workspace` applies a declared session layout to a libtmux `Server`. Describe windows, panes, working directories, and shell commands as data. Applying the same description again reuses matching objects. The package manages tmux structure. It does not supervise processes or restart commands that have exited. Commands run only in newly created panes by default. ## Package and runtime Use `@libtmux/workspace` alongside `libtmux`. The published package supports Node and Bun; its YAML convenience parser requires Bun. You can use validated JavaScript objects under either runtime. Real tmux control requires tmux on the host. The package documents Linux as its supported runtime platform. The workspace format uses familiar [tmuxp](https://tmuxp.git-pull.com) field names, with its own validation and convergence rules. Python plugins and tmuxp's configuration search are not part of this package. [Package documentation](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/README.md) --- # TypeScript workspace builder behavior Source: https://libtmux.org/en/ts/latest/workspace/internals/topics/ > Internal configuration, application, and failure contracts of the TypeScript workspace builder. Applying a workspace reconciles the requested structure with a named session. It can create missing objects, rename windows, and remove eligible surplus objects. It returns the resulting `Session`. ## Ownership and shared objects A session created by the package carries the reserved `@libtmux-workspace` option. The default `prune: "owned"` removes surplus only from a session with that ownership stamp. Applying to a session created elsewhere adds the missing structure and preserves its surplus windows and panes. `prune: "never"` disables removals. `prune: "always"` authorizes removals for that call without stamping persistent ownership. Inspect a fresh plan before using that policy on an existing session. Windows match by position rather than numeric tmux index. A linked surplus window is unlinked from this session. Grouped sessions retain surplus windows, and shared windows retain surplus panes, because removing those objects would affect their other placements. ## Commands and options The default `commands: "create-only"` sends setup and pane commands only in panes created by the current apply. `commands: "always"` replays them in reused panes too. Completion means workspace operations finished; it does not mean a long-running command is ready to accept requests. Named options are applied each time. Removing a key from the description does not unset its current tmux value. Working directories inherit from workspace to window to pane, with the most specific value winning. ## Plans and failures `planWorkspace` describes topology changes without applying them. The plan contains object identities and retention reasons; it does not describe option, layout, focus, or command changes. Another tmux client can change the session after planning, so the plan is advisory. `WorkspaceApplyError` reports completed milestones, the failed stage, and the underlying cause. Its `requiresReplan` flag requires a fresh inspection after failure. There is no rollback, and a transport failure may leave command delivery uncertain. Do not infer that replaying the same command is safe from a failed apply alone. [Workspace behavior](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/README.md) --- # Use the TypeScript workspace builder Source: https://libtmux.org/en/ts/latest/workspace/internals/guides/ > Use the in-development TypeScript workspace builder from application code. Apply a workspace to create its session, windows, and panes. Install tmux on the host, then add the workspace package and core library: ```console $ bun add @libtmux/workspace libtmux ``` ## Create a session Save this as `workspace.ts`. It creates a dedicated session with two panes. The `try` / `finally` removes that session after inspecting it, so the example leaves no session running. ```typescript import { Server } from "libtmux"; import { applyWorkspace } from "@libtmux/workspace"; const server = new Server({ socketName: `workspace-guide-${crypto.randomUUID()}`, }); const session = await applyWorkspace(server, { session_name: "workspace-guide", windows: [{ window_name: "editor", panes: ["echo ready", "echo ready"] }], }); try { console.log(session.name); } finally { await session.kill(); } ``` Run it with Bun: ```console $ bun run workspace.ts ``` The example selects a new socket name so it does not reuse a session on your normal tmux server. For a workspace you want to keep, choose the intended server explicitly and retain the returned session. ## Read configuration `parseWorkspace` from `@libtmux/workspace/config` validates an object produced by your chosen JSON or YAML parser. `parseWorkspaceYaml` reads YAML through Bun's parser. Unknown fields fail validation rather than silently changing the requested workspace. Use `planWorkspace` before changing an existing session. Review removal and retention entries, then apply promptly. Replan after any failure or outside change. See [Topics](../topics/) for command replay and pruning policies and [Examples](../examples/) for the package's integration example. [Parsing implementation](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/config.ts); [Application implementation](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/src/builder.ts). --- # TypeScript workspace builder examples Source: https://libtmux.org/en/ts/latest/workspace/internals/examples/ > Internal examples for building and inspecting workspaces through the TypeScript API. The workspace example declares a development session, applies it through `@libtmux/workspace`, and provides cleanup that tolerates an absent session. It also shows the lower-level core API for constructing a session manually. ## Build and remove a workspace The `buildWorkspace` function applies `DEVELOPMENT_WORKSPACE`. Pass it the `Server` you want to use. `removeWorkspace` checks a fresh snapshot before killing the named session and returns whether a session was present. ```typescript file="examples/workspace/workspace.ts" import type { Server } from "libtmux/server"; import type { Session } from "libtmux/session"; import { applyWorkspace } from "@libtmux/workspace"; import type { WorkspaceInput } from "@libtmux/workspace/config"; /** * Build the shape most people reach for tmux to get: one session, a window * per concern, each pane already running the thing it is there for. * * `server.batch` plans several windows and resolves them from one final * snapshot. `buildWorkspace`, below, delegates declared topology to the * workspace package instead of maintaining another reconciler here. */ export async function buildSimpleWorkspace(server: Server): Promise { const built = await server.newSession({ name: "work", shellCommand: "sleep 30", windowName: "editor", }); const [logs, shell] = await server.batch([ built.plan.newWindow({ name: "logs", shellCommand: "tail -f /dev/null" }), built.plan.newWindow({ name: "shell" }), ]); await logs.selectLayout("even-horizontal"); void shell.name; // "shell" return built; } export const DEVELOPMENT_WORKSPACE = { session_name: "workspace-example", windows: [ { panes: ["sleep 30", "sleep 30"], window_name: "editor" }, { panes: ["sleep 30"], window_name: "server" }, { window_name: "logs" }, ], } satisfies WorkspaceInput; /** Apply the package's declarative workspace to a server. */ export function buildWorkspace(server: Server): Promise { return applyWorkspace(server, DEVELOPMENT_WORKSPACE); } /** * Tear a workspace down without caring whether it is there. * * Killing a session that has already gone is not a failure worth propagating, * which is the one case worth handling separately from every other tmux error. */ export async function removeWorkspace(server: Server, name: string): Promise { const found = (await server.snapshot()).sessions.first({ name }); if (found === undefined) return false; await found.kill(); return true; } ``` The `sleep` commands keep panes alive for inspection. They do not represent application readiness checks. Cleanup removes the session and its processes, so use a dedicated server or a session name owned by the example. ## Verification The port's workspace integration suite calls this source against real tmux. From a prepared source checkout, run that suite with: ```console $ bun test examples/workspace ``` The source include keeps this page's code aligned with the example. Rendering the page alone does not execute the integration suite. For a smaller runnable entry point, use the [application guide](../guides/). [Example source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/examples/workspace/workspace.ts) --- # TypeScript workspace builder API Source: https://libtmux.org/en/ts/latest/workspace/reference/ > Internal reference for the TypeScript workspace builder and configuration APIs. Import planning and application from `@libtmux/workspace`, and parsers from `@libtmux/workspace/config`. Application operates on a core libtmux `Server` supplied by the caller; creating that server does not choose a workspace or apply one automatically. ## Parse and validate [`parseWorkspace`](./config-parseworkspace/) validates data already read by your application. [`parseWorkspaceYaml`](./config-parseworkspaceyaml/) adds Bun's YAML parsing. Both produce the workspace configuration consumed by the builder. ## Plan and apply [`planWorkspace`](./builder-planworkspace/) returns a description of membership changes. [`WorkspacePlan`](./planning-workspaceplan/) records creations, removals, renames, and retained surplus. [`applyWorkspace`](./builder-applyworkspace/) performs the work and resolves to the resulting `Session`. Its options control pruning and whether commands run in existing panes. Planning accepts pruning policy; command policy belongs to application. ## Handle failure [`WorkspaceApplyError`](./builder-workspaceapplyerror/) preserves the cause and completed milestones. Reinspect tmux before retrying; the error is not a transaction log of every effect or a receipt for shell commands. The language API builds workspaces directly. Availability through an MCP server is a separate protocol capability; consult this port's [MCP section](../../mcp/) for its advertised tools. [Public exports](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/workspace/package.json) ## API declarations - [builder.applyWorkspace](https://libtmux.org/en/ts/latest/workspace/reference/builder-applyworkspace/) - [operation_options.ApplyWorkspaceOptions](https://libtmux.org/en/ts/latest/workspace/reference/operation_options-applyworkspaceoptions/) - [ownership.claimSession](https://libtmux.org/en/ts/latest/workspace/reference/ownership-claimsession/) - [operation_options.CommandPolicy](https://libtmux.org/en/ts/latest/workspace/reference/operation_options-commandpolicy/) - [config.initialPaneStartDirectory](https://libtmux.org/en/ts/latest/workspace/reference/config-initialpanestartdirectory/) - [ownership.mayPrune](https://libtmux.org/en/ts/latest/workspace/reference/ownership-mayprune/) - [config.optionValue](https://libtmux.org/en/ts/latest/workspace/reference/config-optionvalue/) - [ownership.ownedByWorkspace](https://libtmux.org/en/ts/latest/workspace/reference/ownership-ownedbyworkspace/) - [config.paneCommands](https://libtmux.org/en/ts/latest/workspace/reference/config-panecommands/) - [config.paneStartDirectory](https://libtmux.org/en/ts/latest/workspace/reference/config-panestartdirectory/) - [config.paneWantsFocus](https://libtmux.org/en/ts/latest/workspace/reference/config-panewantsfocus/) - [config.parseWorkspace](https://libtmux.org/en/ts/latest/workspace/reference/config-parseworkspace/) - [config.parseWorkspaceYaml](https://libtmux.org/en/ts/latest/workspace/reference/config-parseworkspaceyaml/) - [builder.planWorkspace](https://libtmux.org/en/ts/latest/workspace/reference/builder-planworkspace/) - [operation_options.PlanWorkspaceOptions](https://libtmux.org/en/ts/latest/workspace/reference/operation_options-planworkspaceoptions/) - [ownership.PrunePolicy](https://libtmux.org/en/ts/latest/workspace/reference/ownership-prunepolicy/) - [config.windowStartDirectory](https://libtmux.org/en/ts/latest/workspace/reference/config-windowstartdirectory/) - [config.Workspace](https://libtmux.org/en/ts/latest/workspace/reference/config-workspace/) - [builder.WorkspaceApplyError](https://libtmux.org/en/ts/latest/workspace/reference/builder-workspaceapplyerror/) - [builder.WorkspaceApplyMilestone](https://libtmux.org/en/ts/latest/workspace/reference/builder-workspaceapplymilestone/) - [builder.WorkspaceApplyStage](https://libtmux.org/en/ts/latest/workspace/reference/builder-workspaceapplystage/) - [config.WorkspaceInput](https://libtmux.org/en/ts/latest/workspace/reference/config-workspaceinput/) - [config.WorkspaceOptionValue](https://libtmux.org/en/ts/latest/workspace/reference/config-workspaceoptionvalue/) - [config.WorkspacePane](https://libtmux.org/en/ts/latest/workspace/reference/config-workspacepane/) - [planning.WorkspacePaneCreation](https://libtmux.org/en/ts/latest/workspace/reference/planning-workspacepanecreation/) - [planning.WorkspacePaneRemoval](https://libtmux.org/en/ts/latest/workspace/reference/planning-workspacepaneremoval/) - [planning.WorkspacePlan](https://libtmux.org/en/ts/latest/workspace/reference/planning-workspaceplan/) - [planning.WorkspaceRetention](https://libtmux.org/en/ts/latest/workspace/reference/planning-workspaceretention/) - [config.WorkspaceWindow](https://libtmux.org/en/ts/latest/workspace/reference/config-workspacewindow/) - [planning.WorkspaceWindowCreation](https://libtmux.org/en/ts/latest/workspace/reference/planning-workspacewindowcreation/) - [config.WorkspaceWindowInput](https://libtmux.org/en/ts/latest/workspace/reference/config-workspacewindowinput/) - [planning.WorkspaceWindowPlacement](https://libtmux.org/en/ts/latest/workspace/reference/planning-workspacewindowplacement/) - [planning.WorkspaceWindowRemoval](https://libtmux.org/en/ts/latest/workspace/reference/planning-workspacewindowremoval/) - [planning.WorkspaceWindowRename](https://libtmux.org/en/ts/latest/workspace/reference/planning-workspacewindowrename/) --- # Workspace reference generation Source: https://libtmux.org/en/ts/latest/workspace/internals/documentation/ > Keep help, completion, and site references aligned with command metadata. The native TypeScript CLI derives help and exports from its parser definitions. [Compatibility](../../reference/compatibility/) records current coverage. ## Command metadata Commander defines the native command graph. The CLI package generates Markdown, a JSON command catalog, and Bash, Zsh, and Fish completion from those definitions. `docs:check` compares generated files in CI. Keep option event order when paired flags share a destination. The [command reference](../../cli/) covers the Python grammar. Native metadata exports need to record command paths, aliases, positional arity, option spellings, types, defaults, choices, required and exclusive groups, store-constant values, repeat and ordering behavior, environment bindings, and child commands. Output schemas and availability belong alongside that metadata. Generate help, completion, and static reference from the same definitions. Compare generated results in CI and test actual installed help separately. A rendered example or successful parser-only probe does not establish service execution, distribution, or tmux compatibility. ## Site integration Keep equivalent task pages at the same workspace path in every port. The existing page switcher offers authored counterparts and leaves absent pages unavailable. A CLI guide and a builder API guide are different counterparts; preserve [builder reference](../../reference/) as its own surface. Write executable shell examples per port. Shared console, YAML, and JSON blocks survive language filtering. Installing a native library must not be presented as installing a workspace executable when it has no such artifact. The site version and workspace package version can differ. Record the package and source revision independently, and do not use an older version-shaped URL to imply that today's command existed in an older release. The installation guide identifies the available source builds and published artifacts. ## Verification Check [configuration](../../configuration/), [machine output](../../reference/output/), and [compatibility](../../reference/compatibility/) together. Exercise equivalent page paths, API backlinks, fragments, sidebars, metadata exports, search, and preview prefixes in assembled output. Verify command examples against isolated tmux servers, with explicit cleanup and known limitations. --- # Third-party notices Source: https://libtmux.org/en/ts/latest/third-party-notices/ > Licences and attribution for the tools that build libtmux.org and the software libtmux depends on. libtmux and this site are built with open-source software. Several of those licences ask that their notice text travel with the work, so it is reproduced here. ## Documentation toolchain | Tool | Licence | Role | |---|---|---| | [Astro](https://astro.build) | MIT | The site shell | | [Tailwind CSS](https://tailwindcss.com) | MIT | Styling | | [Pagefind](https://pagefind.app) | MIT | Site-wide search | | [Expressive Code](https://expressive-code.com) | MIT | Code blocks | | [Sphinx](https://www.sphinx-doc.org) | BSD-2-Clause | Python and C++ reference | | [Furo](https://github.com/pradyunsg/furo) | MIT | Sphinx theme | | [Breathe](https://github.com/breathe-doc/breathe) | BSD-3-Clause | Doxygen XML into Sphinx | | [Doxygen](https://www.doxygen.nl) | GPL-2.0-only | Parses C++ headers to XML | | [API Extractor](https://api-extractor.com) | MIT | TypeScript API model | | [IBM Plex](https://github.com/IBM/plex) | OFL-1.1 | Typeface | ### A note on Doxygen Doxygen is licensed GPL-2.0-only. It runs as a build step that reads libtmux's own headers and emits XML; that XML is rendered by Breathe and Sphinx, and no Doxygen-generated HTML is published. Running a GPL program over your own input does not place its licence on the output, and libtmux does not distribute Doxygen or any modified version of it. ## Reference hosting Three ports deep-link to the canonical host their ecosystem already uses, rather than duplicating it here: - Rust: [docs.rs](https://docs.rs/libtmux) - Go: [pkg.go.dev](https://pkg.go.dev/github.com/libtmux/libtmux-go/tmux) - Java and Kotlin: [javadoc.io](https://javadoc.io/doc/io.github.libtmux/libtmux) Those sites are operated independently of this project and carry their own terms. ## libtmux itself Each port is MIT licensed. See the `LICENSE` file in that port's repository for the authoritative text. --- # Exit codes and errors Source: https://libtmux.org/en/ts/latest/workspace/reference/exit-codes/ > Observed Python exit behavior and the proposed native error contract. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../compatibility/) describes this port's implemented coverage. Python tmuxp uses 0 for success, 1 for general failures, and 2 for argument usage errors. Those categories do not guarantee every current command propagates every underlying error. ## Current exceptions `edit` ignores the editor child's status. Machine `search` can return normally with no output for an invalid regular expression, and with human help for a missing query. Both import children exit 2 when their source argument is missing. See their command pages before treating status alone as proof of success. The root entry point checks for a supported tmux executable before parsing arguments, including help. Missing or unsupported tmux can print a diagnostic and exit with status 0. `tmuxp freeze` also catches a missing-session error, prints it, and returns normally. These are current implementation quirks. ## Proposed native behavior Parse errors return 2 before backend work. Input validation or execution failures return 1. A partial operation includes completed work and the failed stage in its result and returns 1; it must not claim rollback unless rollback occurred. An interruption returns 130 after owned streams and handles are cleaned up, without killing unrelated tmux sessions. Machine errors on stderr are compact JSON objects, one per line. Parse or validation failures before work leave stdout empty. Human errors stay readable text. Stable error codes complement readable messages; native exception objects and stack traces are not the public JSON schema. ## Machine error codes In `--json` and `--ndjson` mode an error record on stderr is `{"schema_version": 1, "code": ..., "message": ...}`, and every entry of a load summary's `errors` array carries the same `code`. The seven native ports report the same code for the same condition: | Condition | Code | | --- | --- | | Workspace file or name not found | `workspace_not_found` | | Malformed document, wrong type, or invalid value | `invalid_workspace` | | An unknown or unsupported key refused | `unsupported_key` | | tmux missing, or its server unreachable | `tmux_unavailable` | | A tmux command failed while building | `tmux_failed` | | `before_script` exited nonzero, or could not start | `script_failed` | | A target session does not exist | `session_not_found` | | A destination exists without `--force` | `destination_exists` | | A confirmation is needed but impossible | `confirmation_required` | | Interrupted by a signal | `interrupted` | | Arguments or mode misused | `usage`, with status 2 | Other conditions keep port-specific codes, in lower snake_case. Match on the code, not the message. See [output](../output/), [automation](../../guides/automation/), and [troubleshooting](../../guides/troubleshooting/). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # JSON, NDJSON, and semantic color Source: https://libtmux.org/en/ts/latest/workspace/reference/output/ > Python output and the native CLI stream and color contract. **tmuxp compatibility reference.** Examples using `tmuxp` run the Python reference. [Local CLI status](../compatibility/) describes this port's implemented coverage. **Native CLI development contract.** The local `workspace-cli` worktrees provide JSON and NDJSON output. The schema below is the shared target; implemented command coverage remains port-specific. Python tmuxp provides JSON and NDJSON on `ls` and `search`, and JSON on `debug-info`. ## Format selection The shared contract accepts `--json` and `--ndjson` before or after every leaf command. NDJSON wins when both are present. A saved workspace's encoding is separate: `freeze -f yaml` describes the file, while `--json` describes the CLI result stream. Machine output has no ANSI styling, prompts, spinner frames, or raw child output. ## Machine output The document-to-stdout behaviors below are new machine-mode extensions. In the pinned reference, freeze always saves a file and quiet only suppresses status text. Separate the workspace file's encoding from the CLI stream's encoding. `freeze -f json` selects the document format. `--json` selects structured CLI output. Never append a status line to raw YAML or JSON document output. | Command | JSON stdout | NDJSON stdout | | --- | --- | --- | | `ls` | Object containing `workspaces` and `global_workspace_dirs`, retaining tmuxp's existing record fields. Empty discovery yields the same object with an empty array. | One workspace record per line, matching tmuxp. Empty discovery emits zero records. Directory diagnostics stay on stderr. | | `search` | Array of result records with `"name"`, `"path"`, `"session_name"`, `"source"`, `matched_fields`, `"matches"`. Empty results yield `[]`. | One result record per line. Empty results emit zero records. | | `debug-info` | One diagnostics object. Retain home masking for named path fields, define redaction for raw tmux values, and add port/runtime details under named fields. | One compact diagnostics object plus newline. | | `tmuxp load` | Versioned operation summary: command, status, results, errors and completed/failed stages. | Ordered operation events followed by exactly one terminal result. | | `tmuxp freeze` | The workspace document as a JSON object when writing stdout; if saving to a file, a versioned save result containing destination, format and recoverability warnings. | One versioned capture/save result per line, with a `"workspace"` object when returning the document. | | `convert` and importer leaves | The converted document as JSON when writing stdout; a versioned save result when an explicit destination is supplied. | One versioned conversion/save result with a nested document when returning it. | | `edit` | One versioned result after the editor exits, including selected file and child status. | One terminal edit result; interactive editor display uses the terminal rather than machine stdout. | | `"shell"` | For `-c`, one result containing captured Python stdout/stderr and child status. Interactive REPL requires a separate terminal; otherwise reject before execution. | For `-c`, stream captured Python output events and one terminal result. Interactive behavior has the same terminal requirement. | Keep the established read-command JSON shapes rather than forcing a new universal envelope around existing pipelines. New operation envelopes use integer `schema_version: 1`. Search's empty array is a deliberate correction to tmuxp 1.74.0's empty byte stream. No-pattern machine search must return usage status 2, empty stdout and a structured stderr diagnostic. Explicit `--help` remains a documented human-help request. Invalid patterns must return usage status 2 and a diagnostic; tmuxp's current JSON search can silently return no output for an invalid regex. A new load summary has `schema_version`, `"command"`, `"status"` (`ok`, `partial` or `"error"`), `results` and `errors`. Each result identifies its workspace input and created/reused session; IDs are strings because tmux uses prefixes such as `$`, `@` and `%`. Errors include a stable `code`, a readable `message`, the input index and any completed/failed stage. Do not serialize native exception objects, language-specific field capitalization or unserializable handles. Every result names `input`, `input_index`, `session_id`, `session_name` and `reused`; ports add their own fields around those. An input that failed keeps its result record beside its `errors` entry, so a reader can tell which input failed and what became of its session. `status` is `ok` when every input completed, `partial` when some input completed or a failed one left effects behind, such as a borrowed or appended session, and `error` when nothing completed and nothing was retained. TypeScript Python-extension loads report `effects_scope: "observed"` and `effects_unknown: true`, with `observed_windows` and `observed_panes` listing newly observed IDs. These lists may include concurrent changes and do not prove ownership. The CLI does not roll back extension effects; a failed or interrupted extension can leave a partial result. A daemon change is reported without comparing IDs across its lifetimes. Captured `script_output` retains up to 64 KiB of source bytes per stream and reports truncation; NDJSON forwards escaped `script-output` records as output arrives. NDJSON operation events have `schema_version`, `"command"`, `event`, a monotonically increasing `"sequence"`, and event-specific data. The initial vocabulary is `started`, `workspace-started`, `session-created`, `window-created`, `pane-created`, `script-output`, `warning`, `workspace-completed`, `"failed"`, `"completed"`. Emit `"completed"` or `"failed"` once per invocation. Include operation/input identifiers where several files are involved. `workspace-started` names the `input` and `input_index` it belongs to, and `session-created` follows it directly, before any `window-created`, so a reader knows the session a window belongs to as soon as the windows arrive. Flush records as events arrive; buffering the whole run and splitting a JSON array into lines is not streaming. Drain child stdout and stderr concurrently to avoid pipe deadlocks. In machine mode, script text belongs inside escaped JSON strings; it must never be written directly to stdout. Apply backpressure, cap retained output and expose truncation explicitly. Line breaks, tabs, ANSI bytes, Unicode and arbitrary workspace names must remain valid encoded data. Binary output needs an explicit byte encoding or a documented replacement policy. Machine stderr contains one compact diagnostic JSON object per line. Parse/validation failures before work leave stdout empty and return 2 or 1 respectively. A partial load emits a partial/failure result describing completed work and returns 1; do not claim rollback unless it occurred. Human-mode diagnostics remain readable text. Interruptions should stop scheduling new work, drain or close owned streams, release owned process handles, and return 130 without killing unrelated tmux sessions. ## Prompts and file writes Machine mode resolves choices from arguments and never treats missing input as yes. Detached loading avoids mixing terminal attachment with JSON. Interactive editor and REPL display require a separate controlling terminal; otherwise the command rejects the request before execution. The native contract defines explicit save, format, and overwrite controls for conversion/import automation. With no destination, machine conversion returns the document and writes no guessed file. Existing files require explicit overwrite authorization, including explicit freeze destinations. These rules differ from the Python overwrite behavior documented by [freeze](../../cli/freeze/) and [convert](../../cli/convert/). ## Semantic color Use roles at the point that a domain value is rendered. Formatting an entire line with one success color loses the structure the requested style should communicate. Compose a status, subject, identifiers, paths, counts and hints separately, and reset styling after every token. | Role | Default tmuxp-aligned style | Typical values | | --- | --- | --- | | Heading | Bold bright cyan | Command and section headings | | Primary subject | Bold magenta | Workspace/session name, selected window | | Information | Cyan | Paths, targets, useful values | | Success | Green | Created, loaded, saved | | Warning | Yellow | Partial support, lossy capture, retained objects | | Error | Red | Failed operation, invalid field | | Secondary text | Blue or dim text, verified against terminal contrast | Sizes, timestamps, source labels, hints | | Command syntax | Distinct option/argument roles from the same theme | Flags, metavariables, examples | Copy tmuxp's policy explicitly: nonempty `NO_COLOR` disables; explicit never disables; explicit always enables; nonempty `FORCE_COLOR` enables auto; otherwise use the destination stream's terminal capability. To support the supplied reports' extra variables, add `CLICOLOR_FORCE` and `CLICOLOR` below those explicit/reference choices. `CLICOLOR_FORCE=0` does not force. Machine mode takes precedence over all color choices, including forced color. Measure layout using visible terminal width, not byte length or ANSI-bearing string length. Exercise narrow terminals, wrapped paths, Unicode and redirected output. Keep status words and labels even when color is enabled, so meaning survives monochrome output. Progress updates belong on stderr, animate only on a terminal, and become discrete records in NDJSON mode. Honor the reference progress presets, custom tokens and panel-line rules. ## Related reference See [exit codes](../exit-codes/), [command flags](../../cli/), and [environment](../../configuration/environment/). [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # tmuxp compatibility and port status Source: https://libtmux.org/en/ts/latest/workspace/reference/compatibility/ > Current local CLI capabilities, remaining gaps, and the historical builder audit. **Local implementation, unpublished.** The TypeScript `tmux-workspace` CLI is available in the `workspace-cli` source worktree. Its command and configuration coverage remains partial. See [installation](../../guides/installation/) for local setup; installing a published library does not establish availability of this CLI. Compatibility targets useful tmuxp 1.74.0 commands and workspace workflows, with native validation, execution and output conventions. Matching command names does not promise identical runtime behavior or Python semantics. ## This port The CLI runs on Node.js and Bun. Native services cover detached load, exact session reuse, append, capture, discovery, search, conversion, both importers, editor execution and diagnostics. Attached load uses the controlling terminal and selects its tmux client before building; unavailable terminals fail before session creation. Append authenticates the inherited server and current pane. A failed native bootstrap removes its created session and preserves a borrowed append session. Other failures report completed objects and the failed stage. Native loading supports command settings, directories, environment, shells, layouts, explicit window indexes, focus, options and before-script execution. Blank panes skip readiness checks. Conversion preserves extension fields; explicit saves require `--force` to replace an existing file. Capture recovers live state but cannot recover original command arguments, history or plugin intent. Native JavaScript regular expressions power search. Capture accepts an explicit session name or ID, authenticated tmux context, or the only live session. Multiple sessions without context require an explicit target. A `.json` destination selects JSON unless `-f` overrides it; other destinations default to YAML. `--quiet` suppresses human save status while preserving machine results. Every command accepts `--json` and `--ndjson`; NDJSON takes precedence. Machine load requires `-d` or `--append` and never prompts. Human output has semantic colors and terminal progress with preset or custom formats. Log levels filter diagnostics, and `load --log-file` records them separately from result output. Child streams are bounded, escaped and streamed in NDJSON. `load -2` selects 256-color mode; `-8` fails before workspace lookup or tmux access. Python `shell` and explicit plugin/custom-builder loads require tmuxp 1.74.0 in the interpreter selected by `TMUX_WORKSPACE_PYTHON`, defaulting to `python3`. An interactive shell needs a controlling terminal. Ordinary loads and native read commands do not start Python. Empty plugin lists and blank builder names stay native. The load adapter expands common fields and resolves existing import directories relative to the workspace file. Explicit custom builders may omit `windows`. Append retains its authenticated target across inputs and rejects Python `before_script`. Extension progress shows a workspace label and script output without native pane counters. Extension results report `effects_scope: "observed"`, `effects_unknown: true`, and newly observed window and pane IDs. Concurrent changes may appear in these observations; the CLI does not infer ownership or roll back extension effects. Cancellation stops the owned Python process group, then allows up to one second to observe surviving topology. A daemon change or failed observation is reported without comparing IDs across daemon lifetimes. Native command references and Bash, Zsh and fish completion are generated from the parser. `completion` prints the selected shell script; machine modes return a structured script result. Static completion covers commands, options, enum values and file paths without running the CLI. Saved workspace names and live session values are outside its scope. Linux checks use Bash 5.2, Zsh 5.9 and fish 4.8; older shells and newline filenames remain unverified. Bare root/import groups in machine modes return a structured usage error with status 2 and empty stdout. Explicit help requests still print help. The local source reference is packages/workspace-cli/README.md. Use the native executable's `--help` for the options implemented in that checkout. ### Remaining gaps - Interactive confirmation prompts remain unfinished. - Complete configuration/importer coverage, recoverability-aware capture and platform acceptance remain open. Local installed-package checks on Node and Bun do not establish support for every platform or workspace example. ## Historical builder audit Audit date: 2026-09-09. [Native source snapshot](https://github.com/libtmux/libtmux-ts/tree/f85b8de551353f746d50eaf36bf0112f4fe5a528). The following results describe that original library revision, before the local CLI implementation. They are historical evidence, not its current capability list or a support guarantee for a published artifact. The library parsed 8 upstream YAML examples in the original audit. Its reconciliation builder could remove existing windows and panes, unlike tmuxp load policy. Capture/replay preserved the measured topology and directories, but was not a complete freezer. The source snapshot had no native CLI. At that baseline, `@libtmux/workspace` validated a strict subset. Its apply operation used ownership and command policies. Its YAML helper required Bun; Node callers needed a separate YAML decoder. These library observations do not describe the current CLI's YAML support or loading policy. Read the port's [builder topics](../../internals/topics/) and [API](../) for its library interface. Use the language switcher to compare the same topic across ports; each port has its own coverage limits. ## Shared gaps Parser acceptance does not prove execution support. Native regex engines and Python plugin runtimes have different contracts; identical flags alone do not establish compatibility. See [shell](../../cli/shell/), [search](../../cli/search/) and [hooks](../../configuration/hooks/), and apply this port's current limitations above when reading those reference pages. ## Optional format separator The TypeScript library reads `LIBTMUX_TMUX_FORMAT_SEPARATOR` when importing its `FORMAT_SEPARATOR` constant for direct format-list consumers. The workspace CLI's snapshot codec uses separate guarded framing; this setting does not change that transport or its decoding. ## Reading examples The [gallery](../../examples/gallery/) contains the upstream fixture corpus. Parsing a fixture and executing its applications are separate checks. Several require external programs, remote hosts, project directories or plugin packages. A successful YAML read does not establish those dependencies or the complete workspace behavior. [tmuxp reference source](https://github.com/tmux-python/tmuxp/blob/618b398acc05506d3c682906c36cdeb29dcfa1ff/src/tmuxp/cli/__init__.py). --- # Topics Source: https://libtmux.org/en/ts/latest/topics/ > Object traversal, cleanup, pane I/O, configuration, and failure handling. Use these pages for object traversal, cleanup, pane I/O, configuration, and failure handling. [Concepts](/concepts/) introduces the shared object model. The concept guides also cover [control mode vs one-shot](/concepts/transports/), [filtering and queries](/concepts/queries/), and [workspaces](/concepts/workspaces/). Choose a topic below for more detailed behavior: - **[Architecture](architecture/)**: locate operations and field definitions in each port's source. - **[Traversal](traversal/)**: navigate related objects, test membership, and compare identity. - **[Context managers](context-managers/)**: manage cleanup on block exit and identify objects that need an explicit kill. - **[Pane interaction](pane-interaction/)**: choose input modes, capture ranges, and completion waits. - **[Options and hooks](options-and-hooks/)**: configure tmux and register event commands at a supported scope. - **[Format-token fields](format-tokens/)**: read typed state and handle fields absent from a scope, version, or capture. - **[Waiting and retrying](waiting-and-retry/)**: wait on a condition or a named tmux signal. - **[Environment](environment/)**: locate objects from process variables and configure values inherited by new panes. - **[Socket and servers](socket-and-servers/)**: select a server, check liveness, and detect a replacement daemon. - **[Errors and exceptions](errors-and-exceptions/)**: handle command failures and determine whether a mutation can be retried. Use your port's API reference for signatures and defaults. These pages call out differences that affect how you use the APIs. --- # Architecture Source: https://libtmux.org/en/ts/latest/topics/architecture/ > The object hierarchy underneath the API you call, and who actually holds the behavior in each port. This page describes how the language ports organize their code and where operations live. For the object hierarchy and stable IDs, start with [Server, session, window, pane](/concepts/server-session-window-pane/). ## Where behavior lives: on the object, or through the server Python, TypeScript, Go, Rust, Java, .NET, and C++ provide operations on session, window, and pane objects. Each object carries its ID and server context. These examples send input, set an option, and kill a pane: ```typescript await pane.sendKeys("echo hi"); await pane.setOption("automatic-rename", "off"); await pane.kill(); ``` Swift's `Session`, `Window`, and `Pane` are `Sendable` value types holding IDs and state fields. [Format-token fields](../format-tokens/) lists their fields. Perform operations through `Server`, passing the target value: The practical effect is that `Server` is the one thing you hold onto in a Swift program; a `Session` or `Pane` you got back from a `snapshot()` is inert data you hand back to the server that produced it, not a handle you call things on. Every other port's `Server` is also where you start, but `Session`/`Window`/`Pane` stay live actors once you have one. ## A generated data table under a hand-written surface Ports translate object IDs into tmux targets (`-t`) and read state through tmux's `FORMATS` variables (`#{...}`). Their field definitions use generated catalogs, fixed field sets, or captured dictionaries: | Port | Generated table | Hand-written surface | |------|-------------------|----------------------| | Python | `libtmux.constants` (`FORMATS`, gated by scope and tmux version) | dataclass fields on `Obj` (`libtmux.neo`), `None` when a gate excludes a token | | TypeScript | `packages/libtmux/src/_generated/format_fields.ts` (`{ scope, since, token }` per row) | camelCase aliases on `Pane`/`Session`/`Window` (`packages/libtmux/src/_generated/field_aliases.ts`) | | Go | `format_generated.go`, `option_generated.go` (built by `internal/generate/formats`) | `(value, bool)` accessor methods: Go's own "comma ok" idiom for a gate | | Rust | `formats.rs`'s per-token macro rows (`token, wire name, scope, kind, since version, absent-handling`) | typed methods returning `Option` | | Java | (typed field accessors generated for the query layer: see `Pane_`/`Session_` in [Filtering and queries](/concepts/queries/)) | `Optional` for fields introduced after a port's tmux floor | | C++, Swift | fixed field sets; see below | a fixed, curated set of non-optional struct/class fields | | .NET | a snapshot dictionary read at capture time | typed properties that throw `IncompleteSnapshotException` for a field the capture didn't request, rather than gating on tmux version per field | Swift and C++ expose fixed sets of state fields. Swift includes indices, dimensions, active state, command, path, and edge flags. C++ declares its fields in `kFields` arrays and uses `pane->expand("#{...}")` for other tokens. See [Format-token fields](../format-tokens/) for optional fields and tokens outside the fixed sets. ## Module layout, by port Each port's own top-level organization, to orient yourself before opening its source: - **Python**: one module per tier (`libtmux.server`, `.session`, `.window`, `.pane`, `.client`), plus `libtmux.common` for shared plumbing, `libtmux.neo` for the dataclass query layer, `libtmux.options` / `libtmux.hooks` as mixins every tier includes, and `libtmux.exc` for the exception hierarchy. - **TypeScript**: `packages/libtmux/src/{server,session,window,pane,client}.ts` hold the public classes; nearly everything they call into lives under `_internal/operations/` (one file per concern: `pane_io.ts`, `hooks.ts`, `options.ts`, `topology.ts`) and `_generated/` (the format/option/hook catalogs above). Separate packages in the same monorepo cover workspaces (`@libtmux/workspace`) and an MCP server. - **Go**: a single `tmux` package, split by concern into many files rather than many packages (`model.go` for the core structs, `lifecycle_kill.go`, `pane_capture.go`, `pane_geometry.go`, `hierarchy.go`, `plan_server.go` for folded invocations); `tmuxq` is a separate package for predicate queries over an already-read snapshot ([Filtering and queries](/concepts/queries/)), and `workspace` a separate one again. - **Rust**: `crates/libtmux/src/{server,session,window,pane}/` directories, each split into files by concern (a `settings.rs` per tier holding that tier's options-and-hooks methods, matching the pattern in [Options and hooks](../options-and-hooks/)); `hooks.rs`, `options.rs`, and `formats.rs` hold the shared, scope-generic machinery those call into. Workspaces and the MCP server are separate crates in the same workspace. - **Java**: `io.github.libtmux` holds `Server`, `Session`, `Window`, and `Pane` as `final` classes; each exposes its option and hook tables through `.options()` / `.hooks()` accessor methods returning a separate `Options` / `Hooks` view scoped to that object, rather than mixing those methods directly into the entity class the way Python and Go do. `Session_`, `Window_`, and `Pane_` are a parallel set of typed-field classes that exist only for the query layer. - **.NET**: `src/LibTmux/` gives every entity its own name (`Pane.cs`, `Session.cs`, ...) but splits each into several `partial class` files by concern rather than by inheritance: `Pane.Capture.cs`, `Pane.Input.cs`, `Pane.Relations.cs`, `Pane.Scopes.cs`, `Pane.Topology.cs`, and so on all contribute to one `Pane` type. `Options`/`Hooks` are reached through `.Options` / `.Hooks` properties, structurally the same idea as Java's accessor methods. - **C++**: `include/libtmux/entities.hpp` declares `Session`, `Window`, and `Pane` together as value types (`private Row` bases), with their method bodies in `src/` rather than the header; `server.hpp`, `options.hpp`, and `capabilities.hpp` are separate headers. A private `testing` component (`include/libtmux/testing/`) ships separately from the library proper: see [Context managers](../context-managers/) for what it's for. - **Swift**: `Sources/LibTmux/Server.swift` is the hub every operation extends; `Session.swift` and `Pane.swift` declare the thin value types, `Snapshot.swift` holds the relationship queries ([Traversal](../traversal/)), and `Options.swift`, `PaneInteraction.swift`, and `Mutations.swift` are `extension Server` files grouping options/hooks, send/capture, and kill respectively: all reachable only through `Server`, per the section above. ## Naming conventions Method names follow language conventions: Python, Rust, and C++ use `snake_case`; TypeScript, Java, and Swift use `camelCase`; Go and .NET use `PascalCase`. Option and hook names remain tmux's dash-separated strings, such as `automatic-rename`, regardless of the method's spelling. --- # Traversal Source: https://libtmux.org/en/ts/latest/topics/traversal/ > Moving up and down the server/session/window/pane tree, and the two questions that come up once you have more than one object. Use relationships to move between sessions, windows, and panes. [Server, session, window, pane](/concepts/server-session-window-pane/) explains the hierarchy and snapshot model. This page covers relationship calls, collection membership, and object identity. ## Down the hierarchy List children through the parent object or a captured snapshot. Whether a read issues another tmux command depends on the API, independently of whether the call is async; see [Server, session, window, pane](/concepts/server-session-window-pane/). | Port | Server → sessions | Session → windows | Window → panes | |------|--------------------|--------------------|-----------------| | Python | `server.sessions` | `session.windows` | `window.panes` | | TypeScript | `await server.sessions()` | `session.windows` | `window.panes` | | Go | `server.Sessions(ctx)` | `session.Windows()` | `window.Panes()` | | Rust | `await server.sessions()` | `session.windows()` | `window.panes()` | | Java | `server.sessions()` | `session.windows()` | `window.panes()` | | .NET | `server.GetSessionsAsync()` | `session.GetWindowsAsync()` | `window.GetPanesAsync()` | | C++ | `server->sessions()` | `session->windows()` | `window->panes()` | | Swift | `server.sessions()`, or `snapshot.windows(of: session)` for windows/panes once you have a `Snapshot` | see previous column | see previous column | TypeScript's `session.windows` and `window.panes` read the graph loaded by `await server.sessions()` without additional tmux commands. Rust also provides `server.attached_sessions()` to list only sessions with an attached client. ## All panes in a session Use a session-wide pane collection when the task spans several windows, such as finding a command or capturing output from every pane. A window linked to multiple sessions still refers to the same tmux panes. Check whether the API reads live state or traverses a captured graph before reusing its result. ### Python `libtmux.Session.panes` runs a session-scoped `list-panes -s` read. Use it to list panes across the session's windows without manually listing each window. ### TypeScript `session.Session.panes` returns a `Selection` from the session's captured graph. Reading it does not issue another tmux command; refresh the snapshot when you need newer state. ### Rust `session.Session.panes` performs a live listing and returns a `Result` from the async call. Handle a command failure before using the returned panes. ### Go `tmux.Session.Panes` reads captured relations without another tmux command. The relation must have been included in the read that produced the session; an uncaptured relation does not establish that the session has no panes. ### .NET `LibTmux.Session.Panes` reads the session's captured relations. It does not issue a tmux command; an incomplete capture may lack the required relation. ### C++ `libtmux::Session::panes` runs a session-scoped `list-panes` command. Inspect the returned result before traversing the panes. ### Swift `Snapshot.panes(of:)` accepts a session or a window. The session overload traverses the captured graph and deduplicates pane IDs without a tmux read. ### Java Java has no direct session-wide pane member. Traverse `Session.windows()` and then `Window.panes()` in the captured relations. Deduplicate pane IDs when combining results from sessions that may share linked windows. ## Up the hierarchy Parent lookups may read captured data or query tmux again. Check the method's read and failure semantics; [Server, session, window, pane](/concepts/server-session-window-pane/) introduces that distinction: | Port | Pane → window | Window → session | |------|----------------|--------------------| | Python | `pane.window` | `window.session` | | TypeScript | `pane.window` (getter, from the loaded graph) | `window.session` (getter) | | Go | `pane.Window()` → `(Window, bool)` | `window.Session()` → `(Session, bool)` | | Rust | `await pane.window()` → `Result, Error>` | `await window.session()` → `Result, Error>` | | Java | `pane.window()` | `window.session()` | | .NET | `pane.Window` (property) | `window.Session` (property) | | C++ | `pane->window()` | `window->session()` | | Swift | `pane.windowID`, then look it up via `Snapshot` | not a per-window field: join through the snapshot instead | Go and Rust return optional relationship results. .NET's `.Window`, `.Session`, `.ActiveWindow`, and `.ActivePane` read captured state synchronously and throw `IncompleteSnapshotException` if that capture lacks the required context. ## One walk, down and back up Start with a session, traverse to a window and pane, then look up the parent and compare its identity with the starting object: ```typescript const session = (await server.sessions())[0]; const window = session.windows[0]; const pane = window.panes[0]; pane.window.id === window.id; window.session.id === session.id; ``` ## The active child "Which window is in front right now" and "which pane would a command actually reach" are common enough questions that most ports expose the active child directly rather than making you filter a list: | Port | Session's active window | Window's active pane | |------|---------------------------|------------------------| | Python | `session.active_window` | `window.active_pane` | | TypeScript | `session.activeWindow` (getter) | `window.activePane` (getter) | | Go | `session.ActiveWindow()` → `(Window, bool)` | `window.ActivePane()` → `(Pane, bool)` | | Rust | `await session.active_window()` → `Result, Error>` | `await window.active_pane()` → `Result, Error>` | | Java | `session.activeWindow()` → `Optional` | `window.activePane()` → `Optional` | | .NET | `session.ActiveWindow` (property) | `window.ActivePane` (property) | | C++ | `session->active_window()` | `window->active_pane()` | | Swift | filter for `isActive` on `snapshot.windows(of: session)`: `Window` carries its own `window_active` flag rather than the session exposing an accessor | same pattern, on the pane's own active flag | In Swift, filter snapshot children by `isActive`. Other ports expose an active-child method or property on the parent. [Format-token fields](../format-tokens/) describes the underlying `window_active` and `pane_active` fields. ## Is it in that collection? Checking membership generally goes through whatever your language uses for collection membership, since most of these calls already return an ordinary array, slice, or list: - **Python** overloads `in` directly on its `QueryList`: `window in session.windows`, `pane in window.panes`. - **Java**, **C++**, and **.NET** return standard collections. Use their standard membership operations with the identity comparison appropriate to the port. - **TypeScript** returns iterable `Selection` objects. Iterate over the selection and compare IDs, or spread it into an array for standard array operations. - **Go** and **Rust** return slices or vectors. Iterate and compare object IDs when testing membership by tmux identity. ## Is this the same object? Compare IDs to determine whether two handles refer to the same tmux object on the same server. Equality operators vary by port: | Port | How you check | |------|----------------| | Python | `window.window_id == other.window_id` (or `pane.pane_id == ...`) | | TypeScript | compare `.id` | | Go | `pane.ID() == other.ID()`: `PaneID` is a plain, `==`-comparable `string` | | Rust | `pane.id() == other.id()`: verified from the port's own doctests, not struct equality | | Java | `pane.equals(other)`: overridden to compare server identity plus pane ID | | .NET | `pane.Equals(other)`: overridden to compare a generation counter plus ID | | C++ | `pane == other`: `operator==` is defined directly on `Session`/`Window`/`Pane` | | Swift | compare `.id` for identity; see the equality note below | **Swift's equality compares captured state.** Compiler-synthesized equality for `Session`, `Window`, and `Pane` compares every stored property, including dimensions and current command. Two reads can compare unequal even when they describe the same tmux object. Compare `.id` when checking identity on the same server. --- # Context managers Source: https://libtmux.org/en/ts/latest/topics/context-managers/ > Scope-based cleanup for tmux objects, and when your program must kill them explicitly. A tmux session, window, or pane normally remains until you kill it. Scope-based cleanup can kill it when your code leaves a block, including after an exception. See [Workspaces](/concepts/workspaces/) for a temporary layout example. Python provides context managers for tmux objects. .NET provides ownership scopes for servers, sessions, and windows. Other ports require explicit cleanup or offer guards for test servers: | Port | Server | Session | Window | Pane | |------|:------:|:-------:|:------:|:----:| | Python | yes | yes | yes | yes | | .NET | yes | yes | yes | - | | Java | closes conn. | - | - | - | | Rust | test-only | - | - | - | | C++ | test-only | - | - | - | | TypeScript | - | - | - | - | | Go | - | - | - | - | | Swift | - | - | - | - | "test-only" means a guard owns an entire disposable test server. Java's `AutoCloseable` server releases its transport but leaves tmux running. A dash means no built-in cleanup scope is listed for that object; use an explicit kill call with the cleanup mechanism appropriate to your language. ## Python: every level, including nested Python's `Server`, `Session`, `Window`, and `Pane` support context managers. Entry returns the existing object; exit kills it, including when the block raises: Nested scopes exit in reverse order: pane, window, session, then server. ## .NET: an explicit ownership type, stopping at Window .NET's `OwnedSessionScope` and `OwnedWindowScope` wrap the created object and implement `IAsyncDisposable`. The `Session` and `Window` handles themselves are not disposable: There is no `OwnedPaneScope`. For tests, `TmuxTestFactory.CreateHierarchyAsync()` returns a `TemporaryHierarchyScope` containing a private server, session, window, and pane. Disposing it kills the server. ## Java: `Server` is closeable, but closing one doesn't kill it Java's `Server` implements `AutoCloseable`. Exiting `try (Server server = Server.open(config))` releases the owned transport while tmux and its sessions remain running. Kill sessions, windows, panes, or the server explicitly when your program owns their cleanup. ## Rust: no async `Drop`, so cleanup is explicit or best-effort Rust's `Drop::drop` is synchronous and cannot await an async tmux kill. Use explicit shutdown when you need to observe cleanup failures: - **`kill(self)` consumes the handle.** Session, window, and pane kill methods take `self` by value, preventing subsequent use of that handle. - **`libtmux::test::TestServer` provides a test guard.** Call `guard.shutdown().await?` to handle cleanup errors. Its `Drop` implementation falls back to synchronous, best-effort `force_cleanup()`. ## C++: RAII exists, but only for a private test server C++'s `Session`, `Window`, and `Pane` are non-owning values; destroying a handle does not kill its tmux object. `libtmux::test::ScopedTmuxServer`, in the separate `testing` CMake component, owns a private test server and its temporary socket directory: ## TypeScript, Go, Swift: no built-in scoping at all TypeScript, Go, and Swift require explicit cleanup of sessions, windows, and panes. Connection or notification handles may have separate disposal APIs: - **TypeScript** implements `[Symbol.asyncDispose]` on control connections and notification streams. `await using` releases those handles; it does not kill the watched session or pane. See [Control mode vs one-shot](/concepts/transports/). Use `finally` for a session your program owns: ```typescript const session = await server.newSession({ name: "work" }); try { const window = await session.newWindow({ name: "editor" }); await window.panes.at(0)?.sendKeys("echo hi"); } finally { await session.kill(); } ``` - **Go** implements `io.Closer` on `ControlClient`, `PaneObservation`, and `NotificationStream`. Use `defer conn.Close()` for those resources and an explicit `Kill(ctx)` for tmux objects: ```go session, err := server.NewSession(ctx, tmux.NewSessionRequest{Name: "work"}) if err != nil { return err } defer session.Kill(ctx) // idiomatic Go: not a library-provided guarantee ``` - **Swift** uses non-owning session, window, and pane values. Call `try await server.kill(session)` or the corresponding window or pane overload when cleanup is required. ## What this means in practice Use explicit cleanup for objects whose handles have no disposal hook. For an entire disposable test server, prefer your port's test fixture or server guard; see [Testing with libtmux](/guides/testing-with-libtmux/). --- # Pane interaction Source: https://libtmux.org/en/ts/latest/topics/pane-interaction/ > Input defaults, screen capture, and waiting for a command to finish. Send input to a pane and capture its screen to interact with a running program. [Attach and send keys](/examples/attach-and-send-keys/) provides examples. [Sending keys](/guides/sending-keys/) and [Capturing output](/guides/capturing-output/) are task guides; this page compares input defaults, capture ranges, and completion handling. ## Typing into a pane Two questions come up every time you send something to a pane: should tmux press Enter afterward, and should tmux interpret what you sent as key names (`Enter`, `C-c`) rather than literal characters? Ports answer both, but disagree on whether that's one method with flags or two separate methods: ### Python **Type without Enter:** `pane.send_keys(text, enter=False)` **Type + Enter (default):** `pane.send_keys(text)` **How "literal" is chosen:** `literal=True` flag on the same method ### TypeScript **Type without Enter:** `pane.sendKeys(text, { enter: false })` **Type + Enter (default):** `pane.sendKeys(text)` **How "literal" is chosen:** `{ literal: true }` option ### Go **Type without Enter:** `pane.SendKeys(ctx, SendKeysRequest{Command: &text, SkipEnter: true})` **Type + Enter (default):** `pane.SendKeys(ctx, SendKeysRequest{Command: &text})` **How "literal" is chosen:** `Literal: true` field ### Rust **Type without Enter:** `pane.send_keys(keys)`: **always literal**, key names typed as text **Type + Enter (default):** `pane.send_line(text)` **How "literal" is chosen:** `send_keys` sends literal text; `send_key_names` interprets tmux key names. ### Java **Type without Enter:** `pane.send(keys)` **Type + Enter (default):** `pane.sendLine(command)` **How "literal" is chosen:** Separate text and key-sending methods. ### .NET **Type without Enter:** `SendKeysAsync(new SendKeysRequest(text, enter: false))` **Type + Enter (default):** `SendTextAsync(text)` (defaults `enter: true`) **How "literal" is chosen:** `Literal` field on `SendKeysRequest`; `SendTextAsync` hardcodes it ### C++ **Type without Enter:** `pane->send_text(text)` **Type + Enter (default):** `send_text(text)` then `send_key("Enter")` separately: no combined convenience exists **How "literal" is chosen:** `send_text` is always literal; `send_key` is always a key name ### Swift **Type without Enter:** `server.sendKeys([text], to: pane)` **Type + Enter (default):** `server.run(text, in: pane)` (sugar for `sendKeys([text, "Enter"], to: pane)`) **How "literal" is chosen:** `literally: true` option on `sendKeys` ### Examples **Rust's `send_keys` always sends literal text.** Use `send_key_names` for tmux key names. Passing `"Enter"` to `send_keys` types those characters; it does not press the key. **Text and Enter can be separate commands.** Python, TypeScript, Go, and .NET normally send Enter after the text. If the second operation fails, the text may already be in the pane; retrying the entire request can duplicate it. C++'s separate `send_text` and `send_key("Enter")` calls have the same risk. Rust's `send_line` and Java's `sendLine` append a literal `\r` to the text and send it in one `send-keys -l` command. They avoid a separate Enter dispatch. Send a command line and press Enter: ```typescript await pane.sendKeys("echo hi", { enter: false }); await pane.sendKeys("echo hi"); ``` ## Reading a pane back Capture methods return lines from the pane's visible screen by default: `pane.capture_pane()` in Python, `pane.capture()` in TypeScript, Rust, Java, and C++, `pane.Capture(ctx, ...)` in Go, `CaptureAsync(...)` in .NET, and `server.capture(pane)` in Swift. Request scrollback explicitly, such as with Python's `start` and `end` or Swift's `includingHistory`. ```typescript await pane.capture(); ``` ## Waiting for something to finish A send call completes when input reaches tmux. It does not wait for the shell command to finish. Wait for expected output or a completion signal: - **Python** can poll capture output for a marker. Its test-support module also provides `libtmux.test.retry_until(condition, ...)` for arbitrary conditions. [Waiting and retrying](../waiting-and-retry/) covers polling helpers across ports. - **Swift** ships a real primitive for exactly this: `server.waitForOutput(...)` returns an `OutputWait` once a pattern shows up in the pane, rather than leaving you to write the loop. - **Go** and **.NET** expose tmux's `wait-for` signal channel through `server.WaitFor(ctx, WaitForRequest{...})` and `TmuxWaitChannel`. Use a named signal when you control the command and can make it announce completion. [Capture pane output](/examples/capture-pane-output/) has the checked, per-port code for the polling-with-a-marker version of this; reach for a port's native wait primitive above it where one exists. --- # Options and hooks Source: https://libtmux.org/en/ts/latest/topics/options-and-hooks/ > Reading and writing tmux's own configuration knobs, and binding commands to its events, at whichever scope you're holding. Use options to change tmux behavior, such as `automatic-rename` or the status-line format. Use hooks to run commands on events such as `session-renamed` or `after-split-window`. Choose the scope supported by the option or hook. ## Reading and writing options Read an option, set it, or unset it at a chosen scope. Ports differ in how they distinguish values set locally from effective values inherited from another scope: ### Python **Read all (this scope):** `pane.show_options()` **Read effective/inherited:** `pane.show_option(name, global_=True)` reaches the global fallback explicitly; no separate "resolved" call **Set:** `pane.set_option(name, value)` **Unset:** `pane.unset_option(name)` ### TypeScript **Read all (this scope):** `pane.showOptions()` **Read effective/inherited:** `pane.showResolvedOptions()` **Set:** `pane.setOption(name, value)` **Unset:** `pane.unsetOption(name)` ### Go **Read all (this scope):** `pane.Options(ctx)`: a typed struct with one accessor method per option **Read effective/inherited:** Not listed. **Set:** `pane.SetOption(ctx, ...)` **Unset:** `pane.UnsetOption(ctx, ...)` ### Rust **Read all (this scope):** `pane.options()` (typed `BTreeMap`), `pane.option_names()` **Read effective/inherited:** `pane.typed_option(name)` decodes one value by its declared kind **Set:** `pane.set_option(name, value)`, `pane.append_option(name, value)` **Unset:** `pane.unset_option(name)` ### Java **Read all (this scope):** `pane.options().all()` **Read effective/inherited:** `pane.options().get(name)` reads `show-options -A -v`: inherited, not just local **Set:** `pane.options().set(name, value)` **Unset:** `pane.options().unset(name)` ### .NET **Read all (this scope):** `pane.Options.GetAllAsync()` **Read effective/inherited:** `pane.Options.GetAsync(new GetOptionRequest(name, includeInherited: true))`: an explicit opt-in flag, mapped straight to tmux's own `-A` **Set:** `pane.Options.SetAsync(new SetOptionRequest(name, value))` **Unset:** `pane.Options.UnsetAsync(...)` ### C++ **Read all (this scope):** `pane->options()` **Read effective/inherited:** Not listed. **Set:** `pane->set_option(name, value)` **Unset:** `pane->unset_option(name)` ### Swift **Read all (this scope):** `server.options(.pane(pane))` **Read effective/inherited:** `server.option(name, scope: .pane(pane))` reads presence from the listing, then the value with `-v` **Set:** `server.setOption(name, to: value, scope: .pane(pane))` **Unset:** `server.unsetOption(name, scope: .pane(pane))` ### Examples After a successful write completes, read the option to obtain its updated value. These examples set, read, and unset a pane option: ```typescript await pane.setOption("automatic-rename", "off"); await pane.showOptions(); await pane.unsetOption("automatic-rename"); ``` ## Hooks ### Python **Set:** `pane.set_hook(name, command)` **Unset:** `pane.unset_hook(name)` **List:** `pane.show_hook(name)`, `pane.show_hooks()` (all) **Run now, without the event:** Not listed. ### TypeScript **Set:** `pane.setHook(name, command, { append })` **Unset:** `pane.unsetHook(name)` **List:** `pane.showHooks()` (all; no singular `showHook`) **Run now, without the event:** Not listed. ### Go **Set:** `pane.SetHook(ctx, name, command)`, `pane.SetHooks(ctx, ...)` (bulk) **Unset:** `pane.UnsetHook(ctx, name)` **List:** `pane.Hooks(ctx)`: typed struct **Run now, without the event:** Not listed. ### Rust **Set:** `pane.set_hook(name, command)` **Unset:** `pane.unset_hook(name)` **List:** `pane.hook(name)`: one name only; **no listing at pane/window scope**, by design (see below) **Run now, without the event:** Not listed. ### Java **Set:** `pane.hooks().set(event, command)`, `.append(event, command)` **Unset:** `pane.hooks().unset(event)` **List:** `pane.hooks().all()` **Run now, without the event:** `pane.hooks().run(event)`: tmux's `set-hook -R` ### .NET **Set:** `pane.Hooks.SetAsync(new SetHookRequest(event, command))` **Unset:** `pane.Hooks.UnsetAsync(...)` **List:** `pane.Hooks.GetAllAsync()` **Run now, without the event:** `pane.Hooks.RunAsync(...)` ### C++ **Set:** `session.set_hook(name, command)`: no `Window`/`Pane` overload exists at all **Unset:** Not listed. **List:** `session.hooks()`, `server.global_hooks()` **Run now, without the event:** Not listed. ### Swift **Set:** `server.setHook(name, to: command, at: index, in: scope)` **Unset:** `server.unsetHook(name, in: scope)` **List:** `server.hooks(scope)` **Run now, without the event:** `server.runHook(name, in: scope)` ### Examples tmux stores hook commands in indexed arrays, such as `after-new-window[0]`. Python and TypeScript can include the index in the name. Go's `SetHooks` and Swift's `at:` parameter take it separately. Java's `.append()` and TypeScript's `{ append: true }` append without requiring the next index. Set and list a session hook. The next section explains window and pane scope limitations: ```typescript await session.setHook("session-renamed", "display-message 'renamed'"); await session.showHooks(); ``` ## Supported hook scopes tmux stores hooks globally or per session. Accepted `set-hook -w` or `-p` flags do not imply a separate window or pane hook table, and `show-hooks` does not provide a corresponding listing. Check the event's supported scope if a hook is accepted but never fires. Ports handle unsupported hook scopes differently: - **Java's `Hooks.java`** documents that tmux can accept a hook at an unsupported scope without an effective registration. Check `.all()` when diagnosing a hook that does not fire. - **Rust** validates scope in `Pane::set_hook` and `Window::set_hook`, returning `Error::OptionScopeMismatch` for an unsupported scope. - **Swift** restricts `HookScope` to `.global` and `.session`. **C++** exposes `set_hook` on `Session` and `global_hooks()` on `Server`, with no window or pane hook methods. Options have window and pane tables of their own. The hook-scope limitation does not apply to ordinary options. ## tmux version compatibility Python's compatibility notes list these tmux requirements: | Feature | Minimum tmux | |---------|-------------| | All options/hooks features | 3.2+ | | Window/pane hook *scope flags* (`-w`, `-p`) accepted | 3.2+; see the supported-scope caveat above | | `client-active`, `window-resized` hooks | 3.3+ | | `pane-title-changed` hook | 3.5+ | Check your port's compatibility notes before relying on a particular tmux release. --- # Format-token fields Source: https://libtmux.org/en/ts/latest/topics/format-tokens/ > The typed fields every object exposes, mirroring tmux's own format tokens, and why a field is sometimes absent. Object fields expose values from tmux's [FORMATS](https://man.openbsd.org/tmux.1#FORMATS), such as `pane_id`, `window_zoomed_flag`, and `session_name`. The available fields depend on the port, object scope, tmux version, and data requested by the read. A token needs the right **scope** and **tmux version**. For example, a pane token needs a pane context, and a token added after your tmux release may be absent. Ports represent absence with optional values, flags, or errors, as described below. ## The absence idiom, per port | Port | What an excluded field looks like | |------|--------------------------------------| | Python | the attribute is `None` | | TypeScript | the property is `undefined` | | Go | a two-return-value accessor: `pane.DeadSignal()` returns `(string, bool)`: Go's own "comma ok" idiom | | Rust | `Option`; consult the reference for the accessor name | | Java | `Optional`: `pane.floating()` returns `Optional`, empty when the field isn't populated | | .NET | nullable values or `IncompleteSnapshotException`, depending on whether the value or captured field is absent | | C++, Swift | fixed, non-optional fields; see below for access to other tokens | These examples read optional fields, including `pane_dead_signal` on tmux 3.3 or newer: ```typescript pane.deadSignal; // undefined under the same conditions ``` Rust's `formats.rs` marks this token as optional. Consult its generated reference for the accessor name. .NET also distinguishes missing values from incomplete captures. `Pane.Title` is nullable because tmux may report no title. `Pane.Height`, `.Width`, and `.Index` throw `IncompleteSnapshotException` when the read that produced the handle did not request those fields. A handle resolved by ID alone may therefore lack enough data to answer: ## A generated table under the accessor Several ports generate scope- and version-tagged field catalogs from tmux source or documentation. [Architecture](../architecture/) describes the layouts. Examples include: - **TypeScript** uses `_generated/format_fields.ts` rows with `scope`, `since`, and `token`. For example, `pane_zoomed_flag` has pane scope and requires tmux 3.7. `_generated/field_aliases.ts` supplies the camelCase alias `pane.zoomedFlag`. - **Rust** uses a macro row in `formats.rs` for each token's wire name, scope, tmux version, and type. `pane_dead_signal` has `Pane` scope, requires `V3_3`, and is decoded as `Text`. - **Go** generates `format_generated.go` with `internal/generate/formats`. Some accessors decode richer values: `pane.DeadTime()` returns `(time.Time, bool)` and performs timestamp parsing for the caller. Two per-token facts survive across every one of these catalogs, because they're facts about tmux, not about any one port's generator: `pane_dead_signal` and `pane_dead_time` arrived in tmux 3.3, and a cluster of pane-geometry and floating-pane tokens (`pane_floating_flag`, `pane_pb_progress`, `pane_x`, `pane_y`, `pane_z`, `pane_zoomed_flag`, `bracket_paste_flag`, `synchronized_output_flag`, among others) arrived together in 3.7. ## The two ports that didn't generate the full catalog Swift and C++ expose fixed, non-optional fields on `Session`, `Window`, and `Pane`: - **Swift** carries `index`, `width`, `height`, `isActive`, `currentCommand`, `currentPath`, and the four edge flags. - **C++** declares fields in `kFields` arrays. Pane fields include `id`, `command`, `active`, `index`, `title`, `pid`, `tty`, `path`, `width`, `height`, `dead`, `in_mode`, edge flags, and `piping`. Accessors return `std::string_view`, `bool`, or `long long`. For a token outside the fixed fields, C++ provides one-shot expansion with `pane->expand("#{pane_dead_signal}")`. Swift uses `FormatSubscription` on a control connection, delivering `SubscriptionChange` when tmux re-evaluates the token. That API observes changes over time. [Architecture](../architecture/) describes the fixed-field model. ## Fields promoted from the active child Python exposes fields promoted from an active child. For example, `session.pane_id` identifies the active pane of the session's active window: tmux's format engine includes active-child fields when listing a parent. A `list-sessions -F` row can include `window_id` and `pane_id` for the active window and pane. Check the port reference for typed access to those fields, or use the explicit relationships described in [Traversal](../traversal/). A pane context can include parent window and session fields. A session cannot identify one attached client when several clients may be attached, so client tokens such as `client_name` require a client context. --- # Waiting and retrying Source: https://libtmux.org/en/ts/latest/topics/waiting-and-retry/ > Polling a condition instead of guessing a sleep, and tmux's own wait-for signal channel as the alternative to polling. After sending input or starting a process, wait for the state your next step requires. [Pane interaction](../pane-interaction/#waiting-for-something-to-finish) covers waiting for screen text. This page covers arbitrary conditions and tmux's named `wait-for` signal channels. ## Polling a condition Polling checks a condition repeatedly until it succeeds or a deadline expires. The helpers below expose an interval and timeout; several live in test-support packages: ### Python **Helper:** `libtmux.test.retry_until(fn, seconds=, interval=)` **Where it lives:** The `libtmux.test` module in the main package; raises `WaitTimeout`. ### TypeScript **Helper:** `connectedServer.waitFor(matches, options)` **Where it lives:** The public control-connection API. Tests a predicate over `ServerSnapshot`; see [Control mode vs one-shot](/concepts/transports/). ### Go **Helper:** `tmuxtest.WaitFor(ctx, interval, condition)` **Where it lives:** `tmuxtest`, a separate test-support package from `tmux` ### Rust **Helper:** `libtmux::test::retry_until(within, condition)` **Where it lives:** `libtmux::test`, enabled with the `test-support` Cargo feature. ### Java **Helper:** Not listed. **Where it lives:** not found in the shipped library; a package-private `Await.until(...)` exists only inside the `integration-tests` module, which downstream code cannot depend on ### .NET **Helper:** `LibTmux.Testing.TmuxWait.UntilAsync(probe, timeout, interval)` **Where it lives:** `LibTmux.Testing`, part of the same shipped `LibTmux` package ### C++ **Helper:** Not listed. **Where it lives:** no generic condition-poll helper found in the public library; a `wait_until` exists only in the private `testing` component, for waiting on a spawned child process, not on tmux state ### Swift **Helper:** Not listed. **Where it lives:** a `waitUntil` helper exists only inside the test target's own support code, not shipped ### Examples Python, Rust, Go, and .NET provide general polling helpers in their test-support APIs. TypeScript's public `waitFor` instead waits on a server-snapshot predicate through a control connection. It subscribes before reading so it does not miss a change between those steps. ```typescript const live = await server.connect(); await live.waitFor((snapshot) => snapshot.windows.exists({ name: "build" })); ``` For Java, C++, and Swift, this page lists no public arbitrary-condition polling helper. Use a loop with a deadline and interval if a more specific wait API does not fit; [Pane interaction](../pane-interaction/#waiting-for-something-to-finish) covers output waits. ## tmux's own wait-for channel Use `tmux wait-for -S ` to signal and `tmux wait-for ` to block until signalled. This avoids repeated screen captures when the command can announce its own completion: | Port | Signal | Wait | |------|--------|------| | Python | `server.wait_for(channel, set_flag=True)` | `server.wait_for(channel)` | | TypeScript | not exposed as public API: used only inside the test-server's own startup handshake | - | | Go | `server.WaitFor(ctx, tmux.WaitForRequest{Channel: name, Mode: tmux.WaitForModeSignal})` | `tmux.WaitForRequest{Channel: name}` (the zero-value `WaitForRequest.Mode` waits) | | Rust | `server.signal_channel(name).await?` | `server.wait_for_channel(name, timeout).await?` → `ChannelWait::Signalled` or `TimedOut` | | Java | `server.channel(name).signal()` | `server.channel(name).await(timeout)` → a `WakeReason`, never silently "success" | | .NET | `server.OpenWaitChannel(name)` returns a `TmuxWaitChannel`; signalling is the same request with a different mode | `await using` the channel, then `WaitAsync(budget)` | | C++ | `server.signal(channel)` | `server.wait_for(channel, timeout)` | | Swift | `try await server.signal(channel)` | `try await server.wait(for: channel)` | tmux remembers a signal sent before a waiter starts. The next wait on that channel returns immediately, so completion is not lost when the command finishes first. A raw `wait-for` client can exit zero when the server dies, as well as when the channel is signalled. Java's `WakeReason` and Swift's `wait(for:)` distinguish server loss from a signal; Swift checks the server PID before and after the wait. Use a channel name specific to the task, or clear an old signal with Java's `drain()` when appropriate. A remembered signal can otherwise satisfy an unrelated later wait. --- # Environment Source: https://libtmux.org/en/ts/latest/topics/environment/ > Locate tmux objects from process variables and manage the environment inherited by new panes. tmux exposes two environment APIs. Process variables such as `TMUX` and `TMUX_PANE` let code inside a pane identify its server and pane. The server also stores variables through `set-environment` and `show-environment` for new processes to inherit. Like the tables in [Options and hooks](../options-and-hooks/), this persistent store has explicit scopes. ## Locating yourself from inside a pane Inside a pane, `TMUX` contains `,,`, and `TMUX_PANE` contains the pane ID, such as `%1`. Use these variables to locate the current tmux objects. Ports expose different levels of environment lookup: ### Python **Server:** `Server.from_env()` **Session:** `Session.from_env()` **Window:** `Window.from_env()` **Pane:** `Pane.from_env()` ### TypeScript **Server:** Not listed. **Session:** `Session.fromEnv()` **Window:** Not listed. **Pane:** Not listed. ### Go **Server:** `NewServerFromEnv(env)` **Session:** `SessionFromEnv(ctx, env)` **Window:** `WindowFromEnv(ctx, env)` **Pane:** `PaneFromEnv(ctx, env)` ### Rust **Server:** `Server::from_env()` **Session:** `Session::from_env(&server)` **Window:** `Window::from_env(&server)` **Pane:** `Pane::from_env(&server)` ### Java **Server:** Not listed. **Session:** Not listed. **Window:** Not listed. **Pane:** See the Java context example below. ### .NET **Server:** `Server.FromEnvironment(env)` **Session:** `Session.FromEnvironmentAsync()` **Window:** `Window.FromEnvironmentAsync()` **Pane:** `Pane.FromEnvironmentAsync()` ### C++ **Server:** `Server::from_env()` **Session:** Not listed. **Window:** Not listed. **Pane:** Not listed. ### Swift **Server:** `TmuxContext.current()` **Session:** `TmuxContext.current()` (same call: see below) **Window:** Not listed. **Pane:** Not listed. ### Examples Python, Go, and .NET provide standalone environment lookups at each level. Rust's `Session`, `Window`, and `Pane::from_env` require an existing `&Server`. TypeScript provides `Session.fromEnv()`; this page lists no equivalent for its other object types. ```typescript const session = await Session.fromEnv(); ``` A process not started inside a pane has nothing truthful to answer with, so every one of these raises rather than guessing: Python's `NotInsideTmux`, Go's `FromEnvError`, .NET's `TmuxObjectNotFoundException`, and so on, each naming the missing or malformed variable rather than returning an empty or default object. ### Java and C++ stop short of the pane Neither port gives you a live object back the way the other five do, and they stop at different points: - **C++** provides `Server::from_env()` to select the socket. Use the resulting server to resolve sessions or panes. - **Java** parses `TMUX` and `TMUX_PANE` into identifiers: socket path, server PID, `SessionId`, and `Optional`. It returns context data rather than a live pane handle: ```java TmuxEnvironment here = TmuxEnvironment.current().orElseThrow(); try (Server server = Server.open(here.config())) { Session mine = server.sessions().stream() .filter(session -> session.id().equals(here.session())) .findFirst() .orElseThrow(); } ``` ### Swift context fields Swift's `TmuxContext.current()` parses the socket path, server PID, and session ID from `TMUX`. It does not read `TMUX_PANE`, so it cannot identify the current pane: Read `TMUX_PANE` separately if you need the pane ID; `TmuxContext` does not provide it. ## tmux's own environment variable store Like [Options and hooks](../options-and-hooks/#window-and-pane-hook-scopes-are-mostly-fiction), tmux's persistent environment store has global and per-session scopes. It is read with `show-environment` and updated with `set-environment`. Newly spawned processes inherit it; existing processes retain their own environments. ### Python **Set:** `server.set_environment(name, value)`, `session.set_environment(...)` **Read all:** `server.show_environment()`, `session.show_environment()` **Unset:** `server.unset_environment(name)`. See below for `.remove_environment()`. ### TypeScript **Set:** `server.setEnvironment(name, value)`, `session.setEnvironment(...)` **Read all:** `server.showEnvironment()`, `session.showEnvironment()` **Unset:** `server.unsetEnvironment(name)`, `session.unsetEnvironment(name)` ### Go **Set:** `server.SetEnvironment(ctx, name, value, opts)` (global, `-g`) **Read all:** `server.ShowEnvironment(ctx)` **Unset:** `server.UnsetEnvironment(ctx, name)` ### Rust **Set:** `server.set_environment(...)`, `session.set_environment(...)` **Read all:** `server.environment_all()`, `session.environment_all()` **Unset:** `server.unset_environment(name)`, `session.unset_environment(name)` ### Java **Set:** Not documented here; see the Java and C++ note below. **Read all:** Not documented here; see the Java and C++ note below. **Unset:** Not documented here; see the Java and C++ note below. ### .NET **Set:** `server.Environment.SetAsync(name, value)`, `session.Environment.SetAsync(...)` **Read all:** `server.Environment.GetAllAsync()` **Unset:** `server.Environment.UnsetAsync(name)`, `.RemoveAsync(name)` ### C++ **Set:** Not documented here; see the Java and C++ note below. **Read all:** Not documented here; see the Java and C++ note below. **Unset:** Not documented here; see the Java and C++ note below. ### Swift **Set:** `server.setEnvironment(name, to: value, in: scope)` **Read all:** `server.environment(scope)` **Unset:** `server.unsetEnvironment(name, in: scope)`, `.removeEnvironment(name, in:)` ### Examples ```typescript await server.setEnvironment("EDITOR", "vim"); await session.setEnvironment("EDITOR", "hx"); await session.showEnvironment(); ``` Python's `set_environment` writes a value, and `unset_environment` (`-u`) removes the entry. `remove_environment` (`-r`) marks the variable for exclusion from new processes, including when tmux inherited it at server startup; the listing retains it as `-NAME`. Swift exposes the same distinction through `setEnvironment`, `unsetEnvironment`, and `removeEnvironment`. A remove operation is not documented here for TypeScript, Go, or Rust. ### Java and C++: no verified access to this table at all Java and C++ examples here cover environment values supplied at process creation, not reads or writes to an existing persistent table. Java's `SessionSpec.Builder.environment(Map)`, `WindowSpec.Builder.environment(Map)`, and `SplitSpec.Builder.environment(Map)` pass initial variables to `new-session -e`, `new-window -e`, and `split-window -e`. Consult the port reference if you need to change the environment of an existing session. --- # Socket and servers Source: https://libtmux.org/en/ts/latest/topics/socket-and-servers/ > How a port names one specific tmux server among several, checks whether it's actually there, and tells one running instance apart from another. A tmux server is selected by its Unix-domain socket. Use different sockets for independent servers, such as a development session and an isolated test server. Ports expose tmux's default, named (`-L`), and explicit-path (`-S`) socket selectors: ## Naming a server | Port | Default | Named socket (`-L`) | Explicit path (`-S`) | |------|---------|----------------------|------------------------| | Python | `Server()` | `Server(socket_name="work")` | `Server(socket_path="/tmp/tmux-1000/work")` | | TypeScript | `new Server()` | `new Server({ socketName: "work" })` | `new Server({ socketPath: "..." })` | | Go | `tmux.NewServer(tmux.ServerOptions{})` | `tmux.ServerOptions{SocketName: "work"}` | `tmux.ServerOptions{SocketPath: "..."}` | | Rust | `Server::new()` | `Server::builder().socket_name("work").build()?` | `Server::builder().socket_path("...").build()?` | | Java | `ServerEndpoint.defaultSocket()` | `ServerEndpoint.namedSocket("work")` | `ServerEndpoint.socketPath(path)` | | .NET | `new ServerConnectionOptions()` | `new ServerConnectionOptions(socketName: "work")` | `new ServerConnectionOptions(socketPath: "...")` | | C++ | `Server::at_default()` | `Server::at_socket_name("work")` | `Server::at_socket_path("...")` | | Swift | no bare default: see below | `Server(socketName: "work")` | `Server(socketPath: "...")` | ```typescript const named = new Server({ socketName: "work" }); ``` Choose either a socket name or a socket path. TypeScript rejects both together with `TypeError`; Go documents that `SocketPath` takes precedence. tmux uses `TMUX_TMPDIR` to resolve the directory for default and named sockets. Swift requires an explicit `socketPath` or `socketName` argument. To reach tmux's default socket, use `Server(socketName: "default")`. Python's `Server(socket_name_factory=...)` and .NET's `ServerConnectionOptions(socketNameFactory: ...)` accept a callable that generates socket names. Use a unique name for each isolated test server. ## Is the server actually there? A server handle does not prove that the target server is running. Use a liveness check when your program needs to distinguish a live server from an unavailable socket: | Port | Check | |------|-------| | Python | `server.is_alive()` → `bool` | | TypeScript | `await server.isAlive()` → `Promise`; `await server.raiseIfDead()` throws with tmux's own reason instead | | Go | `server.IsAlive(ctx)` → `(bool, error)`: the `error` is reserved for a question that couldn't be answered at all, not for "not alive" | | Rust | `server.is_alive().await` → `bool`; `server.check_alive().await` is the fallible twin, for when the *reason* matters | | Java | `server.isAlive()` → `boolean` | | .NET | `await server.IsAliveAsync()` → `Task` | | C++ | `server.is_alive(timeout)` → `bool` | | Swift | `try await server.isRunning()` → `Bool` | ```typescript if (await server.isAlive()) { await server.snapshot(); } ``` TypeScript's `isAlive()` and Rust's `is_alive()` return a boolean. Use TypeScript's `raiseIfDead()` or Rust's `check_alive()` when you need failure details. ## Killing a server, and telling two apart Kill an entire server with Python's `server.kill_server()`, TypeScript's `await server.kill()`, Go's `server.Kill(ctx)`, Rust's `server.kill().await?`, Java's or Swift's `server.killServer()`, .NET's `await server.KillAsync()`, or C++'s `server.kill()`. Java's `Server.close()` only releases the local connection; see [Context managers](../context-managers/#java-server-is-closeable-but-closing-one-doesnt-kill-it). Two handles can select the same socket. Python's `Server.__eq__` compares `socket_name` and `socket_path`. Go's `server.Equal(other)` resolves relative paths and environment-dependent socket names against each handle's captured binding. A restarted server can reuse a socket path while having different state. TypeScript's `TmuxServerRestartedError` and Go's `ErrDaemonReplaced` detect a handle encountering a replacement daemon. --- # Errors and exceptions Source: https://libtmux.org/en/ts/latest/topics/errors-and-exceptions/ > What a failed tmux command becomes in each port, and the question every one of them has to answer before letting you retry it. A command can fail because tmux rejects it, or because the transport stops before returning a reply. Ports report these failures through typed exceptions or return values. Before retrying a mutation, determine whether tmux may already have received it. For lookup failures caused by zero or multiple matches, see [Filtering and queries](/concepts/queries/#the-cardinality-contract-side-by-side). ## A failed command, as a value | Port | How it fails | Base type | |------|--------------|-----------| | Python | throws | `LibTmuxException`: carries an optional `subcommand`; `str()` reads `": "` | | TypeScript | throws | `LibTmuxException extends Error`, with `TmuxCommandError` (tmux ran and refused) and `TmuxTransportError` (it didn't get an answer) as the two shapes that matter here | | Go | returns `(T, error)` | no shared base type: small typed `...Error` structs plus sentinel `errors.New` values, composed with `errors.Is` / `errors.As` and `%w` wrapping | | Rust | returns `Result` | one `Error` enum, `#[non_exhaustive]`, matched rather than caught | | Java | throws (unchecked) | `LibTmuxException extends RuntimeException` | | .NET | throws | `LibTmuxException`, with typed subclasses per failure (`TmuxCommandException`, `TmuxTransportException`, `TmuxObjectNotFoundException`, and a dozen more) | | C++ | returns `expected` | `CommandFailure { kind, delivery, exit_code, diagnostic }`: no exception type at all | | Swift | throws (typed) | `enum TmuxError: Error`, thrown as `throws(TmuxError)`: Swift's typed-throws syntax, not a bare `throws` | Rust and C++ return result values. Go returns an `error` that callers inspect with `errors.Is` or `errors.As`. Exception-based ports report failures through their exception hierarchies. TypeScript's own docs make the split between its two exception shapes concrete: ```typescript import { TmuxCommandError, TmuxTransportError } from "libtmux"; try { await pane.capture(); } catch (error) { if (error instanceof TmuxCommandError) { error.args; // the argument vector error.exitCode; error.stderr; // tmux's own lines } else if (error instanceof TmuxTransportError) { error.kind; // "cancelled" | "pipe" | "protocol" | "spawn" | "timeout" error.delivery; // see below: this is the question a retry depends on } } ``` ## Is it safe to retry? Retry a mutation automatically only when you know it was not dispatched, or when repeating it is safe for your operation. A timeout, cancellation, or dropped connection can occur after tmux has acted. These APIs expose delivery information: | Port | Name | States | |------|------|--------| | TypeScript | `TmuxTransportError.delivery` | `"not_started"` / `"written"` / `"replied"` / `"indeterminate"`: only `not_started` is safe to retry blindly | | .NET | `LibTmuxException.Dispatch` (`TmuxDispatchState`) | `NotDispatched` / `Dispatched` / `Unknown` (the default) | | Java | `DispatchOutcome`, via `TmuxTimeoutException.outcome()` | `NOT_DISPATCHED` / `COMPLETE` / `UNKNOWN` | | C++ | `DeliveryStatus` | `not_started` / `written` / `replied` / `indeterminate` | | Rust | `ControlModeErrorKind` (behind the `control-mode` feature) | `DispatchTimedOut` (safe to retry) vs. plain `TimedOut` (not: the connection may have already committed the command) | | Python, Go's one-shot path | - | inspect the exit status after normal completion; an interrupted call needs separate state verification | | Go's control-mode pool | handled internally, not exposed | a failed pooled connection is retired rather than reused, rather than handing the caller a retry-safety flag to check | | Swift | documented, not typed | `ControlSession`'s own doc comment states the same rule in prose ("a command that never reached tmux is safe to retry") without a dedicated enum | ```typescript import { TmuxTransportError } from "libtmux"; try { await session.newWindow({ name: "build" }); } catch (error) { if (error instanceof TmuxTransportError && error.delivery === "not_started") { // safe to retry: nothing reached tmux } } ``` A state such as `not_started`, `NotDispatched`, `NOT_DISPATCHED`, or `DispatchTimedOut` identifies a request that did not reach tmux. Treat unknown delivery as potentially executed. C++ and TypeScript also distinguish `written`, where the transport accepted the request but no terminal reply has arrived. For subprocess calls that complete normally, inspect the exit status. If a call is interrupted or times out without a delivery state, check tmux's resulting state before repeating a mutation. --- # MCP for TypeScript Source: https://libtmux.org/en/ts/latest/mcp/ > Run or embed @libtmux/mcp to inspect tmux, drive panes, and wait for commands. `@libtmux/mcp` exposes tmux through an MCP stdio server and an embeddable TypeScript factory. Clients can inspect topology, capture output, run framed shell commands, and create or arrange panes. The package requires Node 22 or newer, or Bun 1.3.14 or newer, plus tmux 3.2a or newer. Real tmux operation is supported on Linux; the package's macOS artifact checks do not establish runtime support. ## Start here - [Install](#install) points an MCP client at this server. - [Tools](./tools/) lists the MCP operations, arguments, and results. - [Guides](./guides/) install and connect a client. - [Topics](./topics/) explain toolsets, socket provenance, and waiting. - [Examples](./examples/) call a tool, then explore server internals. - [Language API](./reference/) documents embedding and implementation types. The server exposes a static `tmux://capabilities` resource. It does not register workflow prompts or dynamic hierarchy resources. Use [Workspace Manager](../workspace/) to load and apply declarative configurations in application code. MCP clients create topology through individual tools; there is no workspace-file tool. [Package and runtime contract](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/mcp/README.md). --- # TypeScript MCP topics Source: https://libtmux.org/en/ts/latest/mcp/topics/ > Understand immutable tool selection, tmux provenance, pane protections, and output cursors. The TypeScript MCP server selects its endpoint and tool surface at startup. Later environment changes do not retarget a running process. ## Toolsets and startup provenance `inspect`, `manage`, `execute`, and `teardown` are independent sets. `LIBTMUX_TOOLSETS` selects sets, `LIBTMUX_TOOLS` adds exact names, and `LIBTMUX_EXCLUDE_TOOLS` removes names last. Exclusions also govern nested operations in `call_read_tools_batch`. An explicitly empty selection starts with no tools. Unknown names, empty comma-separated elements, and retired safety/allowlist variables fail startup before contacting tmux. Without socket or configuration variables, the executable selects `libtmux-mcp`. If it creates that daemon with the shipped minimal configuration and verifies its startup marker, all four sets are enabled by default. Existing or explicitly selected daemons default to `inspect,manage,execute`. Tool filtering controls this MCP interface. A shell command still acts with the tmux user's authority, and a socket name does not confine processes. ## Pane input and human activity Input preflight checks caller context, attached human clients, pane modes, and synchronized-input membership. A `force` argument confirms only the exact MCP caller pane; it does not override the other checks. `run_shell_command` requires one live supported POSIX shell and refuses a synchronized input cohort. It checks before setup and immediately before dispatch. These observations are not an atomic tmux transaction. ## Commands, waits, and cursors Use `run_shell_command` for authored commands. Its result separates command output from the echoed wrapper and includes the real exit status. It runs the command in a subshell, so directory and environment changes do not persist in the parent shell. Use `wait_for_text` for output produced elsewhere and `capture_since` for repeated reads. Waits report why they stopped, their effective timeout, and captured output. Cancelling a request stops its wait. Setting `LIBTMUX_MCP_LIVE=0` prevents control-mode connections. `wait_for_text` remains listed but returns a `no_stream` error; `capture_since` returns a bounded non-streaming capture. [Tool and runtime semantics](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/mcp/README.md). --- # Connect a TypeScript MCP client Source: https://libtmux.org/en/ts/latest/mcp/guides/ > Launch @libtmux/mcp on a named socket and select the tools the client receives. Launch `@libtmux/mcp` from a client with a dedicated socket name and an explicit toolset selection. Ensure the client can locate Node or Bun and tmux in its own environment. ## Start the stdio server With Node 22 or newer: ```console $ LIBTMUX_SOCKET=docs-agent LIBTMUX_TOOLSETS=inspect \ npx -y @libtmux/mcp@latest ``` The process waits for MCP requests on stdin. Its diagnostics go to stderr. For clients using the `mcpServers` format: ```json { "mcpServers": { "tmux-typescript": { "command": "npx", "args": ["-y", "@libtmux/mcp@latest"], "env": { "LIBTMUX_SOCKET": "docs-agent", "LIBTMUX_TOOLSETS": "inspect" } } } } ``` ## Verify and expand the surface After connecting, read `tmux://capabilities` and call `list_sessions`. The resource reports the frozen tool selection and connection provenance. To permit authored commands, choose `inspect,execute`. Add `manage` for topology changes classified in that set. Read each tool's classification instead of assuming that creating a pane has no process effects. For an exact selection, set `LIBTMUX_TOOLSETS` to the empty string and `LIBTMUX_TOOLS` to `list_sessions,capture_pane`. Restart after changing the environment. ## Choose another endpoint `LIBTMUX_SOCKET_PATH` selects an absolute socket path and is mutually exclusive with `LIBTMUX_SOCKET`. `LIBTMUX_TMUX_BIN` selects the executable. `LIBTMUX_TMUX_CONFIG` supplies a nonempty absolute configuration path when starting a daemon; connecting to an existing daemon cannot replace its startup configuration. [Launch and configuration source](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/mcp/README.md). --- # TypeScript MCP examples Source: https://libtmux.org/en/ts/latest/mcp/examples/ > List sessions through the TypeScript MCP server and inspect implementation examples. Connect the server using the [setup guide](../guides/), then call [`list_sessions`](../tools/list_sessions/) from your MCP client. ## List sessions This is the `params` object for an MCP `tools/call` request. Send it through the connected client: ```json { "name": "list_sessions", "arguments": {} } ``` Use the returned session IDs when choosing a window or pane. The [tool reference](../tools/list_sessions/) describes this port's result and optional arguments. ## Internals The following examples are for applications that embed or extend the server. Installing and connecting an MCP client does not require this code. The port's `mcp-agent` example connects an MCP client and server with linked in-memory transports. The same tool names and arguments are used over stdio. ### Connect an embedded client This function comes from the [agent example](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/examples/mcp-agent/mcp-agent.ts). It accepts an existing libtmux `Server`. ```typescript import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; import { createTmuxMcpServer } from "@libtmux/mcp"; import type { Server } from "libtmux/server"; export async function connectAgent(server: Server): Promise { const client = new Client({ name: "example", version: "0.0.0" }); const [clientSide, serverSide] = InMemoryTransport.createLinkedPair(); await Promise.all([ createTmuxMcpServer(server, { environment: { LIBTMUX_TOOLSETS: "inspect,execute" }, }).connect(serverSide), client.connect(clientSide), ]); return client; } ``` The caller owns the returned client. Close it after use. The complete example adds helpers for `run_shell_command`, `wait_for_text`, `capture_since`, and topology creation. ### Run the example's checks From the TypeScript repository after installing its dependencies: ```console $ bun test examples/mcp-agent ``` The [example tests](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/examples/mcp-agent/mcp-agent.test.ts) start real tmux on an isolated socket and drive it through the MCP client. They cover exit status, echoed command text, waits, and successive cursor reads. Rendering this excerpt does not execute those tests. For declarative application code, see [Workspace builder examples](../../workspace/internals/examples/). --- # TypeScript MCP API Source: https://libtmux.org/en/ts/latest/mcp/reference/ > Find the TypeScript server factory and the MCP tool, schema, and capability contracts. For MCP client requests, use the [tool reference](../tools/). This page covers language APIs for embedding or extending the server. Import the server factory to embed MCP. Use protocol tool names when communicating through an MCP client. ## Embedding API `createTmuxMcpServer(tmux, options)` accepts a libtmux `Server` and returns the SDK's [`McpServer`](https://ts.sdk.modelcontextprotocol.io/server). Options can supply the caller environment, tool-selection environment, resolved policy, and startup provenance. Without supplied provenance, the factory treats the server as unprobed with unknown configuration. `serverFromEnvironment(environment)` builds the libtmux endpoint from startup configuration. The executable combines endpoint resolution, startup probing, policy selection, and stdio transport. An embedding application owns its transport and shutdown. [Exported server functions](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/mcp/src/server.ts) and [in-process example](../examples/) describe this boundary. ## MCP reference The [tool reference](../tools/) covers wire operations. Registered tools publish input schemas, output schemas, structured results, and effect annotations. The startup selection determines which tools are listed and callable. `tmux://capabilities` is a static JSON resource containing the frozen surface and its declarations. There are no workflow prompts or dynamic hierarchy-resource subscriptions. [Resource registration](https://github.com/libtmux/libtmux-ts/blob/f85b8de551353f746d50eaf36bf0112f4fe5a528/packages/mcp/src/resources.ts). Workspace parsing and application belong to [`@libtmux/workspace`](../../workspace/reference/), not to a workspace MCP tool. ## API declarations - [mcp.startup.ConfigurationProvenance](https://libtmux.org/en/ts/latest/mcp/reference/mcp-startup-configurationprovenance/) - [mcp.server.createTmuxMcpServer](https://libtmux.org/en/ts/latest/mcp/reference/mcp-server-createtmuxmcpserver/) - [mcp.server.describeStartupFailure](https://libtmux.org/en/ts/latest/mcp/reference/mcp-server-describestartupfailure/) - [mcp.server.main](https://libtmux.org/en/ts/latest/mcp/reference/mcp-server-main/) - [mcp.policy.Policy](https://libtmux.org/en/ts/latest/mcp/reference/mcp-policy-policy/) - [mcp.server.serverFromEnvironment](https://libtmux.org/en/ts/latest/mcp/reference/mcp-server-serverfromenvironment/) - [mcp.startup.ServerStartup](https://libtmux.org/en/ts/latest/mcp/reference/mcp-startup-serverstartup/) - [mcp.startup.ServerState](https://libtmux.org/en/ts/latest/mcp/reference/mcp-startup-serverstate/) - [mcp.policy.Toolset](https://libtmux.org/en/ts/latest/mcp/reference/mcp-policy-toolset/) [Protocol catalog](https://libtmux.org/en/ts/latest/mcp/tools.json) --- # Workspace Manager for TypeScript (in development) Source: https://libtmux.org/en/ts/latest/workspace/ > Run or install the prerelease TypeScript tmux-workspace CLI from npm; implementation coverage remains partial. **Workspace Manager for TypeScript is in development.** The `tmux-workspace` CLI is published to npm as a prerelease. Its command and configuration coverage is partial. Native services load/reuse/append and capture sessions, discover and search documents, convert formats, import configurations and run editors. Loading includes terminal attach/switch, before scripts, progress and diagnostics. Python shell evaluation and explicit plugin/custom-builder loads use an optional tmuxp bridge. Ordinary native loads do not start Python. Native command metadata and Bash, Zsh and fish completion come from the parser; completion starts no Node, Bun or tmux process. ## Load a workspace from the terminal [Install](#install) runs the `@libtmux/workspace-cli` prerelease from npm without installing it, or installs it to put `tmux-workspace` on `PATH`. The [installation walkthrough](./guides/installation/) loads a small workspace on a private socket. Inspect the command without starting tmux: ```console $ npx -y @libtmux/workspace-cli --help ``` Use detached load for the walkthrough. JSON and NDJSON output are available; choose the mode explicitly when scripting. The command/configuration reference below also documents tmuxp behavior and compatibility targets, so it is not a claim that every referenced feature works in this implementation. ## Current coverage Interactive prompts and complete configuration/platform acceptance remain unfinished. Python extensions require tmuxp 1.74.0 and report observed effects without claiming ownership or rollback. See [hooks and builders](./configuration/hooks/) for append restrictions. Capture reports live state rather than recovering the original workspace commands or extension intent. For the released Python workflow, use [tmuxp](https://tmuxp.git-pull.com/) and its [Python workspace guide](/py/latest/workspace/guides/). It is a separate application and remains useful when a required native feature is incomplete. ## Start here The `@libtmux/workspace` library package remains available for applications that build sessions through code. [Internals](./internals/) documents that API: - [Guides](./internals/guides/) show application setup and builder calls. - [Topics](./internals/topics/) explain supported configuration and behavior. - [Examples](./internals/examples/) exercise the library or source consumer. - [API](./reference/) covers the builder and configuration interfaces. ## tmuxp command and configuration reference Use the CLI's help and the limits above when applying these compatibility references to native execution. - [Installation walkthrough](./guides/installation/) installs and runs the published native CLI. - [Inspect through MCP](./guides/inspect-with-mcp/) connects to the loaded session. - [Command reference](./cli/) lists tmuxp commands, flags and compatibility targets. - [Configuration](./configuration/) covers fields, normalization and execution. - [Example gallery](./examples/gallery/) includes upstream fixtures and prerequisites. - [Compatibility status](./reference/compatibility/) records builder/reference gaps. - [JSON, NDJSON, and color](./reference/output/) describes the shared output design.