adhdev 0.8.50 → 0.8.54
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.
- package/dist/cli/index.js +2115 -853
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1391 -613
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/vendor/session-host-daemon/index.d.mts +3 -0
- package/vendor/session-host-daemon/index.d.ts +3 -0
- package/vendor/session-host-daemon/index.js +33 -2
- package/vendor/session-host-daemon/index.js.map +1 -1
- package/vendor/session-host-daemon/index.mjs +34 -2
- package/vendor/session-host-daemon/index.mjs.map +1 -1
- package/vendor/session-host-daemon/node_modules/@adhdev/session-host-core/index.d.mts +16 -1
- package/vendor/session-host-daemon/node_modules/@adhdev/session-host-core/index.d.ts +16 -1
- package/vendor/session-host-daemon/node_modules/@adhdev/session-host-core/index.js +59 -0
- package/vendor/session-host-daemon/node_modules/@adhdev/session-host-core/index.js.map +1 -1
- package/vendor/session-host-daemon/node_modules/@adhdev/session-host-core/index.mjs +54 -0
- package/vendor/session-host-daemon/node_modules/@adhdev/session-host-core/index.mjs.map +1 -1
- package/vendor/terminal-mux-cli/index.d.mts +1 -0
- package/vendor/terminal-mux-cli/index.d.ts +1 -0
- package/vendor/terminal-mux-cli/index.js +2070 -0
- package/vendor/terminal-mux-cli/index.mjs +2062 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.d.mts +442 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.d.ts +442 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.js +676 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.js.map +1 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.mjs +627 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/index.mjs.map +1 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/session-host-core/package.json +7 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.d.mts +16 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.d.ts +16 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.js +206 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/api.mjs +17 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-7RNMRPVZ.mjs +183 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-R4EFW6W3.mjs +46 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/chunk-XZWWVN5W.mjs +164 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.d.mts +35 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.d.ts +35 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.js +219 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/control-socket.mjs +13 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.d.mts +5 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.d.ts +5 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.js +427 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/index.mjs +34 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/package.json +33 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.d.mts +49 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.d.ts +49 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.js +222 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-control/storage.mjs +16 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.d.mts +164 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.d.ts +164 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.js +993 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/index.mjs +957 -0
- package/vendor/terminal-mux-cli/node_modules/@adhdev/terminal-mux-core/package.json +7 -0
|
@@ -0,0 +1,2062 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
import { spawn } from "child_process";
|
|
11
|
+
import {
|
|
12
|
+
getDefaultSessionHostEndpoint,
|
|
13
|
+
resolveRuntimeRecord,
|
|
14
|
+
SessionHostClient
|
|
15
|
+
} from "@adhdev/session-host-core";
|
|
16
|
+
|
|
17
|
+
// src/clipboard.ts
|
|
18
|
+
import { spawnSync } from "child_process";
|
|
19
|
+
function tryCopy(command, args, text) {
|
|
20
|
+
try {
|
|
21
|
+
const result = spawnSync(command, args, {
|
|
22
|
+
input: text,
|
|
23
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
24
|
+
encoding: "utf8"
|
|
25
|
+
});
|
|
26
|
+
return result.status === 0;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function copyTextToClipboard(text) {
|
|
32
|
+
if (process.platform === "darwin") {
|
|
33
|
+
if (tryCopy("pbcopy", [], text)) return;
|
|
34
|
+
throw new Error("pbcopy is not available");
|
|
35
|
+
}
|
|
36
|
+
if (process.platform === "win32") {
|
|
37
|
+
if (tryCopy("clip", [], text)) return;
|
|
38
|
+
if (tryCopy("powershell", ["-NoProfile", "-Command", "Set-Clipboard"], text)) return;
|
|
39
|
+
throw new Error("No clipboard command is available on Windows");
|
|
40
|
+
}
|
|
41
|
+
if (tryCopy("wl-copy", [], text)) return;
|
|
42
|
+
if (tryCopy("xclip", ["-selection", "clipboard"], text)) return;
|
|
43
|
+
if (tryCopy("xsel", ["--clipboard", "--input"], text)) return;
|
|
44
|
+
throw new Error("No clipboard command is available");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/index.ts
|
|
48
|
+
import {
|
|
49
|
+
createAdhMuxControlServer,
|
|
50
|
+
withAdhMuxControlClient
|
|
51
|
+
} from "@adhdev/terminal-mux-control/control-socket";
|
|
52
|
+
|
|
53
|
+
// src/render.ts
|
|
54
|
+
function blankGrid(width, height) {
|
|
55
|
+
return Array.from({ length: height }, () => Array.from({ length: width }, () => " "));
|
|
56
|
+
}
|
|
57
|
+
function write(grid, x, y, text) {
|
|
58
|
+
const row = grid[y];
|
|
59
|
+
if (!row) return;
|
|
60
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
61
|
+
const cell = x + i;
|
|
62
|
+
if (cell < 0 || cell >= row.length) break;
|
|
63
|
+
row[cell] = text[i] || " ";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function renderBorder(grid, rect, title, focused) {
|
|
67
|
+
const right = rect.x + rect.width - 1;
|
|
68
|
+
const bottom = rect.y + rect.height - 1;
|
|
69
|
+
if (rect.width < 4 || rect.height < 3) return;
|
|
70
|
+
const h = focused ? "=" : "-";
|
|
71
|
+
write(grid, rect.x, rect.y, `+${h.repeat(rect.width - 2)}+`);
|
|
72
|
+
write(grid, rect.x, bottom, `+${h.repeat(rect.width - 2)}+`);
|
|
73
|
+
for (let y = rect.y + 1; y < bottom; y += 1) {
|
|
74
|
+
write(grid, rect.x, y, "|");
|
|
75
|
+
write(grid, right, y, "|");
|
|
76
|
+
}
|
|
77
|
+
const safeTitle = ` ${title.slice(0, Math.max(0, rect.width - 4))} `;
|
|
78
|
+
write(grid, rect.x + 2, rect.y, safeTitle);
|
|
79
|
+
}
|
|
80
|
+
function fitContent(text, width, height, lineOffset = 0) {
|
|
81
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
82
|
+
const out = [];
|
|
83
|
+
const visibleLines = lineOffset > 0 ? lines.slice(lineOffset) : lines;
|
|
84
|
+
for (const line of visibleLines) {
|
|
85
|
+
if (out.length >= height) break;
|
|
86
|
+
if (line.length <= width) {
|
|
87
|
+
out.push(line);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
let start = 0;
|
|
91
|
+
while (start < line.length && out.length < height) {
|
|
92
|
+
out.push(line.slice(start, start + width));
|
|
93
|
+
start += width;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
while (out.length < height) out.push("");
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function renderPane(grid, rect, pane, focused, indicator, lineOffset = 0) {
|
|
100
|
+
renderBorder(
|
|
101
|
+
grid,
|
|
102
|
+
rect,
|
|
103
|
+
`${pane.runtimeKey}${pane.paneKind === "mirror" ? " [mirror]" : ""}${indicator ? ` ${indicator}` : ""} \xB7 ${pane.writeOwner ? `${pane.writeOwner.ownerType}` : pane.accessMode === "interactive" ? "interactive" : "view"}`,
|
|
104
|
+
focused
|
|
105
|
+
);
|
|
106
|
+
const contentWidth = Math.max(0, rect.width - 2);
|
|
107
|
+
const contentHeight = Math.max(0, rect.height - 2);
|
|
108
|
+
const contentLines = fitContent(pane.viewport.text, contentWidth, contentHeight, lineOffset);
|
|
109
|
+
for (let i = 0; i < contentLines.length && i < contentHeight; i += 1) {
|
|
110
|
+
const line = contentLines[i] || "";
|
|
111
|
+
const padded = line.padEnd(contentWidth, " ");
|
|
112
|
+
write(grid, rect.x + 1, rect.y + 1 + i, padded);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function walkLayout(node, rect, workspace, grid, indicators, lineOffsets) {
|
|
116
|
+
if (node.type === "pane") {
|
|
117
|
+
const pane = workspace.panes[node.paneId];
|
|
118
|
+
if (!pane) return;
|
|
119
|
+
renderPane(
|
|
120
|
+
grid,
|
|
121
|
+
rect,
|
|
122
|
+
pane,
|
|
123
|
+
workspace.focusedPaneId === node.paneId,
|
|
124
|
+
indicators?.[node.paneId],
|
|
125
|
+
lineOffsets?.[node.paneId] || 0
|
|
126
|
+
);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (node.axis === "vertical") {
|
|
130
|
+
const firstWidth = Math.max(10, Math.floor(rect.width * node.ratio));
|
|
131
|
+
const secondWidth = Math.max(10, rect.width - firstWidth);
|
|
132
|
+
walkLayout(node.first, { ...rect, width: firstWidth }, workspace, grid, indicators, lineOffsets);
|
|
133
|
+
walkLayout(node.second, { x: rect.x + firstWidth, y: rect.y, width: secondWidth, height: rect.height }, workspace, grid, indicators, lineOffsets);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const firstHeight = Math.max(4, Math.floor(rect.height * node.ratio));
|
|
137
|
+
const secondHeight = Math.max(4, rect.height - firstHeight);
|
|
138
|
+
walkLayout(node.first, { ...rect, height: firstHeight }, workspace, grid, indicators, lineOffsets);
|
|
139
|
+
walkLayout(node.second, { x: rect.x, y: rect.y + firstHeight, width: rect.width, height: secondHeight }, workspace, grid, indicators, lineOffsets);
|
|
140
|
+
}
|
|
141
|
+
function collectRects(node, rect, rects) {
|
|
142
|
+
if (node.type === "pane") {
|
|
143
|
+
rects.set(node.paneId, rect);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (node.axis === "vertical") {
|
|
147
|
+
const firstWidth = Math.max(10, Math.floor(rect.width * node.ratio));
|
|
148
|
+
const secondWidth = Math.max(10, rect.width - firstWidth);
|
|
149
|
+
collectRects(node.first, { ...rect, width: firstWidth }, rects);
|
|
150
|
+
collectRects(node.second, { x: rect.x + firstWidth, y: rect.y, width: secondWidth, height: rect.height }, rects);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const firstHeight = Math.max(4, Math.floor(rect.height * node.ratio));
|
|
154
|
+
const secondHeight = Math.max(4, rect.height - firstHeight);
|
|
155
|
+
collectRects(node.first, { ...rect, height: firstHeight }, rects);
|
|
156
|
+
collectRects(node.second, { x: rect.x, y: rect.y + firstHeight, width: rect.width, height: secondHeight }, rects);
|
|
157
|
+
}
|
|
158
|
+
function computePaneRects(workspace, cols, rows) {
|
|
159
|
+
const width = Math.max(40, cols);
|
|
160
|
+
const height = Math.max(12, rows);
|
|
161
|
+
const rects = /* @__PURE__ */ new Map();
|
|
162
|
+
if (workspace.zoomedPaneId && workspace.panes[workspace.zoomedPaneId]) {
|
|
163
|
+
rects.set(workspace.zoomedPaneId, { x: 0, y: 0, width, height: height - 2 });
|
|
164
|
+
return rects;
|
|
165
|
+
}
|
|
166
|
+
collectRects(workspace.root, { x: 0, y: 0, width, height: height - 2 }, rects);
|
|
167
|
+
return rects;
|
|
168
|
+
}
|
|
169
|
+
function renderWorkspace(workspace, cols, rows, options = {}) {
|
|
170
|
+
const width = Math.max(40, cols);
|
|
171
|
+
const height = Math.max(12, rows);
|
|
172
|
+
const grid = blankGrid(width, height);
|
|
173
|
+
if (workspace.zoomedPaneId && workspace.panes[workspace.zoomedPaneId]) {
|
|
174
|
+
renderPane(
|
|
175
|
+
grid,
|
|
176
|
+
{ x: 0, y: 0, width, height: height - 2 },
|
|
177
|
+
workspace.panes[workspace.zoomedPaneId],
|
|
178
|
+
true,
|
|
179
|
+
options.paneIndicators?.[workspace.zoomedPaneId],
|
|
180
|
+
options.paneLineOffsets?.[workspace.zoomedPaneId] || 0
|
|
181
|
+
);
|
|
182
|
+
} else {
|
|
183
|
+
walkLayout(
|
|
184
|
+
workspace.root,
|
|
185
|
+
{ x: 0, y: 0, width, height: height - 2 },
|
|
186
|
+
workspace,
|
|
187
|
+
grid,
|
|
188
|
+
options.paneIndicators,
|
|
189
|
+
options.paneLineOffsets
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const status = options.statusLine || "";
|
|
193
|
+
if (status) {
|
|
194
|
+
write(grid, 0, height - 2, status.slice(0, width).padEnd(width, " "));
|
|
195
|
+
}
|
|
196
|
+
const footer = options.footerLine || `^B prefix focus=${workspace.focusedPaneId.slice(0, 8)} workspace=${workspace.title}${workspace.zoomedPaneId ? " [zoom]" : ""}`;
|
|
197
|
+
write(grid, 0, height - 1, footer.slice(0, width).padEnd(width, " "));
|
|
198
|
+
return grid.map((row) => row.join("")).join("\n");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/search.ts
|
|
202
|
+
function searchPaneText(text, query) {
|
|
203
|
+
const needle = query.trim().toLowerCase();
|
|
204
|
+
if (!needle) return [];
|
|
205
|
+
const matches = [];
|
|
206
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
207
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
208
|
+
const line = lines[lineIndex] || "";
|
|
209
|
+
const lower = line.toLowerCase();
|
|
210
|
+
let start = 0;
|
|
211
|
+
while (start < lower.length) {
|
|
212
|
+
const found = lower.indexOf(needle, start);
|
|
213
|
+
if (found < 0) break;
|
|
214
|
+
matches.push({
|
|
215
|
+
line: lineIndex + 1,
|
|
216
|
+
column: found + 1,
|
|
217
|
+
preview: line
|
|
218
|
+
});
|
|
219
|
+
start = found + Math.max(needle.length, 1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return matches;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/index.ts
|
|
226
|
+
import { getWorkspaceControlEndpoint, TerminalMuxStorage } from "@adhdev/terminal-mux-control/storage";
|
|
227
|
+
import { buildWorkspaceName, sanitizeWorkspaceName, toWorkspaceRef } from "@adhdev/terminal-mux-control/storage";
|
|
228
|
+
import {
|
|
229
|
+
SessionHostMuxClient
|
|
230
|
+
} from "@adhdev/terminal-mux-core";
|
|
231
|
+
var STARTUP_TIMEOUT_MS = 8e3;
|
|
232
|
+
var STARTUP_POLL_MS = 200;
|
|
233
|
+
var SESSION_HOST_APP_NAME = process.env.ADHDEV_SESSION_HOST_NAME || "adhdev";
|
|
234
|
+
var CONTROL_SOCKET_TIMEOUT_MS = 4e3;
|
|
235
|
+
var CONTROL_SOCKET_POLL_MS = 150;
|
|
236
|
+
function usage(exitCode = 1) {
|
|
237
|
+
const stream = exitCode === 0 ? process.stdout : process.stderr;
|
|
238
|
+
stream.write("adhmux \u2014 power-user terminal mux for self-hosted ADHDev runtimes\n\n");
|
|
239
|
+
stream.write("Use this when you explicitly want local pane/workspace control around already-live hosted runtimes.\n");
|
|
240
|
+
stream.write("For ordinary runtime supervision, recovery, and direct attach flows, start with `adhdev runtime ...`.\n\n");
|
|
241
|
+
stream.write("Usage:\n");
|
|
242
|
+
stream.write(" adhmux <command> [options] [runtimeKey...]\n\n");
|
|
243
|
+
stream.write("Core commands:\n");
|
|
244
|
+
stream.write(" list List hosted runtimes visible to session host\n");
|
|
245
|
+
stream.write(" open <runtimeKey> Open a mux workspace for a live runtime\n");
|
|
246
|
+
stream.write(" snapshot <id> Print the latest terminal snapshot for a runtime\n");
|
|
247
|
+
stream.write(" sessions List saved mux sessions\n");
|
|
248
|
+
stream.write(" workspaces List saved mux workspaces\n");
|
|
249
|
+
stream.write(" attach-session Reattach a previously saved mux workspace/session\n\n");
|
|
250
|
+
stream.write("Notes:\n");
|
|
251
|
+
stream.write(" - `open` only accepts live runtimes. Recover or restart snapshots first.\n");
|
|
252
|
+
stream.write(" - `adhmux` is an expert/self-hosted surface, not the primary cloud runtime workflow.\n");
|
|
253
|
+
stream.write(" - Run `adhdev runtime --help` for the main user-facing runtime commands.\n");
|
|
254
|
+
process.exit(exitCode);
|
|
255
|
+
}
|
|
256
|
+
function normalizeLayoutPreset(layoutName) {
|
|
257
|
+
switch (layoutName) {
|
|
258
|
+
case "even":
|
|
259
|
+
case "even-horizontal":
|
|
260
|
+
case "even-vertical":
|
|
261
|
+
return "even";
|
|
262
|
+
case "balanced":
|
|
263
|
+
return "balanced";
|
|
264
|
+
case "tiled":
|
|
265
|
+
return "tiled";
|
|
266
|
+
case "main-vertical":
|
|
267
|
+
case "main-v":
|
|
268
|
+
return "main-vertical";
|
|
269
|
+
case "main-horizontal":
|
|
270
|
+
case "main-h":
|
|
271
|
+
return "main-horizontal";
|
|
272
|
+
default:
|
|
273
|
+
throw new Error(`Unsupported layout: ${layoutName}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function parseCommandFlags(args) {
|
|
277
|
+
const rest = [];
|
|
278
|
+
let json = false;
|
|
279
|
+
for (const arg of args) {
|
|
280
|
+
if (arg === "--json") {
|
|
281
|
+
json = true;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
rest.push(arg);
|
|
285
|
+
}
|
|
286
|
+
return { flags: { json }, rest };
|
|
287
|
+
}
|
|
288
|
+
function isControlSocketUnavailable(error) {
|
|
289
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
290
|
+
return /ENOENT|ECONNREFUSED|EPIPE|socket/i.test(message);
|
|
291
|
+
}
|
|
292
|
+
async function requestWorkspaceControl(workspaceName, request) {
|
|
293
|
+
try {
|
|
294
|
+
return await withAdhMuxControlClient(workspaceName, (client) => client.request(request));
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (isControlSocketUnavailable(error)) return null;
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async function waitForWorkspaceControlReady(workspaceName, timeoutMs = CONTROL_SOCKET_TIMEOUT_MS) {
|
|
301
|
+
const deadline = Date.now() + timeoutMs;
|
|
302
|
+
while (Date.now() < deadline) {
|
|
303
|
+
try {
|
|
304
|
+
await withAdhMuxControlClient(workspaceName, async (client) => {
|
|
305
|
+
await client.connect();
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (!isControlSocketUnavailable(error)) {
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
await new Promise((resolve) => setTimeout(resolve, CONTROL_SOCKET_POLL_MS));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
throw new Error(`Workspace control socket did not become ready within ${timeoutMs}ms`);
|
|
316
|
+
}
|
|
317
|
+
async function listRuntimes(flags = { json: false }) {
|
|
318
|
+
await ensureSessionHostReady();
|
|
319
|
+
const client = new SessionHostClient({ appName: SESSION_HOST_APP_NAME });
|
|
320
|
+
const result = await client.request({ type: "list_sessions" });
|
|
321
|
+
if (!result.success || !result.result) {
|
|
322
|
+
throw new Error(result.error || "Failed to list runtimes");
|
|
323
|
+
}
|
|
324
|
+
if (flags.json) {
|
|
325
|
+
process.stdout.write(JSON.stringify(result.result, null, 2));
|
|
326
|
+
process.stdout.write("\n");
|
|
327
|
+
await client.close();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
for (const record of result.result) {
|
|
331
|
+
process.stdout.write(
|
|
332
|
+
`${record.runtimeKey} ${record.lifecycle} ${record.workspaceLabel} ${record.writeOwner ? `${record.writeOwner.ownerType}:${record.writeOwner.clientId}` : "none"}
|
|
333
|
+
`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
await client.close();
|
|
337
|
+
}
|
|
338
|
+
async function listWorkspaces(flags = { json: false }) {
|
|
339
|
+
const storage = new TerminalMuxStorage();
|
|
340
|
+
const workspaces = storage.listWorkspaces();
|
|
341
|
+
if (flags.json) {
|
|
342
|
+
process.stdout.write(JSON.stringify(workspaces, null, 2));
|
|
343
|
+
process.stdout.write("\n");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
for (const workspace of workspaces) {
|
|
347
|
+
process.stdout.write(
|
|
348
|
+
`${workspace.name} ${workspace.title} panes=${workspace.paneCount} updated=${new Date(workspace.updatedAt).toISOString()}
|
|
349
|
+
`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function listSessions(flags = { json: false }) {
|
|
354
|
+
const storage = new TerminalMuxStorage();
|
|
355
|
+
const sessions = storage.listSessions();
|
|
356
|
+
if (flags.json) {
|
|
357
|
+
process.stdout.write(JSON.stringify(sessions, null, 2));
|
|
358
|
+
process.stdout.write("\n");
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
for (const session of sessions) {
|
|
362
|
+
process.stdout.write(
|
|
363
|
+
`${session.name} windows=${session.windowCount} active=${session.activeWindowName} updated=${new Date(session.updatedAt).toISOString()}
|
|
364
|
+
`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function listWindows(sessionName, flags = { json: false }) {
|
|
369
|
+
const storage = new TerminalMuxStorage();
|
|
370
|
+
const windows = storage.listSessionWindows(sessionName).map((workspace) => ({
|
|
371
|
+
...workspace,
|
|
372
|
+
windowName: toWorkspaceRef(workspace.name).windowName
|
|
373
|
+
}));
|
|
374
|
+
if (flags.json) {
|
|
375
|
+
process.stdout.write(JSON.stringify(windows, null, 2));
|
|
376
|
+
process.stdout.write("\n");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
for (const window of windows) {
|
|
380
|
+
process.stdout.write(
|
|
381
|
+
`${window.windowName} workspace=${window.name} panes=${window.paneCount} updated=${new Date(window.updatedAt).toISOString()}
|
|
382
|
+
`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function printLastWorkspace(flags = { json: false }) {
|
|
387
|
+
const storage = new TerminalMuxStorage();
|
|
388
|
+
const lastWorkspace = storage.getLastWorkspace();
|
|
389
|
+
if (flags.json) {
|
|
390
|
+
process.stdout.write(JSON.stringify({ lastWorkspace }, null, 2));
|
|
391
|
+
process.stdout.write("\n");
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (lastWorkspace) {
|
|
395
|
+
process.stdout.write(`${lastWorkspace}
|
|
396
|
+
`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async function printWorkspaceTree(flags = { json: false }) {
|
|
400
|
+
const storage = new TerminalMuxStorage();
|
|
401
|
+
const workspaces = storage.listWorkspaces().map((workspace) => {
|
|
402
|
+
const saved = storage.loadWorkspace(workspace.name);
|
|
403
|
+
return {
|
|
404
|
+
...workspace,
|
|
405
|
+
focusedPaneId: saved?.focusedPaneId || null,
|
|
406
|
+
zoomedPaneId: saved?.zoomedPaneId || null,
|
|
407
|
+
panes: saved?.panes || {}
|
|
408
|
+
};
|
|
409
|
+
});
|
|
410
|
+
if (flags.json) {
|
|
411
|
+
process.stdout.write(JSON.stringify(workspaces, null, 2));
|
|
412
|
+
process.stdout.write("\n");
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
for (const workspace of workspaces) {
|
|
416
|
+
process.stdout.write(`${workspace.name} panes=${workspace.paneCount} focus=${workspace.focusedPaneId || "-"} zoom=${workspace.zoomedPaneId || "-"}
|
|
417
|
+
`);
|
|
418
|
+
for (const [paneId, pane] of Object.entries(workspace.panes || {})) {
|
|
419
|
+
process.stdout.write(` ${paneId} ${pane.paneKind} ${pane.runtimeKey} ${pane.accessMode}
|
|
420
|
+
`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function printWorkspaceState(workspaceName, flags = { json: false }) {
|
|
425
|
+
const live = await requestWorkspaceControl(workspaceName, { type: "workspace_state" });
|
|
426
|
+
if (live?.success && live.result) {
|
|
427
|
+
if (flags.json) {
|
|
428
|
+
process.stdout.write(JSON.stringify(live.result, null, 2));
|
|
429
|
+
process.stdout.write("\n");
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
process.stdout.write(
|
|
433
|
+
`${live.result.workspaceName} panes=${live.result.panes.length} focus=${live.result.workspace.focusedPaneId} zoom=${live.result.workspace.zoomedPaneId || "-"}
|
|
434
|
+
`
|
|
435
|
+
);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const storage = new TerminalMuxStorage();
|
|
439
|
+
const saved = storage.loadWorkspace(workspaceName);
|
|
440
|
+
if (!saved) {
|
|
441
|
+
throw new Error(`Workspace not found: ${workspaceName}`);
|
|
442
|
+
}
|
|
443
|
+
if (flags.json) {
|
|
444
|
+
process.stdout.write(JSON.stringify({ workspaceName, workspace: saved }, null, 2));
|
|
445
|
+
process.stdout.write("\n");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
process.stdout.write(
|
|
449
|
+
`${workspaceName} panes=${Object.keys(saved.panes || {}).length} focus=${saved.focusedPaneId} zoom=${saved.zoomedPaneId || "-"}
|
|
450
|
+
`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
async function printSocketInfo(workspaceName, flags = { json: false }) {
|
|
454
|
+
const endpoint = getWorkspaceControlEndpoint(workspaceName);
|
|
455
|
+
const live = await requestWorkspaceControl(workspaceName, { type: "workspace_state" });
|
|
456
|
+
const result = {
|
|
457
|
+
workspaceName,
|
|
458
|
+
endpoint,
|
|
459
|
+
live: !!live?.success
|
|
460
|
+
};
|
|
461
|
+
if (flags.json) {
|
|
462
|
+
process.stdout.write(JSON.stringify(result, null, 2));
|
|
463
|
+
process.stdout.write("\n");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
process.stdout.write(`${workspaceName} ${endpoint.path} ${result.live ? "live" : "offline"}
|
|
467
|
+
`);
|
|
468
|
+
}
|
|
469
|
+
async function controlWorkspace(workspaceName, requestType, payloadRaw, flags = { json: false }) {
|
|
470
|
+
const payload = payloadRaw ? JSON.parse(payloadRaw) : {};
|
|
471
|
+
const response = await requestWorkspaceControl(workspaceName, { type: requestType, payload });
|
|
472
|
+
if (!response) {
|
|
473
|
+
throw new Error(`Workspace control socket unavailable: ${workspaceName}`);
|
|
474
|
+
}
|
|
475
|
+
if (flags.json || true) {
|
|
476
|
+
process.stdout.write(JSON.stringify(response, null, 2));
|
|
477
|
+
process.stdout.write("\n");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async function streamWorkspaceEvents(workspaceName, flags = { json: false }) {
|
|
482
|
+
await waitForWorkspaceControlReady(workspaceName);
|
|
483
|
+
await withAdhMuxControlClient(workspaceName, async (client) => {
|
|
484
|
+
await client.connect();
|
|
485
|
+
const writeEvent = (event) => {
|
|
486
|
+
if (flags.json) {
|
|
487
|
+
process.stdout.write(JSON.stringify(event, null, 2));
|
|
488
|
+
process.stdout.write("\n");
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
process.stdout.write(`${event.type} ${JSON.stringify(event.payload)}
|
|
492
|
+
`);
|
|
493
|
+
};
|
|
494
|
+
const unsub = client.onEvent(writeEvent);
|
|
495
|
+
const initial = await client.request({ type: "workspace_state" });
|
|
496
|
+
if (initial.success && initial.result) {
|
|
497
|
+
writeEvent({ type: "workspace_update", payload: initial.result });
|
|
498
|
+
}
|
|
499
|
+
await new Promise((resolve) => {
|
|
500
|
+
const keepAlive = setInterval(() => {
|
|
501
|
+
}, 1e3);
|
|
502
|
+
const onSigint = () => {
|
|
503
|
+
clearInterval(keepAlive);
|
|
504
|
+
process.off("SIGINT", onSigint);
|
|
505
|
+
unsub();
|
|
506
|
+
resolve();
|
|
507
|
+
};
|
|
508
|
+
process.on("SIGINT", onSigint);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
async function renameWorkspace(fromName, toName) {
|
|
513
|
+
const storage = new TerminalMuxStorage();
|
|
514
|
+
storage.renameWorkspace(fromName, toName);
|
|
515
|
+
}
|
|
516
|
+
async function deleteWorkspace(name) {
|
|
517
|
+
const storage = new TerminalMuxStorage();
|
|
518
|
+
storage.deleteWorkspace(name);
|
|
519
|
+
}
|
|
520
|
+
async function hasSession(name) {
|
|
521
|
+
const storage = new TerminalMuxStorage();
|
|
522
|
+
return storage.listSessionWindows(name).length > 0;
|
|
523
|
+
}
|
|
524
|
+
async function renameSession(fromName, toName) {
|
|
525
|
+
const storage = new TerminalMuxStorage();
|
|
526
|
+
const windows = storage.listSessionWindows(fromName);
|
|
527
|
+
if (windows.length === 0) {
|
|
528
|
+
throw new Error(`Session not found: ${fromName}`);
|
|
529
|
+
}
|
|
530
|
+
for (const window of windows) {
|
|
531
|
+
const ref = toWorkspaceRef(window.name);
|
|
532
|
+
storage.renameWorkspace(window.name, buildWorkspaceName(toName, ref.windowName));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async function killSession(name) {
|
|
536
|
+
const storage = new TerminalMuxStorage();
|
|
537
|
+
const windows = storage.listSessionWindows(name);
|
|
538
|
+
if (windows.length === 0) {
|
|
539
|
+
throw new Error(`Session not found: ${name}`);
|
|
540
|
+
}
|
|
541
|
+
for (const window of windows) {
|
|
542
|
+
storage.deleteWorkspace(window.name);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
async function newWindow(sessionName, windowName, runtimeTargets) {
|
|
546
|
+
if (runtimeTargets.length === 0) {
|
|
547
|
+
throw new Error("At least one runtime target is required");
|
|
548
|
+
}
|
|
549
|
+
const resolvedWindowName = sanitizeWorkspaceName(windowName || runtimeTargets[0]);
|
|
550
|
+
await openWorkspace({
|
|
551
|
+
workspaceName: buildWorkspaceName(sessionName, resolvedWindowName),
|
|
552
|
+
runtimeTargets,
|
|
553
|
+
readOnly: false,
|
|
554
|
+
takeover: false
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
async function selectWindow(sessionName, windowName) {
|
|
558
|
+
const storage = new TerminalMuxStorage();
|
|
559
|
+
const workspaceName = storage.resolveSessionWindowWorkspace(sessionName, windowName);
|
|
560
|
+
if (!workspaceName) {
|
|
561
|
+
throw new Error(`Window not found: ${sessionName}/${windowName}`);
|
|
562
|
+
}
|
|
563
|
+
await openWorkspace({
|
|
564
|
+
workspaceName,
|
|
565
|
+
runtimeTargets: [],
|
|
566
|
+
readOnly: false,
|
|
567
|
+
takeover: false
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
async function renameWindow(sessionName, fromWindowName, toWindowName) {
|
|
571
|
+
const storage = new TerminalMuxStorage();
|
|
572
|
+
const fromWorkspace = storage.resolveSessionWindowWorkspace(sessionName, fromWindowName);
|
|
573
|
+
if (!fromWorkspace) {
|
|
574
|
+
throw new Error(`Window not found: ${sessionName}/${fromWindowName}`);
|
|
575
|
+
}
|
|
576
|
+
storage.renameWorkspace(fromWorkspace, buildWorkspaceName(sessionName, toWindowName));
|
|
577
|
+
}
|
|
578
|
+
async function killWindow(sessionName, windowName) {
|
|
579
|
+
const storage = new TerminalMuxStorage();
|
|
580
|
+
const workspaceName = storage.resolveSessionWindowWorkspace(sessionName, windowName);
|
|
581
|
+
if (!workspaceName) {
|
|
582
|
+
throw new Error(`Window not found: ${sessionName}/${windowName}`);
|
|
583
|
+
}
|
|
584
|
+
storage.deleteWorkspace(workspaceName);
|
|
585
|
+
}
|
|
586
|
+
async function snapshotRuntime(target) {
|
|
587
|
+
await ensureSessionHostReady();
|
|
588
|
+
const client = new SessionHostClient({ appName: SESSION_HOST_APP_NAME });
|
|
589
|
+
const list = await client.request({ type: "list_sessions" });
|
|
590
|
+
if (!list.success || !list.result) throw new Error(list.error || "Failed to list runtimes");
|
|
591
|
+
const record = resolveRuntimeRecord(list.result, target);
|
|
592
|
+
const snapshot = await client.request({
|
|
593
|
+
type: "get_snapshot",
|
|
594
|
+
payload: { sessionId: record.sessionId }
|
|
595
|
+
});
|
|
596
|
+
if (!snapshot.success || !snapshot.result) throw new Error(snapshot.error || "Failed to get snapshot");
|
|
597
|
+
process.stdout.write(snapshot.result.text);
|
|
598
|
+
await client.close();
|
|
599
|
+
}
|
|
600
|
+
function parseOpenArgs(args) {
|
|
601
|
+
const runtimeTargets = [];
|
|
602
|
+
let workspaceName;
|
|
603
|
+
let readOnly = false;
|
|
604
|
+
let takeover = false;
|
|
605
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
606
|
+
const arg = args[i];
|
|
607
|
+
if (!arg) continue;
|
|
608
|
+
if (arg === "--workspace" || arg === "-w") {
|
|
609
|
+
workspaceName = args[i + 1];
|
|
610
|
+
i += 1;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (arg === "--read-only") {
|
|
614
|
+
readOnly = true;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (arg === "--takeover") {
|
|
618
|
+
takeover = true;
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
runtimeTargets.push(arg);
|
|
622
|
+
}
|
|
623
|
+
return { runtimeTargets, workspaceName, readOnly, takeover };
|
|
624
|
+
}
|
|
625
|
+
function parseSessionTargetArgs(args) {
|
|
626
|
+
let workspaceName = "";
|
|
627
|
+
const remainingArgs = [];
|
|
628
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
629
|
+
const arg = args[i];
|
|
630
|
+
if (!arg) continue;
|
|
631
|
+
if (arg === "-t" || arg === "-s") {
|
|
632
|
+
workspaceName = args[i + 1] || "";
|
|
633
|
+
i += 1;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (!workspaceName) {
|
|
637
|
+
workspaceName = arg;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
remainingArgs.push(arg);
|
|
641
|
+
}
|
|
642
|
+
if (!workspaceName) {
|
|
643
|
+
throw new Error("Workspace name is required");
|
|
644
|
+
}
|
|
645
|
+
return { workspaceName, remainingArgs };
|
|
646
|
+
}
|
|
647
|
+
function parsePaneTargetArgs(args) {
|
|
648
|
+
const session = parseSessionTargetArgs(args);
|
|
649
|
+
let paneTarget;
|
|
650
|
+
const remainingArgs = [];
|
|
651
|
+
for (let i = 0; i < session.remainingArgs.length; i += 1) {
|
|
652
|
+
const arg = session.remainingArgs[i];
|
|
653
|
+
if (!arg) continue;
|
|
654
|
+
if (arg === "-p") {
|
|
655
|
+
paneTarget = session.remainingArgs[i + 1];
|
|
656
|
+
i += 1;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
remainingArgs.push(arg);
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
workspaceName: session.workspaceName,
|
|
663
|
+
paneTarget,
|
|
664
|
+
remainingArgs
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function resolvePaneTarget(workspace, paneTarget) {
|
|
668
|
+
if (!paneTarget) {
|
|
669
|
+
return workspace.focusedPaneId;
|
|
670
|
+
}
|
|
671
|
+
if (workspace.panes[paneTarget]) {
|
|
672
|
+
return paneTarget;
|
|
673
|
+
}
|
|
674
|
+
const paneIds = Object.keys(workspace.panes);
|
|
675
|
+
const index = Number.parseInt(paneTarget, 10);
|
|
676
|
+
if (!Number.isNaN(index) && index >= 0 && index < paneIds.length) {
|
|
677
|
+
return paneIds[index];
|
|
678
|
+
}
|
|
679
|
+
throw new Error(`Unknown pane target: ${paneTarget}`);
|
|
680
|
+
}
|
|
681
|
+
async function withWorkspace(workspaceName, fn) {
|
|
682
|
+
await ensureSessionHostReady();
|
|
683
|
+
const storage = new TerminalMuxStorage();
|
|
684
|
+
const savedWorkspace = storage.loadWorkspace(workspaceName);
|
|
685
|
+
if (!savedWorkspace) {
|
|
686
|
+
throw new Error(`Workspace not found: ${workspaceName}`);
|
|
687
|
+
}
|
|
688
|
+
const mux = new SessionHostMuxClient({ appName: SESSION_HOST_APP_NAME });
|
|
689
|
+
await mux.connect();
|
|
690
|
+
let workspace = await mux.restoreWorkspace(savedWorkspace);
|
|
691
|
+
const save = () => {
|
|
692
|
+
storage.saveWorkspace(workspaceName, mux.serializeWorkspace(workspace.workspaceId));
|
|
693
|
+
};
|
|
694
|
+
try {
|
|
695
|
+
return await fn({
|
|
696
|
+
mux,
|
|
697
|
+
get workspace() {
|
|
698
|
+
return workspace;
|
|
699
|
+
},
|
|
700
|
+
set workspace(next) {
|
|
701
|
+
workspace = next;
|
|
702
|
+
},
|
|
703
|
+
save,
|
|
704
|
+
storage
|
|
705
|
+
});
|
|
706
|
+
} finally {
|
|
707
|
+
await mux.close();
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
async function canConnect() {
|
|
711
|
+
const client = new SessionHostClient({ endpoint: getDefaultSessionHostEndpoint(SESSION_HOST_APP_NAME) });
|
|
712
|
+
try {
|
|
713
|
+
await client.connect();
|
|
714
|
+
await client.close();
|
|
715
|
+
return true;
|
|
716
|
+
} catch {
|
|
717
|
+
return false;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
async function waitForSessionHostReady(timeoutMs = STARTUP_TIMEOUT_MS) {
|
|
721
|
+
const deadline = Date.now() + timeoutMs;
|
|
722
|
+
while (Date.now() < deadline) {
|
|
723
|
+
if (await canConnect()) return;
|
|
724
|
+
await new Promise((resolve) => setTimeout(resolve, STARTUP_POLL_MS));
|
|
725
|
+
}
|
|
726
|
+
throw new Error(`Session host did not become ready within ${timeoutMs}ms`);
|
|
727
|
+
}
|
|
728
|
+
function resolveSessionHostEntry() {
|
|
729
|
+
return __require.resolve("@adhdev/session-host-daemon");
|
|
730
|
+
}
|
|
731
|
+
async function ensureSessionHostReady() {
|
|
732
|
+
if (await canConnect()) return;
|
|
733
|
+
const entry = resolveSessionHostEntry();
|
|
734
|
+
const child = spawn(process.execPath, [entry], {
|
|
735
|
+
detached: true,
|
|
736
|
+
stdio: "ignore",
|
|
737
|
+
windowsHide: true,
|
|
738
|
+
env: {
|
|
739
|
+
...process.env,
|
|
740
|
+
ADHDEV_SESSION_HOST_NAME: SESSION_HOST_APP_NAME
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
child.unref();
|
|
744
|
+
await waitForSessionHostReady();
|
|
745
|
+
}
|
|
746
|
+
async function listPanes(workspaceName, flags = { json: false }) {
|
|
747
|
+
const live = await requestWorkspaceControl(workspaceName, { type: "list_panes" });
|
|
748
|
+
if (live?.success && live.result) {
|
|
749
|
+
if (flags.json) {
|
|
750
|
+
process.stdout.write(JSON.stringify(live.result, null, 2));
|
|
751
|
+
process.stdout.write("\n");
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
for (const pane of live.result) {
|
|
755
|
+
process.stdout.write(
|
|
756
|
+
`${pane.index} ${pane.paneId} ${pane.paneKind} ${pane.runtimeKey} ${pane.accessMode} ${pane.focused ? "focused" : ""}
|
|
757
|
+
`
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
await withWorkspace(workspaceName, async ({ workspace }) => {
|
|
763
|
+
const panes = Object.keys(workspace.panes).map((paneId, index) => {
|
|
764
|
+
const pane = workspace.panes[paneId];
|
|
765
|
+
return {
|
|
766
|
+
index,
|
|
767
|
+
paneId,
|
|
768
|
+
paneKind: pane.paneKind,
|
|
769
|
+
runtimeKey: pane.runtimeKey,
|
|
770
|
+
accessMode: pane.accessMode,
|
|
771
|
+
focused: workspace.focusedPaneId === paneId
|
|
772
|
+
};
|
|
773
|
+
});
|
|
774
|
+
if (flags.json) {
|
|
775
|
+
process.stdout.write(JSON.stringify(panes, null, 2));
|
|
776
|
+
process.stdout.write("\n");
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
Object.keys(workspace.panes).forEach((paneId, index) => {
|
|
780
|
+
const pane = workspace.panes[paneId];
|
|
781
|
+
process.stdout.write(
|
|
782
|
+
`${index} ${paneId} ${pane.paneKind} ${pane.runtimeKey} ${pane.accessMode} ${workspace.focusedPaneId === paneId ? "focused" : ""}
|
|
783
|
+
`
|
|
784
|
+
);
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
async function capturePane(workspaceName, paneTarget, flags = { json: false }) {
|
|
789
|
+
const live = await requestWorkspaceControl(workspaceName, {
|
|
790
|
+
type: "capture_pane",
|
|
791
|
+
payload: { paneTarget }
|
|
792
|
+
});
|
|
793
|
+
if (live?.success && live.result) {
|
|
794
|
+
if (flags.json) {
|
|
795
|
+
process.stdout.write(JSON.stringify(live.result, null, 2));
|
|
796
|
+
process.stdout.write("\n");
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
process.stdout.write(live.result.text);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
await withWorkspace(workspaceName, async ({ workspace }) => {
|
|
803
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
804
|
+
const text = workspace.panes[paneId].viewport.text;
|
|
805
|
+
if (flags.json) {
|
|
806
|
+
process.stdout.write(JSON.stringify({ paneId, text }, null, 2));
|
|
807
|
+
process.stdout.write("\n");
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
process.stdout.write(text);
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
async function copyPane(workspaceName, paneTarget, options) {
|
|
814
|
+
const live = await requestWorkspaceControl(
|
|
815
|
+
workspaceName,
|
|
816
|
+
{
|
|
817
|
+
type: "copy_pane",
|
|
818
|
+
payload: { paneTarget, clipboard: options.clipboard, output: options.output }
|
|
819
|
+
}
|
|
820
|
+
);
|
|
821
|
+
if (live?.success && live.result) {
|
|
822
|
+
if (options.json) {
|
|
823
|
+
process.stdout.write(JSON.stringify(live.result, null, 2));
|
|
824
|
+
process.stdout.write("\n");
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
if (!options.output && !options.clipboard && live.result.text) {
|
|
828
|
+
process.stdout.write(live.result.text);
|
|
829
|
+
}
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
await withWorkspace(workspaceName, async ({ workspace }) => {
|
|
833
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
834
|
+
const text = workspace.panes[paneId].viewport.text;
|
|
835
|
+
if (options.output) {
|
|
836
|
+
const { writeFileSync } = await import("fs");
|
|
837
|
+
writeFileSync(options.output, text, "utf8");
|
|
838
|
+
}
|
|
839
|
+
if (options.clipboard) {
|
|
840
|
+
copyTextToClipboard(text);
|
|
841
|
+
}
|
|
842
|
+
if (options.json) {
|
|
843
|
+
process.stdout.write(
|
|
844
|
+
JSON.stringify({ paneId, copiedToClipboard: options.clipboard, output: options.output || null }, null, 2)
|
|
845
|
+
);
|
|
846
|
+
process.stdout.write("\n");
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (!options.output && !options.clipboard) {
|
|
850
|
+
process.stdout.write(text);
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
async function searchPane(workspaceName, paneTarget, query, flags = { json: false }) {
|
|
855
|
+
const live = await requestWorkspaceControl(
|
|
856
|
+
workspaceName,
|
|
857
|
+
{ type: "search_pane", payload: { paneTarget, query } }
|
|
858
|
+
);
|
|
859
|
+
if (live?.success && live.result) {
|
|
860
|
+
if (flags.json) {
|
|
861
|
+
process.stdout.write(JSON.stringify(live.result, null, 2));
|
|
862
|
+
process.stdout.write("\n");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
for (const match of live.result.matches) {
|
|
866
|
+
process.stdout.write(`${match.line}:${match.column} ${match.preview}
|
|
867
|
+
`);
|
|
868
|
+
}
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
await withWorkspace(workspaceName, async ({ workspace }) => {
|
|
872
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
873
|
+
const matches = searchPaneText(workspace.panes[paneId].viewport.text, query);
|
|
874
|
+
if (flags.json) {
|
|
875
|
+
process.stdout.write(JSON.stringify({ paneId, query, count: matches.length, matches }, null, 2));
|
|
876
|
+
process.stdout.write("\n");
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
for (const match of matches) {
|
|
880
|
+
process.stdout.write(`${match.line}:${match.column} ${match.preview}
|
|
881
|
+
`);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
async function selectPane(workspaceName, paneTarget) {
|
|
886
|
+
const live = await requestWorkspaceControl(workspaceName, { type: "select_pane", payload: { paneTarget } });
|
|
887
|
+
if (live?.success) return;
|
|
888
|
+
await withWorkspace(workspaceName, async ({ mux, workspace, save }) => {
|
|
889
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
890
|
+
const next = await mux.focusPane(workspace.workspaceId, paneId);
|
|
891
|
+
workspace = next;
|
|
892
|
+
save();
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
async function killPane(workspaceName, paneTarget) {
|
|
896
|
+
const live = await requestWorkspaceControl(workspaceName, {
|
|
897
|
+
type: "kill_pane",
|
|
898
|
+
payload: { paneTarget }
|
|
899
|
+
});
|
|
900
|
+
if (live?.success) return;
|
|
901
|
+
await withWorkspace(workspaceName, async ({ mux, workspace, save, storage }) => {
|
|
902
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
903
|
+
const next = await mux.closePane(workspace.workspaceId, paneId);
|
|
904
|
+
if (!next) {
|
|
905
|
+
storage.deleteWorkspace(workspaceName);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
workspace = next;
|
|
909
|
+
save();
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
async function replacePane(workspaceName, paneTarget, runtimeTarget) {
|
|
913
|
+
const live = await requestWorkspaceControl(workspaceName, {
|
|
914
|
+
type: "replace_pane",
|
|
915
|
+
payload: { paneTarget, runtimeTarget }
|
|
916
|
+
});
|
|
917
|
+
if (live?.success) return;
|
|
918
|
+
await withWorkspace(workspaceName, async ({ mux, workspace, save }) => {
|
|
919
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
920
|
+
workspace = await mux.replacePaneRuntime(workspace.workspaceId, paneId, runtimeTarget, {
|
|
921
|
+
readOnly: false,
|
|
922
|
+
takeover: false
|
|
923
|
+
});
|
|
924
|
+
save();
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
async function resizePane(workspaceName, paneTarget, args) {
|
|
928
|
+
let direction = null;
|
|
929
|
+
let amount = 0.05;
|
|
930
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
931
|
+
const arg = args[i];
|
|
932
|
+
if (arg === "-L") direction = "left";
|
|
933
|
+
else if (arg === "-R") direction = "right";
|
|
934
|
+
else if (arg === "-U") direction = "up";
|
|
935
|
+
else if (arg === "-D") direction = "down";
|
|
936
|
+
else if (arg === "--amount" && args[i + 1]) {
|
|
937
|
+
amount = Number.parseFloat(args[i + 1]) || amount;
|
|
938
|
+
i += 1;
|
|
939
|
+
} else {
|
|
940
|
+
const parsed = Number.parseFloat(arg);
|
|
941
|
+
if (!Number.isNaN(parsed)) {
|
|
942
|
+
amount = parsed;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (!direction) {
|
|
947
|
+
throw new Error("One of -L, -R, -U, -D is required");
|
|
948
|
+
}
|
|
949
|
+
const live = await requestWorkspaceControl(workspaceName, {
|
|
950
|
+
type: "resize_pane",
|
|
951
|
+
payload: { paneTarget, direction, amount }
|
|
952
|
+
});
|
|
953
|
+
if (live?.success) return;
|
|
954
|
+
await withWorkspace(workspaceName, async ({ mux, workspace, save }) => {
|
|
955
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
956
|
+
workspace = await mux.resizeLayoutPane(workspace.workspaceId, paneId, direction, amount);
|
|
957
|
+
save();
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
async function selectLayout(workspaceName, layoutName) {
|
|
961
|
+
const preset = normalizeLayoutPreset(layoutName);
|
|
962
|
+
const live = await requestWorkspaceControl(workspaceName, {
|
|
963
|
+
type: "select_layout",
|
|
964
|
+
payload: { layoutName: preset }
|
|
965
|
+
});
|
|
966
|
+
if (live?.success) return;
|
|
967
|
+
await withWorkspace(workspaceName, async ({ mux, workspace, save }) => {
|
|
968
|
+
workspace = preset === "balanced" ? await mux.rebalanceWorkspaceLayout(workspace.workspaceId) : await mux.applyLayoutPreset(workspace.workspaceId, preset);
|
|
969
|
+
save();
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
async function swapPane(workspaceName, firstTarget, secondTarget) {
|
|
973
|
+
const live = await requestWorkspaceControl(workspaceName, {
|
|
974
|
+
type: "swap_panes",
|
|
975
|
+
payload: { firstPaneTarget: firstTarget, secondPaneTarget: secondTarget }
|
|
976
|
+
});
|
|
977
|
+
if (live?.success) return;
|
|
978
|
+
await withWorkspace(workspaceName, async ({ mux, workspace, save }) => {
|
|
979
|
+
const firstPaneId = resolvePaneTarget(workspace, firstTarget);
|
|
980
|
+
const secondPaneId = resolvePaneTarget(workspace, secondTarget);
|
|
981
|
+
workspace = await mux.swapPanePositions(workspace.workspaceId, firstPaneId, secondPaneId);
|
|
982
|
+
save();
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
async function zoomPane(workspaceName, paneTarget) {
|
|
986
|
+
const live = await requestWorkspaceControl(workspaceName, { type: "zoom_pane", payload: { paneTarget } });
|
|
987
|
+
if (live?.success) return;
|
|
988
|
+
await withWorkspace(workspaceName, async ({ mux, workspace, save }) => {
|
|
989
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
990
|
+
workspace = await mux.togglePaneZoom(workspace.workspaceId, paneId);
|
|
991
|
+
save();
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
async function splitWindow(workspaceName, args) {
|
|
995
|
+
let axis = "vertical";
|
|
996
|
+
let mirror = false;
|
|
997
|
+
const remaining = [];
|
|
998
|
+
for (const arg of args) {
|
|
999
|
+
if (arg === "-h") {
|
|
1000
|
+
axis = "horizontal";
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
if (arg === "-v") {
|
|
1004
|
+
axis = "vertical";
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (arg === "-m" || arg === "--mirror") {
|
|
1008
|
+
mirror = true;
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
remaining.push(arg);
|
|
1012
|
+
}
|
|
1013
|
+
const live = await requestWorkspaceControl(workspaceName, {
|
|
1014
|
+
type: "split_window",
|
|
1015
|
+
payload: { axis, mirror, runtimeKey: remaining[0] }
|
|
1016
|
+
});
|
|
1017
|
+
if (live?.success) return;
|
|
1018
|
+
await withWorkspace(workspaceName, async ({ mux, workspace, save }) => {
|
|
1019
|
+
let next;
|
|
1020
|
+
if (mirror) {
|
|
1021
|
+
next = await mux.splitWorkspaceMirror(
|
|
1022
|
+
workspace.workspaceId,
|
|
1023
|
+
workspace.focusedPaneId,
|
|
1024
|
+
workspace.focusedPaneId,
|
|
1025
|
+
axis
|
|
1026
|
+
);
|
|
1027
|
+
} else {
|
|
1028
|
+
const runtimeKey = remaining[0];
|
|
1029
|
+
if (!runtimeKey) {
|
|
1030
|
+
throw new Error("Runtime key is required unless --mirror is used");
|
|
1031
|
+
}
|
|
1032
|
+
next = await mux.splitWorkspacePane(
|
|
1033
|
+
workspace.workspaceId,
|
|
1034
|
+
workspace.focusedPaneId,
|
|
1035
|
+
runtimeKey,
|
|
1036
|
+
{ axis, readOnly: false }
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
workspace = next;
|
|
1040
|
+
save();
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
async function sendKeys(workspaceName, paneTarget, textParts) {
|
|
1044
|
+
const text = textParts.join(" ");
|
|
1045
|
+
const live = await requestWorkspaceControl(workspaceName, {
|
|
1046
|
+
type: "send_keys",
|
|
1047
|
+
payload: { paneTarget, text }
|
|
1048
|
+
});
|
|
1049
|
+
if (live?.success) return;
|
|
1050
|
+
await withWorkspace(workspaceName, async ({ mux, workspace }) => {
|
|
1051
|
+
const paneId = resolvePaneTarget(workspace, paneTarget);
|
|
1052
|
+
await mux.sendInput(paneId, text);
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
function clearScreen() {
|
|
1056
|
+
process.stdout.write("\x1B[?1049h\x1B[2J\x1B[H");
|
|
1057
|
+
}
|
|
1058
|
+
function restoreScreen() {
|
|
1059
|
+
process.stdout.write("\x1B[?1049l");
|
|
1060
|
+
}
|
|
1061
|
+
function cycleFocus(workspace) {
|
|
1062
|
+
const paneIds = Object.keys(workspace.panes);
|
|
1063
|
+
if (paneIds.length <= 1) return workspace.focusedPaneId;
|
|
1064
|
+
const current = paneIds.indexOf(workspace.focusedPaneId);
|
|
1065
|
+
const next = current >= 0 ? (current + 1) % paneIds.length : 0;
|
|
1066
|
+
return paneIds[next] || workspace.focusedPaneId;
|
|
1067
|
+
}
|
|
1068
|
+
function buildChooserStatus(runtimes) {
|
|
1069
|
+
if (runtimes.length === 0) return "[adhmux] no running runtimes available";
|
|
1070
|
+
return runtimes.slice(0, 9).map((runtime, index) => `${index + 1}:${runtime.runtimeKey}(${runtime.lifecycle})`).join(" ");
|
|
1071
|
+
}
|
|
1072
|
+
function buildPaneIndicators(activityByPaneId) {
|
|
1073
|
+
return Object.fromEntries(
|
|
1074
|
+
Array.from(activityByPaneId.entries()).map(([paneId, activity]) => {
|
|
1075
|
+
if (activity.kind === "output") {
|
|
1076
|
+
return [paneId, `\u2022${activity.count}`];
|
|
1077
|
+
}
|
|
1078
|
+
if (activity.kind === "done") {
|
|
1079
|
+
return [paneId, "\u2713"];
|
|
1080
|
+
}
|
|
1081
|
+
return [paneId, "!"];
|
|
1082
|
+
})
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
async function openWorkspace(options) {
|
|
1086
|
+
const storage = new TerminalMuxStorage();
|
|
1087
|
+
const mux = new SessionHostMuxClient({ appName: SESSION_HOST_APP_NAME });
|
|
1088
|
+
await mux.connect();
|
|
1089
|
+
const savedWorkspace = options.workspaceName ? storage.loadWorkspace(options.workspaceName) : null;
|
|
1090
|
+
let workspace;
|
|
1091
|
+
if (savedWorkspace && options.runtimeTargets.length === 0) {
|
|
1092
|
+
workspace = await mux.restoreWorkspace(savedWorkspace);
|
|
1093
|
+
} else {
|
|
1094
|
+
if (options.runtimeTargets.length === 0) usage();
|
|
1095
|
+
workspace = await mux.createWorkspace(options.runtimeTargets[0], {
|
|
1096
|
+
readOnly: options.readOnly,
|
|
1097
|
+
takeover: options.takeover,
|
|
1098
|
+
title: options.workspaceName || options.runtimeTargets.join(" + ")
|
|
1099
|
+
});
|
|
1100
|
+
for (let i = 1; i < options.runtimeTargets.length; i += 1) {
|
|
1101
|
+
workspace = await mux.splitWorkspacePane(
|
|
1102
|
+
workspace.workspaceId,
|
|
1103
|
+
workspace.focusedPaneId,
|
|
1104
|
+
options.runtimeTargets[i],
|
|
1105
|
+
{
|
|
1106
|
+
axis: i % 2 === 1 ? "vertical" : "horizontal",
|
|
1107
|
+
readOnly: options.readOnly,
|
|
1108
|
+
takeover: options.takeover
|
|
1109
|
+
}
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (options.workspaceName) {
|
|
1114
|
+
storage.saveWorkspace(options.workspaceName, mux.serializeWorkspace(workspace.workspaceId));
|
|
1115
|
+
storage.setLastWorkspace(options.workspaceName);
|
|
1116
|
+
}
|
|
1117
|
+
let statusLine = options.workspaceName ? `workspace=${options.workspaceName}` : "temporary workspace";
|
|
1118
|
+
let mode = "normal";
|
|
1119
|
+
let promptAxis = null;
|
|
1120
|
+
let promptAction = "split";
|
|
1121
|
+
let promptMode = "runtime";
|
|
1122
|
+
let promptBuffer = "";
|
|
1123
|
+
let chooserAxis = null;
|
|
1124
|
+
let chooserAction = "split";
|
|
1125
|
+
let chooserRuntimes = [];
|
|
1126
|
+
let shouldExit = false;
|
|
1127
|
+
let syncRunning = false;
|
|
1128
|
+
let syncQueued = false;
|
|
1129
|
+
let controlServer = null;
|
|
1130
|
+
const paneActivityById = /* @__PURE__ */ new Map();
|
|
1131
|
+
const paneSearchById = /* @__PURE__ */ new Map();
|
|
1132
|
+
const paneScrollOffsetById = /* @__PURE__ */ new Map();
|
|
1133
|
+
const paneSearchIndexById = /* @__PURE__ */ new Map();
|
|
1134
|
+
const clearPaneActivity = (paneId) => {
|
|
1135
|
+
paneActivityById.delete(paneId);
|
|
1136
|
+
};
|
|
1137
|
+
const getSplitCandidates = async () => {
|
|
1138
|
+
const runtimes = (await mux.listRuntimes()).filter((runtime) => runtime.lifecycle === "running");
|
|
1139
|
+
const openRuntimeKeys = new Set(Object.values(workspace.panes).map((pane) => pane.runtimeKey));
|
|
1140
|
+
const unseen = runtimes.filter((runtime) => !openRuntimeKeys.has(runtime.runtimeKey));
|
|
1141
|
+
return unseen.length > 0 ? unseen : runtimes;
|
|
1142
|
+
};
|
|
1143
|
+
const enterChooser = async (axis, action = "split") => {
|
|
1144
|
+
chooserAxis = axis;
|
|
1145
|
+
chooserAction = action;
|
|
1146
|
+
chooserRuntimes = await getSplitCandidates();
|
|
1147
|
+
mode = "chooser";
|
|
1148
|
+
statusLine = buildChooserStatus(chooserRuntimes);
|
|
1149
|
+
render();
|
|
1150
|
+
};
|
|
1151
|
+
const footerLine = () => {
|
|
1152
|
+
if (mode === "prefix") {
|
|
1153
|
+
return '^B [% vertical] [" horizontal] [c replace] [[] copy-mode] [/ search] [y copy] [z zoom] [HJKL resize] [= rebalance] [n next] [t takeover] [r release] [x close] [s save] [d detach]';
|
|
1154
|
+
}
|
|
1155
|
+
if (mode === "chooser") {
|
|
1156
|
+
return `${chooserAction} ${chooserAxis} choose [1-9] [/] manual key [esc] cancel`;
|
|
1157
|
+
}
|
|
1158
|
+
if (mode === "prompt") {
|
|
1159
|
+
if (promptMode === "search") {
|
|
1160
|
+
return `search query> ${promptBuffer}`;
|
|
1161
|
+
}
|
|
1162
|
+
return `${promptAction} ${promptAxis} runtime> ${promptBuffer}`;
|
|
1163
|
+
}
|
|
1164
|
+
if (mode === "copy") {
|
|
1165
|
+
return "copy-mode [j/k down/up] [d/u page] [g/G top/bottom] [n/N next/prev match] [y copy pane] [enter/esc exit]";
|
|
1166
|
+
}
|
|
1167
|
+
const focused = workspace.panes[workspace.focusedPaneId];
|
|
1168
|
+
return `^B prefix pane=${focused?.runtimeKey || "n/a"} mode=${focused?.accessMode || "n/a"} workspace=${workspace.title}`;
|
|
1169
|
+
};
|
|
1170
|
+
const persistWorkspace = () => {
|
|
1171
|
+
if (!options.workspaceName) return;
|
|
1172
|
+
storage.saveWorkspace(options.workspaceName, mux.serializeWorkspace(workspace.workspaceId));
|
|
1173
|
+
storage.setLastWorkspace(options.workspaceName);
|
|
1174
|
+
};
|
|
1175
|
+
const listWorkspacePanes = () => Object.keys(workspace.panes).map((paneId, index) => {
|
|
1176
|
+
const pane = workspace.panes[paneId];
|
|
1177
|
+
return {
|
|
1178
|
+
index,
|
|
1179
|
+
paneId,
|
|
1180
|
+
paneKind: pane.paneKind,
|
|
1181
|
+
runtimeKey: pane.runtimeKey,
|
|
1182
|
+
accessMode: pane.accessMode,
|
|
1183
|
+
focused: workspace.focusedPaneId === paneId
|
|
1184
|
+
};
|
|
1185
|
+
});
|
|
1186
|
+
const render = () => {
|
|
1187
|
+
clearScreen();
|
|
1188
|
+
const cols = process.stdout.columns || 120;
|
|
1189
|
+
const rows = process.stdout.rows || 40;
|
|
1190
|
+
process.stdout.write(
|
|
1191
|
+
renderWorkspace(workspace, cols, rows, {
|
|
1192
|
+
footerLine: footerLine(),
|
|
1193
|
+
statusLine,
|
|
1194
|
+
paneIndicators: {
|
|
1195
|
+
...buildPaneIndicators(paneActivityById),
|
|
1196
|
+
...Object.fromEntries(
|
|
1197
|
+
Array.from(paneSearchById.entries()).map(([paneId, search]) => [paneId, `/${search.matches.length}`])
|
|
1198
|
+
)
|
|
1199
|
+
},
|
|
1200
|
+
paneLineOffsets: Object.fromEntries(paneScrollOffsetById.entries())
|
|
1201
|
+
})
|
|
1202
|
+
);
|
|
1203
|
+
};
|
|
1204
|
+
const scheduleSync = () => {
|
|
1205
|
+
if (shouldExit) return;
|
|
1206
|
+
if (syncRunning) {
|
|
1207
|
+
syncQueued = true;
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
syncRunning = true;
|
|
1211
|
+
void (async () => {
|
|
1212
|
+
do {
|
|
1213
|
+
syncQueued = false;
|
|
1214
|
+
const rects = computePaneRects(workspace, process.stdout.columns || 120, process.stdout.rows || 40);
|
|
1215
|
+
const seenRuntimeIds = /* @__PURE__ */ new Set();
|
|
1216
|
+
for (const [paneId, rect] of rects) {
|
|
1217
|
+
const pane = workspace.panes[paneId];
|
|
1218
|
+
if (pane?.paneKind === "mirror") continue;
|
|
1219
|
+
if (!pane || seenRuntimeIds.has(pane.runtimeId)) continue;
|
|
1220
|
+
seenRuntimeIds.add(pane.runtimeId);
|
|
1221
|
+
const cols = Math.max(1, rect.width - 2);
|
|
1222
|
+
const rows = Math.max(1, rect.height - 2);
|
|
1223
|
+
if (pane.viewport.cols === cols && pane.viewport.rows === rows) continue;
|
|
1224
|
+
try {
|
|
1225
|
+
await mux.resizePane(paneId, cols, rows);
|
|
1226
|
+
} catch (error) {
|
|
1227
|
+
statusLine = `[adhmux] resize failed: ${error?.message || error}`;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
} while (syncQueued);
|
|
1231
|
+
syncRunning = false;
|
|
1232
|
+
if (!shouldExit) {
|
|
1233
|
+
render();
|
|
1234
|
+
}
|
|
1235
|
+
})();
|
|
1236
|
+
};
|
|
1237
|
+
const setStatus = (next) => {
|
|
1238
|
+
statusLine = next;
|
|
1239
|
+
render();
|
|
1240
|
+
};
|
|
1241
|
+
const updateSearchStatus = (paneId, query) => {
|
|
1242
|
+
const pane = workspace.panes[paneId];
|
|
1243
|
+
const matches = searchPaneText(pane?.viewport.text || "", query);
|
|
1244
|
+
if (matches.length === 0) {
|
|
1245
|
+
paneSearchById.delete(paneId);
|
|
1246
|
+
paneSearchIndexById.delete(paneId);
|
|
1247
|
+
setStatus(`[adhmux] no matches for "${query}"`);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
paneSearchById.set(paneId, { query, matches });
|
|
1251
|
+
paneSearchIndexById.set(paneId, 0);
|
|
1252
|
+
paneScrollOffsetById.set(paneId, Math.max(0, matches[0].line - 1));
|
|
1253
|
+
const first = matches[0];
|
|
1254
|
+
setStatus(`[adhmux] ${matches.length} matches for "${query}" at ${first.line}:${first.column}`);
|
|
1255
|
+
};
|
|
1256
|
+
const setPaneScroll = (paneId, nextOffset) => {
|
|
1257
|
+
const maxOffset = Math.max(
|
|
1258
|
+
0,
|
|
1259
|
+
(workspace.panes[paneId]?.viewport.text.replace(/\r\n/g, "\n").split("\n").length || 1) - 1
|
|
1260
|
+
);
|
|
1261
|
+
paneScrollOffsetById.set(paneId, Math.min(maxOffset, Math.max(0, nextOffset)));
|
|
1262
|
+
render();
|
|
1263
|
+
};
|
|
1264
|
+
const moveSearchMatch = (paneId, delta) => {
|
|
1265
|
+
const search = paneSearchById.get(paneId);
|
|
1266
|
+
if (!search || search.matches.length === 0) {
|
|
1267
|
+
setStatus("[adhmux] no active search");
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
const currentIndex = paneSearchIndexById.get(paneId) || 0;
|
|
1271
|
+
const nextIndex = (currentIndex + delta + search.matches.length) % search.matches.length;
|
|
1272
|
+
paneSearchIndexById.set(paneId, nextIndex);
|
|
1273
|
+
const match = search.matches[nextIndex];
|
|
1274
|
+
paneScrollOffsetById.set(paneId, Math.max(0, match.line - 1));
|
|
1275
|
+
setStatus(`[adhmux] ${search.query} ${nextIndex + 1}/${search.matches.length} at ${match.line}:${match.column}`);
|
|
1276
|
+
};
|
|
1277
|
+
const onMuxEvent = (event) => {
|
|
1278
|
+
if (event.kind === "runtime") {
|
|
1279
|
+
const paneId = event.pane.paneId;
|
|
1280
|
+
if (workspace.focusedPaneId !== paneId) {
|
|
1281
|
+
if (event.event?.type === "session_output") {
|
|
1282
|
+
const previous = paneActivityById.get(paneId);
|
|
1283
|
+
paneActivityById.set(paneId, {
|
|
1284
|
+
kind: "output",
|
|
1285
|
+
count: previous?.kind === "output" ? previous.count + 1 : 1
|
|
1286
|
+
});
|
|
1287
|
+
if (!previous) {
|
|
1288
|
+
process.stdout.write("\x07");
|
|
1289
|
+
}
|
|
1290
|
+
} else if (event.event?.type === "session_exit") {
|
|
1291
|
+
paneActivityById.set(paneId, { kind: "done", count: 1 });
|
|
1292
|
+
process.stdout.write("\x07");
|
|
1293
|
+
} else if (event.event?.type === "write_owner_changed") {
|
|
1294
|
+
paneActivityById.set(paneId, { kind: "owner", count: 1 });
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (options.workspaceName) {
|
|
1298
|
+
controlServer?.broadcast({
|
|
1299
|
+
type: "runtime_update",
|
|
1300
|
+
payload: {
|
|
1301
|
+
workspaceName: options.workspaceName,
|
|
1302
|
+
pane: event.pane,
|
|
1303
|
+
event: event.event || null
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
render();
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (event.kind === "workspace" && event.workspace?.workspaceId === workspace.workspaceId) {
|
|
1311
|
+
if (event.workspace.focusedPaneId !== workspace.focusedPaneId) {
|
|
1312
|
+
clearPaneActivity(event.workspace.focusedPaneId);
|
|
1313
|
+
}
|
|
1314
|
+
workspace = event.workspace;
|
|
1315
|
+
persistWorkspace();
|
|
1316
|
+
if (options.workspaceName) {
|
|
1317
|
+
controlServer?.broadcast({
|
|
1318
|
+
type: "workspace_update",
|
|
1319
|
+
payload: {
|
|
1320
|
+
workspaceName: options.workspaceName,
|
|
1321
|
+
workspace,
|
|
1322
|
+
panes: listWorkspacePanes()
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
if (!shouldExit) {
|
|
1327
|
+
render();
|
|
1328
|
+
scheduleSync();
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
const unsub = mux.onEvent(onMuxEvent);
|
|
1333
|
+
controlServer = options.workspaceName ? createAdhMuxControlServer(options.workspaceName, async (request) => {
|
|
1334
|
+
const payload = request.payload || {};
|
|
1335
|
+
if (request.type === "list_panes") {
|
|
1336
|
+
return { success: true, result: listWorkspacePanes() };
|
|
1337
|
+
}
|
|
1338
|
+
if (request.type === "workspace_state") {
|
|
1339
|
+
return {
|
|
1340
|
+
success: true,
|
|
1341
|
+
result: {
|
|
1342
|
+
workspaceName: options.workspaceName,
|
|
1343
|
+
workspace,
|
|
1344
|
+
panes: listWorkspacePanes()
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
if (request.type === "capture_pane") {
|
|
1349
|
+
const paneId = resolvePaneTarget(workspace, payload.paneTarget);
|
|
1350
|
+
return { success: true, result: { paneId, text: workspace.panes[paneId].viewport.text } };
|
|
1351
|
+
}
|
|
1352
|
+
if (request.type === "copy_pane") {
|
|
1353
|
+
const paneId = resolvePaneTarget(workspace, payload.paneTarget);
|
|
1354
|
+
const text = workspace.panes[paneId].viewport.text;
|
|
1355
|
+
const clipboard = !!payload.clipboard;
|
|
1356
|
+
const output = typeof payload.output === "string" ? payload.output : void 0;
|
|
1357
|
+
if (output) {
|
|
1358
|
+
const { writeFileSync } = await import("fs");
|
|
1359
|
+
writeFileSync(output, text, "utf8");
|
|
1360
|
+
}
|
|
1361
|
+
if (clipboard) {
|
|
1362
|
+
copyTextToClipboard(text);
|
|
1363
|
+
}
|
|
1364
|
+
return {
|
|
1365
|
+
success: true,
|
|
1366
|
+
result: { paneId, copiedToClipboard: clipboard, output: output || null, text: !clipboard && !output ? text : void 0 }
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
if (request.type === "search_pane") {
|
|
1370
|
+
const paneId = resolvePaneTarget(workspace, payload.paneTarget);
|
|
1371
|
+
const query = String(payload.query || "");
|
|
1372
|
+
const matches = searchPaneText(workspace.panes[paneId].viewport.text, query);
|
|
1373
|
+
return { success: true, result: { paneId, query, count: matches.length, matches } };
|
|
1374
|
+
}
|
|
1375
|
+
if (request.type === "select_pane") {
|
|
1376
|
+
workspace = await mux.focusPane(workspace.workspaceId, resolvePaneTarget(workspace, payload.paneTarget));
|
|
1377
|
+
clearPaneActivity(workspace.focusedPaneId);
|
|
1378
|
+
persistWorkspace();
|
|
1379
|
+
render();
|
|
1380
|
+
return { success: true };
|
|
1381
|
+
}
|
|
1382
|
+
if (request.type === "replace_pane") {
|
|
1383
|
+
workspace = await mux.replacePaneRuntime(
|
|
1384
|
+
workspace.workspaceId,
|
|
1385
|
+
resolvePaneTarget(workspace, payload.paneTarget),
|
|
1386
|
+
String(payload.runtimeTarget || ""),
|
|
1387
|
+
{ readOnly: false, takeover: false }
|
|
1388
|
+
);
|
|
1389
|
+
persistWorkspace();
|
|
1390
|
+
render();
|
|
1391
|
+
scheduleSync();
|
|
1392
|
+
return { success: true };
|
|
1393
|
+
}
|
|
1394
|
+
if (request.type === "split_window") {
|
|
1395
|
+
const axis = payload.axis || "vertical";
|
|
1396
|
+
workspace = payload.mirror ? await mux.splitWorkspaceMirror(workspace.workspaceId, workspace.focusedPaneId, workspace.focusedPaneId, axis) : await mux.splitWorkspacePane(workspace.workspaceId, workspace.focusedPaneId, String(payload.runtimeKey || ""), {
|
|
1397
|
+
axis,
|
|
1398
|
+
readOnly: false
|
|
1399
|
+
});
|
|
1400
|
+
persistWorkspace();
|
|
1401
|
+
render();
|
|
1402
|
+
scheduleSync();
|
|
1403
|
+
return { success: true };
|
|
1404
|
+
}
|
|
1405
|
+
if (request.type === "resize_pane") {
|
|
1406
|
+
workspace = await mux.resizeLayoutPane(
|
|
1407
|
+
workspace.workspaceId,
|
|
1408
|
+
resolvePaneTarget(workspace, payload.paneTarget),
|
|
1409
|
+
payload.direction,
|
|
1410
|
+
Number(payload.amount || 0.05)
|
|
1411
|
+
);
|
|
1412
|
+
persistWorkspace();
|
|
1413
|
+
render();
|
|
1414
|
+
return { success: true };
|
|
1415
|
+
}
|
|
1416
|
+
if (request.type === "select_layout") {
|
|
1417
|
+
const preset = normalizeLayoutPreset(String(payload.layoutName || "balanced"));
|
|
1418
|
+
workspace = preset === "balanced" ? await mux.rebalanceWorkspaceLayout(workspace.workspaceId) : await mux.applyLayoutPreset(workspace.workspaceId, preset);
|
|
1419
|
+
persistWorkspace();
|
|
1420
|
+
render();
|
|
1421
|
+
return { success: true };
|
|
1422
|
+
}
|
|
1423
|
+
if (request.type === "swap_panes") {
|
|
1424
|
+
workspace = await mux.swapPanePositions(
|
|
1425
|
+
workspace.workspaceId,
|
|
1426
|
+
resolvePaneTarget(workspace, payload.firstPaneTarget),
|
|
1427
|
+
resolvePaneTarget(workspace, payload.secondPaneTarget)
|
|
1428
|
+
);
|
|
1429
|
+
persistWorkspace();
|
|
1430
|
+
render();
|
|
1431
|
+
return { success: true };
|
|
1432
|
+
}
|
|
1433
|
+
if (request.type === "zoom_pane") {
|
|
1434
|
+
workspace = await mux.togglePaneZoom(
|
|
1435
|
+
workspace.workspaceId,
|
|
1436
|
+
resolvePaneTarget(workspace, payload.paneTarget)
|
|
1437
|
+
);
|
|
1438
|
+
persistWorkspace();
|
|
1439
|
+
render();
|
|
1440
|
+
return { success: true };
|
|
1441
|
+
}
|
|
1442
|
+
if (request.type === "kill_pane") {
|
|
1443
|
+
const next = await mux.closePane(
|
|
1444
|
+
workspace.workspaceId,
|
|
1445
|
+
resolvePaneTarget(workspace, payload.paneTarget)
|
|
1446
|
+
);
|
|
1447
|
+
if (!next) {
|
|
1448
|
+
if (options.workspaceName) storage.deleteWorkspace(options.workspaceName);
|
|
1449
|
+
await finish();
|
|
1450
|
+
return { success: true, result: { workspaceDeleted: true } };
|
|
1451
|
+
}
|
|
1452
|
+
workspace = next;
|
|
1453
|
+
persistWorkspace();
|
|
1454
|
+
render();
|
|
1455
|
+
scheduleSync();
|
|
1456
|
+
return { success: true };
|
|
1457
|
+
}
|
|
1458
|
+
if (request.type === "send_keys") {
|
|
1459
|
+
await mux.sendInput(
|
|
1460
|
+
resolvePaneTarget(workspace, payload.paneTarget),
|
|
1461
|
+
String(payload.text || "")
|
|
1462
|
+
);
|
|
1463
|
+
return { success: true };
|
|
1464
|
+
}
|
|
1465
|
+
return { success: false, error: `Unsupported control request: ${request.type}` };
|
|
1466
|
+
}) : null;
|
|
1467
|
+
const onResize = () => {
|
|
1468
|
+
render();
|
|
1469
|
+
scheduleSync();
|
|
1470
|
+
};
|
|
1471
|
+
process.stdout.on("resize", onResize);
|
|
1472
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
1473
|
+
process.stdin.resume();
|
|
1474
|
+
process.stdin.setEncoding("utf8");
|
|
1475
|
+
render();
|
|
1476
|
+
scheduleSync();
|
|
1477
|
+
const finish = async () => {
|
|
1478
|
+
if (shouldExit) return;
|
|
1479
|
+
shouldExit = true;
|
|
1480
|
+
persistWorkspace();
|
|
1481
|
+
process.stdin.off("data", onData);
|
|
1482
|
+
process.stdout.off("resize", onResize);
|
|
1483
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1484
|
+
process.stdin.pause();
|
|
1485
|
+
restoreScreen();
|
|
1486
|
+
unsub();
|
|
1487
|
+
controlServer?.close();
|
|
1488
|
+
await mux.close();
|
|
1489
|
+
};
|
|
1490
|
+
const handlePromptChar = async (char) => {
|
|
1491
|
+
if (char === "\x07" || char === "\x1B") {
|
|
1492
|
+
mode = "normal";
|
|
1493
|
+
promptAxis = null;
|
|
1494
|
+
promptAction = "split";
|
|
1495
|
+
promptMode = "runtime";
|
|
1496
|
+
promptBuffer = "";
|
|
1497
|
+
statusLine = options.workspaceName ? `workspace=${options.workspaceName}` : "temporary workspace";
|
|
1498
|
+
render();
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
if (char === "\r" || char === "\n") {
|
|
1502
|
+
const value = promptBuffer.trim();
|
|
1503
|
+
mode = "normal";
|
|
1504
|
+
const axis = promptAxis;
|
|
1505
|
+
const action = promptAction;
|
|
1506
|
+
const modeType = promptMode;
|
|
1507
|
+
promptAxis = null;
|
|
1508
|
+
promptAction = "split";
|
|
1509
|
+
promptMode = "runtime";
|
|
1510
|
+
promptBuffer = "";
|
|
1511
|
+
statusLine = options.workspaceName ? `workspace=${options.workspaceName}` : "temporary workspace";
|
|
1512
|
+
if (modeType === "search") {
|
|
1513
|
+
if (!value) {
|
|
1514
|
+
setStatus("[adhmux] search cancelled");
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
updateSearchStatus(workspace.focusedPaneId, value);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
if (!value || !axis) {
|
|
1521
|
+
setStatus("[adhmux] split cancelled");
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
try {
|
|
1525
|
+
workspace = action === "replace" ? await mux.replacePaneRuntime(workspace.workspaceId, workspace.focusedPaneId, value, {
|
|
1526
|
+
readOnly: false,
|
|
1527
|
+
takeover: false
|
|
1528
|
+
}) : await mux.splitWorkspacePane(workspace.workspaceId, workspace.focusedPaneId, value, {
|
|
1529
|
+
axis,
|
|
1530
|
+
readOnly: false
|
|
1531
|
+
});
|
|
1532
|
+
persistWorkspace();
|
|
1533
|
+
render();
|
|
1534
|
+
scheduleSync();
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
setStatus(`[adhmux] split failed: ${error?.message || error}`);
|
|
1537
|
+
}
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
if (char === "\x7F") {
|
|
1541
|
+
promptBuffer = promptBuffer.slice(0, -1);
|
|
1542
|
+
render();
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
if (char >= " " && char <= "~") {
|
|
1546
|
+
promptBuffer += char;
|
|
1547
|
+
render();
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
const handleChooserChar = async (char) => {
|
|
1551
|
+
if (char === "\x07" || char === "\x1B") {
|
|
1552
|
+
mode = "normal";
|
|
1553
|
+
chooserAxis = null;
|
|
1554
|
+
chooserAction = "split";
|
|
1555
|
+
chooserRuntimes = [];
|
|
1556
|
+
statusLine = options.workspaceName ? `workspace=${options.workspaceName}` : "temporary workspace";
|
|
1557
|
+
render();
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
if (char === "/") {
|
|
1561
|
+
mode = "prompt";
|
|
1562
|
+
promptAxis = chooserAxis;
|
|
1563
|
+
promptAction = chooserAction;
|
|
1564
|
+
promptMode = "runtime";
|
|
1565
|
+
chooserAxis = null;
|
|
1566
|
+
chooserAction = "split";
|
|
1567
|
+
chooserRuntimes = [];
|
|
1568
|
+
promptBuffer = "";
|
|
1569
|
+
render();
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
const digit = Number.parseInt(char, 10);
|
|
1573
|
+
if (!Number.isNaN(digit) && digit >= 1 && digit <= 9) {
|
|
1574
|
+
const selected = chooserRuntimes[digit - 1];
|
|
1575
|
+
const axis = chooserAxis;
|
|
1576
|
+
const action = chooserAction;
|
|
1577
|
+
mode = "normal";
|
|
1578
|
+
chooserAxis = null;
|
|
1579
|
+
chooserAction = "split";
|
|
1580
|
+
chooserRuntimes = [];
|
|
1581
|
+
statusLine = options.workspaceName ? `workspace=${options.workspaceName}` : "temporary workspace";
|
|
1582
|
+
if (!selected || !axis) {
|
|
1583
|
+
setStatus("[adhmux] invalid chooser selection");
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
try {
|
|
1587
|
+
workspace = action === "replace" ? await mux.replacePaneRuntime(workspace.workspaceId, workspace.focusedPaneId, selected.runtimeKey, {
|
|
1588
|
+
readOnly: false,
|
|
1589
|
+
takeover: false
|
|
1590
|
+
}) : await mux.splitWorkspacePane(workspace.workspaceId, workspace.focusedPaneId, selected.runtimeKey, {
|
|
1591
|
+
axis,
|
|
1592
|
+
readOnly: false
|
|
1593
|
+
});
|
|
1594
|
+
persistWorkspace();
|
|
1595
|
+
render();
|
|
1596
|
+
scheduleSync();
|
|
1597
|
+
} catch (error) {
|
|
1598
|
+
setStatus(`[adhmux] split failed: ${error?.message || error}`);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
const handlePrefixChar = async (char) => {
|
|
1603
|
+
mode = "normal";
|
|
1604
|
+
try {
|
|
1605
|
+
if (char === "") {
|
|
1606
|
+
await mux.sendInput(workspace.focusedPaneId, "");
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
if (char === "%") {
|
|
1610
|
+
await enterChooser("vertical", "split");
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
if (char === '"') {
|
|
1614
|
+
await enterChooser("horizontal", "split");
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
if (char === "m") {
|
|
1618
|
+
workspace = await mux.splitWorkspaceMirror(
|
|
1619
|
+
workspace.workspaceId,
|
|
1620
|
+
workspace.focusedPaneId,
|
|
1621
|
+
workspace.focusedPaneId,
|
|
1622
|
+
"vertical"
|
|
1623
|
+
);
|
|
1624
|
+
persistWorkspace();
|
|
1625
|
+
render();
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
if (char === "[") {
|
|
1629
|
+
mode = "copy";
|
|
1630
|
+
paneScrollOffsetById.set(workspace.focusedPaneId, paneScrollOffsetById.get(workspace.focusedPaneId) || 0);
|
|
1631
|
+
render();
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (char === "c") {
|
|
1635
|
+
await enterChooser("vertical", "replace");
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1638
|
+
if (char === "/") {
|
|
1639
|
+
mode = "prompt";
|
|
1640
|
+
promptAxis = null;
|
|
1641
|
+
promptAction = "split";
|
|
1642
|
+
promptMode = "search";
|
|
1643
|
+
promptBuffer = "";
|
|
1644
|
+
render();
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
if (char === "y") {
|
|
1648
|
+
copyTextToClipboard(workspace.panes[workspace.focusedPaneId].viewport.text);
|
|
1649
|
+
setStatus("[adhmux] copied focused pane to clipboard");
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
if (char === "n" || char === " ") {
|
|
1653
|
+
workspace = await mux.focusPane(workspace.workspaceId, cycleFocus(workspace));
|
|
1654
|
+
clearPaneActivity(workspace.focusedPaneId);
|
|
1655
|
+
render();
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
if (char === "H") {
|
|
1659
|
+
workspace = await mux.resizeLayoutPane(workspace.workspaceId, workspace.focusedPaneId, "left");
|
|
1660
|
+
persistWorkspace();
|
|
1661
|
+
render();
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
if (char === "L") {
|
|
1665
|
+
workspace = await mux.resizeLayoutPane(workspace.workspaceId, workspace.focusedPaneId, "right");
|
|
1666
|
+
persistWorkspace();
|
|
1667
|
+
render();
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (char === "K") {
|
|
1671
|
+
workspace = await mux.resizeLayoutPane(workspace.workspaceId, workspace.focusedPaneId, "up");
|
|
1672
|
+
persistWorkspace();
|
|
1673
|
+
render();
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
if (char === "J") {
|
|
1677
|
+
workspace = await mux.resizeLayoutPane(workspace.workspaceId, workspace.focusedPaneId, "down");
|
|
1678
|
+
persistWorkspace();
|
|
1679
|
+
render();
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (char === "=") {
|
|
1683
|
+
workspace = await mux.rebalanceWorkspaceLayout(workspace.workspaceId);
|
|
1684
|
+
persistWorkspace();
|
|
1685
|
+
render();
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
if (char === "t") {
|
|
1689
|
+
await mux.takeoverPane(workspace.focusedPaneId);
|
|
1690
|
+
setStatus("[adhmux] write ownership acquired");
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
if (char === "z") {
|
|
1694
|
+
workspace = await mux.togglePaneZoom(workspace.workspaceId, workspace.focusedPaneId);
|
|
1695
|
+
persistWorkspace();
|
|
1696
|
+
render();
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
if (char === "r") {
|
|
1700
|
+
await mux.releasePane(workspace.focusedPaneId);
|
|
1701
|
+
setStatus("[adhmux] write ownership released");
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
if (char === "x") {
|
|
1705
|
+
const next = await mux.closePane(workspace.workspaceId, workspace.focusedPaneId);
|
|
1706
|
+
if (!next) {
|
|
1707
|
+
await finish();
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
workspace = next;
|
|
1711
|
+
persistWorkspace();
|
|
1712
|
+
render();
|
|
1713
|
+
scheduleSync();
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
if (char === "s") {
|
|
1717
|
+
persistWorkspace();
|
|
1718
|
+
setStatus(options.workspaceName ? `[adhmux] saved workspace ${options.workspaceName}` : "[adhmux] temporary workspace");
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
if (char === "d" || char === "q") {
|
|
1722
|
+
await finish();
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
setStatus(`[adhmux] unknown prefix command: ${JSON.stringify(char)}`);
|
|
1726
|
+
} catch (error) {
|
|
1727
|
+
setStatus(`[adhmux] ${error?.message || error}`);
|
|
1728
|
+
} finally {
|
|
1729
|
+
if (!shouldExit) {
|
|
1730
|
+
render();
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
const handleNormalChunk = async (chunk) => {
|
|
1735
|
+
if (chunk === "") {
|
|
1736
|
+
mode = "prefix";
|
|
1737
|
+
render();
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
try {
|
|
1741
|
+
await mux.sendInput(workspace.focusedPaneId, chunk);
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
setStatus(`[adhmux] ${error?.message || error}`);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
const onData = (chunk) => {
|
|
1747
|
+
void (async () => {
|
|
1748
|
+
for (const char of chunk) {
|
|
1749
|
+
if (shouldExit) break;
|
|
1750
|
+
if (mode === "chooser") {
|
|
1751
|
+
await handleChooserChar(char);
|
|
1752
|
+
continue;
|
|
1753
|
+
}
|
|
1754
|
+
if (mode === "prompt") {
|
|
1755
|
+
await handlePromptChar(char);
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
if (mode === "copy") {
|
|
1759
|
+
const paneId = workspace.focusedPaneId;
|
|
1760
|
+
const current = paneScrollOffsetById.get(paneId) || 0;
|
|
1761
|
+
if (char === "\x07" || char === "\x1B" || char === "\r" || char === "\n") {
|
|
1762
|
+
mode = "normal";
|
|
1763
|
+
render();
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
if (char === "j") {
|
|
1767
|
+
setPaneScroll(paneId, current + 1);
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
if (char === "k") {
|
|
1771
|
+
setPaneScroll(paneId, current - 1);
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (char === "d") {
|
|
1775
|
+
setPaneScroll(paneId, current + Math.max(5, Math.floor((process.stdout.rows || 40) / 2)));
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
if (char === "u") {
|
|
1779
|
+
setPaneScroll(paneId, current - Math.max(5, Math.floor((process.stdout.rows || 40) / 2)));
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
if (char === "g") {
|
|
1783
|
+
setPaneScroll(paneId, 0);
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1786
|
+
if (char === "G") {
|
|
1787
|
+
setPaneScroll(paneId, 1e6);
|
|
1788
|
+
continue;
|
|
1789
|
+
}
|
|
1790
|
+
if (char === "n") {
|
|
1791
|
+
moveSearchMatch(paneId, 1);
|
|
1792
|
+
continue;
|
|
1793
|
+
}
|
|
1794
|
+
if (char === "N") {
|
|
1795
|
+
moveSearchMatch(paneId, -1);
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
if (char === "y") {
|
|
1799
|
+
try {
|
|
1800
|
+
copyTextToClipboard(workspace.panes[paneId].viewport.text);
|
|
1801
|
+
setStatus("[adhmux] copied focused pane to clipboard");
|
|
1802
|
+
} catch (error) {
|
|
1803
|
+
setStatus(`[adhmux] ${error?.message || error}`);
|
|
1804
|
+
}
|
|
1805
|
+
continue;
|
|
1806
|
+
}
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
if (mode === "prefix") {
|
|
1810
|
+
await handlePrefixChar(char);
|
|
1811
|
+
continue;
|
|
1812
|
+
}
|
|
1813
|
+
await handleNormalChunk(char);
|
|
1814
|
+
}
|
|
1815
|
+
})();
|
|
1816
|
+
};
|
|
1817
|
+
process.stdin.on("data", onData);
|
|
1818
|
+
await new Promise((resolve) => {
|
|
1819
|
+
const poll = () => {
|
|
1820
|
+
if (shouldExit) {
|
|
1821
|
+
resolve();
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
setTimeout(poll, 50);
|
|
1825
|
+
};
|
|
1826
|
+
poll();
|
|
1827
|
+
});
|
|
1828
|
+
}
|
|
1829
|
+
async function main() {
|
|
1830
|
+
const [, , command, ...args] = process.argv;
|
|
1831
|
+
if (!command || command === "--help" || command === "-h" || command === "help") usage(0);
|
|
1832
|
+
const { flags, rest } = parseCommandFlags(args);
|
|
1833
|
+
if (command === "list" || command === "list-runtimes") {
|
|
1834
|
+
await listRuntimes(flags);
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if (command === "sessions" || command === "list-sessions") {
|
|
1838
|
+
await listSessions(flags);
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (command === "workspaces" || command === "ls" || command === "list-workspaces") {
|
|
1842
|
+
await listWorkspaces(flags);
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
if (command === "windows" || command === "list-windows") {
|
|
1846
|
+
const { workspaceName } = parseSessionTargetArgs(rest);
|
|
1847
|
+
await listWindows(workspaceName, flags);
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
if (command === "tree") {
|
|
1851
|
+
await printWorkspaceTree(flags);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (command === "state") {
|
|
1855
|
+
const { workspaceName } = parseSessionTargetArgs(rest);
|
|
1856
|
+
await printWorkspaceState(workspaceName, flags);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
if (command === "socket-info") {
|
|
1860
|
+
const { workspaceName } = parseSessionTargetArgs(rest);
|
|
1861
|
+
await printSocketInfo(workspaceName, flags);
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if (command === "events") {
|
|
1865
|
+
const { workspaceName } = parseSessionTargetArgs(rest);
|
|
1866
|
+
await streamWorkspaceEvents(workspaceName, flags);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
if (command === "control") {
|
|
1870
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
1871
|
+
if (!remainingArgs[0]) usage();
|
|
1872
|
+
await controlWorkspace(workspaceName, remainingArgs[0], remainingArgs[1], flags);
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
if (command === "last-session") {
|
|
1876
|
+
await printLastWorkspace(flags);
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
if (command === "rename-workspace") {
|
|
1880
|
+
if (!rest[0] || !rest[1]) usage();
|
|
1881
|
+
await renameWorkspace(rest[0], rest[1]);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
if (command === "delete-workspace") {
|
|
1885
|
+
if (!rest[0]) usage();
|
|
1886
|
+
await deleteWorkspace(rest[0]);
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
if (command === "new-session") {
|
|
1890
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
1891
|
+
await openWorkspace({
|
|
1892
|
+
workspaceName: buildWorkspaceName(workspaceName, workspaceName),
|
|
1893
|
+
runtimeTargets: remainingArgs,
|
|
1894
|
+
readOnly: false,
|
|
1895
|
+
takeover: false
|
|
1896
|
+
});
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
if (command === "attach-session") {
|
|
1900
|
+
const storage = new TerminalMuxStorage();
|
|
1901
|
+
const sessionTarget = rest.length > 0 ? parseSessionTargetArgs(rest).workspaceName : storage.getLastWorkspace();
|
|
1902
|
+
if (!sessionTarget) {
|
|
1903
|
+
throw new Error("No workspace specified and no last session recorded");
|
|
1904
|
+
}
|
|
1905
|
+
const workspaceTarget = storage.resolveSessionWindowWorkspace(sessionTarget) || sessionTarget;
|
|
1906
|
+
await openWorkspace({
|
|
1907
|
+
workspaceName: workspaceTarget,
|
|
1908
|
+
runtimeTargets: [],
|
|
1909
|
+
readOnly: false,
|
|
1910
|
+
takeover: false
|
|
1911
|
+
});
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
if (command === "kill-session") {
|
|
1915
|
+
const { workspaceName } = parseSessionTargetArgs(rest);
|
|
1916
|
+
await killSession(workspaceName);
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
if (command === "rename-session") {
|
|
1920
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
1921
|
+
if (!remainingArgs[0]) usage();
|
|
1922
|
+
await renameSession(workspaceName, remainingArgs[0]);
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
if (command === "has-session") {
|
|
1926
|
+
const { workspaceName } = parseSessionTargetArgs(rest);
|
|
1927
|
+
process.exit(await hasSession(workspaceName) ? 0 : 1);
|
|
1928
|
+
}
|
|
1929
|
+
if (command === "new-window") {
|
|
1930
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
1931
|
+
let windowName;
|
|
1932
|
+
const runtimeTargets = [];
|
|
1933
|
+
for (let i = 0; i < remainingArgs.length; i += 1) {
|
|
1934
|
+
const arg = remainingArgs[i];
|
|
1935
|
+
if (arg === "-n" && remainingArgs[i + 1]) {
|
|
1936
|
+
windowName = remainingArgs[i + 1];
|
|
1937
|
+
i += 1;
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
runtimeTargets.push(arg);
|
|
1941
|
+
}
|
|
1942
|
+
await newWindow(workspaceName, windowName, runtimeTargets);
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
if (command === "select-window") {
|
|
1946
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
1947
|
+
if (!remainingArgs[0]) usage();
|
|
1948
|
+
await selectWindow(workspaceName, remainingArgs[0]);
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
if (command === "rename-window") {
|
|
1952
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
1953
|
+
if (!remainingArgs[0] || !remainingArgs[1]) usage();
|
|
1954
|
+
await renameWindow(workspaceName, remainingArgs[0], remainingArgs[1]);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
if (command === "kill-window") {
|
|
1958
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
1959
|
+
if (!remainingArgs[0]) usage();
|
|
1960
|
+
await killWindow(workspaceName, remainingArgs[0]);
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
if (command === "list-panes") {
|
|
1964
|
+
const { workspaceName } = parseSessionTargetArgs(rest);
|
|
1965
|
+
await listPanes(workspaceName, flags);
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (command === "capture-pane") {
|
|
1969
|
+
const { workspaceName, paneTarget } = parsePaneTargetArgs(rest);
|
|
1970
|
+
await capturePane(workspaceName, paneTarget, flags);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
if (command === "copy-pane") {
|
|
1974
|
+
const { workspaceName, paneTarget, remainingArgs } = parsePaneTargetArgs(rest);
|
|
1975
|
+
let clipboard = false;
|
|
1976
|
+
let output;
|
|
1977
|
+
for (let i = 0; i < remainingArgs.length; i += 1) {
|
|
1978
|
+
const arg = remainingArgs[i];
|
|
1979
|
+
if (arg === "--clipboard") {
|
|
1980
|
+
clipboard = true;
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
if (arg === "--output" && remainingArgs[i + 1]) {
|
|
1984
|
+
output = remainingArgs[i + 1];
|
|
1985
|
+
i += 1;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
await copyPane(workspaceName, paneTarget, { json: flags.json, clipboard, output });
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
if (command === "search-pane") {
|
|
1992
|
+
const { workspaceName, paneTarget, remainingArgs } = parsePaneTargetArgs(rest);
|
|
1993
|
+
if (!remainingArgs[0]) usage();
|
|
1994
|
+
await searchPane(workspaceName, paneTarget, remainingArgs.join(" "), flags);
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
if (command === "select-pane") {
|
|
1998
|
+
const { workspaceName, paneTarget } = parsePaneTargetArgs(rest);
|
|
1999
|
+
if (!paneTarget) usage();
|
|
2000
|
+
await selectPane(workspaceName, paneTarget);
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
if (command === "replace-pane") {
|
|
2004
|
+
const { workspaceName, paneTarget, remainingArgs } = parsePaneTargetArgs(rest);
|
|
2005
|
+
if (!remainingArgs[0]) usage();
|
|
2006
|
+
await replacePane(workspaceName, paneTarget, remainingArgs[0]);
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
if (command === "kill-pane") {
|
|
2010
|
+
const { workspaceName, paneTarget } = parsePaneTargetArgs(rest);
|
|
2011
|
+
if (!paneTarget) usage();
|
|
2012
|
+
await killPane(workspaceName, paneTarget);
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (command === "split-window") {
|
|
2016
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
2017
|
+
await splitWindow(workspaceName, remainingArgs);
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
if (command === "resize-pane") {
|
|
2021
|
+
const { workspaceName, paneTarget, remainingArgs } = parsePaneTargetArgs(rest);
|
|
2022
|
+
await resizePane(workspaceName, paneTarget, remainingArgs);
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
if (command === "select-layout") {
|
|
2026
|
+
const { workspaceName, remainingArgs } = parseSessionTargetArgs(rest);
|
|
2027
|
+
if (!remainingArgs[0]) usage();
|
|
2028
|
+
await selectLayout(workspaceName, remainingArgs[0]);
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
if (command === "swap-pane") {
|
|
2032
|
+
const { workspaceName, paneTarget, remainingArgs } = parsePaneTargetArgs(rest);
|
|
2033
|
+
if (!paneTarget || !remainingArgs[0]) usage();
|
|
2034
|
+
await swapPane(workspaceName, paneTarget, remainingArgs[0]);
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
if (command === "zoom-pane") {
|
|
2038
|
+
const { workspaceName, paneTarget } = parsePaneTargetArgs(rest);
|
|
2039
|
+
await zoomPane(workspaceName, paneTarget);
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
if (command === "send-keys") {
|
|
2043
|
+
const { workspaceName, paneTarget, remainingArgs } = parsePaneTargetArgs(rest);
|
|
2044
|
+
if (remainingArgs.length === 0) usage();
|
|
2045
|
+
await sendKeys(workspaceName, paneTarget, remainingArgs);
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
if (command === "snapshot") {
|
|
2049
|
+
if (!rest[0]) usage();
|
|
2050
|
+
await snapshotRuntime(rest[0]);
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
if (command === "open") {
|
|
2054
|
+
await openWorkspace(parseOpenArgs(rest));
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
usage();
|
|
2058
|
+
}
|
|
2059
|
+
main().catch((error) => {
|
|
2060
|
+
console.error("[adhmux]", error?.message || error);
|
|
2061
|
+
process.exit(1);
|
|
2062
|
+
});
|