@wastedcode/claudemux 0.2.0

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.
Files changed (249) hide show
  1. package/CHANGELOG.md +257 -0
  2. package/LICENSE +21 -0
  3. package/README.md +493 -0
  4. package/bin/claudemux +6 -0
  5. package/dist/agents/claude.d.ts +3 -0
  6. package/dist/agents/claude.d.ts.map +1 -0
  7. package/dist/agents/claude.js +585 -0
  8. package/dist/agents/claude.js.map +1 -0
  9. package/dist/agents/index.d.ts +2 -0
  10. package/dist/agents/index.d.ts.map +1 -0
  11. package/dist/agents/index.js +2 -0
  12. package/dist/agents/index.js.map +1 -0
  13. package/dist/agents/types.d.ts +252 -0
  14. package/dist/agents/types.d.ts.map +1 -0
  15. package/dist/agents/types.js +2 -0
  16. package/dist/agents/types.js.map +1 -0
  17. package/dist/backends/tmux/capture.d.ts +25 -0
  18. package/dist/backends/tmux/capture.d.ts.map +1 -0
  19. package/dist/backends/tmux/capture.js +35 -0
  20. package/dist/backends/tmux/capture.js.map +1 -0
  21. package/dist/backends/tmux/exec.d.ts +105 -0
  22. package/dist/backends/tmux/exec.d.ts.map +1 -0
  23. package/dist/backends/tmux/exec.js +226 -0
  24. package/dist/backends/tmux/exec.js.map +1 -0
  25. package/dist/backends/tmux/index.d.ts +22 -0
  26. package/dist/backends/tmux/index.d.ts.map +1 -0
  27. package/dist/backends/tmux/index.js +108 -0
  28. package/dist/backends/tmux/index.js.map +1 -0
  29. package/dist/backends/tmux/keys.d.ts +38 -0
  30. package/dist/backends/tmux/keys.d.ts.map +1 -0
  31. package/dist/backends/tmux/keys.js +63 -0
  32. package/dist/backends/tmux/keys.js.map +1 -0
  33. package/dist/backends/tmux/options.d.ts +24 -0
  34. package/dist/backends/tmux/options.d.ts.map +1 -0
  35. package/dist/backends/tmux/options.js +84 -0
  36. package/dist/backends/tmux/options.js.map +1 -0
  37. package/dist/backends/tmux/sessions.d.ts +70 -0
  38. package/dist/backends/tmux/sessions.d.ts.map +1 -0
  39. package/dist/backends/tmux/sessions.js +156 -0
  40. package/dist/backends/tmux/sessions.js.map +1 -0
  41. package/dist/backends/tmux/socket.d.ts +26 -0
  42. package/dist/backends/tmux/socket.d.ts.map +1 -0
  43. package/dist/backends/tmux/socket.js +31 -0
  44. package/dist/backends/tmux/socket.js.map +1 -0
  45. package/dist/backends/types.d.ts +110 -0
  46. package/dist/backends/types.d.ts.map +1 -0
  47. package/dist/backends/types.js +24 -0
  48. package/dist/backends/types.js.map +1 -0
  49. package/dist/cli/ask.d.ts +11 -0
  50. package/dist/cli/ask.d.ts.map +1 -0
  51. package/dist/cli/ask.js +17 -0
  52. package/dist/cli/ask.js.map +1 -0
  53. package/dist/cli/capture.d.ts +8 -0
  54. package/dist/cli/capture.d.ts.map +1 -0
  55. package/dist/cli/capture.js +15 -0
  56. package/dist/cli/capture.js.map +1 -0
  57. package/dist/cli/context.d.ts +71 -0
  58. package/dist/cli/context.d.ts.map +1 -0
  59. package/dist/cli/context.js +82 -0
  60. package/dist/cli/context.js.map +1 -0
  61. package/dist/cli/exists.d.ts +7 -0
  62. package/dist/cli/exists.d.ts.map +1 -0
  63. package/dist/cli/exists.js +16 -0
  64. package/dist/cli/exists.js.map +1 -0
  65. package/dist/cli/interrupt.d.ts +10 -0
  66. package/dist/cli/interrupt.d.ts.map +1 -0
  67. package/dist/cli/interrupt.js +13 -0
  68. package/dist/cli/interrupt.js.map +1 -0
  69. package/dist/cli/kill.d.ts +7 -0
  70. package/dist/cli/kill.d.ts.map +1 -0
  71. package/dist/cli/kill.js +14 -0
  72. package/dist/cli/kill.js.map +1 -0
  73. package/dist/cli/list.d.ts +10 -0
  74. package/dist/cli/list.d.ts.map +1 -0
  75. package/dist/cli/list.js +19 -0
  76. package/dist/cli/list.js.map +1 -0
  77. package/dist/cli/main.d.ts +13 -0
  78. package/dist/cli/main.d.ts.map +1 -0
  79. package/dist/cli/main.js +143 -0
  80. package/dist/cli/main.js.map +1 -0
  81. package/dist/cli/messages.d.ts +9 -0
  82. package/dist/cli/messages.d.ts.map +1 -0
  83. package/dist/cli/messages.js +13 -0
  84. package/dist/cli/messages.js.map +1 -0
  85. package/dist/cli/respond.d.ts +10 -0
  86. package/dist/cli/respond.d.ts.map +1 -0
  87. package/dist/cli/respond.js +23 -0
  88. package/dist/cli/respond.js.map +1 -0
  89. package/dist/cli/resume.d.ts +12 -0
  90. package/dist/cli/resume.d.ts.map +1 -0
  91. package/dist/cli/resume.js +21 -0
  92. package/dist/cli/resume.js.map +1 -0
  93. package/dist/cli/send.d.ts +9 -0
  94. package/dist/cli/send.d.ts.map +1 -0
  95. package/dist/cli/send.js +16 -0
  96. package/dist/cli/send.js.map +1 -0
  97. package/dist/cli/spawn.d.ts +14 -0
  98. package/dist/cli/spawn.d.ts.map +1 -0
  99. package/dist/cli/spawn.js +21 -0
  100. package/dist/cli/spawn.js.map +1 -0
  101. package/dist/cli/state.d.ts +7 -0
  102. package/dist/cli/state.d.ts.map +1 -0
  103. package/dist/cli/state.js +11 -0
  104. package/dist/cli/state.js.map +1 -0
  105. package/dist/cli/turn-complete.d.ts +8 -0
  106. package/dist/cli/turn-complete.d.ts.map +1 -0
  107. package/dist/cli/turn-complete.js +14 -0
  108. package/dist/cli/turn-complete.js.map +1 -0
  109. package/dist/cli/wait.d.ts +13 -0
  110. package/dist/cli/wait.d.ts.map +1 -0
  111. package/dist/cli/wait.js +17 -0
  112. package/dist/cli/wait.js.map +1 -0
  113. package/dist/compose.d.ts +81 -0
  114. package/dist/compose.d.ts.map +1 -0
  115. package/dist/compose.js +64 -0
  116. package/dist/compose.js.map +1 -0
  117. package/dist/errors.d.ts +250 -0
  118. package/dist/errors.d.ts.map +1 -0
  119. package/dist/errors.js +300 -0
  120. package/dist/errors.js.map +1 -0
  121. package/dist/index.d.ts +22 -0
  122. package/dist/index.d.ts.map +1 -0
  123. package/dist/index.js +17 -0
  124. package/dist/index.js.map +1 -0
  125. package/dist/io/baseline.d.ts +53 -0
  126. package/dist/io/baseline.d.ts.map +1 -0
  127. package/dist/io/baseline.js +97 -0
  128. package/dist/io/baseline.js.map +1 -0
  129. package/dist/io/capture.d.ts +15 -0
  130. package/dist/io/capture.d.ts.map +1 -0
  131. package/dist/io/capture.js +13 -0
  132. package/dist/io/capture.js.map +1 -0
  133. package/dist/io/interrupt.d.ts +46 -0
  134. package/dist/io/interrupt.d.ts.map +1 -0
  135. package/dist/io/interrupt.js +60 -0
  136. package/dist/io/interrupt.js.map +1 -0
  137. package/dist/io/respond.d.ts +28 -0
  138. package/dist/io/respond.d.ts.map +1 -0
  139. package/dist/io/respond.js +33 -0
  140. package/dist/io/respond.js.map +1 -0
  141. package/dist/io/send.d.ts +44 -0
  142. package/dist/io/send.d.ts.map +1 -0
  143. package/dist/io/send.js +66 -0
  144. package/dist/io/send.js.map +1 -0
  145. package/dist/io/stabilize.d.ts +28 -0
  146. package/dist/io/stabilize.d.ts.map +1 -0
  147. package/dist/io/stabilize.js +20 -0
  148. package/dist/io/stabilize.js.map +1 -0
  149. package/dist/io/wait.d.ts +47 -0
  150. package/dist/io/wait.d.ts.map +1 -0
  151. package/dist/io/wait.js +117 -0
  152. package/dist/io/wait.js.map +1 -0
  153. package/dist/observe/incremental.d.ts +28 -0
  154. package/dist/observe/incremental.d.ts.map +1 -0
  155. package/dist/observe/incremental.js +57 -0
  156. package/dist/observe/incremental.js.map +1 -0
  157. package/dist/observe/observer.d.ts +86 -0
  158. package/dist/observe/observer.d.ts.map +1 -0
  159. package/dist/observe/observer.js +167 -0
  160. package/dist/observe/observer.js.map +1 -0
  161. package/dist/observe/session-observer.d.ts +49 -0
  162. package/dist/observe/session-observer.d.ts.map +1 -0
  163. package/dist/observe/session-observer.js +123 -0
  164. package/dist/observe/session-observer.js.map +1 -0
  165. package/dist/session/adopt.d.ts +52 -0
  166. package/dist/session/adopt.d.ts.map +1 -0
  167. package/dist/session/adopt.js +57 -0
  168. package/dist/session/adopt.js.map +1 -0
  169. package/dist/session/boot.d.ts +66 -0
  170. package/dist/session/boot.d.ts.map +1 -0
  171. package/dist/session/boot.js +216 -0
  172. package/dist/session/boot.js.map +1 -0
  173. package/dist/session/constants.d.ts +57 -0
  174. package/dist/session/constants.d.ts.map +1 -0
  175. package/dist/session/constants.js +54 -0
  176. package/dist/session/constants.js.map +1 -0
  177. package/dist/session/create.d.ts +88 -0
  178. package/dist/session/create.d.ts.map +1 -0
  179. package/dist/session/create.js +66 -0
  180. package/dist/session/create.js.map +1 -0
  181. package/dist/session/default-backend.d.ts +27 -0
  182. package/dist/session/default-backend.d.ts.map +1 -0
  183. package/dist/session/default-backend.js +58 -0
  184. package/dist/session/default-backend.js.map +1 -0
  185. package/dist/session/handle.d.ts +63 -0
  186. package/dist/session/handle.d.ts.map +1 -0
  187. package/dist/session/handle.js +284 -0
  188. package/dist/session/handle.js.map +1 -0
  189. package/dist/session/hooks.d.ts +37 -0
  190. package/dist/session/hooks.d.ts.map +1 -0
  191. package/dist/session/hooks.js +42 -0
  192. package/dist/session/hooks.js.map +1 -0
  193. package/dist/session/mutex.d.ts +15 -0
  194. package/dist/session/mutex.d.ts.map +1 -0
  195. package/dist/session/mutex.js +29 -0
  196. package/dist/session/mutex.js.map +1 -0
  197. package/dist/session/recover.d.ts +43 -0
  198. package/dist/session/recover.d.ts.map +1 -0
  199. package/dist/session/recover.js +45 -0
  200. package/dist/session/recover.js.map +1 -0
  201. package/dist/session/ref.d.ts +2 -0
  202. package/dist/session/ref.d.ts.map +1 -0
  203. package/dist/session/ref.js +5 -0
  204. package/dist/session/ref.js.map +1 -0
  205. package/dist/session/registry.d.ts +31 -0
  206. package/dist/session/registry.d.ts.map +1 -0
  207. package/dist/session/registry.js +32 -0
  208. package/dist/session/registry.js.map +1 -0
  209. package/dist/session/resolve.d.ts +30 -0
  210. package/dist/session/resolve.d.ts.map +1 -0
  211. package/dist/session/resolve.js +24 -0
  212. package/dist/session/resolve.js.map +1 -0
  213. package/dist/session/resume.d.ts +68 -0
  214. package/dist/session/resume.d.ts.map +1 -0
  215. package/dist/session/resume.js +54 -0
  216. package/dist/session/resume.js.map +1 -0
  217. package/dist/session/spawn-boot.d.ts +44 -0
  218. package/dist/session/spawn-boot.d.ts.map +1 -0
  219. package/dist/session/spawn-boot.js +87 -0
  220. package/dist/session/spawn-boot.js.map +1 -0
  221. package/dist/session/validate.d.ts +10 -0
  222. package/dist/session/validate.d.ts.map +1 -0
  223. package/dist/session/validate.js +0 -0
  224. package/dist/session/validate.js.map +1 -0
  225. package/dist/state/classifier.d.ts +29 -0
  226. package/dist/state/classifier.d.ts.map +1 -0
  227. package/dist/state/classifier.js +37 -0
  228. package/dist/state/classifier.js.map +1 -0
  229. package/dist/state/types.d.ts +32 -0
  230. package/dist/state/types.d.ts.map +1 -0
  231. package/dist/state/types.js +2 -0
  232. package/dist/state/types.js.map +1 -0
  233. package/dist/types.d.ts +401 -0
  234. package/dist/types.d.ts.map +1 -0
  235. package/dist/types.js +9 -0
  236. package/dist/types.js.map +1 -0
  237. package/dist/util/ansi.d.ts +14 -0
  238. package/dist/util/ansi.d.ts.map +1 -0
  239. package/dist/util/ansi.js +18 -0
  240. package/dist/util/ansi.js.map +1 -0
  241. package/dist/util/emitter.d.ts +17 -0
  242. package/dist/util/emitter.d.ts.map +1 -0
  243. package/dist/util/emitter.js +33 -0
  244. package/dist/util/emitter.js.map +1 -0
  245. package/dist/util/sleep.d.ts +8 -0
  246. package/dist/util/sleep.d.ts.map +1 -0
  247. package/dist/util/sleep.js +10 -0
  248. package/dist/util/sleep.js.map +1 -0
  249. package/package.json +50 -0
