aimux-cli 0.1.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 (207) hide show
  1. package/README.md +743 -0
  2. package/bin/aimux +2 -0
  3. package/dist/agent-events.d.ts +20 -0
  4. package/dist/agent-events.js +2 -0
  5. package/dist/agent-events.js.map +1 -0
  6. package/dist/agent-message-parts.d.ts +17 -0
  7. package/dist/agent-message-parts.js +31 -0
  8. package/dist/agent-message-parts.js.map +1 -0
  9. package/dist/agent-output-parser.d.ts +16 -0
  10. package/dist/agent-output-parser.js +229 -0
  11. package/dist/agent-output-parser.js.map +1 -0
  12. package/dist/agent-tracker.d.ts +9 -0
  13. package/dist/agent-tracker.js +144 -0
  14. package/dist/agent-tracker.js.map +1 -0
  15. package/dist/agent-watcher.d.ts +15 -0
  16. package/dist/agent-watcher.js +2 -0
  17. package/dist/agent-watcher.js.map +1 -0
  18. package/dist/attachment-store.d.ts +35 -0
  19. package/dist/attachment-store.js +129 -0
  20. package/dist/attachment-store.js.map +1 -0
  21. package/dist/builtin-metadata-watchers.d.ts +2 -0
  22. package/dist/builtin-metadata-watchers.js +275 -0
  23. package/dist/builtin-metadata-watchers.js.map +1 -0
  24. package/dist/claude-hooks.d.ts +29 -0
  25. package/dist/claude-hooks.js +106 -0
  26. package/dist/claude-hooks.js.map +1 -0
  27. package/dist/config.d.ts +78 -0
  28. package/dist/config.js +172 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/context/compactor.d.ts +20 -0
  31. package/dist/context/compactor.js +212 -0
  32. package/dist/context/compactor.js.map +1 -0
  33. package/dist/context/context-bridge.d.ts +67 -0
  34. package/dist/context/context-bridge.js +471 -0
  35. package/dist/context/context-bridge.js.map +1 -0
  36. package/dist/context/context-file.d.ts +11 -0
  37. package/dist/context/context-file.js +93 -0
  38. package/dist/context/context-file.js.map +1 -0
  39. package/dist/context/history.d.ts +40 -0
  40. package/dist/context/history.js +108 -0
  41. package/dist/context/history.js.map +1 -0
  42. package/dist/daemon.d.ts +39 -0
  43. package/dist/daemon.js +344 -0
  44. package/dist/daemon.js.map +1 -0
  45. package/dist/dashboard-session-registry.d.ts +47 -0
  46. package/dist/dashboard-session-registry.js +161 -0
  47. package/dist/dashboard-session-registry.js.map +1 -0
  48. package/dist/dashboard-state.d.ts +18 -0
  49. package/dist/dashboard-state.js +26 -0
  50. package/dist/dashboard-state.js.map +1 -0
  51. package/dist/dashboard.d.ts +118 -0
  52. package/dist/dashboard.js +91 -0
  53. package/dist/dashboard.js.map +1 -0
  54. package/dist/debug.d.ts +7 -0
  55. package/dist/debug.js +41 -0
  56. package/dist/debug.js.map +1 -0
  57. package/dist/fast-control.d.ts +45 -0
  58. package/dist/fast-control.js +174 -0
  59. package/dist/fast-control.js.map +1 -0
  60. package/dist/hotkeys.d.ts +44 -0
  61. package/dist/hotkeys.js +118 -0
  62. package/dist/hotkeys.js.map +1 -0
  63. package/dist/http-client.d.ts +10 -0
  64. package/dist/http-client.js +54 -0
  65. package/dist/http-client.js.map +1 -0
  66. package/dist/instance-directory.d.ts +32 -0
  67. package/dist/instance-directory.js +82 -0
  68. package/dist/instance-directory.js.map +1 -0
  69. package/dist/instance-registry.d.ts +38 -0
  70. package/dist/instance-registry.js +208 -0
  71. package/dist/instance-registry.js.map +1 -0
  72. package/dist/key-parser.d.ts +30 -0
  73. package/dist/key-parser.js +272 -0
  74. package/dist/key-parser.js.map +1 -0
  75. package/dist/last-used.d.ts +31 -0
  76. package/dist/last-used.js +93 -0
  77. package/dist/last-used.js.map +1 -0
  78. package/dist/main.d.ts +1 -0
  79. package/dist/main.js +2483 -0
  80. package/dist/main.js.map +1 -0
  81. package/dist/metadata-server.d.ts +268 -0
  82. package/dist/metadata-server.js +1379 -0
  83. package/dist/metadata-server.js.map +1 -0
  84. package/dist/metadata-store.d.ts +80 -0
  85. package/dist/metadata-store.js +87 -0
  86. package/dist/metadata-store.js.map +1 -0
  87. package/dist/multiplexer.d.ts +471 -0
  88. package/dist/multiplexer.js +5714 -0
  89. package/dist/multiplexer.js.map +1 -0
  90. package/dist/notification-context.d.ts +18 -0
  91. package/dist/notification-context.js +68 -0
  92. package/dist/notification-context.js.map +1 -0
  93. package/dist/notifications.d.ts +38 -0
  94. package/dist/notifications.js +111 -0
  95. package/dist/notifications.js.map +1 -0
  96. package/dist/notify.d.ts +10 -0
  97. package/dist/notify.js +62 -0
  98. package/dist/notify.js.map +1 -0
  99. package/dist/orchestration-actions.d.ts +76 -0
  100. package/dist/orchestration-actions.js +310 -0
  101. package/dist/orchestration-actions.js.map +1 -0
  102. package/dist/orchestration-dispatcher.d.ts +22 -0
  103. package/dist/orchestration-dispatcher.js +49 -0
  104. package/dist/orchestration-dispatcher.js.map +1 -0
  105. package/dist/orchestration-routing.d.ts +20 -0
  106. package/dist/orchestration-routing.js +78 -0
  107. package/dist/orchestration-routing.js.map +1 -0
  108. package/dist/orchestration.d.ts +26 -0
  109. package/dist/orchestration.js +110 -0
  110. package/dist/orchestration.js.map +1 -0
  111. package/dist/osc-notifications.d.ts +15 -0
  112. package/dist/osc-notifications.js +180 -0
  113. package/dist/osc-notifications.js.map +1 -0
  114. package/dist/paths.d.ts +55 -0
  115. package/dist/paths.js +259 -0
  116. package/dist/paths.js.map +1 -0
  117. package/dist/plugin-runtime.d.ts +46 -0
  118. package/dist/plugin-runtime.js +180 -0
  119. package/dist/plugin-runtime.js.map +1 -0
  120. package/dist/project-events.d.ts +36 -0
  121. package/dist/project-events.js +63 -0
  122. package/dist/project-events.js.map +1 -0
  123. package/dist/project-scanner.d.ts +38 -0
  124. package/dist/project-scanner.js +243 -0
  125. package/dist/project-scanner.js.map +1 -0
  126. package/dist/project-service-manifest.d.ts +18 -0
  127. package/dist/project-service-manifest.js +56 -0
  128. package/dist/project-service-manifest.js.map +1 -0
  129. package/dist/recency.d.ts +2 -0
  130. package/dist/recency.js +34 -0
  131. package/dist/recency.js.map +1 -0
  132. package/dist/recorder.d.ts +14 -0
  133. package/dist/recorder.js +130 -0
  134. package/dist/recorder.js.map +1 -0
  135. package/dist/session-bootstrap.d.ts +45 -0
  136. package/dist/session-bootstrap.js +436 -0
  137. package/dist/session-bootstrap.js.map +1 -0
  138. package/dist/session-message-history.d.ts +27 -0
  139. package/dist/session-message-history.js +105 -0
  140. package/dist/session-message-history.js.map +1 -0
  141. package/dist/session-runtime.d.ts +44 -0
  142. package/dist/session-runtime.js +56 -0
  143. package/dist/session-runtime.js.map +1 -0
  144. package/dist/session-semantics.d.ts +35 -0
  145. package/dist/session-semantics.js +110 -0
  146. package/dist/session-semantics.js.map +1 -0
  147. package/dist/status-detector.d.ts +17 -0
  148. package/dist/status-detector.js +67 -0
  149. package/dist/status-detector.js.map +1 -0
  150. package/dist/statusline-model.d.ts +103 -0
  151. package/dist/statusline-model.js +177 -0
  152. package/dist/statusline-model.js.map +1 -0
  153. package/dist/task-dispatcher.d.ts +63 -0
  154. package/dist/task-dispatcher.js +210 -0
  155. package/dist/task-dispatcher.js.map +1 -0
  156. package/dist/task-workflow.d.ts +13 -0
  157. package/dist/task-workflow.js +153 -0
  158. package/dist/task-workflow.js.map +1 -0
  159. package/dist/tasks.d.ts +60 -0
  160. package/dist/tasks.js +120 -0
  161. package/dist/tasks.js.map +1 -0
  162. package/dist/team.d.ts +28 -0
  163. package/dist/team.js +91 -0
  164. package/dist/team.js.map +1 -0
  165. package/dist/terminal-host.d.ts +10 -0
  166. package/dist/terminal-host.js +52 -0
  167. package/dist/terminal-host.js.map +1 -0
  168. package/dist/threads.d.ts +61 -0
  169. package/dist/threads.js +200 -0
  170. package/dist/threads.js.map +1 -0
  171. package/dist/tmux-doctor.d.ts +47 -0
  172. package/dist/tmux-doctor.js +112 -0
  173. package/dist/tmux-doctor.js.map +1 -0
  174. package/dist/tmux-runtime-manager.d.ts +164 -0
  175. package/dist/tmux-runtime-manager.js +794 -0
  176. package/dist/tmux-runtime-manager.js.map +1 -0
  177. package/dist/tmux-session-transport.d.ts +31 -0
  178. package/dist/tmux-session-transport.js +115 -0
  179. package/dist/tmux-session-transport.js.map +1 -0
  180. package/dist/tmux-statusline.d.ts +17 -0
  181. package/dist/tmux-statusline.js +166 -0
  182. package/dist/tmux-statusline.js.map +1 -0
  183. package/dist/tool-output-watchers.d.ts +10 -0
  184. package/dist/tool-output-watchers.js +190 -0
  185. package/dist/tool-output-watchers.js.map +1 -0
  186. package/dist/tui/render/box.d.ts +1 -0
  187. package/dist/tui/render/box.js +20 -0
  188. package/dist/tui/render/box.js.map +1 -0
  189. package/dist/tui/render/text.d.ts +8 -0
  190. package/dist/tui/render/text.js +92 -0
  191. package/dist/tui/render/text.js.map +1 -0
  192. package/dist/tui/screens/dashboard-renderers.d.ts +23 -0
  193. package/dist/tui/screens/dashboard-renderers.js +411 -0
  194. package/dist/tui/screens/dashboard-renderers.js.map +1 -0
  195. package/dist/tui/screens/overlay-renderers.d.ts +10 -0
  196. package/dist/tui/screens/overlay-renderers.js +274 -0
  197. package/dist/tui/screens/overlay-renderers.js.map +1 -0
  198. package/dist/tui/screens/subscreen-renderers.d.ts +9 -0
  199. package/dist/tui/screens/subscreen-renderers.js +327 -0
  200. package/dist/tui/screens/subscreen-renderers.js.map +1 -0
  201. package/dist/workflow.d.ts +19 -0
  202. package/dist/workflow.js +111 -0
  203. package/dist/workflow.js.map +1 -0
  204. package/dist/worktree.d.ts +23 -0
  205. package/dist/worktree.js +101 -0
  206. package/dist/worktree.js.map +1 -0
  207. package/package.json +70 -0
