aimux-cli 0.1.1 → 0.1.2
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/daemon.js +35 -3
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-feedback.d.ts +38 -0
- package/dist/dashboard-feedback.js +113 -0
- package/dist/dashboard-feedback.js.map +1 -0
- package/dist/dashboard-pending-actions.d.ts +12 -0
- package/dist/dashboard-pending-actions.js +52 -0
- package/dist/dashboard-pending-actions.js.map +1 -0
- package/dist/dashboard-session-actions.d.ts +29 -0
- package/dist/dashboard-session-actions.js +82 -0
- package/dist/dashboard-session-actions.js.map +1 -0
- package/dist/dashboard-targets.d.ts +18 -0
- package/dist/dashboard-targets.js +236 -0
- package/dist/dashboard-targets.js.map +1 -0
- package/dist/dashboard-ui-state-store.d.ts +12 -0
- package/dist/dashboard-ui-state-store.js +106 -0
- package/dist/dashboard-ui-state-store.js.map +1 -0
- package/dist/dashboard.d.ts +3 -1
- package/dist/dashboard.js +4 -0
- package/dist/dashboard.js.map +1 -1
- package/dist/main.js +41 -207
- package/dist/main.js.map +1 -1
- package/dist/metadata-server.js +6 -77
- package/dist/metadata-server.js.map +1 -1
- package/dist/multiplexer-runtime-sync.d.ts +26 -0
- package/dist/multiplexer-runtime-sync.js +58 -0
- package/dist/multiplexer-runtime-sync.js.map +1 -0
- package/dist/multiplexer.d.ts +25 -10
- package/dist/multiplexer.js +353 -274
- package/dist/multiplexer.js.map +1 -1
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +3 -0
- package/dist/paths.js.map +1 -1
- package/dist/statusline-model.js +1 -0
- package/dist/statusline-model.js.map +1 -1
- package/dist/tmux-runtime-manager.d.ts +0 -8
- package/dist/tmux-runtime-manager.js +34 -36
- package/dist/tmux-runtime-manager.js.map +1 -1
- package/dist/tmux-switcher.d.ts +11 -0
- package/dist/tmux-switcher.js +115 -0
- package/dist/tmux-switcher.js.map +1 -0
- package/dist/tmux-window-open.d.ts +9 -0
- package/dist/tmux-window-open.js +71 -0
- package/dist/tmux-window-open.js.map +1 -0
- package/dist/tool-output-watchers.d.ts +14 -1
- package/dist/tool-output-watchers.js +18 -7
- package/dist/tool-output-watchers.js.map +1 -1
- package/dist/tui/screens/dashboard-renderers.js +2 -1
- package/dist/tui/screens/dashboard-renderers.js.map +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync, copyFileSync, mkdirSync, chmodSync
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, copyFileSync, mkdirSync, chmodSync } from "node:fs";
|
|
3
3
|
import { join as pathJoin, resolve as pathResolve, dirname as pathDirname } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -21,6 +21,8 @@ import { acceptHandoff, approveReview, acceptTask, assignTask, blockTask, comple
|
|
|
21
21
|
import { addNotification, clearNotifications, listNotifications, markNotificationsRead, unreadNotificationCount, } from "./notifications.js";
|
|
22
22
|
import { parseClaudeHookPayload, summarizeClaudeNotification, summarizeClaudeStop } from "./claude-hooks.js";
|
|
23
23
|
import { requestJson } from "./http-client.js";
|
|
24
|
+
import { runTmuxSwitcher } from "./tmux-switcher.js";
|
|
25
|
+
import { findLiveDashboardTarget, getDashboardCommandSpec, openDashboardTarget, pruneDashboardArtifacts, resolveDashboardTarget, } from "./dashboard-targets.js";
|
|
24
26
|
const program = new Command();
|
|
25
27
|
class ProjectServiceVersionError extends Error {
|
|
26
28
|
projectRoot;
|
|
@@ -58,7 +60,7 @@ async function restartStaleControlPlane(projectRoot) {
|
|
|
58
60
|
await ensureDaemonRunning();
|
|
59
61
|
await ensureProjectService(projectRoot);
|
|
60
62
|
const { dashboardBuildStamp } = getDashboardCommandSpec(projectRoot);
|
|
61
|
-
pruneDashboardArtifacts(projectRoot, dashboardBuildStamp);
|
|
63
|
+
pruneDashboardArtifacts(projectRoot, dashboardBuildStamp, new TmuxRuntimeManager());
|
|
62
64
|
}
|
|
63
65
|
async function fetchProjectServiceHealth(endpoint) {
|
|
64
66
|
const { status, json } = await requestJson(`http://${endpoint.host}:${endpoint.port}/health`);
|
|
@@ -217,7 +219,10 @@ async function ensureDaemonProjectReady(projectRoot, opts) {
|
|
|
217
219
|
await waitForVerifiedProjectService(projectRoot);
|
|
218
220
|
}
|
|
219
221
|
catch (error) {
|
|
220
|
-
if (
|
|
222
|
+
if (opts?.repairVersionDrift === false) {
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
if (!(error instanceof ProjectServiceVersionError) && !(error instanceof Error)) {
|
|
221
226
|
throw error;
|
|
222
227
|
}
|
|
223
228
|
await restartStaleControlPlane(projectRoot);
|
|
@@ -242,203 +247,6 @@ function ensureTmuxAvailable(tmux) {
|
|
|
242
247
|
process.exit(1);
|
|
243
248
|
}
|
|
244
249
|
}
|
|
245
|
-
function shellQuote(value) {
|
|
246
|
-
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
247
|
-
}
|
|
248
|
-
function getDashboardCommandSpec(projectRoot) {
|
|
249
|
-
const scriptPath = fileURLToPath(import.meta.url);
|
|
250
|
-
const wrappedDashboardCommand = [
|
|
251
|
-
"set -o pipefail",
|
|
252
|
-
";",
|
|
253
|
-
shellQuote(process.execPath),
|
|
254
|
-
shellQuote(scriptPath),
|
|
255
|
-
"--tmux-dashboard-internal",
|
|
256
|
-
"2>&1",
|
|
257
|
-
"|",
|
|
258
|
-
"tee",
|
|
259
|
-
"-a",
|
|
260
|
-
shellQuote("/tmp/aimux-debug.log"),
|
|
261
|
-
";",
|
|
262
|
-
"code=$?",
|
|
263
|
-
";",
|
|
264
|
-
"if",
|
|
265
|
-
"[",
|
|
266
|
-
"$code",
|
|
267
|
-
"-ne",
|
|
268
|
-
"0",
|
|
269
|
-
"]",
|
|
270
|
-
";",
|
|
271
|
-
"then",
|
|
272
|
-
"printf",
|
|
273
|
-
"'\\033[?1049l\\033[H\\033[2J'",
|
|
274
|
-
";",
|
|
275
|
-
"printf",
|
|
276
|
-
"%s\\n%s\\n%s\\n%s\\n",
|
|
277
|
-
shellQuote(""),
|
|
278
|
-
shellQuote("aimux dashboard failed to start."),
|
|
279
|
-
shellQuote("The error above was captured from the dashboard process."),
|
|
280
|
-
shellQuote("Press q, Enter, or Ctrl+C to close this pane."),
|
|
281
|
-
";",
|
|
282
|
-
"while",
|
|
283
|
-
"IFS= read -rsn1 key",
|
|
284
|
-
";",
|
|
285
|
-
"do",
|
|
286
|
-
"if",
|
|
287
|
-
"[",
|
|
288
|
-
"-z",
|
|
289
|
-
'"$key"',
|
|
290
|
-
"]",
|
|
291
|
-
"||",
|
|
292
|
-
"[",
|
|
293
|
-
'"$key"',
|
|
294
|
-
"=",
|
|
295
|
-
shellQuote("q"),
|
|
296
|
-
"]",
|
|
297
|
-
";",
|
|
298
|
-
"then",
|
|
299
|
-
"exit 0",
|
|
300
|
-
";",
|
|
301
|
-
"fi",
|
|
302
|
-
";",
|
|
303
|
-
"done",
|
|
304
|
-
";",
|
|
305
|
-
"fi",
|
|
306
|
-
].join(" ");
|
|
307
|
-
return {
|
|
308
|
-
scriptPath,
|
|
309
|
-
dashboardBuildStamp: String(statSync(scriptPath).mtimeMs),
|
|
310
|
-
dashboardCommand: {
|
|
311
|
-
cwd: projectRoot,
|
|
312
|
-
command: "bash",
|
|
313
|
-
args: ["-lc", wrappedDashboardCommand],
|
|
314
|
-
},
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
function ensureDashboardTarget(projectRoot, tmux = new TmuxRuntimeManager()) {
|
|
318
|
-
const { dashboardBuildStamp, dashboardCommand } = getDashboardCommandSpec(projectRoot);
|
|
319
|
-
pruneDashboardArtifacts(projectRoot, dashboardBuildStamp, tmux);
|
|
320
|
-
const dashboardSession = tmux.ensureProjectSession(projectRoot, {
|
|
321
|
-
cwd: dashboardCommand.cwd,
|
|
322
|
-
command: dashboardCommand.command,
|
|
323
|
-
args: dashboardCommand.args,
|
|
324
|
-
});
|
|
325
|
-
const openSessionName = tmux.getOpenSessionName(dashboardSession.sessionName, tmux.isInsideTmux());
|
|
326
|
-
const dashboardTarget = tmux.ensureDashboardWindow(openSessionName, projectRoot, dashboardCommand);
|
|
327
|
-
const currentBuildStamp = tmux.getWindowOption(dashboardTarget, "@aimux-dashboard-build");
|
|
328
|
-
const shouldRespawnDashboard = !tmux.isWindowAlive(dashboardTarget) || currentBuildStamp !== dashboardBuildStamp;
|
|
329
|
-
if (shouldRespawnDashboard) {
|
|
330
|
-
tmux.respawnWindow(dashboardTarget, dashboardCommand);
|
|
331
|
-
tmux.setWindowOption(dashboardTarget, "@aimux-dashboard-build", dashboardBuildStamp);
|
|
332
|
-
}
|
|
333
|
-
return { dashboardSession, dashboardTarget };
|
|
334
|
-
}
|
|
335
|
-
function pruneDashboardArtifacts(projectRoot, dashboardBuildStamp, tmux = new TmuxRuntimeManager()) {
|
|
336
|
-
const hostSession = tmux.getProjectSession(projectRoot).sessionName;
|
|
337
|
-
const sessions = tmux
|
|
338
|
-
.listSessionNames()
|
|
339
|
-
.filter((sessionName) => sessionName === hostSession || sessionName.startsWith(`${hostSession}-client-`));
|
|
340
|
-
for (const sessionName of sessions) {
|
|
341
|
-
const windows = tmux.listWindows(sessionName);
|
|
342
|
-
const dashboardWindows = windows.filter((window) => window.name.startsWith("dashboard"));
|
|
343
|
-
for (const window of dashboardWindows) {
|
|
344
|
-
const target = {
|
|
345
|
-
sessionName,
|
|
346
|
-
windowId: window.id,
|
|
347
|
-
windowIndex: window.index,
|
|
348
|
-
windowName: window.name,
|
|
349
|
-
};
|
|
350
|
-
const paneCommand = tmux.displayMessage("#{pane_current_command}", window.id);
|
|
351
|
-
const currentBuildStamp = tmux.getWindowOption(target, "@aimux-dashboard-build");
|
|
352
|
-
const invalid = !tmux.isWindowAlive(target) ||
|
|
353
|
-
paneCommand === "cat" ||
|
|
354
|
-
paneCommand === "tail" ||
|
|
355
|
-
!currentBuildStamp ||
|
|
356
|
-
currentBuildStamp !== dashboardBuildStamp;
|
|
357
|
-
if (!invalid)
|
|
358
|
-
continue;
|
|
359
|
-
try {
|
|
360
|
-
tmux.killWindow(target);
|
|
361
|
-
}
|
|
362
|
-
catch { }
|
|
363
|
-
}
|
|
364
|
-
if (sessionName === hostSession || !tmux.hasSession(sessionName))
|
|
365
|
-
continue;
|
|
366
|
-
const remaining = tmux.listWindows(sessionName);
|
|
367
|
-
const hasValidDashboard = remaining.some((window) => window.name.startsWith("dashboard"));
|
|
368
|
-
if (hasValidDashboard)
|
|
369
|
-
continue;
|
|
370
|
-
const hasNonDashboardWindows = remaining.some((window) => !window.name.startsWith("dashboard"));
|
|
371
|
-
if (hasNonDashboardWindows)
|
|
372
|
-
continue;
|
|
373
|
-
try {
|
|
374
|
-
tmux.killSession(sessionName);
|
|
375
|
-
}
|
|
376
|
-
catch { }
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
function getLiveDashboardTarget(projectRoot, tmux = new TmuxRuntimeManager()) {
|
|
380
|
-
const { dashboardBuildStamp } = getDashboardCommandSpec(projectRoot);
|
|
381
|
-
pruneDashboardArtifacts(projectRoot, dashboardBuildStamp, tmux);
|
|
382
|
-
const dashboardSession = tmux.getProjectSession(projectRoot);
|
|
383
|
-
const isUsableDashboardTarget = (dashboardTarget) => {
|
|
384
|
-
const currentBuildStamp = tmux.getWindowOption(dashboardTarget, "@aimux-dashboard-build");
|
|
385
|
-
const paneCommand = tmux.displayMessage("#{pane_current_command}", dashboardTarget.windowId);
|
|
386
|
-
return tmux.isWindowAlive(dashboardTarget) && currentBuildStamp === dashboardBuildStamp && paneCommand !== "cat";
|
|
387
|
-
};
|
|
388
|
-
if (!tmux.hasSession(dashboardSession.sessionName)) {
|
|
389
|
-
return null;
|
|
390
|
-
}
|
|
391
|
-
const openSessionName = tmux.peekOpenSessionName(dashboardSession.sessionName, tmux.isInsideTmux());
|
|
392
|
-
if (!tmux.hasSession(openSessionName)) {
|
|
393
|
-
const candidateSessions = tmux
|
|
394
|
-
.listSessionNames()
|
|
395
|
-
.filter((sessionName) => sessionName === dashboardSession.sessionName ||
|
|
396
|
-
sessionName.startsWith(`${dashboardSession.sessionName}-client-`));
|
|
397
|
-
const candidates = candidateSessions
|
|
398
|
-
.flatMap((sessionName) => tmux
|
|
399
|
-
.listWindows(sessionName)
|
|
400
|
-
.filter((window) => window.name.startsWith("dashboard"))
|
|
401
|
-
.map((window) => ({
|
|
402
|
-
sessionName,
|
|
403
|
-
windowId: window.id,
|
|
404
|
-
windowIndex: window.index,
|
|
405
|
-
windowName: window.name,
|
|
406
|
-
})))
|
|
407
|
-
.filter(isUsableDashboardTarget);
|
|
408
|
-
if (candidates.length === 1) {
|
|
409
|
-
return { dashboardSession, dashboardTarget: candidates[0] };
|
|
410
|
-
}
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
const dashboardWindow = tmux.listWindows(openSessionName).find((window) => window.name.startsWith("dashboard"));
|
|
414
|
-
if (!dashboardWindow) {
|
|
415
|
-
return null;
|
|
416
|
-
}
|
|
417
|
-
const dashboardTarget = {
|
|
418
|
-
sessionName: openSessionName,
|
|
419
|
-
windowId: dashboardWindow.id,
|
|
420
|
-
windowIndex: dashboardWindow.index,
|
|
421
|
-
windowName: dashboardWindow.name,
|
|
422
|
-
};
|
|
423
|
-
if (!isUsableDashboardTarget(dashboardTarget)) {
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
return { dashboardSession, dashboardTarget };
|
|
427
|
-
}
|
|
428
|
-
function forceReloadDashboardTarget(projectRoot, tmux = new TmuxRuntimeManager()) {
|
|
429
|
-
const { dashboardBuildStamp, dashboardCommand } = getDashboardCommandSpec(projectRoot);
|
|
430
|
-
pruneDashboardArtifacts(projectRoot, dashboardBuildStamp, tmux);
|
|
431
|
-
const dashboardSession = tmux.ensureProjectSession(projectRoot, {
|
|
432
|
-
cwd: dashboardCommand.cwd,
|
|
433
|
-
command: dashboardCommand.command,
|
|
434
|
-
args: dashboardCommand.args,
|
|
435
|
-
});
|
|
436
|
-
const openSessionName = tmux.getOpenSessionName(dashboardSession.sessionName, tmux.isInsideTmux());
|
|
437
|
-
const dashboardTarget = tmux.ensureDashboardWindow(openSessionName, projectRoot, dashboardCommand);
|
|
438
|
-
tmux.respawnWindow(dashboardTarget, dashboardCommand);
|
|
439
|
-
tmux.setWindowOption(dashboardTarget, "@aimux-dashboard-build", dashboardBuildStamp);
|
|
440
|
-
return { dashboardSession, dashboardTarget };
|
|
441
|
-
}
|
|
442
250
|
program
|
|
443
251
|
.name("aimux")
|
|
444
252
|
.description("Native CLI agent multiplexer")
|
|
@@ -479,7 +287,7 @@ program
|
|
|
479
287
|
const tmux = new TmuxRuntimeManager();
|
|
480
288
|
ensureTmuxAvailable(tmux);
|
|
481
289
|
if (!tool && !opts.resume && !opts.restore) {
|
|
482
|
-
const liveDashboard =
|
|
290
|
+
const liveDashboard = findLiveDashboardTarget(projectRoot, tmux);
|
|
483
291
|
if (liveDashboard) {
|
|
484
292
|
tmux.openTarget(liveDashboard.dashboardTarget, {
|
|
485
293
|
insideTmux: tmux.isInsideTmux(),
|
|
@@ -489,9 +297,8 @@ program
|
|
|
489
297
|
}
|
|
490
298
|
}
|
|
491
299
|
await ensureDaemonProjectSpawned(projectRoot);
|
|
492
|
-
const { dashboardTarget } = ensureDashboardTarget(projectRoot, tmux);
|
|
493
300
|
if (!tool && !opts.resume && !opts.restore) {
|
|
494
|
-
|
|
301
|
+
openDashboardTarget(projectRoot, tmux);
|
|
495
302
|
return;
|
|
496
303
|
}
|
|
497
304
|
}
|
|
@@ -568,7 +375,7 @@ program
|
|
|
568
375
|
await ensureDaemonProjectSpawned(projectRoot);
|
|
569
376
|
const tmux = new TmuxRuntimeManager();
|
|
570
377
|
ensureTmuxAvailable(tmux);
|
|
571
|
-
const { dashboardSession, dashboardTarget } =
|
|
378
|
+
const { dashboardSession, dashboardTarget } = resolveDashboardTarget(projectRoot, tmux, { forceReload: true });
|
|
572
379
|
if (opts.open) {
|
|
573
380
|
tmux.openTarget(dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
|
|
574
381
|
return;
|
|
@@ -690,7 +497,7 @@ hostCmd
|
|
|
690
497
|
}
|
|
691
498
|
const tmux = new TmuxRuntimeManager();
|
|
692
499
|
ensureTmuxAvailable(tmux);
|
|
693
|
-
const { dashboardSession, dashboardTarget } =
|
|
500
|
+
const { dashboardSession, dashboardTarget } = resolveDashboardTarget(projectRoot, tmux, { forceReload: true });
|
|
694
501
|
if (opts.open) {
|
|
695
502
|
tmux.openTarget(dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
|
|
696
503
|
return;
|
|
@@ -1896,8 +1703,11 @@ program
|
|
|
1896
1703
|
.action(async (sessionId, opts) => {
|
|
1897
1704
|
try {
|
|
1898
1705
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
1899
|
-
|
|
1900
|
-
const result = await
|
|
1706
|
+
await ensureDaemonProjectReady(projectRoot);
|
|
1707
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/agents/rename", { sessionId, label: opts.label }, () => {
|
|
1708
|
+
const mux = new Multiplexer();
|
|
1709
|
+
return mux.renameAgent(sessionId, opts.label);
|
|
1710
|
+
});
|
|
1901
1711
|
if (opts.json) {
|
|
1902
1712
|
console.log(JSON.stringify({
|
|
1903
1713
|
ok: true,
|
|
@@ -1915,6 +1725,30 @@ program
|
|
|
1915
1725
|
process.exit(1);
|
|
1916
1726
|
}
|
|
1917
1727
|
});
|
|
1728
|
+
program
|
|
1729
|
+
.command("switcher")
|
|
1730
|
+
.description("Internal tmux popup switcher")
|
|
1731
|
+
.requiredOption("--project-root <path>", "Project root")
|
|
1732
|
+
.requiredOption("--project-state-dir <path>", "Project state dir")
|
|
1733
|
+
.option("--current-client-session <name>", "Current client session")
|
|
1734
|
+
.option("--client-tty <tty>", "Client tty")
|
|
1735
|
+
.option("--current-window <name>", "Current window name")
|
|
1736
|
+
.option("--current-window-id <id>", "Current window id")
|
|
1737
|
+
.option("--current-path <path>", "Current path")
|
|
1738
|
+
.option("--pane-id <id>", "Current pane id")
|
|
1739
|
+
.action(async (opts) => {
|
|
1740
|
+
const code = await runTmuxSwitcher({
|
|
1741
|
+
projectRoot: pathResolve(opts.projectRoot),
|
|
1742
|
+
projectStateDir: pathResolve(opts.projectStateDir),
|
|
1743
|
+
currentClientSession: opts.currentClientSession,
|
|
1744
|
+
clientTty: opts.clientTty,
|
|
1745
|
+
currentWindow: opts.currentWindow,
|
|
1746
|
+
currentWindowId: opts.currentWindowId,
|
|
1747
|
+
currentPath: opts.currentPath,
|
|
1748
|
+
paneId: opts.paneId,
|
|
1749
|
+
});
|
|
1750
|
+
process.exit(code);
|
|
1751
|
+
});
|
|
1918
1752
|
program
|
|
1919
1753
|
.command("kill <sessionId>")
|
|
1920
1754
|
.description("Send an agent to the graveyard from running or offline state")
|