@@ -0,0 +1,226 @@
1
+ import { spawn } from "node:child_process";
2
+ import { BackendError, BackendUnreachable, SessionExists, SessionGone } from "../../errors.js";
3
+ import { Emitter } from "../../util/emitter.js";
4
+ /**
5
+ * tmux stderr patterns that indicate "the backend's server is not reachable
6
+ * on this socket." Older tmux says `no server running on /tmp/.../sock`;
7
+ * tmux ≥3.3 says `error connecting to /tmp/.../sock (No such file or
8
+ * directory)`. We treat both as the same condition (server's gone).
9
+ *
10
+ * Single-source — callers (`sessions.ts`, `classifyTmuxFailure`) import
11
+ * from here so the regex can't drift.
12
+ */
13
+ export function isNoServerStderr(text) {
14
+ return /no server running on/i.test(text) || /error connecting to /i.test(text);
15
+ }
16
+ /**
17
+ * tmux stderr substrings that indicate "the target you asked about doesn't
18
+ * exist on this server." tmux emits different `can't find …:` strings
19
+ * depending on which level of the `session:window.pane` grammar failed.
20
+ * All three are semantically "the session you wanted isn't here" — callers
21
+ * that need a boolean (`hasSession`) treat them all as "no" rather than
22
+ * letting the routine `can't find pane:`/`can't find window:` shapes
23
+ * escape as `SessionGone` throws.
24
+ */
25
+ const SESSION_GONE_PATTERNS = ["can't find session:", "can't find pane:", "can't find window:"];
26
+ export function isSessionGoneStderr(text) {
27
+ const lower = text.toLowerCase();
28
+ return SESSION_GONE_PATTERNS.some((p) => lower.includes(p));
29
+ }
30
+ /**
31
+ * tmux stderr shape when `new-session -s <name>` races another process
32
+ * creating the same target. With the shared default socket (ADR 0006),
33
+ * concurrent `spawn`s of the same name are routine, and the TOCTOU window
34
+ * between `create()`'s exists-check and `backend.spawn()` means tmux —
35
+ * not the substrate's check — sometimes discovers the collision. The
36
+ * semantically-correct typed error is `SessionExists`, same as a check-time
37
+ * collision: the substrate refuses to silently adopt either way.
38
+ */
39
+ export function isDuplicateSessionStderr(text) {
40
+ return /duplicate session:/i.test(text);
41
+ }
42
+ /**
43
+ * Default per-invocation timeout. tmux read/control ops (capture-pane,
44
+ * has-session, new-session, …) return in milliseconds against a healthy
45
+ * server; 10s is generous headroom. The long-lived "wait for the agent"
46
+ * budget belongs to `io/wait.ts`'s loop, NOT to a single subprocess — so
47
+ * a wedged tmux (process alive but unresponsive: NFS stall, server bug,
48
+ * modal pane) surfaces as a typed `BackendUnreachable[timeout]` inside
49
+ * `wait()`'s budget rather than hanging the consumer's `await` forever.
50
+ */
51
+ const DEFAULT_EXEC_TIMEOUT_MS = 10_000;
52
+ /**
53
+ * One executor instance per backend. Holds the private socket name and the
54
+ * observability emitter. Every tmux invocation goes through {@link run}; that
55
+ * is what enforces the `-f /dev/null` + `-L <socket>` discipline tree-wide.
56
+ *
57
+ * See `engineer/wiki/tmux-private-server-bootstrap` — bare `-L` does not
58
+ * prevent `~/.tmux.conf` reads; both flags are required.
59
+ */
60
+ export class TmuxExec {
61
+ socket;
62
+ #events = new Emitter();
63
+ constructor(socket) {
64
+ this.socket = socket;
65
+ }
66
+ /** Subscribe to every tmux invocation + result. Returns unsubscribe. */
67
+ onCommand(handler) {
68
+ return this.#events.on(handler);
69
+ }
70
+ /**
71
+ * Spawn `tmux -L <socket> -f /dev/null <args...>` and capture both streams —
72
+ * stdout for the op's payload (e.g. `capture-pane`), stderr for failure
73
+ * classification (`can't find …` → `SessionGone`, etc.).
74
+ *
75
+ * @throws `BackendUnreachable` — `spawn-failed` on spawn error (ENOENT,
76
+ * EPIPE), `no-server` when the server isn't running on a connect-only
77
+ * operation, `timeout` when the invocation doesn't return within
78
+ * `opts.timeoutMs` (default {@link DEFAULT_EXEC_TIMEOUT_MS}). The
79
+ * session-name field on the typed error is the *requested* session.
80
+ */
81
+ async run(args, opts = {}) {
82
+ const fullArgs = ["-L", this.socket, "-f", "/dev/null", ...args];
83
+ const sessionName = opts.sessionName ?? "<unknown>";
84
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
85
+ const startedAt = Date.now();
86
+ return new Promise((resolve, reject) => {
87
+ const child = spawn("tmux", fullArgs, {
88
+ stdio: [opts.input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
89
+ env: process.env,
90
+ });
91
+ if (opts.input !== undefined && child.stdin) {
92
+ child.stdin.write(opts.input);
93
+ child.stdin.end();
94
+ }
95
+ let stdout = "";
96
+ let stderr = "";
97
+ let spawnErr = null;
98
+ let settled = false;
99
+ // Per-invocation timeout: SIGKILL the child WE spawned (peer-process-safe
100
+ // by construction — exact PID, ADR 0004) and reject. The `close` handler
101
+ // still fires after the kill, but `settled` guards against double-settle.
102
+ const timer = setTimeout(() => {
103
+ if (settled)
104
+ return;
105
+ settled = true;
106
+ child.kill("SIGKILL");
107
+ reject(new BackendUnreachable(sessionName, "timeout", new Error(`backend command did not return within ${timeoutMs}ms`)));
108
+ }, timeoutMs);
109
+ timer.unref?.();
110
+ child.stdout?.on("data", (b) => {
111
+ stdout += b.toString("utf8");
112
+ });
113
+ child.stderr?.on("data", (b) => {
114
+ stderr += b.toString("utf8");
115
+ });
116
+ child.on("error", (err) => {
117
+ spawnErr = err;
118
+ });
119
+ child.on("close", (code) => {
120
+ if (settled)
121
+ return; // already rejected via timeout
122
+ settled = true;
123
+ clearTimeout(timer);
124
+ const durationMs = Date.now() - startedAt;
125
+ const exit = code ?? -1;
126
+ this.#events.emit({
127
+ ts: startedAt,
128
+ argv: ["tmux", ...fullArgs],
129
+ durationMs,
130
+ exit,
131
+ stdout,
132
+ stderr,
133
+ });
134
+ if (spawnErr) {
135
+ reject(new BackendUnreachable(sessionName, "spawn-failed", spawnErr));
136
+ return;
137
+ }
138
+ // tmux is on PATH but its server is down on a connect-only
139
+ // operation. Both shapes ("no server running on …" and
140
+ // "error connecting to …") are the same condition — promote.
141
+ // Don't echo the raw stderr (it leaks "tmux" via the socket path).
142
+ if (exit !== 0 && isNoServerStderr(stderr)) {
143
+ reject(new BackendUnreachable(sessionName, "no-server", new Error("no server running on the configured socket")));
144
+ return;
145
+ }
146
+ resolve({ exit, stdout, stderr, durationMs });
147
+ });
148
+ });
149
+ }
150
+ }
151
+ /**
152
+ * Classify a non-zero `TmuxResult` into a typed error. Callers use this when
153
+ * the operation was expected to succeed; for idempotent ops (kill), callers
154
+ * inspect `result.exit` + stderr directly and choose to swallow SessionGone.
155
+ *
156
+ * Returns `null` if the result looks successful (exit 0 with no error
157
+ * annotation on stdout), otherwise the right typed error to throw.
158
+ *
159
+ * `sessionName` is the requested target (so the error carries the right
160
+ * context even when tmux's stderr names something else).
161
+ *
162
+ * Order matters: `BackendUnreachable` (no server) > `SessionExists`
163
+ * (duplicate-session race) > `SessionGone` (target doesn't exist) >
164
+ * `BackendError` (unrecognized failure). The routine failure modes are
165
+ * promoted above `BackendError` so the substrate surfaces a clean typed
166
+ * error rather than the catch-all. As a structural backstop,
167
+ * `BackendError.message` itself does not embed the tmux argv (see
168
+ * `errors.ts`), so even an unclassified shape cannot leak the backend's
169
+ * vocabulary into user-facing text.
170
+ */
171
+ export function classifyTmuxFailure(sessionName, argv, result) {
172
+ if (result.exit === 0)
173
+ return null;
174
+ if (isNoServerStderr(result.stderr)) {
175
+ // Don't echo the backend's stderr (path + "tmux" substring leak).
176
+ // The clean public summary tells the user what they can do; the raw
177
+ // stderr is still available via observability (`onBackendCommand`).
178
+ return new BackendUnreachable(sessionName, "no-server", new Error("no server running on the configured socket"));
179
+ }
180
+ if (isDuplicateSessionStderr(result.stderr)) {
181
+ // A concurrent spawn won the race — same outcome as a check-time
182
+ // collision. The substrate never silently adopts.
183
+ return new SessionExists(sessionName);
184
+ }
185
+ if (isSessionGoneStderr(result.stderr)) {
186
+ return new SessionGone(sessionName);
187
+ }
188
+ return new BackendError(sessionName, argv, result.exit, result.stderr);
189
+ }
190
+ /** Is this the `no-server` {@link BackendUnreachable} (empty/down server)? */
191
+ export function isNoServer(err) {
192
+ return err instanceof BackendUnreachable && err.kind === "no-server";
193
+ }
194
+ /**
195
+ * Run a tmux op that targets ONE named session, applying the **canonical
196
+ * per-session failure mapping** — the single place reads and writes share so the
197
+ * same crash can't surface as two different errors again (the read/write drift).
198
+ *
199
+ * From a single session's vantage, a `no-server` failure means *this session is
200
+ * gone* → {@link SessionGone}, NOT a distinct backend fault. (Registry ops —
201
+ * `list`/`exists` — keep treating no-server as plain absence; `BackendUnreachable`
202
+ * is left to mean a *real* backend fault: `spawn-failed` / `timeout`.) The
203
+ * canonical answer holds whether no-server arrives as a rejection (the common
204
+ * path) or as a returned non-zero result.
205
+ *
206
+ * Returns the raw {@link TmuxResult} on success; throws the typed error otherwise.
207
+ */
208
+ export async function runForSession(exec, args, label, opts = {}) {
209
+ let r;
210
+ try {
211
+ r = await exec.run([...args], { sessionName: label, ...opts });
212
+ }
213
+ catch (err) {
214
+ if (isNoServer(err))
215
+ throw new SessionGone(label);
216
+ throw err; // spawn-failed / timeout / a real fault — surface as-is
217
+ }
218
+ const classified = classifyTmuxFailure(label, ["tmux", ...args], r);
219
+ if (classified !== null) {
220
+ if (isNoServer(classified))
221
+ throw new SessionGone(label);
222
+ throw classified;
223
+ }
224
+ return r;
225
+ }
226
+ //# sourceMappingURL=exec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../../src/backends/tmux/exec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type Backend } from "../types.js";
2
+ /**
3
+ * Assemble the `Backend` implementation from the tmux subsystem.
4
+ *
5
+ * This file is the *only* place where the tmux backend's concrete pieces
6
+ * compose into the seam contract — and the *only* place that bridges
7
+ * `SessionRef` → the tmux target-name encoding (`<ns>--<name>`). Callers
8
+ * outside `src/backends/tmux/**` never construct or parse target strings.
9
+ *
10
+ * **Validation policy (QA P1, 7360b35b):** name validation is a *mutating*
11
+ * concern. `spawn` / `send` / `capture` reject reserved-char names with
12
+ * `InvalidSessionName` (you cannot meaningfully write to / read from a name
13
+ * the substrate can't address). But `exists` and `kill` are *query /
14
+ * idempotent* verbs with total contracts — `exists` returns a boolean,
15
+ * `kill` is a no-op on a missing session. A reserved-char name simply
16
+ * *cannot* name a live session, so `exists` → `false` and `kill` → no-op,
17
+ * rather than throwing and breaking those documented contracts.
18
+ */
19
+ export declare function tmuxBackend(opts: {
20
+ socket: string;
21
+ }): Backend;
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backends/tmux/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,aAAa,CAAC;AAOrB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CA+E7D"}
@@ -0,0 +1,108 @@
1
+ import { InvalidSessionName } from "../../errors.js";
2
+ import { validateNamePart } from "../../session/validate.js";
3
+ import { formatSessionLabel, } from "../types.js";
4
+ import { capturePane } from "./capture.js";
5
+ import { TmuxExec } from "./exec.js";
6
+ import { pasteText, sendKey } from "./keys.js";
7
+ import { getSessionOption, setSessionOption } from "./options.js";
8
+ import { hasSession, killSession, listSessions, newSession, targetOf } from "./sessions.js";
9
+ /**
10
+ * Assemble the `Backend` implementation from the tmux subsystem.
11
+ *
12
+ * This file is the *only* place where the tmux backend's concrete pieces
13
+ * compose into the seam contract — and the *only* place that bridges
14
+ * `SessionRef` → the tmux target-name encoding (`<ns>--<name>`). Callers
15
+ * outside `src/backends/tmux/**` never construct or parse target strings.
16
+ *
17
+ * **Validation policy (QA P1, 7360b35b):** name validation is a *mutating*
18
+ * concern. `spawn` / `send` / `capture` reject reserved-char names with
19
+ * `InvalidSessionName` (you cannot meaningfully write to / read from a name
20
+ * the substrate can't address). But `exists` and `kill` are *query /
21
+ * idempotent* verbs with total contracts — `exists` returns a boolean,
22
+ * `kill` is a no-op on a missing session. A reserved-char name simply
23
+ * *cannot* name a live session, so `exists` → `false` and `kill` → no-op,
24
+ * rather than throwing and breaking those documented contracts.
25
+ */
26
+ export function tmuxBackend(opts) {
27
+ const exec = new TmuxExec(opts.socket);
28
+ // Validate the ref + produce both the tmux-internal target and the public
29
+ // label. Throws InvalidSessionName for reserved-char names — used by the
30
+ // mutating/I-O verbs. Defense-in-depth: create() validates at the public
31
+ // entry; a direct tmuxBackend caller could still bypass that.
32
+ const refToTarget = (ref) => {
33
+ validateNamePart("namespace", ref.namespace);
34
+ validateNamePart("name", ref.name);
35
+ return { target: targetOf(ref.namespace, ref.name), label: formatSessionLabel(ref) };
36
+ };
37
+ // Non-throwing variant for the total query/idempotent verbs. Returns null
38
+ // when the ref is invalid — an invalid name cannot name a live session.
39
+ const tryRefToTarget = (ref) => {
40
+ try {
41
+ return refToTarget(ref);
42
+ }
43
+ catch (err) {
44
+ if (err instanceof InvalidSessionName)
45
+ return null;
46
+ throw err;
47
+ }
48
+ };
49
+ // The mutating/I-O methods are `async` so a synchronous `validateNamePart`
50
+ // throw becomes a promise *rejection* — consistent with their Promise
51
+ // return type (a consumer's `.catch` / `await … rejects` works uniformly).
52
+ return {
53
+ id: "tmux",
54
+ spawn: async (o) => {
55
+ validateNamePart("namespace", o.namespace);
56
+ validateNamePart("name", o.name);
57
+ await newSession(exec, {
58
+ namespace: o.namespace,
59
+ name: o.name,
60
+ cwd: o.cwd,
61
+ ...(o.env ? { env: o.env } : {}),
62
+ cmd: o.cmd,
63
+ argv: o.argv,
64
+ label: formatSessionLabel({ namespace: o.namespace, name: o.name }),
65
+ });
66
+ },
67
+ kill: (ref) => {
68
+ const t = tryRefToTarget(ref);
69
+ if (t === null)
70
+ return Promise.resolve(); // invalid name → nothing to kill
71
+ return killSession(exec, t.target, t.label);
72
+ },
73
+ exists: (ref) => {
74
+ const t = tryRefToTarget(ref);
75
+ if (t === null)
76
+ return Promise.resolve(false); // invalid name can't be alive
77
+ return hasSession(exec, t.target, t.label);
78
+ },
79
+ list: async (namespace) => {
80
+ validateNamePart("namespace", namespace);
81
+ return listSessions(exec, namespace);
82
+ },
83
+ send: async (ref, payload) => {
84
+ const { target, label } = refToTarget(ref);
85
+ await (payload.kind === "paste"
86
+ ? pasteText(exec, target, payload.text, label)
87
+ : sendKey(exec, target, payload.key, label));
88
+ },
89
+ capture: async (ref, o) => {
90
+ const { target, label } = refToTarget(ref);
91
+ return capturePane(exec, target, { ...o, label });
92
+ },
93
+ setSessionMeta: async (ref, key, value) => {
94
+ const { target, label } = refToTarget(ref);
95
+ await setSessionOption(exec, target, key, value, label);
96
+ },
97
+ getSessionMeta: async (ref, key) => {
98
+ // Total/best-effort, like exists/kill: an invalid name can't name a live
99
+ // session, so it has no metadata — return undefined rather than throw.
100
+ const t = tryRefToTarget(ref);
101
+ if (t === null)
102
+ return undefined;
103
+ return getSessionOption(exec, t.target, key, t.label);
104
+ },
105
+ onCommand: (h) => exec.onCommand(h),
106
+ };
107
+ }
108
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backends/tmux/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { type TmuxExec } from "./exec.js";
2
+ /**
3
+ * Send a `paste` payload to a tmux session via `load-buffer + paste-buffer -p`.
4
+ *
5
+ * `-p` lets tmux emit bracketed-paste sequences if the receiver advertised
6
+ * support — empirically verified byte-perfect end-to-end against a passive
7
+ * sink on tmux 3.6 (see `docs/decisions/0001-tmux-paste-mechanism.md`). Body
8
+ * line terminators normalize
9
+ * to `\n` so claude (and other TUI agents that opt into bracketed paste)
10
+ * see literal newlines inside the bracket, not stray `\r`s.
11
+ *
12
+ * **Does NOT auto-append Enter.** Submission is a separate `key` call. This
13
+ * is the architectural lock-in from `engineer/wiki/tmux-private-server-bootstrap`'s
14
+ * sibling page — multi-line input cannot leak around the seam because
15
+ * `Backend.send` has no `sendRawText` primitive.
16
+ *
17
+ * **Pre-checks liveness.** `send-keys` returns exit 0 against a dead pane
18
+ * (silent input drop trap — see `engineer/wiki/tmux-pane-death-detection`).
19
+ * We check `has-session` first; if the session is gone, throw `SessionGone`
20
+ * rather than letting the paste land in the void.
21
+ *
22
+ * `label` is the user-facing identifier used in error messages (defaults to
23
+ * `target`); the wrapper in `tmuxBackend` passes the public label.
24
+ */
25
+ /**
26
+ * Strip control bytes from a paste body that could break out of the bracketed
27
+ * paste or inject terminal control. The danger (F48): a body containing the
28
+ * paste-END marker `ESC[201~` closes the bracket early, so its tail submits as
29
+ * *typed* input — content carrying terminal escapes (logs, diffs, adversarial
30
+ * input) could run commands. Keep `\n` (literal newlines — the point of bracketed
31
+ * paste) and `\t`; drop the bracketed-paste markers explicitly (no `[201~`
32
+ * residue) plus every other C0/DEL control byte (incl. bare ESC). Normalize CRs
33
+ * to `\n` BEFORE stripping so a lone `\r` becomes a newline, not nothing.
34
+ */
35
+ export declare function sanitizePasteBody(text: string): string;
36
+ export declare function pasteText(exec: TmuxExec, target: string, text: string, label?: string): Promise<void>;
37
+ export declare function sendKey(exec: TmuxExec, target: string, key: "Enter" | "Escape" | "1" | "2" | "3" | "y" | "n", label?: string): Promise<void>;
38
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/backends/tmux/keys.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,WAAW,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUtD;AAED,wBAAsB,SAAS,CAC7B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,OAAO,CAC3B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EACrD,KAAK,GAAE,MAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAGf"}
@@ -0,0 +1,63 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { SessionGone } from "../../errors.js";
3
+ import { runForSession } from "./exec.js";
4
+ import { hasSession } from "./sessions.js";
5
+ /**
6
+ * Send a `paste` payload to a tmux session via `load-buffer + paste-buffer -p`.
7
+ *
8
+ * `-p` lets tmux emit bracketed-paste sequences if the receiver advertised
9
+ * support — empirically verified byte-perfect end-to-end against a passive
10
+ * sink on tmux 3.6 (see `docs/decisions/0001-tmux-paste-mechanism.md`). Body
11
+ * line terminators normalize
12
+ * to `\n` so claude (and other TUI agents that opt into bracketed paste)
13
+ * see literal newlines inside the bracket, not stray `\r`s.
14
+ *
15
+ * **Does NOT auto-append Enter.** Submission is a separate `key` call. This
16
+ * is the architectural lock-in from `engineer/wiki/tmux-private-server-bootstrap`'s
17
+ * sibling page — multi-line input cannot leak around the seam because
18
+ * `Backend.send` has no `sendRawText` primitive.
19
+ *
20
+ * **Pre-checks liveness.** `send-keys` returns exit 0 against a dead pane
21
+ * (silent input drop trap — see `engineer/wiki/tmux-pane-death-detection`).
22
+ * We check `has-session` first; if the session is gone, throw `SessionGone`
23
+ * rather than letting the paste land in the void.
24
+ *
25
+ * `label` is the user-facing identifier used in error messages (defaults to
26
+ * `target`); the wrapper in `tmuxBackend` passes the public label.
27
+ */
28
+ /**
29
+ * Strip control bytes from a paste body that could break out of the bracketed
30
+ * paste or inject terminal control. The danger (F48): a body containing the
31
+ * paste-END marker `ESC[201~` closes the bracket early, so its tail submits as
32
+ * *typed* input — content carrying terminal escapes (logs, diffs, adversarial
33
+ * input) could run commands. Keep `\n` (literal newlines — the point of bracketed
34
+ * paste) and `\t`; drop the bracketed-paste markers explicitly (no `[201~`
35
+ * residue) plus every other C0/DEL control byte (incl. bare ESC). Normalize CRs
36
+ * to `\n` BEFORE stripping so a lone `\r` becomes a newline, not nothing.
37
+ */
38
+ export function sanitizePasteBody(text) {
39
+ return (text
40
+ .replace(/\r\n/g, "\n")
41
+ .replace(/\r/g, "\n")
42
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point.
43
+ .replace(/\x1b\[20[01]~/g, "")
44
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point.
45
+ .replace(/[\x00-\x08\x0b-\x1f\x7f]/g, ""));
46
+ }
47
+ export async function pasteText(exec, target, text, label = target) {
48
+ await ensureLive(exec, target, label);
49
+ const normalized = sanitizePasteBody(text);
50
+ const bufferName = `claudemux-${randomBytes(4).toString("hex")}`;
51
+ await runForSession(exec, ["load-buffer", "-b", bufferName, "-"], label, { input: normalized });
52
+ await runForSession(exec, ["paste-buffer", "-p", "-d", "-b", bufferName, "-t", target], label);
53
+ }
54
+ export async function sendKey(exec, target, key, label = target) {
55
+ await ensureLive(exec, target, label);
56
+ await runForSession(exec, ["send-keys", "-t", target, key], label);
57
+ }
58
+ async function ensureLive(exec, target, label) {
59
+ if (!(await hasSession(exec, target, label))) {
60
+ throw new SessionGone(label);
61
+ }
62
+ }
63
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/backends/tmux/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAiB,aAAa,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,CACL,IAAI;SACD,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;SACtB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;QACrB,wFAAwF;SACvF,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC9B,wFAAwF;SACvF,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAC5C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAc,EACd,MAAc,EACd,IAAY,EACZ,QAAgB,MAAM;IAEtB,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAEtC,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,aAAa,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IAEjE,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;IAChG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;AACjG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAc,EACd,MAAc,EACd,GAAqD,EACrD,QAAgB,MAAM;IAEtB,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;AACrE,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAc,EAAE,MAAc,EAAE,KAAa;IACrE,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type TmuxExec } from "./exec.js";
2
+ /**
3
+ * Build the argv prefix that sets all server-globals, ready to be `;`-joined
4
+ * with a `new-session` (or any other command) into one tmux invocation.
5
+ *
6
+ * Returns chunks separated by `;` markers — callers concat with the next
7
+ * command's argv.
8
+ */
9
+ export declare function serverOptionsArgv(): string[];
10
+ /**
11
+ * Persist a session-scoped key/value as a tmux user option on `target`. The
12
+ * value is passed as a single argv element (no shell), so no escaping is
13
+ * needed. The option lives with the session and is dropped when the session
14
+ * is killed — no cleanup required.
15
+ */
16
+ export declare function setSessionOption(exec: TmuxExec, target: string, key: string, value: string, label?: string): Promise<void>;
17
+ /**
18
+ * Read a session user option previously set via {@link setSessionOption}.
19
+ * Returns `undefined` when the option is unset or the session/server is
20
+ * unreadable — this is best-effort metadata, never a hard error. `-v` prints
21
+ * just the value; an unset user option yields empty output (trimmed → unset).
22
+ */
23
+ export declare function getSessionOption(exec: TmuxExec, target: string, key: string, label?: string): Promise<string | undefined>;
24
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/backends/tmux/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,WAAW,CAAC;AA4B/D;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAM5C;AAYD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,KAAK,GAAE,MAAe,GACrB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAY7B"}
@@ -0,0 +1,84 @@
1
+ import { classifyTmuxFailure } from "./exec.js";
2
+ /**
3
+ * Argv chunks that set the substrate's four server-global tmux options.
4
+ * Joined into a multi-command invocation in front of `new-session` so the
5
+ * globals are set **in the same tmux client connection** that creates the
6
+ * session — see `engineer/wiki/tmux-private-server-bootstrap`.
7
+ *
8
+ * Why this shape: `start-server` does not keep the server alive on its own
9
+ * (tmux exits the server when no session remains). `set-option -g` against
10
+ * an empty server fails with "no server running." Combining all commands
11
+ * into one `tmux …` invocation with `;` separators avoids the dead-window
12
+ * between "server starts" and "first session lands."
13
+ *
14
+ * `history-limit` is allocated at pane creation, so it must be set as a
15
+ * `-g` window-option BEFORE the pane is created. The other three follow the
16
+ * same pattern for consistency.
17
+ *
18
+ * `LC_ALL=C.UTF-8` lives on the session env (`-e` on new-session), so it is
19
+ * not in this list.
20
+ */
21
+ const SERVER_OPTION_COMMANDS = [
22
+ ["set-option", "-g", "escape-time", "0"],
23
+ ["set-option", "-g", "default-terminal", "tmux-256color"],
24
+ ["set-window-option", "-g", "history-limit", "50000"],
25
+ ["set-window-option", "-g", "remain-on-exit", "off"],
26
+ ];
27
+ /**
28
+ * Build the argv prefix that sets all server-globals, ready to be `;`-joined
29
+ * with a `new-session` (or any other command) into one tmux invocation.
30
+ *
31
+ * Returns chunks separated by `;` markers — callers concat with the next
32
+ * command's argv.
33
+ */
34
+ export function serverOptionsArgv() {
35
+ const out = [];
36
+ for (const cmd of SERVER_OPTION_COMMANDS) {
37
+ out.push(...cmd, ";");
38
+ }
39
+ return out;
40
+ }
41
+ /**
42
+ * Map a backend-neutral session-meta key to a tmux **session user option**.
43
+ * User option names must begin with `@`; we further namespace under
44
+ * `@claudemux-` so the substrate's keys never collide with a consumer's own
45
+ * tmux options on the shared server.
46
+ */
47
+ function userOptionName(key) {
48
+ return `@claudemux-${key}`;
49
+ }
50
+ /**
51
+ * Persist a session-scoped key/value as a tmux user option on `target`. The
52
+ * value is passed as a single argv element (no shell), so no escaping is
53
+ * needed. The option lives with the session and is dropped when the session
54
+ * is killed — no cleanup required.
55
+ */
56
+ export async function setSessionOption(exec, target, key, value, label = target) {
57
+ const args = ["set-option", "-t", target, userOptionName(key), value];
58
+ const r = await exec.run(args, { sessionName: label });
59
+ const err = classifyTmuxFailure(label, ["tmux", ...args], r);
60
+ if (err)
61
+ throw err;
62
+ }
63
+ /**
64
+ * Read a session user option previously set via {@link setSessionOption}.
65
+ * Returns `undefined` when the option is unset or the session/server is
66
+ * unreadable — this is best-effort metadata, never a hard error. `-v` prints
67
+ * just the value; an unset user option yields empty output (trimmed → unset).
68
+ */
69
+ export async function getSessionOption(exec, target, key, label = target) {
70
+ let r;
71
+ try {
72
+ r = await exec.run(["show-options", "-t", target, "-v", userOptionName(key)], {
73
+ sessionName: label,
74
+ });
75
+ }
76
+ catch {
77
+ return undefined; // session gone / no server / wedged — treat as "unset"
78
+ }
79
+ if (r.exit !== 0)
80
+ return undefined;
81
+ const v = r.stdout.trim();
82
+ return v.length > 0 ? v : undefined;
83
+ }
84
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/backends/tmux/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAE/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,sBAAsB,GAAqC;IAC/D,CAAC,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC;IACxC,CAAC,YAAY,EAAE,IAAI,EAAE,kBAAkB,EAAE,eAAe,CAAC;IACzD,CAAC,mBAAmB,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC;IACrD,CAAC,mBAAmB,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC;CACrD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,sBAAsB,EAAE,CAAC;QACzC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,GAAW;IACjC,OAAO,cAAc,GAAG,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAc,EACd,MAAc,EACd,GAAW,EACX,KAAa,EACb,QAAgB,MAAM;IAEtB,MAAM,IAAI,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACtE,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,mBAAmB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,IAAI,GAAG;QAAE,MAAM,GAAG,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAc,EACd,MAAc,EACd,GAAW,EACX,QAAgB,MAAM;IAEtB,IAAI,CAAuC,CAAC;IAC5C,IAAI,CAAC;QACH,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE;YAC5E,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC,CAAC,uDAAuD;IAC3E,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACnC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC1B,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACtC,CAAC"}