Workspace guide
libtmux-workspace 0.1.0.alpha.3 · Source
Load bounded YAML or JSON, inspect an immutable creation plan, then explicitly apply it to an open libtmux server.
API reference · Command reference · Guides · RubyGems
Part of libtmux for Ruby. Built on libtmux.
Check a workspace file, see the ordered steps a load would run, and create the
session on a running tmux server, from the libtmux-workspace command or from
Ruby.
Validation and offline planning need no running daemon, and imports start no tmux process.
Contents — Quick start · Write the file · Create the session · Failed apply · Attach or switch · Scripts · Leaves out · Support · Documentation
Quick start¶
Install the alpha and its libtmux-workspace executable. It needs Ruby 3.3 or
newer, and tmux 3.3a or newer to load a plan:
$ gem install libtmux-workspace --preSave this declared subset of tmuxp-style data as workspace.yaml. Relative
directories resolve against the configuration file’s directory.
session_name: workenvironment: PROJECT_MODE: developmentwindows: - window_name: editor window_index: 1 layout: tiled panes: - shell_command: printf 'editor ready\n' - {}Check the file. validate reads no tmux state:
$ libtmux-workspace validate workspace.yamlWorkspace configuration is valid.Print the ordered operations a load would run. Offline plan does
not contact tmux either:
$ libtmux-workspace plan workspace.yamloffline_create: 11 ordered steps1. create_session session (creation)2. set_session_option session (configuration)3. set_window_option window:0 (configuration)4. move_initial_window window:0 (configuration)5. split_pane pane:0:0 (creation)6. select_layout window:0 (configuration)7. send_command pane:0:0 (dispatch_only)8. unset_window_option window:0 (configuration)9. select_pane pane:0:0 (configuration)10. select_window window:0 (configuration)11. unset_session_option session (configuration)Write the file¶
Workspace.load: the declared subset¶
load reads a regular file of bounded size and infers JSON or YAML from its
extension. Workspace.parse takes the text and a base
directory instead. Both return an immutable workspace:
- Root:
session_name, nonemptywindows,options,window_options. - Window:
window_name, nonemptypanes,window_index,focus,layout,options. - Pane: a command string, or a mapping with
focus,splitandsize. - Root, windows and panes accept
start_directory,environment,shell_commandandshell_command_before. - Layouts are
even-horizontal,even-vertical,main-horizontal,main-verticalandtiled. - At most one window and one pane per window declare focus.
- Without a filename, the command looks for exactly one
.tmuxp.yaml,.tmuxp.ymlor.tmuxp.jsonin the current directory. - An unknown field or an unsupported feature fails with a
ConfigErrorthat names its position in the file. - YAML tags, anchors and aliases are rejected, and no Ruby, ERB, plugins or callbacks run.
${NAME}substitution is opt-in: passexpand_environment: true, environment: {"NAME" => "value"}toloadorparse, or--expand-environment --env NAME=VALUEto the command.workspace.to_hexports an immutable, normalized and reloadable configuration, with expanded values and absolute directories.
The full field, option and limit lists are in Workspace parsing.
Create the session¶
libtmux-workspace load: build it on a running server¶
load needs a running server. It never starts one, so start a private server
with a keepalive session and no user configuration:
$ WORKSPACE_TMP=$(mktemp -d)$ tmux -S "$WORKSPACE_TMP/tmux.sock" -f /dev/null new-session -d -s keepaliveCreate the workspace’s session on that socket. It stays detached:
$ libtmux-workspace load \ --socket "$WORKSPACE_TMP/tmux.sock" \ workspace.yamlWorkspace created; 11 steps completed. Shell commands were dispatched.The new work session holds the editor window:
$ tmux -S "$WORKSPACE_TMP/tmux.sock" list-windows -t workStop only the private server and remove its directory:
$ tmux -S "$WORKSPACE_TMP/tmux.sock" kill-server$ rm -rf "$WORKSPACE_TMP"Loading the same file again fails with status 1: a plan creates a new session and never reconciles an existing one.
Plan#apply: the same from Ruby¶
Workspace.load reads the file,
workspace.plan plans it against a snapshot, and
apply creates the session. Calling apply authorizes the configuration’s
shell commands:
require "libtmux"require "libtmux/workspace"
LibTmux::Server.start do |server| workspace = LibTmux::Workspace.load(File.expand_path("../workspace.yaml", __dir__)) plan = workspace.plan(snapshot: server.snapshot) puts "planned steps: #{plan.steps.length}" result = plan.apply(server: server) puts "applied: #{result.success?}" puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}"endThe program runs next to the workspace.yaml above, on a private server, and
prints:
planned steps: 11applied: truesessions: work- A snapshot makes the plan reject a name that already exists, and every
applytakes a fresh snapshot and checks again. - The plan creates a new session. It does not reconcile, replace or remove an existing one.
- Success shows that tmux accepted the dispatch of each shell command. It does not show that the command finished.
The complete program also runs the
installed command with --switch. The contract is in
Workspace apply.
Recover from a failed apply¶
ApplyError#result: what exists after a failure¶
A failed apply raises ApplyError. Its immutable
result holds the completed step IDs, the
created_refs tmux returned, the
effects it observed, and the failed action. Pass compensate: true to remove
only the session the apply created:
require "json"require "libtmux"require "libtmux/workspace"
LibTmux::Server.start do |server| server.new_session(name: "borrowed", command: ["/bin/cat"]) crowded = LibTmux::Workspace.parse( JSON.generate({session_name: "crowded", windows: [{window_name: "small", panes: Array.new(40) { {} }}]}), format: :json, base_directory: __dir__ ) begin crowded.plan.apply(server: server, compensate: true) rescue LibTmux::Workspace::ApplyError => error puts "failed action: #{error.result.failed_action}" puts "created a session: #{error.result.created_refs.key?('session')}" puts "compensation: #{error.result.compensation}" end puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}"endThat many panes do not fit in one window, so tmux refuses a split after the session exists. It prints:
failed action: split_panecreated a session: truecompensation: completedsessions: borrowed- Compensation removes the new session only after a tmux guard shows that every window and pane in it has a positively identified created reference.
- An unknown initial entity, or a borrowed entity moved into the session, makes it refuse.
- Compensation cannot undo what shell commands did. Without it, the partial state stays for you to inspect.
- Diagnostics omit command payloads and paths. Names are never used to guess ownership.
The command takes --compensate. The contracts are in
Workspace results.
Attach or switch to the new session¶
load --switch: move a client to the session¶
load --attachopens the CLI’s/dev/ttyafter creation and runs an owned terminal client until you detach. It requires a validTERM.load --switch CLIENTswitches a current tmux client to the created session after apply, keeping the session’s environment.CLIENTis a client name, a full TTY path or a TTY path without/dev/. Use--switch=VALUEfor a selector that begins with-.- A missing client, or a failure to open or attach the terminal, keeps the created session and returns status 3.
- Attach and switch are mutually exclusive, and neither infers a latest
client. The library’s
Plan#applydoes neither.
The selector rules are in Client switching.
Script it¶
--json: one object on stdout¶
Check the installed gem version without reading a configuration or contacting tmux:
$ libtmux-workspace --versionHuman plans list ordered operations and their effects. --json prints the
same plan data as Plan#to_h, including configured
command text and paths:
$ libtmux-workspace plan \ --json \ workspace.yamlload and plan --live require --socket for an existing
server. This detached creation example uses an explicitly supplied
TMUX_SOCKET value:
$ libtmux-workspace load \ --socket "$TMUX_SOCKET" \ --json \ workspace.yamllibtmux-workspace --help lists every option. The
command reference has a page for each command.
| Option | Effect |
|---|---|
--json¶ | Write one structured result or error object to stdout |
--socket PATH¶ | Select the existing server’s socket for load or plan --live |
--live¶ | Capture a snapshot before planning |
--timeout SECONDS¶ | Bound each apply, live capture or switch operation; the default is 5 |
--compensate¶ | Run guarded cleanup after an apply failure |
--attach¶ | Attach this CLI’s terminal after a successful load |
--switch CLIENT¶ | Switch an explicit current client to the new session after load |
--expand-environment¶ | Expand ${NAME} in directories and environment values; requires --env |
--env NAME=VALUE¶ | Supply a value for expansion; ambient environment variables are not copied |
| Exit status | Meaning |
|---|---|
| 0¶ | Validation, planning or apply succeeded; requested attach/switch succeeded |
| 1¶ | Execution failed before known application effects |
| 2¶ | Configuration or arguments are invalid |
| 3¶ | Application was partial or uncertain, or a later attach/switch/cleanup failed |
| 130¶ | Interrupted; available effect ledger is retained |
Apply errors include ApplyResult#to_h. Human
errors write the diagnostic and any ledger to stderr. Diagnostics omit
configuration payloads; plan rendering and canonical export contain those
values by design.
What it leaves out¶
- Reconciliation. A plan creates a new session and never replaces or removes an existing workspace. The contract is in Workspace plans.
- Shell command completion. Apply records a shell command as dispatched. Embedded newlines can execute while the text is inserted. See Workspace apply.
- Rollback of shell effects. Compensation removes only tmux objects the apply created. See Workspace results.
- Features beyond the declared subset. Plugins, ERB and callbacks are
rejected, and a
ConfigErrornames the position. - A guessed client. Attach and switch need an explicit client. See Client switching.
Support and status¶
Alpha. APIs may change between releases.
- Ruby 3.3 or newer. The gem depends on the same version of
libtmux, JSON 3.0 and Psych 5.5. - tmux 3.3a or newer to load or apply a plan.
- Older daemons.
loadcreates nothing and prints the refusal with the floor and the remedy. With--json, the refusal is inerror.messageanderror.kindistmux_unsupported. - Linux, including WSL, and macOS. The library and the command share the core compatibility workflow: Ruby 3.3, 3.4 and 4.0 with tmux 3.3a through 3.8 on Ubuntu 24.04, with per-revision results. macOS is supported on a best-effort basis, and CI coverage for it is highly limited.
- History limit. On tmux 3.3a to 3.6 the reused initial pane keeps the
global
history-limit. Set the server’s global value before you apply to get one history size.
Documentation¶
| Reference¶ | Every public workspace class and method |
| Command reference¶ | A page for validate, plan and load |
| Guides¶ | Validate, inspect and explicitly apply a plan |
| Topics¶ | Format, planning boundaries, effects and failure ledgers |
| Public behavior¶ | Parsing, plans, apply, results and the command |
| Recipes¶ | The workspace plan, load and failure program |