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.
Files changed (50) hide show
  1. package/dist/daemon.js +35 -3
  2. package/dist/daemon.js.map +1 -1
  3. package/dist/dashboard-feedback.d.ts +38 -0
  4. package/dist/dashboard-feedback.js +113 -0
  5. package/dist/dashboard-feedback.js.map +1 -0
  6. package/dist/dashboard-pending-actions.d.ts +12 -0
  7. package/dist/dashboard-pending-actions.js +52 -0
  8. package/dist/dashboard-pending-actions.js.map +1 -0
  9. package/dist/dashboard-session-actions.d.ts +29 -0
  10. package/dist/dashboard-session-actions.js +82 -0
  11. package/dist/dashboard-session-actions.js.map +1 -0
  12. package/dist/dashboard-targets.d.ts +18 -0
  13. package/dist/dashboard-targets.js +236 -0
  14. package/dist/dashboard-targets.js.map +1 -0
  15. package/dist/dashboard-ui-state-store.d.ts +12 -0
  16. package/dist/dashboard-ui-state-store.js +106 -0
  17. package/dist/dashboard-ui-state-store.js.map +1 -0
  18. package/dist/dashboard.d.ts +3 -1
  19. package/dist/dashboard.js +4 -0
  20. package/dist/dashboard.js.map +1 -1
  21. package/dist/main.js +41 -207
  22. package/dist/main.js.map +1 -1
  23. package/dist/metadata-server.js +6 -77
  24. package/dist/metadata-server.js.map +1 -1
  25. package/dist/multiplexer-runtime-sync.d.ts +26 -0
  26. package/dist/multiplexer-runtime-sync.js +58 -0
  27. package/dist/multiplexer-runtime-sync.js.map +1 -0
  28. package/dist/multiplexer.d.ts +25 -10
  29. package/dist/multiplexer.js +353 -274
  30. package/dist/multiplexer.js.map +1 -1
  31. package/dist/paths.d.ts +1 -0
  32. package/dist/paths.js +3 -0
  33. package/dist/paths.js.map +1 -1
  34. package/dist/statusline-model.js +1 -0
  35. package/dist/statusline-model.js.map +1 -1
  36. package/dist/tmux-runtime-manager.d.ts +0 -8
  37. package/dist/tmux-runtime-manager.js +34 -36
  38. package/dist/tmux-runtime-manager.js.map +1 -1
  39. package/dist/tmux-switcher.d.ts +11 -0
  40. package/dist/tmux-switcher.js +115 -0
  41. package/dist/tmux-switcher.js.map +1 -0
  42. package/dist/tmux-window-open.d.ts +9 -0
  43. package/dist/tmux-window-open.js +71 -0
  44. package/dist/tmux-window-open.js.map +1 -0
  45. package/dist/tool-output-watchers.d.ts +14 -1
  46. package/dist/tool-output-watchers.js +18 -7
  47. package/dist/tool-output-watchers.js.map +1 -1
  48. package/dist/tui/screens/dashboard-renderers.js +2 -1
  49. package/dist/tui/screens/dashboard-renderers.js.map +1 -1
  50. 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, statSync, } from "node:fs";
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 (!(error instanceof ProjectServiceVersionError) || opts?.repairVersionDrift === false) {
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 = getLiveDashboardTarget(projectRoot, tmux);
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
- tmux.openTarget(dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
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 } = forceReloadDashboardTarget(projectRoot, tmux);
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 } = forceReloadDashboardTarget(projectRoot, tmux);
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
- const mux = new Multiplexer();
1900
- const result = await mux.renameAgent(sessionId, opts.label);
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")