libtmux-workspace 0.1.0.alpha.1 → 0.1.0.alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9514613d11f2325d349731f229675510da8a0e11a1c053fae88f198464e38d84
4
- data.tar.gz: a367212c8b49b482cfe9bca60323f8360d38814af096f0f306ac49f642727450
3
+ metadata.gz: 0c27c9bbe4d2cf44b5cbee027902519d4443858717ac679f12d879b426869d1c
4
+ data.tar.gz: b8da758f2977f2a1b4fb15230951db6e094704b24f117b54d0eae4aa42e2b821
5
5
  SHA512:
6
- metadata.gz: cbce1a3ca65ec06fd22647ee9e2ff4af2bd85c488e1a9466c88d5dfe091cdacf7585ae9ad8310d995e1c87a033bca7869d1cf1face0f8041fd812a9531fd62e2
7
- data.tar.gz: a972900033105a2cc722f20811a72cd065d124e026eee5f55ed65ae7fb5d9dcde6a64e9b2730932542eded315946f31cada5dd557835ab662c22aafc4a639239
6
+ metadata.gz: 4688b7848b4408a2a7f397d8468ddbb0581f8e330dbea1472d414b37f12ebf32be957b43a3a3b386b758f3b51ebd1416255ddd2714978441978f3c50e5c909aa
7
+ data.tar.gz: d6fcee3d35cd4261025cd62e6a9beef1b353ccab5bc08c1ceb56f05cd5535a9f02746371f2136e558c662741e4cfa4490395b732d85174543ca26f4f82745aac
data/README.md CHANGED
@@ -1,24 +1,54 @@
1
+ <!-- libtmux-logo -->
2
+ <div align="center">
3
+ <picture>
4
+ <source srcset="assets/logo.svg" type="image/svg+xml">
5
+ <img src="assets/logo.png" width="128" height="128" alt="libtmux for Ruby workspace">
6
+ </picture>
7
+ </div>
8
+ <!-- /libtmux-logo -->
9
+
10
+ <div align="center">
11
+
1
12
  # libtmux-workspace
2
13
 