@@ -0,0 +1,794 @@
1
+ import { createHash } from "node:crypto";
2
+ import { basename, dirname, join } from "node:path";
3
+ import { execFileSync, spawnSync } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
5
+ import { loadConfig } from "./config.js";
6
+ import { debug } from "./debug.js";
7
+ import { getProjectStateDirFor } from "./paths.js";
8
+ export function isDashboardWindowName(name) {
9
+ return name === "dashboard" || name.startsWith("dashboard-");
10
+ }
11
+ export const MANAGED_TMUX_SESSION_OPTIONS = Object.freeze({
12
+ prefix: "C-a",
13
+ prefix2: "C-b",
14
+ mouse: "on",
15
+ extendedKeys: "always",
16
+ extendedKeysFormat: "csi-u",
17
+ });
18
+ export const MANAGED_TMUX_TERMINAL_FEATURES = Object.freeze(["xterm*:extkeys", "xterm*:hyperlinks"]);
19
+ export const MANAGED_TMUX_AGENT_WINDOW_OPTIONS = Object.freeze({
20
+ allowPassthrough: "on",
21
+ });
22
+ const MODIFIED_ENTER_HEX = "1b 5b 31 33 3b 32 75";
23
+ const DEFAULT_EXEC = (args, options) => execFileSync("tmux", args, {
24
+ cwd: options?.cwd,
25
+ encoding: "utf8",
26
+ stdio: ["ignore", "pipe", "pipe"],
27
+ }).trim();
28
+ const DEFAULT_INTERACTIVE_EXEC = (args, options) => {
29
+ const result = spawnSync("tmux", args, {
30
+ cwd: options?.cwd,
31
+ stdio: "inherit",
32
+ });
33
+ if (result.status !== 0) {
34
+ throw new Error(result.stderr?.toString() || `tmux ${args.join(" ")} failed`);
35
+ }
36
+ };
37
+ export class TmuxRuntimeManager {
38
+ exec;
39
+ interactiveExec;
40
+ constructor(exec = DEFAULT_EXEC, interactiveExec = DEFAULT_INTERACTIVE_EXEC) {
41
+ this.exec = exec;
42
+ this.interactiveExec = interactiveExec;
43
+ }
44
+ isAvailable() {
45
+ try {
46
+ this.exec(["-V"]);
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ getVersion() {
54
+ try {
55
+ return this.exec(["-V"]);
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ getProjectSession(projectRoot) {
62
+ const projectId = createHash("sha1").update(projectRoot).digest("hex").slice(0, 10);
63
+ const slug = basename(projectRoot).replace(/[^a-zA-Z0-9_-]+/g, "-") || "project";
64
+ let prefix = "aimux";
65
+ try {
66
+ prefix = loadConfig().runtime.tmux.sessionPrefix || "aimux";
67
+ }
68
+ catch { }
69
+ return {
70
+ projectRoot,
71
+ projectId,
72
+ sessionName: `${prefix}-${slug}-${projectId}`,
73
+ };
74
+ }
75
+ getProjectClientSessionName(hostSessionName, clientSuffix) {
76
+ return `${hostSessionName}-client-${clientSuffix}`;
77
+ }
78
+ isClientSessionName(sessionName) {
79
+ return /-client-[a-f0-9]{8}$/.test(sessionName);
80
+ }
81
+ getOpenSessionName(sessionName, insideTmux = this.isInsideTmux()) {
82
+ return this.resolveOpenSessionName(sessionName, insideTmux);
83
+ }
84
+ peekOpenSessionName(sessionName, insideTmux = this.isInsideTmux()) {
85
+ if (!this.isManagedSessionName(sessionName) || this.isClientSessionName(sessionName))
86
+ return sessionName;
87
+ const clientSuffix = this.resolveClientSuffix(insideTmux);
88
+ if (!clientSuffix)
89
+ return sessionName;
90
+ return this.getProjectClientSessionName(sessionName, clientSuffix);
91
+ }
92
+ ensureClientSession(hostSessionName, clientSessionName, projectRoot) {
93
+ const dashboardName = `dashboard-${clientSessionName.match(/-client-([a-f0-9]{8})$/)?.[1] ?? "client"}`;
94
+ const clientSessionExists = this.hasSession(clientSessionName);
95
+ const hostDashboard = this.listWindows(hostSessionName).find((window) => isDashboardWindowName(window.name));
96
+ const existingDashboard = clientSessionExists
97
+ ? this.listWindows(clientSessionName).find((window) => isDashboardWindowName(window.name))
98
+ : undefined;
99
+ const needsRecreate = !!hostDashboard &&
100
+ !!existingDashboard &&
101
+ (existingDashboard.id === hostDashboard.id || existingDashboard.name === "dashboard");
102
+ if (needsRecreate) {
103
+ this.exec(["kill-session", "-t", clientSessionName]);
104
+ }
105
+ if (!clientSessionExists || needsRecreate) {
106
+ this.exec([
107
+ "new-session",
108
+ "-d",
109
+ "-s",
110
+ clientSessionName,
111
+ "-c",
112
+ projectRoot,
113
+ "-n",
114
+ dashboardName,
115
+ "sh",
116
+ "-lc",
117
+ "tail -f /dev/null",
118
+ ], { cwd: projectRoot });
119
+ this.configureSession(clientSessionName, projectRoot);
120
+ this.exec(["set-option", "-t", clientSessionName, "@aimux-host-session", hostSessionName]);
121
+ return;
122
+ }
123
+ this.configureSession(clientSessionName, projectRoot);
124
+ this.exec(["set-option", "-t", clientSessionName, "@aimux-host-session", hostSessionName]);
125
+ }
126
+ ensureLinkedWindow(clientSessionName, target) {
127
+ const existing = this.getTargetByWindowId(clientSessionName, target.windowId);
128
+ if (existing)
129
+ return existing;
130
+ this.exec(["link-window", "-d", "-s", target.windowId, "-t", clientSessionName]);
131
+ const linked = this.getTargetByWindowId(clientSessionName, target.windowId);
132
+ if (!linked) {
133
+ throw new Error(`Failed to link window ${target.windowId} into tmux session ${clientSessionName}`);
134
+ }
135
+ return linked;
136
+ }
137
+ getSessionPrefix() {
138
+ try {
139
+ return loadConfig().runtime.tmux.sessionPrefix || "aimux";
140
+ }
141
+ catch {
142
+ return "aimux";
143
+ }
144
+ }
145
+ isManagedSessionName(sessionName) {
146
+ return sessionName.startsWith(`${this.getSessionPrefix()}-`);
147
+ }
148
+ hasSession(sessionName) {
149
+ try {
150
+ this.exec(["has-session", "-t", sessionName]);
151
+ return true;
152
+ }
153
+ catch {
154
+ return false;
155
+ }
156
+ }
157
+ ensureProjectSession(projectRoot, dashboardCommand) {
158
+ const session = this.getProjectSession(projectRoot);
159
+ const exists = this.hasSession(session.sessionName);
160
+ if (!exists) {
161
+ const argv = dashboardCommand && dashboardCommand.args.length >= 0
162
+ ? [
163
+ "new-session",
164
+ "-d",
165
+ "-s",
166
+ session.sessionName,
167
+ "-c",
168
+ dashboardCommand.cwd,
169
+ "-n",
170
+ "dashboard",
171
+ dashboardCommand.command,
172
+ ...dashboardCommand.args,
173
+ ]
174
+ : [
175
+ "new-session",
176
+ "-d",
177
+ "-s",
178
+ session.sessionName,
179
+ "-c",
180
+ projectRoot,
181
+ "-n",
182
+ "dashboard",
183
+ "sh",
184
+ "-lc",
185
+ "printf ''",
186
+ ];
187
+ this.exec(argv, { cwd: projectRoot });
188
+ }
189
+ this.configureSession(session.sessionName, projectRoot);
190
+ return session;
191
+ }
192
+ listWindows(sessionName) {
193
+ const raw = this.exec([
194
+ "list-windows",
195
+ "-t",
196
+ sessionName,
197
+ "-F",
198
+ "#{window_id}\t#{window_index}\t#{window_name}\t#{window_active}\t#{window_activity}",
199
+ ]);
200
+ if (!raw)
201
+ return [];
202
+ return raw
203
+ .split("\n")
204
+ .filter(Boolean)
205
+ .map((line) => {
206
+ const [id, index, name, active, activity] = line.split("\t");
207
+ return {
208
+ id,
209
+ index: Number(index),
210
+ name,
211
+ active: active === "1",
212
+ activity: activity ? Number(activity) : undefined,
213
+ };
214
+ });
215
+ }
216
+ listSessionNames() {
217
+ const raw = this.exec(["list-sessions", "-F", "#{session_name}"]);
218
+ if (!raw)
219
+ return [];
220
+ return raw
221
+ .split("\n")
222
+ .map((line) => line.trim())
223
+ .filter(Boolean);
224
+ }
225
+ getTargetByWindowId(sessionName, windowId) {
226
+ const window = this.listWindows(sessionName).find((entry) => entry.id === windowId);
227
+ if (!window)
228
+ return null;
229
+ return {
230
+ sessionName,
231
+ windowId: window.id,
232
+ windowIndex: window.index,
233
+ windowName: window.name,
234
+ };
235
+ }
236
+ hasWindow(target) {
237
+ return this.getTargetByWindowId(target.sessionName, target.windowId) !== null;
238
+ }
239
+ isWindowAlive(target) {
240
+ try {
241
+ const paneDead = this.exec(["display-message", "-p", "-t", target.windowId, "#{pane_dead}"]).trim();
242
+ return paneDead !== "1";
243
+ }
244
+ catch {
245
+ return false;
246
+ }
247
+ }
248
+ ensureDashboardWindow(sessionName, projectRoot, dashboardCommand) {
249
+ const dashboardName = this.getDashboardWindowName();
250
+ const existing = this.listWindows(sessionName).find((window) => window.name === dashboardName);
251
+ if (existing) {
252
+ this.renameWindow(existing.id, dashboardName);
253
+ return {
254
+ sessionName,
255
+ windowId: existing.id,
256
+ windowIndex: existing.index,
257
+ windowName: dashboardName,
258
+ };
259
+ }
260
+ const argv = dashboardCommand && dashboardCommand.args.length >= 0
261
+ ? [
262
+ "new-window",
263
+ "-d",
264
+ "-t",
265
+ sessionName,
266
+ "-c",
267
+ dashboardCommand.cwd,
268
+ "-n",
269
+ dashboardName,
270
+ dashboardCommand.command,
271
+ ...dashboardCommand.args,
272
+ ]
273
+ : ["new-window", "-d", "-t", sessionName, "-c", projectRoot, "-n", dashboardName, "sh", "-lc", "printf ''"];
274
+ this.exec(argv, {
275
+ cwd: projectRoot,
276
+ });
277
+ const created = this.listWindows(sessionName).find((window) => window.name === dashboardName);
278
+ if (!created) {
279
+ throw new Error(`Failed to create dashboard window in tmux session ${sessionName}`);
280
+ }
281
+ return {
282
+ sessionName,
283
+ windowId: created.id,
284
+ windowIndex: created.index,
285
+ windowName: created.name,
286
+ };
287
+ }
288
+ createWindow(sessionName, name, cwd, command, args, options = {}) {
289
+ const argv = [
290
+ "new-window",
291
+ ...(options.detached ? ["-d"] : []),
292
+ "-P",
293
+ "-t",
294
+ sessionName,
295
+ "-c",
296
+ cwd,
297
+ "-n",
298
+ name,
299
+ "-F",
300
+ "#{window_id}\t#{window_index}\t#{window_name}",
301
+ command,
302
+ ...args,
303
+ ];
304
+ const raw = this.exec(argv, { cwd });
305
+ const [windowId, index, windowName] = raw.split("\t");
306
+ return {
307
+ sessionName,
308
+ windowId,
309
+ windowIndex: Number(index),
310
+ windowName,
311
+ };
312
+ }
313
+ killWindow(target) {
314
+ this.exec(["kill-window", "-t", target.windowId]);
315
+ }
316
+ killSession(sessionName) {
317
+ this.exec(["kill-session", "-t", sessionName]);
318
+ }
319
+ renameWindow(windowTarget, name) {
320
+ this.exec(["rename-window", "-t", windowTarget, name]);
321
+ }
322
+ respawnWindow(target, spec) {
323
+ this.exec(["respawn-window", "-k", "-t", target.windowId, "-c", spec.cwd, spec.command, ...spec.args], {
324
+ cwd: spec.cwd,
325
+ });
326
+ }
327
+ selectWindow(target) {
328
+ this.exec(["select-window", "-t", target.windowId]);
329
+ }
330
+ sendFocusIn(target) {
331
+ this.exec(["send-keys", "-t", target.windowId, "-H", "1b", "5b", "49"]);
332
+ }
333
+ switchClientToTarget(clientTty, target) {
334
+ debug(`tmux switchClientToTarget: client=${clientTty} target=${target.windowId}`, "fork");
335
+ this.exec(["switch-client", "-c", clientTty, "-t", target.windowId]);
336
+ }
337
+ displayWindowMenu(title, items) {
338
+ if (items.length === 0)
339
+ return;
340
+ const args = ["display-menu", "-T", title, "-x", "P", "-y", "P"];
341
+ for (const item of items) {
342
+ args.push(item.label, "", `select-window -t ${item.target.windowId}`);
343
+ }
344
+ this.exec(args);
345
+ }
346
+ displayWindowMenuForClient(clientTty, title, items) {
347
+ if (items.length === 0)
348
+ return;
349
+ const args = ["display-menu", "-c", clientTty, "-T", title, "-x", "P", "-y", "P"];
350
+ for (const item of items) {
351
+ args.push(item.label, "", `select-window -t ${item.target.windowId}`);
352
+ }
353
+ this.exec(args);
354
+ }
355
+ captureTarget(target, options = {}) {
356
+ const startLine = options.startLine ?? "-";
357
+ return this.exec(["capture-pane", "-p", "-J", "-t", target.windowId, "-S", String(startLine)]);
358
+ }
359
+ listClients() {
360
+ const raw = this.exec(["list-clients", "-F", "#{client_tty}\t#{session_name}\t#{window_id}\t#{client_name}"]);
361
+ if (!raw)
362
+ return [];
363
+ return raw
364
+ .split("\n")
365
+ .filter(Boolean)
366
+ .map((line) => {
367
+ const [tty, sessionName, windowId, name] = line.split("\t");
368
+ return { tty, sessionName, windowId, name };
369
+ });
370
+ }
371
+ findClientByTty(clientTty) {
372
+ const normalized = clientTty.trim();
373
+ if (!normalized)
374
+ return null;
375
+ return this.listClients().find((client) => client.tty === normalized) ?? null;
376
+ }
377
+ getAttachedClientForTarget(target) {
378
+ const clientPrefix = `${target.sessionName}-client-`;
379
+ const clients = this.listClients().filter((client) => client.sessionName === target.sessionName || client.sessionName.startsWith(clientPrefix));
380
+ if (clients.length === 0)
381
+ return null;
382
+ return clients.find((client) => client.windowId === target.windowId) ?? clients[0] ?? null;
383
+ }
384
+ sendText(target, text) {
385
+ if (!text)
386
+ return;
387
+ debug(`tmux sendText: target=${target.windowId} bytes=${Buffer.byteLength(text)} preview=${JSON.stringify(text.slice(0, 180))}`, "fork");
388
+ this.exec(["send-keys", "-t", target.windowId, "-l", text]);
389
+ }
390
+ sendEnter(target) {
391
+ debug(`tmux sendEnter: target=${target.windowId}`, "fork");
392
+ this.exec(["send-keys", "-t", target.windowId, "Enter"]);
393
+ }
394
+ sendClientEnter(clientTty) {
395
+ debug(`tmux sendClientEnter: client=${clientTty}`, "fork");
396
+ this.exec(["send-keys", "-K", "-c", clientTty, "Enter"]);
397
+ }
398
+ sendClientCarriageReturn(clientTty, target) {
399
+ debug(`tmux sendClientCarriageReturn: client=${clientTty} target=${target.windowId}`, "fork");
400
+ this.exec(["send-keys", "-c", clientTty, "-t", target.windowId, "-H", "0d"]);
401
+ }
402
+ sendCarriageReturn(target) {
403
+ debug(`tmux sendCarriageReturn: target=${target.windowId}`, "fork");
404
+ this.exec(["send-keys", "-t", target.windowId, "-H", "0d"]);
405
+ }
406
+ sendEscape(target) {
407
+ debug(`tmux sendEscape: target=${target.windowId}`, "fork");
408
+ this.exec(["send-keys", "-t", target.windowId, "-H", "1b"]);
409
+ }
410
+ sendModifiedEnter(target) {
411
+ debug(`tmux sendModifiedEnter: target=${target.windowId} hex=${MODIFIED_ENTER_HEX}`, "fork");
412
+ this.exec(["send-keys", "-t", target.windowId, "-H", ...MODIFIED_ENTER_HEX.split(" ")]);
413
+ }
414
+ sendKey(target, key) {
415
+ debug(`tmux sendKey: target=${target.windowId} key=${key}`, "fork");
416
+ this.exec(["send-keys", "-t", target.windowId, key]);
417
+ }
418
+ setWindowMetadata(target, metadata) {
419
+ const windowTarget = typeof target === "string" ? target : target.windowId;
420
+ this.exec(["set-window-option", "-q", "-t", windowTarget, "@aimux-meta", JSON.stringify(metadata)]);
421
+ }
422
+ setWindowOption(target, key, value) {
423
+ const windowTarget = typeof target === "string" ? target : target.windowId;
424
+ this.exec(["set-window-option", "-q", "-t", windowTarget, key, value]);
425
+ }
426
+ setSessionOption(sessionName, key, value) {
427
+ this.exec(["set-option", "-t", sessionName, key, value]);
428
+ }
429
+ applyManagedAgentWindowPolicy(target, toolConfigKey) {
430
+ this.setWindowOption(target, "@aimux-tool", toolConfigKey);
431
+ this.setWindowOption(target, "allow-passthrough", MANAGED_TMUX_AGENT_WINDOW_OPTIONS.allowPassthrough);
432
+ }
433
+ getWindowOption(target, key) {
434
+ const windowTarget = typeof target === "string" ? target : target.windowId;
435
+ try {
436
+ const value = this.exec(["show-window-options", "-v", "-t", windowTarget, key]);
437
+ return value.trim() || null;
438
+ }
439
+ catch {
440
+ return null;
441
+ }
442
+ }
443
+ getWindowMetadata(target) {
444
+ const windowTarget = typeof target === "string" ? target : target.windowId;
445
+ try {
446
+ const raw = this.exec(["show-window-options", "-v", "-t", windowTarget, "@aimux-meta"]);
447
+ return JSON.parse(raw);
448
+ }
449
+ catch {
450
+ return null;
451
+ }
452
+ }
453
+ listManagedWindows(sessionName) {
454
+ const windows = this.listWindows(sessionName);
455
+ const managed = [];
456
+ for (const window of windows) {
457
+ const target = {
458
+ sessionName,
459
+ windowId: window.id,
460
+ windowIndex: window.index,
461
+ windowName: window.name,
462
+ };
463
+ const metadata = this.getWindowMetadata(target);
464
+ if (!metadata)
465
+ continue;
466
+ managed.push({ target, metadata });
467
+ }
468
+ return managed;
469
+ }
470
+ findManagedWindow(sessionName, matcher) {
471
+ if (!matcher.sessionId && !matcher.backendSessionId)
472
+ return null;
473
+ for (const entry of this.listManagedWindows(sessionName)) {
474
+ if (matcher.sessionId && entry.metadata.sessionId === matcher.sessionId)
475
+ return entry;
476
+ if (matcher.backendSessionId && entry.metadata.backendSessionId === matcher.backendSessionId)
477
+ return entry;
478
+ }
479
+ return null;
480
+ }
481
+ attachSession(sessionName, windowIndex) {
482
+ const target = windowIndex === undefined ? sessionName : `${sessionName}:${windowIndex}`;
483
+ this.interactiveExec(["attach-session", "-t", target]);
484
+ }
485
+ detachClient() {
486
+ this.interactiveExec(["detach-client"]);
487
+ }
488
+ switchToLastClientSession() {
489
+ this.interactiveExec(["switch-client", "-l"]);
490
+ }
491
+ currentClientSession() {
492
+ try {
493
+ return this.exec(["display-message", "-p", "#{client_session}"]);
494
+ }
495
+ catch {
496
+ return null;
497
+ }
498
+ }
499
+ displayMessage(format, target) {
500
+ try {
501
+ const args = target ? ["display-message", "-p", "-t", target, format] : ["display-message", "-p", format];
502
+ const value = this.exec(args);
503
+ return value.trim() || null;
504
+ }
505
+ catch {
506
+ return null;
507
+ }
508
+ }
509
+ getSessionOption(sessionName, key) {
510
+ try {
511
+ const value = this.exec(["show-options", "-v", "-t", sessionName, key]);
512
+ return value.trim() || null;
513
+ }
514
+ catch {
515
+ return null;
516
+ }
517
+ }
518
+ setReturnSession(sessionName, returnSessionName) {
519
+ this.exec(["set-option", "-t", sessionName, "@aimux-return-session", returnSessionName]);
520
+ }
521
+ getReturnSession(sessionName) {
522
+ try {
523
+ const value = this.exec(["show-options", "-v", "-t", sessionName, "@aimux-return-session"]);
524
+ return value.trim() || null;
525
+ }
526
+ catch {
527
+ return null;
528
+ }
529
+ }
530
+ leaveManagedSession(options = {}) {
531
+ const activeSession = options.insideTmux
532
+ ? (this.currentClientSession() ?? options.sessionName)
533
+ : options.sessionName;
534
+ if (options.insideTmux && activeSession) {
535
+ const returnSession = this.getReturnSession(activeSession);
536
+ const managedPrefix = `${this.getSessionPrefix()}-`;
537
+ const isExternalReturn = returnSession && returnSession !== activeSession && !returnSession.startsWith(managedPrefix);
538
+ if (isExternalReturn) {
539
+ try {
540
+ this.interactiveExec(["switch-client", "-t", returnSession]);
541
+ return;
542
+ }
543
+ catch { }
544
+ }
545
+ }
546
+ this.detachClient();
547
+ }
548
+ switchClient(sessionName, windowIndex = 0) {
549
+ this.interactiveExec(["switch-client", "-t", `${sessionName}:${windowIndex}`]);
550
+ }
551
+ isInsideTmux(env = process.env) {
552
+ return Boolean(env.TMUX);
553
+ }
554
+ openTarget(target, options = {}) {
555
+ const sessionName = options.alreadyResolved
556
+ ? target.sessionName
557
+ : this.resolveOpenSessionName(target.sessionName, options.insideTmux === true);
558
+ const effectiveTarget = sessionName !== target.sessionName && !isDashboardWindowName(target.windowName)
559
+ ? this.ensureLinkedWindow(sessionName, target)
560
+ : {
561
+ ...target,
562
+ sessionName,
563
+ };
564
+ if (options.insideTmux) {
565
+ const current = this.currentClientSession();
566
+ if (current && current !== sessionName) {
567
+ this.setReturnSession(sessionName, current);
568
+ }
569
+ this.switchClient(sessionName, effectiveTarget.windowIndex);
570
+ return;
571
+ }
572
+ this.attachSession(sessionName, effectiveTarget.windowIndex);
573
+ }
574
+ configureSession(sessionName, projectRoot) {
575
+ const controlScript = this.getControlScriptShellCommand();
576
+ const statuslineCommand = this.getStatuslineCommandSpec();
577
+ const projectStateDir = getProjectStateDirFor(projectRoot);
578
+ this.exec(["set-option", "-t", sessionName, "@aimux-project-root", projectRoot]);
579
+ this.exec(["set-option", "-t", sessionName, "prefix", MANAGED_TMUX_SESSION_OPTIONS.prefix]);
580
+ this.exec(["set-option", "-t", sessionName, "prefix2", MANAGED_TMUX_SESSION_OPTIONS.prefix2]);
581
+ this.exec(["set-option", "-t", sessionName, "mouse", MANAGED_TMUX_SESSION_OPTIONS.mouse]);
582
+ this.exec(["set-option", "-t", sessionName, "focus-events", "on"]);
583
+ this.exec(["set-option", "-t", sessionName, "extended-keys", MANAGED_TMUX_SESSION_OPTIONS.extendedKeys]);
584
+ this.exec([
585
+ "set-option",
586
+ "-t",
587
+ sessionName,
588
+ "extended-keys-format",
589
+ MANAGED_TMUX_SESSION_OPTIONS.extendedKeysFormat,
590
+ ]);
591
+ for (const feature of MANAGED_TMUX_TERMINAL_FEATURES) {
592
+ this.ensureTerminalFeature(sessionName, feature);
593
+ }
594
+ this.exec(["unbind-key", "-T", "root", "C-j"]);
595
+ this.exec(["unbind-key", "-T", "root", "S-Enter"]);
596
+ this.exec(["unbind-key", "-T", "root", "WheelUpPane"]);
597
+ this.exec([
598
+ "bind-key",
599
+ "-T",
600
+ "root",
601
+ "C-j",
602
+ "if-shell",
603
+ "-F",
604
+ "#{m/r:^(claude|codex)$,#{@aimux-tool}}",
605
+ `send-keys -H ${MODIFIED_ENTER_HEX}`,
606
+ "send-keys C-j",
607
+ ]);
608
+ this.exec([
609
+ "bind-key",
610
+ "-T",
611
+ "root",
612
+ "S-Enter",
613
+ "if-shell",
614
+ "-F",
615
+ "#{m/r:^(claude|codex)$,#{@aimux-tool}}",
616
+ `send-keys -H ${MODIFIED_ENTER_HEX}`,
617
+ "send-keys S-Enter",
618
+ ]);
619
+ this.exec([
620
+ "bind-key",
621
+ "-T",
622
+ "root",
623
+ "WheelUpPane",
624
+ "if-shell",
625
+ "-F",
626
+ "#{m/r:^(claude|codex)$,#{@aimux-tool}}",
627
+ "copy-mode -e",
628
+ "if-shell -F '#{||:#{alternate_on},#{pane_in_mode},#{mouse_any_flag}}' 'send-keys -M' 'copy-mode -e'",
629
+ ]);
630
+ this.exec(["unbind-key", "-T", "prefix", "s"]);
631
+ this.exec(["unbind-key", "-T", "prefix", "n"]);
632
+ this.exec(["unbind-key", "-T", "prefix", "p"]);
633
+ this.exec(["unbind-key", "-T", "prefix", "d"]);
634
+ this.exec(["unbind-key", "-T", "prefix", "u"]);
635
+ this.exec(["bind-key", "-T", "prefix", "C-a", "send-prefix"]);
636
+ this.exec([
637
+ "bind-key",
638
+ "-T",
639
+ "prefix",
640
+ "n",
641
+ "run-shell",
642
+ "-b",
643
+ `${controlScript} next --project-root ${shellQuote(projectRoot)} --project-state-dir ${shellQuote(projectStateDir)} --current-client-session '#{client_session}' --client-tty '#{client_tty}' --current-window '#{window_name}' --current-window-id '#{window_id}' --current-path '#{pane_current_path}' --pane-id '#{pane_id}' >/dev/null 2>&1`,
644
+ ]);
645
+ this.exec([
646
+ "bind-key",
647
+ "-T",
648
+ "prefix",
649
+ "p",
650
+ "run-shell",
651
+ "-b",
652
+ `${controlScript} prev --project-root ${shellQuote(projectRoot)} --project-state-dir ${shellQuote(projectStateDir)} --current-client-session '#{client_session}' --client-tty '#{client_tty}' --current-window '#{window_name}' --current-window-id '#{window_id}' --current-path '#{pane_current_path}' --pane-id '#{pane_id}' >/dev/null 2>&1`,
653
+ ]);
654
+ this.exec([
655
+ "bind-key",
656
+ "-T",
657
+ "prefix",
658
+ "s",
659
+ "run-shell",
660
+ "-b",
661
+ `${controlScript} menu --project-root ${shellQuote(projectRoot)} --project-state-dir ${shellQuote(projectStateDir)} --current-client-session '#{client_session}' --client-tty '#{client_tty}' --current-window '#{window_name}' --current-window-id '#{window_id}' --current-path '#{pane_current_path}' --pane-id '#{pane_id}' >/dev/null 2>&1`,
662
+ ]);
663
+ this.exec([
664
+ "bind-key",
665
+ "-T",
666
+ "prefix",
667
+ "u",
668
+ "run-shell",
669
+ "-b",
670
+ `${controlScript} attention --project-root ${shellQuote(projectRoot)} --project-state-dir ${shellQuote(projectStateDir)} --current-client-session '#{client_session}' --client-tty '#{client_tty}' --current-window '#{window_name}' --current-window-id '#{window_id}' --current-path '#{pane_current_path}' --pane-id '#{pane_id}' >/dev/null 2>&1`,
671
+ ]);
672
+ this.exec([
673
+ "bind-key",
674
+ "-T",
675
+ "prefix",
676
+ "d",
677
+ "run-shell",
678
+ "-b",
679
+ `${controlScript} dashboard --project-root ${shellQuote(projectRoot)} --project-state-dir ${shellQuote(projectStateDir)} --current-client-session '#{client_session}' --client-tty '#{client_tty}' --current-window '#{window_name}' --current-window-id '#{window_id}' --current-path '#{pane_current_path}' --pane-id '#{pane_id}' >/dev/null 2>&1`,
680
+ ]);
681
+ this.exec(["set-option", "-t", sessionName, "status", "2"]);
682
+ this.exec(["set-option", "-t", sessionName, "status-interval", "0"]);
683
+ this.exec(["set-option", "-t", sessionName, "status-style", "bg=colour236,fg=colour252"]);
684
+ this.exec(["set-option", "-t", sessionName, "message-style", "bg=colour24,fg=colour255,bold"]);
685
+ this.exec(["set-option", "-t", sessionName, "message-command-style", "bg=colour24,fg=colour255"]);
686
+ this.exec(["set-option", "-t", sessionName, "window-status-separator", " "]);
687
+ this.exec(["set-option", "-t", sessionName, "window-status-format", ""]);
688
+ this.exec(["set-option", "-t", sessionName, "window-status-current-format", ""]);
689
+ const top = `${statuslineCommand.command} ${statuslineCommand.args.map(shellQuote).join(" ")} --line top --project-state-dir ${shellQuote(projectStateDir)} --current-session '#{session_name}' --current-window '#{window_name}' --current-window-id '#{window_id}'`;
690
+ const bottom = `${statuslineCommand.command} ${statuslineCommand.args.map(shellQuote).join(" ")} --line bottom --project-state-dir ${shellQuote(projectStateDir)} --current-session '#{session_name}' --current-window '#{window_name}' --current-window-id '#{window_id}'`;
691
+ this.exec(["set-option", "-t", sessionName, "status-left", ""]);
692
+ this.exec(["set-option", "-t", sessionName, "status-right", ""]);
693
+ this.exec([
694
+ "set-option",
695
+ "-t",
696
+ sessionName,
697
+ "status-format[0]",
698
+ `#[bg=colour238,fg=colour255,bold] #(${top}) #[default]`,
699
+ ]);
700
+ this.exec([
701
+ "set-option",
702
+ "-t",
703
+ sessionName,
704
+ "status-format[1]",
705
+ `#[bg=colour236,fg=colour252] #(${bottom}) #[default]`,
706
+ ]);
707
+ }
708
+ refreshStatus() {
709
+ try {
710
+ this.exec(["refresh-client", "-S"]);
711
+ }
712
+ catch { }
713
+ }
714
+ ensureTerminalFeature(sessionName, feature) {
715
+ const current = this.getSessionOption(sessionName, "terminal-features");
716
+ const features = current
717
+ ?.split("\n")
718
+ .map((entry) => entry.trim())
719
+ .filter(Boolean);
720
+ if (features?.includes(feature))
721
+ return;
722
+ this.exec(["set-option", "-as", "-t", sessionName, "terminal-features", `,${feature}`]);
723
+ }
724
+ resolveOpenSessionName(sessionName, insideTmux) {
725
+ if (!this.isManagedSessionName(sessionName) || this.isClientSessionName(sessionName))
726
+ return sessionName;
727
+ const clientSuffix = this.resolveClientSuffix(insideTmux);
728
+ if (!clientSuffix)
729
+ return sessionName;
730
+ const clientSessionName = this.getProjectClientSessionName(sessionName, clientSuffix);
731
+ const projectRoot = this.getSessionOption(sessionName, "@aimux-project-root");
732
+ if (projectRoot) {
733
+ this.ensureClientSession(sessionName, clientSessionName, projectRoot);
734
+ }
735
+ return clientSessionName;
736
+ }
737
+ getDashboardWindowName() {
738
+ const clientSuffix = this.resolveClientSuffix(this.isInsideTmux());
739
+ if (!clientSuffix)
740
+ return "dashboard";
741
+ return `dashboard-${clientSuffix}`;
742
+ }
743
+ normalizeClientSuffix(value) {
744
+ if (/^[a-f0-9]{8}$/.test(value))
745
+ return value;
746
+ return createHash("sha1").update(value).digest("hex").slice(0, 8);
747
+ }
748
+ resolveClientSuffix(insideTmux) {
749
+ const override = process.env.AIMUX_CLIENT_KEY?.trim();
750
+ if (override)
751
+ return this.normalizeClientSuffix(override);
752
+ if (insideTmux) {
753
+ const currentSession = this.currentClientSession();
754
+ if (currentSession) {
755
+ const match = currentSession.match(/-client-([a-f0-9]{8})$/);
756
+ if (match)
757
+ return match[1];
758
+ }
759
+ const clientTty = this.displayMessage("#{client_tty}");
760
+ const clientPid = this.displayMessage("#{client_pid}");
761
+ if (clientTty || clientPid)
762
+ return this.normalizeClientSuffix(`${clientTty ?? "tty"}:${clientPid ?? "pid"}`);
763
+ return null;
764
+ }
765
+ try {
766
+ const tty = execFileSync("tty", {
767
+ encoding: "utf8",
768
+ stdio: ["inherit", "pipe", "ignore"],
769
+ }).trim();
770
+ return this.normalizeClientSuffix(tty);
771
+ }
772
+ catch {
773
+ return null;
774
+ }
775
+ }
776
+ getStatuslineCommandSpec() {
777
+ const currentFile = fileURLToPath(import.meta.url);
778
+ const scriptPath = join(dirname(currentFile), "..", "scripts", "tmux-statusline.sh");
779
+ return {
780
+ cwd: process.cwd(),
781
+ command: "sh",
782
+ args: [scriptPath],
783
+ };
784
+ }
785
+ getControlScriptShellCommand() {
786
+ const currentFile = fileURLToPath(import.meta.url);
787
+ const scriptPath = join(dirname(currentFile), "..", "scripts", "tmux-control.sh");
788
+ return `sh ${shellQuote(scriptPath)}`;
789
+ }
790
+ }
791
+ function shellQuote(value) {
792
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
793
+ }
794
+ //# sourceMappingURL=tmux-runtime-manager.js.map