3
- Load bounded YAML or JSON, inspect an immutable creation plan, then explicitly
4
- apply it to an open libtmux server. The library
5
- and installed command-line executable share the core
6
- [compatibility matrix](https://github.com/libtmux/libtmux-ruby/actions/workflows/compatibility.yml);
7
- consult its exact per-revision results.
14
+ [Load][workspace-load] bounded YAML or JSON, inspect an immutable
15
+ [creation plan][workspace-plan-class], then explicitly
16
+ [apply][workspace-plan-apply] it to an open libtmux server.
17
+
18
+ [API reference](https://libtmux.org/en/ruby/latest/workspace/reference/) ·
19
+ [Command reference](https://libtmux.org/en/ruby/latest/workspace/cli/) ·
20
+ [Guides](https://libtmux.org/en/ruby/latest/workspace/guides/) ·
21
+ [RubyGems](https://rubygems.org/gems/libtmux-workspace)
22
+
23
+ </div>
24
+
25
+ Part of [libtmux for Ruby](https://github.com/libtmux/libtmux-ruby#readme).
26
+ Built on [libtmux](https://github.com/libtmux/libtmux-ruby/tree/master/gems/libtmux).
27
+
28
+ Check a workspace file, see the ordered steps a load would run, and create the
29
+ session on a running tmux server, from the `libtmux-workspace` command or from
30
+ Ruby.
8
31
 
9
- The gem declares Ruby 3.3 or newer and depends on the same-version `libtmux`
10
- gem, JSON 3.0 and Psych 5.5. See the repository's
11
- [contribution guide](../../.github/CONTRIBUTING.md) for local builds and checks.
12
- Imports and planning do not start tmux or run commands.
32
+ Validation and offline planning need no running daemon, and imports start no
33
+ tmux process.
13
34
 
14
- Install the alpha and its `libtmux-workspace` executable:
35
+ **Contents** — [Quick start](#quick-start) ·
36
+ [Write the file](#write-the-file) ·
37
+ [Create the session](#create-the-session) ·
38
+ [Failed apply](#recover-from-a-failed-apply) ·
39
+ [Attach or switch](#attach-or-switch-to-the-new-session) ·
40
+ [Scripts](#script-it) · [Leaves out](#what-it-leaves-out) ·
41
+ [Support](#support-and-status) · [Documentation](#documentation)
42
+
43
+ ## Quick start
44
+
45
+ Install the alpha and its `libtmux-workspace` executable. It needs Ruby 3.3 or
46
+ newer, and tmux 3.3a or newer to load a plan:
15
47
 
16
48
  ```console
17
49
  $ gem install libtmux-workspace --pre
18
50
  ```
19
51
 
20
- ## Example
21
-
22
52
  Save this declared subset of tmuxp-style data as `workspace.yaml`. Relative
23
53
  directories resolve against the configuration file's directory.
24
54
 
@@ -37,127 +67,235 @@ windows:
37
67
  ```
38
68
  <!-- /example -->
39
69
 
40
- The [complete workspace program](../../examples/workspace_apply.rb) creates an
41
- isolated server, applies this document and checks compensation after a later
42
- failure. Calling `apply` authorizes the configuration's shell commands. This
43
- excerpt runs inside that program's cleanup wrapper:
70
+ Check the file. [`validate`][cli-validate] reads no tmux state:
71
+
72
+ ```console
73
+ $ libtmux-workspace validate workspace.yaml
74
+ ```
75
+
76
+ ```text
77
+ Workspace configuration is valid.
78
+ ```
79
+
80
+ Print the ordered operations a load would run. Offline [`plan`][cli-plan] does
81
+ not contact tmux either:
82
+
83
+ ```console
84
+ $ libtmux-workspace plan workspace.yaml
85
+ ```
86
+
87
+ ```text
88
+ offline_create: 11 ordered steps
89
+ 1. create_session session (creation)
90
+ 2. set_session_option session (configuration)
91
+ 3. set_window_option window:0 (configuration)
92
+ 4. move_initial_window window:0 (configuration)
93
+ 5. split_pane pane:0:0 (creation)
94
+ 6. select_layout window:0 (configuration)
95
+ 7. send_command pane:0:0 (dispatch_only)
96
+ 8. unset_window_option window:0 (configuration)
97
+ 9. select_pane pane:0:0 (configuration)
98
+ 10. select_window window:0 (configuration)
99
+ 11. unset_session_option session (configuration)
100
+ ```
101
+
102
+ ## Write the file
44
103
 
45
- <!-- example: workspace_apply/main -->
104
+ ### [`Workspace.load`][workspace-load]: the declared subset
105
+
106
+ `load` reads a regular file of bounded size and infers JSON or YAML from its
107
+ extension. [`Workspace.parse`][workspace-parse] takes the text and a base
108
+ directory instead. Both return an immutable workspace:
109
+
110
+ - Root: `session_name`, nonempty `windows`, `options`, `window_options`.
111
+ - Window: `window_name`, nonempty `panes`, `window_index`, `focus`, `layout`,
112
+ `options`.
113
+ - Pane: a command string, or a mapping with `focus`, `split` and `size`.
114
+ - Root, windows and panes accept `start_directory`, `environment`,
115
+ `shell_command` and `shell_command_before`.
116
+ - Layouts are `even-horizontal`, `even-vertical`, `main-horizontal`,
117
+ `main-vertical` and `tiled`.
118
+ - At most one window and one pane per window declare focus.
119
+ - Without a filename, the command looks for exactly one `.tmuxp.yaml`,
120
+ `.tmuxp.yml` or `.tmuxp.json` in the current directory.
121
+ - An unknown field or an unsupported feature fails with a
122
+ [`ConfigError`][workspace-configerror] that names its position in the file.
123
+ - YAML tags, anchors and aliases are rejected, and no Ruby, ERB, plugins or
124
+ callbacks run.
125
+ - `${NAME}` substitution is opt-in: pass
126
+ `expand_environment: true, environment: {"NAME" => "value"}` to `load` or
127
+ `parse`, or `--expand-environment --env NAME=VALUE` to the command.
128
+ - [`workspace.to_h`][workspace-to_h] exports an immutable, normalized and
129
+ reloadable configuration, with expanded values and absolute directories.
130
+
131
+ The full field, option and limit lists are in
132
+ [Workspace parsing][behavior-workspace-parsing].
133
+
134
+ ## Create the session
135
+
136
+ ### [`libtmux-workspace load`][cli-load]: build it on a running server
137
+
138
+ `load` needs a running server. It never starts one, so start a private server
139
+ with a keepalive session and no user configuration:
140
+
141
+ ```console
142
+ $ WORKSPACE_TMP=$(mktemp -d)
143
+ ```
144
+
145
+ ```console
146
+ $ tmux -S "$WORKSPACE_TMP/tmux.sock" -f /dev/null new-session -d -s keepalive
147
+ ```
148
+
149
+ Create the workspace's session on that socket. It stays detached:
150
+
151
+ ```console
152
+ $ libtmux-workspace load \
153
+ --socket "$WORKSPACE_TMP/tmux.sock" \
154
+ workspace.yaml
155
+ ```
156
+
157
+ ```text
158
+ Workspace created; 11 steps completed. Shell commands were dispatched.
159
+ ```
160
+
161
+ The new `work` session holds the `editor` window:
162
+
163
+ ```console
164
+ $ tmux -S "$WORKSPACE_TMP/tmux.sock" list-windows -t work
165
+ ```
166
+
167
+ Stop only the private server and remove its directory:
168
+
169
+ ```console
170
+ $ tmux -S "$WORKSPACE_TMP/tmux.sock" kill-server
171
+ ```
172
+
173
+ ```console
174
+ $ rm -rf "$WORKSPACE_TMP"
175
+ ```
176
+
177
+ Loading the same file again fails with status 1: a plan creates a new session
178
+ and never reconciles an existing one.
179
+
180
+ ### [`Plan#apply`][workspace-plan-apply]: the same from Ruby
181
+
182
+ [`Workspace.load`][workspace-load] reads the file,
183
+ [`workspace.plan`][workspace-plan-method] plans it against a snapshot, and
184
+ `apply` creates the session. Calling `apply` authorizes the configuration's
185
+ shell commands:
186
+
187
+ <!-- example: api_workspace_apply/main -->
46
188
  ```ruby
47
- workspace = LibTmux::Workspace.load(File.join(__dir__, "workspace.yaml"))
48
- plan = workspace.plan(snapshot: server.snapshot)
49
- Example.check(server.list_sessions.size == 1, "planning changed tmux")
50
- result = plan.apply(server: server)
51
- Example.check(result.success?, "workspace apply failed")
52
- Example.check(result.effects.any? { |effect| effect.outcome == :dispatch_only }, "shell dispatch overclaims completion")
53
- crowded = LibTmux::Workspace.parse(JSON.generate({
54
- session_name: "crowded", windows: [{window_name: "small", panes: Array.new(40) { {} }}]
55
- }), format: :json, base_directory: __dir__)
56
- error = Example.raises(LibTmux::Workspace::ApplyError) do
57
- crowded.plan.apply(server: server, compensate: true)
189
+ require "libtmux"
190
+ require "libtmux/workspace"
191
+
192
+ LibTmux::Server.start do |server|
193
+ workspace = LibTmux::Workspace.load(File.expand_path("../workspace.yaml", __dir__))
194
+ plan = workspace.plan(snapshot: server.snapshot)
195
+ puts "planned steps: #{plan.steps.length}"
196
+ result = plan.apply(server: server)
197
+ puts "applied: #{result.success?}"
198
+ puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}"
58
199
  end
59
- Example.check(!error.result.created_refs.empty?, "failure lost partial creation ledger")
60
- Example.check(error.result.compensation == :completed, "owned compensation failed")
61
- Example.check(server.list_sessions.map(&:ref).include?(borrowed.ref), "borrowed session was removed")
62
200
  ```
63
201
  <!-- /example -->
64
202
 
65
- ## Configuration
203
+ The program runs next to the `workspace.yaml` above, on a private server, and
204
+ prints:
66
205
 
67
- The plain declared subset has effective version 1. Optional `profile` and
68
- `version` must appear together as `libtmux-ruby.workspace` and `1`.
69
- `workspace.to_h` exports an immutable, normalized, reloadable configuration.
70
- It contains expanded values and absolute directories; callers control its
71
- storage and disclosure.
206
+ ```text
207
+ planned steps: 11
208
+ applied: true
209
+ sessions: work
210
+ ```
72
211
 
73
- - Root: `session_name`, nonempty `windows`, `options`, `window_options`.
74
- - Window: `window_name`, nonempty `panes`, `window_index`, `focus`, `layout`,
75
- `options`. Indexes are unique nonnegative integers; unspecified indexes use
76
- the lowest available value starting at the declared `base-index` or zero.
77
- - Pane: a command string or a mapping with `focus`, `split`, `size`. Split is
78
- `horizontal` or `vertical`. Size is positive cells or `1%` through `99%`;
79
- neither applies to the initial pane. Explicit sizes cannot accompany a
80
- final named layout.
81
- - Root, windows and pane mappings accept `start_directory`, `environment`,
82
- `shell_command` and `shell_command_before`. Environment maps merge from
83
- parent to child. Commands inherit unless overridden; before-commands append
84
- in parent-to-child order. Command values accept a string or string array.
85
-
86
- At most one window and one pane per window can declare focus; each defaults
87
- to the first. Layouts are `even-horizontal`, `even-vertical`, `main-horizontal`,
88
- `main-vertical` and `tiled`. Unknown fields and unsupported features fail with
89
- a `ConfigError` identifying their configuration position.
90
-
91
- Session options accept boolean `status`, `mouse`, `renumber-windows`;
92
- nonnegative `base-index`, `history-limit`, `status-interval`; and enumerated
93
- `status-position` and `status-justify`. Window options accept boolean
94
- `automatic-rename`, `allow-rename`, `remain-on-exit`, `synchronize-panes`,
95
- `aggressive-resize`; nonnegative `pane-base-index`, `main-pane-width`,
96
- `main-pane-height`; text `window-status-format`, `window-status-current-format`;
97
- and enumerated `pane-border-status`. Option text remains tmux option text,
98
- including any formats that tmux evaluates.
99
- `pane-base-index` cannot exceed 65535; the other numeric options accept
100
- integers through 2147483647.
101
-
102
- YAML tags, anchors, aliases, duplicate keys, multiple documents and complex
103
- mapping keys are rejected. JSON duplicate keys are rejected too. No Ruby,
104
- ERB, plugins or callbacks are evaluated. Defaults bound source and canonical
105
- bytes to 1 MiB, strings to 64 KiB, nesting to 32, nodes to 10,000, windows to
106
- 128 and panes to 1,024. The corresponding `max_*` parse/load keywords can
107
- adjust these positive limits. Configuration files must be regular files.
108
-
109
- `${NAME}` substitution is opt-in for directories and environment values:
110
- pass `expand_environment: true, environment: {"NAME" => "value"}` to load or
111
- parse. Only the supplied bounded mapping is consulted. Shell text and option
112
- values are unchanged; `~` has no special path meaning. Missing variables fail
113
- validation. Parsing checks path syntax; apply checks current accessibility.
114
- Concurrent filesystem changes can still trigger tmux's cwd fallback.
115
-
116
- ## Apply and failure results
117
-
118
- `workspace.plan(snapshot: snapshot)` retains that capture's binding identity
119
- and rejects a captured name conflict. Every apply takes a fresh snapshot and
120
- rechecks identity and name absence. The plan creates a new session; it does
121
- not reconcile, replace or remove a preexisting workspace. The first window
122
- and pane from each creation command are reused.
123
-
124
- Apply is synchronous, uses one monotonic timeout across its core operations,
125
- and accepts a cancellation token. Panes run `/bin/sh`; explicit initial pane
126
- environment overrides do not modify the session environment. Window indexes,
127
- splits, options, layout and focus follow the plan's order. Temporary local
128
- option overrides disable renumbering and pane synchronization during setup;
129
- the plan then restores declared values or inheritance.
130
-
131
- Session options apply before subsequent windows and split panes are created.
132
- On tmux 3.2a–3.6, the reused initial pane retains the global `history-limit`
133
- inherited at session creation. Later panes use the configured session value.
134
- For uniform history on these versions, configure the server's global value
135
- before applying the workspace. Apply does not change global options or replace
136
- the initial pane. On tmux 3.7+, setting the option also updates existing grids.
137
-
138
- Shell commands are sent as literal text followed by Enter. Both insertion
139
- and Enter are dispatch effects: embedded newlines can execute during text
140
- insertion. Success proves tmux accepted the dispatch, not that a shell
141
- command finished or succeeded. Shell commands can leave effects beyond tmux.
142
-
143
- An `ApplyError` exposes an immutable `result`: completed step IDs, positively
144
- identified `created_refs`, observed or dispatch-only effects, failed action,
145
- uncertainty and cleanup diagnostics. Diagnostics omit command payloads and
146
- paths. Lost creation replies stay uncertain; names are never used to guess
147
- ownership. A caller may pass `compensate: true` to kill only the positively
148
- returned new session on failure, after an atomic tmux guard proves that every
149
- current window and pane has a positively identified created reference. Unknown
150
- initial entities or borrowed entities moved into that session cause refusal.
151
- Cleanup uses a separate 0.5-second budget. Compensation status is explicit and
152
- cannot undo shell effects. The default
153
- preserves partial state for inspection. Applying or compensating does not
154
- close the supplied server binding.
155
-
156
- ## Command-line interface
157
-
158
- The gem installs `libtmux-workspace`. `validate` and offline
159
- `plan` do not contact tmux. Without a filename, discovery requires exactly one
160
- `.tmuxp.yaml`, `.tmuxp.yml` or `.tmuxp.json` in the current directory.
212
+ - A snapshot makes the plan reject a name that already exists, and every
213
+ `apply` takes a fresh snapshot and checks again.
214
+ - The plan creates a new session. It does not reconcile, replace or remove an
215
+ existing one.
216
+ - Success shows that tmux accepted the dispatch of each shell command. It does
217
+ not show that the command finished.
218
+
219
+ The [complete program](../../examples/workspace_apply.rb) also runs the
220
+ installed command with `--switch`. The contract is in
221
+ [Workspace apply][behavior-workspace-apply].
222
+
223
+ ## Recover from a failed apply
224
+
225
+ ### [`ApplyError#result`][workspace-applyerror-result]: what exists after a failure
226
+
227
+ A failed apply raises [`ApplyError`][workspace-applyerror]. Its immutable
228
+ `result` holds the completed step IDs, the
229
+ [`created_refs`][workspace-applyresult-created_refs] tmux returned, the
230
+ effects it observed, and the failed action. Pass `compensate: true` to remove
231
+ only the session the apply created:
232
+
233
+ <!-- example: api_workspace_failure/main -->
234
+ ```ruby
235
+ require "json"
236
+ require "libtmux"
237
+ require "libtmux/workspace"
238
+
239
+ LibTmux::Server.start do |server|
240
+ server.new_session(name: "borrowed", command: ["/bin/cat"])
241
+ crowded = LibTmux::Workspace.parse(
242
+ JSON.generate({session_name: "crowded", windows: [{window_name: "small", panes: Array.new(40) { {} }}]}),
243
+ format: :json, base_directory: __dir__
244
+ )
245
+ begin
246
+ crowded.plan.apply(server: server, compensate: true)
247
+ rescue LibTmux::Workspace::ApplyError => error
248
+ puts "failed action: #{error.result.failed_action}"
249
+ puts "created a session: #{error.result.created_refs.key?('session')}"
250
+ puts "compensation: #{error.result.compensation}"
251
+ end
252
+ puts "sessions: #{server.list_sessions.map { |session| session.snapshot.name }.join(', ')}"
253
+ end
254
+ ```
255
+ <!-- /example -->
256
+
257
+ That many panes do not fit in one window, so tmux refuses a split after the
258
+ session exists. It prints:
259
+
260
+ ```text
261
+ failed action: split_pane
262
+ created a session: true
263
+ compensation: completed
264
+ sessions: borrowed
265
+ ```
266
+
267
+ - Compensation removes the new session only after a tmux guard shows that
268
+ every window and pane in it has a positively identified created reference.
269
+ - An unknown initial entity, or a borrowed entity moved into the session,
270
+ makes it refuse.
271
+ - Compensation cannot undo what shell commands did. Without it, the partial
272
+ state stays for you to inspect.
273
+ - Diagnostics omit command payloads and paths. Names are never used to guess
274
+ ownership.
275
+
276
+ The command takes `--compensate`. The contracts are in
277
+ [Workspace results][behavior-workspace-results].
278
+
279
+ ## Attach or switch to the new session
280
+
281
+ ### [`load --switch`][cli-load]: move a client to the session
282
+
283
+ - `load --attach` opens the CLI's `/dev/tty` after creation and runs an owned
284
+ terminal client until you detach. It requires a valid `TERM`.
285
+ - `load --switch CLIENT` switches a current tmux client to the created session
286
+ after apply, keeping the session's environment.
287
+ - `CLIENT` is a client name, a full TTY path or a TTY path without `/dev/`.
288
+ Use `--switch=VALUE` for a selector that begins with `-`.
289
+ - A missing client, or a failure to open or attach the terminal, keeps the
290
+ created session and returns status 3.
291
+ - Attach and switch are mutually exclusive, and neither infers a latest
292
+ client. The library's [`Plan#apply`][workspace-plan-apply] does neither.
293
+
294
+ The selector rules are in [Client switching][behavior-client-switching].
295
+
296
+ ## Script it
297
+
298
+ ### [`--json`][cli-load]: one object on stdout
161
299
 
162
300
  Check the installed gem version without reading a configuration or contacting
163
301
  tmux:
@@ -166,12 +304,9 @@ tmux:
166
304
  $ libtmux-workspace --version
167
305
  ```
168
306
 
169
- ```console
170
- $ libtmux-workspace validate workspace.yaml
171
- ```
172
-
173
307
  Human plans list ordered operations and their effects. `--json` prints the
174
- same plan data as `Plan#to_h`, including configured command text and paths.
308
+ same plan data as [`Plan#to_h`][workspace-plan-to_h], including configured
309
+ command text and paths:
175
310
 
176
311
  ```console
177
312
  $ libtmux-workspace plan \
@@ -179,8 +314,9 @@ $ libtmux-workspace plan \
179
314
  workspace.yaml
180
315
  ```
181
316
 
182
- `load` and `plan --live` require `--socket` for an existing server. This
183
- detached creation example uses an explicitly supplied `TMUX_SOCKET` value:
317
+ [`load`][cli-load] and `plan --live` require `--socket` for an existing
318
+ server. This detached creation example uses an explicitly supplied
319
+ `TMUX_SOCKET` value:
184
320
 
185
321
  ```console
186
322
  $ libtmux-workspace load \
@@ -189,27 +325,20 @@ $ libtmux-workspace load \
189
325
  workspace.yaml
190
326
  ```
191
327
 
192
- `--timeout` bounds each apply, live capture or subsequent switch operation
193
- and defaults to 5 seconds.
194
- `--compensate` enables guarded cleanup after apply failure. Environment
195
- expansion requires both `--expand-environment` and explicit `--env NAME=VALUE`
196
- arguments; ambient environment variables are not copied into that mapping.
197
-
198
- `load --attach` opens the CLI's `/dev/tty` after creation and runs an owned
199
- terminal client until the user detaches. It requires a valid `TERM`. Failure
200
- to open or attach the terminal retains the successful apply ledger and
201
- returns status 3. `load --switch CLIENT` switches the explicit current tmux
202
- client selector to the created session after apply, preserving the session's
203
- environment. It accepts a current client name, full TTY path or TTY path
204
- without `/dev/`; native first-match behavior applies. A missing client fails
205
- without fallback and retains the created session and ledger with status 3.
206
- Missing or invalid selector arguments fail before creation. Use
207
- `--switch=VALUE` for a selector beginning with `-`.
208
-
209
- The selector is resolved at dispatch; a reconnect matching it is eligible.
210
- It is not a captured client reference or proof of terminal ownership. Attach
211
- and switch are mutually exclusive. Neither operation infers a latest client,
212
- and library `Plan#apply` performs neither operation.
328
+ `libtmux-workspace --help` lists every option. The
329
+ [command reference][ws-cli] has a page for each command.
330
+
331
+ | Option | Effect |
332
+ | --- | --- |
333
+ | `--json` | Write one structured result or error object to stdout |
334
+ | `--socket PATH` | Select the existing server's socket for `load` or `plan --live` |
335
+ | `--live` | Capture a snapshot before planning |
336
+ | `--timeout SECONDS` | Bound each apply, live capture or switch operation; the default is 5 |
337
+ | `--compensate` | Run guarded cleanup after an apply failure |
338
+ | `--attach` | Attach this CLI's terminal after a successful `load` |
339
+ | `--switch CLIENT` | Switch an explicit current client to the new session after `load` |
340
+ | `--expand-environment` | Expand `${NAME}` in directories and environment values; requires `--env` |
341
+ | `--env NAME=VALUE` | Supply a value for expansion; ambient environment variables are not copied |
213
342
 
214
343
  | Exit status | Meaning |
215
344
  | --- | --- |
@@ -219,7 +348,77 @@ and library `Plan#apply` performs neither operation.
219
348
  | 3 | Application was partial or uncertain, or a later attach/switch/cleanup failed |
220
349
  | 130 | Interrupted; available effect ledger is retained |
221
350
 
222
- JSON mode writes one result or error object to stdout. Apply errors include
223
- `ApplyResult#to_h`; human errors write the diagnostic and any ledger to stderr.
224
- Diagnostics omit configuration payloads. Explicit plan rendering and canonical
225
- configuration export contain those values by design.
351
+ Apply errors include [`ApplyResult#to_h`][workspace-applyresult-to_h]. Human
352
+ errors write the diagnostic and any ledger to stderr. Diagnostics omit
353
+ configuration payloads; plan rendering and canonical export contain those
354
+ values by design.
355
+
356
+ ## What it leaves out
357
+
358
+ - **Reconciliation.** A plan creates a new session and never replaces or
359
+ removes an existing workspace. The contract is in
360
+ [Workspace plans][behavior-workspace-plans].
361
+ - **Shell command completion.** Apply records a shell command as dispatched.
362
+ Embedded newlines can execute while the text is inserted. See
363
+ [Workspace apply][behavior-workspace-apply].
364
+ - **Rollback of shell effects.** Compensation removes only tmux objects the
365
+ apply created. See [Workspace results][behavior-workspace-results].
366
+ - **Features beyond the declared subset.** Plugins, ERB and callbacks are
367
+ rejected, and a [`ConfigError`][workspace-configerror] names the position.
368
+ - **A guessed client.** Attach and switch need an explicit client. See
369
+ [Client switching][behavior-client-switching].
370
+
371
+ ## Support and status
372
+
373
+ **Alpha.** APIs may change between releases.
374
+
375
+ - **Ruby 3.3 or newer.** The gem depends on the same version of `libtmux`,
376
+ JSON 3.0 and Psych 5.5.
377
+ - **tmux 3.3a or newer** to load or apply a plan.
378
+ - **Older daemons.** `load` creates nothing and prints the refusal with the
379
+ floor and the remedy. With `--json`, the refusal is in `error.message` and
380
+ `error.kind` is `tmux_unsupported`.
381
+ - **Linux, including WSL, and macOS.** The library and the command share the
382
+ core [compatibility workflow][compat]: Ruby 3.3, 3.4 and 4.0 with tmux 3.3a
383
+ through 3.8 on Ubuntu 24.04, with per-revision results. macOS is supported on
384
+ a best-effort basis, and CI coverage for it is highly limited.
385
+ - **History limit.** On tmux 3.3a to 3.6 the reused initial pane keeps the
386
+ global `history-limit`. Set the server's global value before you apply to
387
+ get one history size.
388
+
389
+ ## Documentation
390
+
391
+ | | |
392
+ | --- | --- |
393
+ | [Reference](https://libtmux.org/en/ruby/latest/workspace/reference/) | Every public workspace class and method |
394
+ | [Command reference][ws-cli] | A page for `validate`, `plan` and `load` |
395
+ | [Guides][workspace-guides] | Validate, inspect and explicitly apply a plan |
396
+ | [Topics][workspace-topics] | Format, planning boundaries, effects and failure ledgers |
397
+ | [Public behavior][behavior-workspace-parsing] | Parsing, plans, apply, results and the command |
398
+ | [Recipes][doc-recipes] | The workspace plan, load and failure program |
399
+
400
+ [behavior-client-switching]: ../../docs/reference/behavior.md#client-switching
401
+ [behavior-workspace-apply]: ../../docs/reference/behavior.md#workspace-apply
402
+ [behavior-workspace-parsing]: ../../docs/reference/behavior.md#workspace-parsing
403
+ [behavior-workspace-plans]: ../../docs/reference/behavior.md#workspace-plans
404
+ [behavior-workspace-results]: ../../docs/reference/behavior.md#workspace-results
405
+ [cli-load]: https://libtmux.org/en/ruby/latest/workspace/cli/load/
406
+ [cli-plan]: https://libtmux.org/en/ruby/latest/workspace/cli/plan/
407
+ [cli-validate]: https://libtmux.org/en/ruby/latest/workspace/cli/validate/
408
+ [compat]: https://github.com/libtmux/libtmux-ruby/actions/workflows/compatibility.yml
409
+ [doc-recipes]: ../../docs/recipes.md
410
+ [workspace-applyerror]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-applyerror/
411
+ [workspace-applyerror-result]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-applyerror-result/
412
+ [workspace-applyresult-created_refs]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-applyresult-created_refs/
413
+ [workspace-applyresult-to_h]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-applyresult-to_h/
414
+ [workspace-configerror]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-configerror/
415
+ [workspace-guides]: https://libtmux.org/en/ruby/latest/workspace/guides/
416
+ [workspace-load]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-load/
417
+ [workspace-parse]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-parse/
418
+ [workspace-plan-apply]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-plan-apply/
419
+ [workspace-plan-class]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-plan-1qko/
420
+ [workspace-plan-method]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-plan-1c5c/
421
+ [workspace-plan-to_h]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-plan-to_h/
422
+ [workspace-to_h]: https://libtmux.org/en/ruby/latest/workspace/reference/libtmux-workspace-to_h/
423
+ [workspace-topics]: https://libtmux.org/en/ruby/latest/workspace/topics/
424
+ [ws-cli]: https://libtmux.org/en/ruby/latest/workspace/cli/
data/assets/logo.png ADDED
Binary file
data/assets/logo.svg ADDED
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 135.46667 135.46666" role="img" aria-label="libtmux for Ruby — workspace CLI" class="libtmux-logo" data-language="ruby" data-variant="workspace" focusable="false"><title>libtmux for Ruby — workspace CLI</title><g transform="translate(-210.28959,-409.98108)" data-part="artwork"><path d="m 334.66493,533.88598 v 11.56176 h 3.76501 c 4.04619,2.1e-4 7.32623,-3.28012 7.32632,-7.32631 v -4.23545 z" fill="#333333" data-part="base-edge"></path><g transform="matrix(1.024,0,0,1.024,-5.0469422,-9.8395302)" data-part="primary-tile"><path d="M 227.78906,0 213.98242,3.6992188 V 460.62109 H 241.4082 V 15.564453 Z" transform="matrix(0.26458333,0,0,0.26458333,210.28959,409.98108)" fill="#2D0002" data-part="primary-edge"></path><path d="m 210.28981,417.13553 v 113.86425 l 60.26885,2.60089 V 409.98109 h -53.11441 c -3.95129,0 -7.15444,3.20315 -7.15444,7.15444 z" fill="#701516" data-part="primary"></path></g><g transform="matrix(1.024,0,0,1.024,-5.0469422,-9.8395302)" data-part="lower-tile"><path d="m 486.38086,246.03906 -5.24805,4.99805 v 207.2207 l 5.24805,-0.93164 L 500,465.18945 V 261.60352 Z" transform="matrix(0.26458333,0,0,0.26458333,210.28959,409.98108)" fill="#67221F" data-part="lower-edge"></path><path d="m 278.70874,475.07917 v 64.8875 l 60.26883,-8.98455 v -55.90295 z" fill="#984640" data-part="lower"></path></g><path d="m 210.28959,533.88598 v 4.23545 c 9e-5,4.04619 3.28012,7.32651 7.32631,7.32631 h 117.12417 c 4.04619,2e-4 7.32621,-3.28012 7.32631,-7.32631 v -4.23545 z" fill="#231F20" data-part="base"></path><g transform="matrix(1.024,0,0,1.024,-5.0469422,-9.8395302)" data-part="upper-tile"><path d="m 459.33984,0 -25.33593,2.1074219 h 21.38281 c 14.93397,0 27.04101,12.1070061 27.04101,27.0410161 l 1.51727,183.649512 2.43542,2.38046 -1.84468,1.91057 15.46424,13.65284 L 500,42.603516 C 499.99913,8.8176763 474.27408,-6.8031496e-4 459.33984,0 Z" transform="matrix(0.26458333,0,0,0.26458333,210.28959,409.98108)" fill="#854A46" data-part="upper-side"></path><path d="m 331.82331,409.98109 h -53.11442 v 56.93261 h 60.26885 v -49.77817 c 0,-3.95129 -3.20315,-7.15444 -7.15443,-7.15444 z" fill="#B6706A" data-part="upper"></path><path d="m 278.7089,466.9137 h 60.26885 l 3.6035,4.11782 h -60.26932 z" fill="#733A36" data-part="upper-bottom"></path></g></g></svg>
@@ -76,7 +76,7 @@ module LibTmux
76
76
  rescue Interrupt, LibTmux::Cancelled => error
77
77
  report(error, kind: "interrupted", status: 130)
78
78
  rescue LibTmux::Error, SystemCallError, IOError => error
79
- report(error, kind: "execution", status: @result ? 3 : 1)
79
+ report(error, kind: version_refusal?(error) ? "tmux_unsupported" : "execution", status: @result ? 3 : 1)
80
80
  end
81
81
 
82
82
  private
@@ -157,10 +157,21 @@ module LibTmux
157
157
  @out.puts(@json ? JSON.generate(value) : human)
158
158
  end
159
159
 
160
+ # A refusal before dispatch sent nothing, and its message says what to
161
+ # change, so it replaces the generic failure line.
162
+ def pre_dispatch_refusal?(error)
163
+ error.is_a?(LibTmux::UnsupportedFeatureError) && error.phase == :admission && error.delivery == :not_sent
164
+ end
165
+
166
+ # Only a refusal that names the tmux floor carries the machine code.
167
+ def version_refusal?(error)
168
+ LibTmux::Internal::TmuxVersion.refusal?(error)
169
+ end
170
+
160
171
  def report(error, kind:, status:)
161
172
  message = if kind == "arguments"
162
173
  @argument_message || "Invalid arguments; use --help for supported options."
163
- elsif error.is_a?(Workspace::ConfigError) || error.is_a?(Workspace::ApplyError)
174
+ elsif error.is_a?(Workspace::ConfigError) || error.is_a?(Workspace::ApplyError) || pre_dispatch_refusal?(error)
164
175
  error.message
165
176
  else
166
177
  "Workspace #{kind} failed#{error ? " (#{error.class})" : ''}."
@@ -2,6 +2,6 @@
2
2
 
3
3
  module LibTmux
4
4
  class Workspace
5
- VERSION = "0.1.0.alpha.1"
5
+ VERSION = "0.1.0.alpha.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libtmux-workspace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha.1
4
+ version: 0.1.0.alpha.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - libtmux contributors
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.1.0.alpha.1
18
+ version: 0.1.0.alpha.3
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.1.0.alpha.1
25
+ version: 0.1.0.alpha.3
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: json
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -60,6 +60,8 @@ extra_rdoc_files: []
60
60
  files:
61
61
  - LICENSE
62
62
  - README.md
63
+ - assets/logo.png
64
+ - assets/logo.svg
63
65
  - exe/libtmux-workspace
64
66
  - lib/libtmux/workspace.rb
65
67
  - lib/libtmux/workspace/apply.rb
@@ -74,6 +76,7 @@ licenses:
74
76
  - MIT
75
77
  metadata:
76
78
  source_code_uri: https://github.com/libtmux/libtmux-ruby
79
+ documentation_uri: https://libtmux.org/en/ruby/v0.1.0.alpha.3/workspace/
77
80
  rdoc_options: []
78
81
  require_paths:
79
82
  - lib