aimux-cli 0.1.3 → 0.1.5

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/README.md +20 -19
  2. package/dist/agent-tracker.js +15 -13
  3. package/dist/agent-tracker.js.map +1 -1
  4. package/dist/context/context-bridge.js +8 -1
  5. package/dist/context/context-bridge.js.map +1 -1
  6. package/dist/daemon.js +8 -1
  7. package/dist/daemon.js.map +1 -1
  8. package/dist/dashboard-pending-actions.js +1 -1
  9. package/dist/dashboard-pending-actions.js.map +1 -1
  10. package/dist/dashboard-session-actions.d.ts +4 -4
  11. package/dist/dashboard-session-actions.js.map +1 -1
  12. package/dist/dashboard.d.ts +2 -2
  13. package/dist/key-parser.js +7 -0
  14. package/dist/key-parser.js.map +1 -1
  15. package/dist/main.js +248 -49
  16. package/dist/main.js.map +1 -1
  17. package/dist/metadata-server.d.ts +18 -0
  18. package/dist/metadata-server.js +23 -0
  19. package/dist/metadata-server.js.map +1 -1
  20. package/dist/multiplexer.d.ts +14 -0
  21. package/dist/multiplexer.js +329 -41
  22. package/dist/multiplexer.js.map +1 -1
  23. package/dist/notification-context.d.ts +1 -0
  24. package/dist/notification-context.js +18 -1
  25. package/dist/notification-context.js.map +1 -1
  26. package/dist/notify.js +19 -1
  27. package/dist/notify.js.map +1 -1
  28. package/dist/plugin-runtime.js +8 -2
  29. package/dist/plugin-runtime.js.map +1 -1
  30. package/dist/project-events.d.ts +2 -0
  31. package/dist/project-events.js +1 -0
  32. package/dist/project-events.js.map +1 -1
  33. package/dist/project-scanner.js +14 -3
  34. package/dist/project-scanner.js.map +1 -1
  35. package/dist/shell-hooks.d.ts +27 -0
  36. package/dist/shell-hooks.js +137 -0
  37. package/dist/shell-hooks.js.map +1 -0
  38. package/dist/tmux-doctor.d.ts +10 -0
  39. package/dist/tmux-doctor.js +62 -0
  40. package/dist/tmux-doctor.js.map +1 -1
  41. package/dist/tmux-runtime-manager.d.ts +10 -0
  42. package/dist/tmux-runtime-manager.js +106 -22
  43. package/dist/tmux-runtime-manager.js.map +1 -1
  44. package/dist/tmux-statusline.js +1 -1
  45. package/dist/tmux-statusline.js.map +1 -1
  46. package/dist/tool-output-watchers.js +128 -33
  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
@@ -5,13 +5,13 @@ import { homedir } from "node:os";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { Multiplexer } from "./multiplexer.js";
7
7
  import { llmCompact } from "./context/compactor.js";
8
- import { initProject } from "./config.js";
9
- import { initPaths, getHistoryDir, getGraveyardPath, getStatePath, getContextDir } from "./paths.js";
8
+ import { initProject, loadConfig } from "./config.js";
9
+ import { initPaths, getHistoryDir, getGraveyardPath, getStatePath, getContextDir, getProjectId } from "./paths.js";
10
10
  import { loadTeamConfig, saveTeamConfig, getDefaultTeamConfig } from "./team.js";
11
11
  import { createWorktree, findMainRepo, listWorktrees } from "./worktree.js";
12
12
  import { TmuxRuntimeManager } from "./tmux-runtime-manager.js";
13
- import { buildTmuxDoctorReport, renderTmuxDoctorReport } from "./tmux-doctor.js";
14
- import { loadMetadataEndpoint, resolveProjectServiceEndpoint as resolveStoredProjectServiceEndpoint, updateSessionMetadata, clearSessionLogs, removeMetadataEndpoint, } from "./metadata-store.js";
13
+ import { buildTmuxDoctorReport, renderTmuxDoctorReport, renderTmuxRepairResult, repairTmuxRuntime, } from "./tmux-doctor.js";
14
+ import { loadMetadataEndpoint, loadMetadataState, resolveProjectServiceEndpoint as resolveStoredProjectServiceEndpoint, updateSessionMetadata, clearSessionLogs, removeMetadataEndpoint, } from "./metadata-store.js";
15
15
  import { AgentTracker } from "./agent-tracker.js";
16
16
  import { AimuxDaemon, ensureDaemonRunning, ensureProjectService, loadDaemonInfo, loadDaemonState, projectServiceStatus, requestDaemonJson, stopDaemon, stopProjectService, } from "./daemon.js";
17
17
  import { getProjectServiceManifest, manifestsMatch } from "./project-service-manifest.js";
@@ -19,6 +19,7 @@ import { createThread, listThreadSummaries, markThreadSeen, readMessages, readTh
19
19
  import { sendDirectMessage, sendThreadMessage } from "./orchestration.js";
20
20
  import { acceptHandoff, approveReview, acceptTask, assignTask, blockTask, completeHandoff, completeTask, reopenTask, requestTaskChanges, sendHandoff, } from "./orchestration-actions.js";
21
21
  import { addNotification, clearNotifications, listNotifications, markNotificationsRead, unreadNotificationCount, } from "./notifications.js";
22
+ import { notifyAlert } from "./notify.js";
22
23
  import { parseClaudeHookPayload, summarizeClaudeNotification, summarizeClaudeStop } from "./claude-hooks.js";
23
24
  import { requestJson } from "./http-client.js";
24
25
  import { runTmuxSwitcher } from "./tmux-switcher.js";
@@ -37,7 +38,6 @@ class ProjectServiceVersionError extends Error {
37
38
  }
38
39
  }
39
40
  function renderProjectServiceVersionHelp(error) {
40
- const quotedProject = JSON.stringify(error.projectRoot);
41
41
  const lines = [
42
42
  "aimux: the running project service is from a different local build.",
43
43
  "",
@@ -45,11 +45,11 @@ function renderProjectServiceVersionHelp(error) {
45
45
  `Expected build: ${error.expected.buildStamp}`,
46
46
  `Running build: ${error.actual?.buildStamp ?? "unknown"}`,
47
47
  "",
48
- "Restart the daemon-managed control plane, then retry:",
49
- ` aimux daemon restart`,
50
- ` aimux daemon project-ensure --project ${quotedProject}`,
48
+ "Restart the project runtime, then retry:",
49
+ " aimux restart-runtime --open",
51
50
  "",
52
- "Or just restart the daemon and rerun `aimux` if you only changed this local checkout.",
51
+ "If the mismatch persists, use the advanced daemon restart path:",
52
+ " aimux daemon restart",
53
53
  ];
54
54
  return lines.join("\n");
55
55
  }
@@ -233,6 +233,51 @@ async function ensureDaemonProjectSpawned(projectRoot) {
233
233
  await ensureDaemonRunning();
234
234
  await ensureProjectService(projectRoot);
235
235
  }
236
+ function listManagedProjectSessionNames(tmux, projectRoot) {
237
+ const hostSession = tmux.getProjectSession(projectRoot).sessionName;
238
+ return tmux
239
+ .listSessionNames()
240
+ .filter((sessionName) => sessionName === hostSession || sessionName.startsWith(`${hostSession}-client-`))
241
+ .sort((a, b) => {
242
+ const aIsHost = a === hostSession ? 1 : 0;
243
+ const bIsHost = b === hostSession ? 1 : 0;
244
+ return aIsHost - bIsHost;
245
+ });
246
+ }
247
+ function stopProjectTmuxRuntime(tmux, projectRoot) {
248
+ const killed = [];
249
+ for (const sessionName of listManagedProjectSessionNames(tmux, projectRoot)) {
250
+ if (!tmux.hasSession(sessionName))
251
+ continue;
252
+ tmux.killSession(sessionName);
253
+ killed.push(sessionName);
254
+ }
255
+ return killed;
256
+ }
257
+ async function stopProjectRuntime(projectRoot) {
258
+ const projectService = await stopProjectService(projectRoot);
259
+ removeMetadataEndpoint(projectRoot);
260
+ const tmux = new TmuxRuntimeManager();
261
+ const tmuxSessionsKilled = tmux.isAvailable() ? stopProjectTmuxRuntime(tmux, projectRoot) : [];
262
+ return {
263
+ projectServiceStopped: Boolean(projectService),
264
+ tmuxSessionsKilled,
265
+ };
266
+ }
267
+ async function restartProjectRuntime(projectRoot, opts = {}) {
268
+ await stopProjectRuntime(projectRoot);
269
+ await ensureDaemonProjectSpawned(projectRoot);
270
+ const tmux = new TmuxRuntimeManager();
271
+ ensureTmuxAvailable(tmux);
272
+ const resolved = resolveDashboardTarget(projectRoot, tmux, { forceReload: true });
273
+ if (opts.open) {
274
+ tmux.openTarget(resolved.dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
275
+ }
276
+ return {
277
+ dashboardSessionName: resolved.dashboardSession.sessionName,
278
+ dashboardTarget: resolved.dashboardTarget,
279
+ };
280
+ }
236
281
  function resolveProjectRoot(cwd) {
237
282
  try {
238
283
  return findMainRepo(cwd);
@@ -280,7 +325,7 @@ program
280
325
  }
281
326
  await initPaths(projectRoot);
282
327
  if (opts.tmuxDashboardInternal) {
283
- await ensureDaemonProjectReady(projectRoot);
328
+ await ensureDaemonProjectSpawned(projectRoot);
284
329
  }
285
330
  else {
286
331
  initProject();
@@ -366,7 +411,7 @@ program
366
411
  });
367
412
  program
368
413
  .command("dashboard-reload")
369
- .description("Force reload the managed tmux dashboard for this project")
414
+ .description("Recreate and optionally reopen the dashboard window only")
370
415
  .option("--open", "Open the dashboard after reloading")
371
416
  .action(async (opts) => {
372
417
  try {
@@ -392,10 +437,93 @@ program
392
437
  process.exit(1);
393
438
  }
394
439
  });
395
- const hostCmd = program.command("host").description("Compatibility wrappers for daemon-managed project services");
440
+ program
441
+ .command("stop [sessionId]")
442
+ .description("Stop the current project runtime, or stop a specific running agent by session ID")
443
+ .option("--project <path>", "Project path")
444
+ .option("--json", "Emit JSON")
445
+ .action(async (sessionId, opts) => {
446
+ try {
447
+ if (sessionId) {
448
+ const projectRoot = await prepareProjectContext(opts.project);
449
+ const mux = new Multiplexer();
450
+ const result = await mux.stopAgent(sessionId);
451
+ if (opts.json) {
452
+ console.log(JSON.stringify({
453
+ ok: true,
454
+ projectRoot,
455
+ sessionId: result.sessionId,
456
+ status: result.status,
457
+ }, null, 2));
458
+ return;
459
+ }
460
+ console.log(`stopped ${result.sessionId}`);
461
+ return;
462
+ }
463
+ const projectRoot = resolveProjectRoot(opts.project ?? process.cwd());
464
+ await initPaths(projectRoot);
465
+ const result = await stopProjectRuntime(projectRoot);
466
+ if (opts.json) {
467
+ console.log(JSON.stringify({
468
+ ok: true,
469
+ projectRoot,
470
+ projectServiceStopped: result.projectServiceStopped,
471
+ tmuxSessionsKilled: result.tmuxSessionsKilled,
472
+ }, null, 2));
473
+ return;
474
+ }
475
+ console.log(`Stopped project runtime for ${projectRoot}`);
476
+ if (result.tmuxSessionsKilled.length > 0) {
477
+ console.log(`Removed tmux sessions: ${result.tmuxSessionsKilled.join(", ")}`);
478
+ }
479
+ }
480
+ catch (err) {
481
+ const msg = err instanceof Error ? err.message : String(err);
482
+ console.error(`Error: ${msg}`);
483
+ process.exit(1);
484
+ }
485
+ });
486
+ program
487
+ .command("restart-runtime")
488
+ .description("Hard restart the current project runtime and rebuild its managed tmux topology")
489
+ .option("--project-root <path>", "Project root", process.cwd())
490
+ .option("--open", "Open the dashboard after restarting the runtime")
491
+ .option("--json", "Emit JSON")
492
+ .action(async (opts) => {
493
+ try {
494
+ const projectRoot = resolveProjectRoot(opts.projectRoot);
495
+ await initPaths(projectRoot);
496
+ const result = await restartProjectRuntime(projectRoot, { open: opts.open });
497
+ if (opts.open)
498
+ return;
499
+ if (opts.json) {
500
+ console.log(JSON.stringify({
501
+ ok: true,
502
+ projectRoot,
503
+ dashboardSession: result.dashboardSessionName,
504
+ dashboardTarget: result.dashboardTarget,
505
+ }, null, 2));
506
+ return;
507
+ }
508
+ console.log(`Restarted project runtime for ${projectRoot}`);
509
+ console.log(`Dashboard: ${result.dashboardSessionName}:${result.dashboardTarget.windowIndex}`);
510
+ }
511
+ catch (err) {
512
+ if (err instanceof ProjectServiceVersionError) {
513
+ console.error(renderProjectServiceVersionHelp(err));
514
+ process.exit(1);
515
+ }
516
+ const msg = err instanceof Error ? err.message : String(err);
517
+ console.error(`Error: ${msg}`);
518
+ process.exit(1);
519
+ }
520
+ });
521
+ const hostCmd = program
522
+ .command("host")
523
+ .description("Advanced compatibility wrappers for legacy daemon-managed project services");
396
524
  program
397
525
  .command("serve")
398
- .description("Ensure the daemon-backed project control service is running")
526
+ .description("Advanced: ensure the legacy daemon-backed project control service is running")
399
527
  .action(async () => {
400
528
  const projectRoot = resolveProjectRoot(process.cwd());
401
529
  if (projectRoot !== process.cwd()) {
@@ -639,7 +767,7 @@ hostCmd
639
767
  hostCmd.action(() => {
640
768
  console.log("`aimux host` is a compatibility alias for daemon-managed project services.");
641
769
  });
642
- const daemonCmd = program.command("daemon").description("Manage the global aimux control-plane daemon");
770
+ const daemonCmd = program.command("daemon").description("Advanced: manage the global aimux control-plane daemon");
643
771
  daemonCmd
644
772
  .command("run")
645
773
  .description("Internal daemon entrypoint")
@@ -1667,33 +1795,6 @@ graveyardCmd
1667
1795
  process.exit(1);
1668
1796
  }
1669
1797
  });
1670
- program
1671
- .command("stop <sessionId>")
1672
- .description("Stop a running agent and move it to offline state")
1673
- .option("--project <path>", "Project path")
1674
- .option("--json", "Emit JSON")
1675
- .action(async (sessionId, opts) => {
1676
- try {
1677
- const projectRoot = await prepareProjectContext(opts.project);
1678
- const mux = new Multiplexer();
1679
- const result = await mux.stopAgent(sessionId);
1680
- if (opts.json) {
1681
- console.log(JSON.stringify({
1682
- ok: true,
1683
- projectRoot,
1684
- sessionId: result.sessionId,
1685
- status: result.status,
1686
- }, null, 2));
1687
- return;
1688
- }
1689
- console.log(`stopped ${result.sessionId}`);
1690
- }
1691
- catch (err) {
1692
- const msg = err instanceof Error ? err.message : String(err);
1693
- console.error(`Error: ${msg}`);
1694
- process.exit(1);
1695
- }
1696
- });
1697
1798
  program
1698
1799
  .command("rename <sessionId>")
1699
1800
  .description("Rename an agent label in running or offline state")
@@ -1808,10 +1909,11 @@ program
1808
1909
  });
1809
1910
  // ── Statusline commands ────────────────────────────────────────────
1810
1911
  const statuslineCmd = program.command("statusline").description("Manage Claude Code statusline integration");
1811
- const doctorCmd = program.command("doctor").description("Inspect aimux runtime compatibility");
1912
+ const doctorCmd = program.command("doctor").description("Inspect aimux runtime state");
1913
+ const repairCmd = program.command("repair").description("Repair the current project runtime in place");
1812
1914
  doctorCmd
1813
1915
  .command("tmux")
1814
- .description("Inspect managed tmux session compatibility state")
1916
+ .description("Inspect managed tmux runtime state")
1815
1917
  .option("--project-root <path>", "Project root", process.cwd())
1816
1918
  .option("--session <name>", "Managed tmux session name override")
1817
1919
  .option("--window-id <id>", "Specific tmux window id to inspect")
@@ -1830,6 +1932,28 @@ doctorCmd
1830
1932
  }
1831
1933
  console.log(renderTmuxDoctorReport(report));
1832
1934
  });
1935
+ repairCmd
1936
+ .option("--project-root <path>", "Project root", process.cwd())
1937
+ .option("--open", "Open the repaired dashboard after fixing runtime state")
1938
+ .option("--json", "Emit JSON")
1939
+ .action(async (opts) => {
1940
+ const projectRoot = resolveProjectRoot(opts.projectRoot);
1941
+ await initPaths(projectRoot);
1942
+ await ensureDaemonProjectSpawned(projectRoot);
1943
+ const tmux = new TmuxRuntimeManager();
1944
+ ensureTmuxAvailable(tmux);
1945
+ const result = repairTmuxRuntime(tmux, { projectRoot });
1946
+ if (opts.open) {
1947
+ const { dashboardTarget } = resolveDashboardTarget(projectRoot, tmux);
1948
+ tmux.openTarget(dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
1949
+ return;
1950
+ }
1951
+ if (opts.json) {
1952
+ console.log(JSON.stringify(result, null, 2));
1953
+ return;
1954
+ }
1955
+ console.log(renderTmuxRepairResult(result));
1956
+ });
1833
1957
  const metadataCmd = program.command("metadata").description("Push metadata into aimux tmux status integration");
1834
1958
  const metadataTracker = new AgentTracker();
1835
1959
  metadataCmd
@@ -1903,16 +2027,28 @@ program
1903
2027
  message: body,
1904
2028
  sessionId: opts.session?.trim() || undefined,
1905
2029
  kind: opts.kind?.trim() || "notification",
1906
- }, () => ({
1907
- ok: true,
1908
- notification: addNotification({
2030
+ force: true,
2031
+ }, () => {
2032
+ const kind = (opts.kind?.trim() || "notification");
2033
+ const notification = addNotification({
1909
2034
  title,
1910
2035
  subtitle: opts.subtitle?.trim() || undefined,
1911
2036
  body,
1912
2037
  sessionId: opts.session?.trim() || undefined,
1913
- kind: opts.kind?.trim() || "notification",
1914
- }),
1915
- }));
2038
+ kind,
2039
+ });
2040
+ notifyAlert({
2041
+ type: "alert",
2042
+ kind,
2043
+ projectId: getProjectId(),
2044
+ sessionId: opts.session?.trim() || undefined,
2045
+ title,
2046
+ message: [opts.subtitle?.trim(), body].filter(Boolean).join(" — "),
2047
+ ts: notification.createdAt,
2048
+ forceNotify: true,
2049
+ });
2050
+ return { ok: true, notification };
2051
+ });
1916
2052
  if (opts.json) {
1917
2053
  console.log(JSON.stringify(result));
1918
2054
  return;
@@ -2006,6 +2142,69 @@ program
2006
2142
  }
2007
2143
  console.log("OK");
2008
2144
  });
2145
+ program
2146
+ .command("shell-hook <state>")
2147
+ .description("Internal generic shell-state adapter modeled after cmux")
2148
+ .requiredOption("--session <sessionId>", "Aimux session id")
2149
+ .requiredOption("--project <path>", "Project path")
2150
+ .option("--tool <tool>", "Tool label", "shell")
2151
+ .option("--json", "Emit JSON output")
2152
+ .action(async (state, opts) => {
2153
+ const projectRoot = resolveProjectRoot(pathResolve(opts.project));
2154
+ await initPaths(projectRoot);
2155
+ const sessionId = opts.session.trim();
2156
+ const tool = opts.tool?.trim() || "shell";
2157
+ const previous = loadMetadataState(projectRoot).sessions[sessionId]?.derived;
2158
+ const previousActivity = previous?.activity;
2159
+ const result = { ok: true, state, sessionId, tool };
2160
+ const setActivity = async (activity) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-activity", { session: sessionId, activity }, () => metadataTracker.setActivity(sessionId, activity, projectRoot));
2161
+ const setAttention = async (attention) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-attention", { session: sessionId, attention }, () => metadataTracker.setAttention(sessionId, attention, projectRoot));
2162
+ const clearSessionNotifications = async () => postLiveProjectServiceJsonOrLocal(projectRoot, "/notifications/clear", { sessionId }, () => ({
2163
+ ok: true,
2164
+ cleared: clearNotifications({ sessionId }),
2165
+ }));
2166
+ if (state === "running" || state === "command" || state === "busy") {
2167
+ if (previousActivity !== "running") {
2168
+ await clearSessionNotifications();
2169
+ await setActivity("running");
2170
+ await setAttention("normal");
2171
+ await postLiveProjectServiceJsonOrLocal(projectRoot, "/mark-seen", { session: sessionId }, () => metadataTracker.markSeen(sessionId, projectRoot));
2172
+ }
2173
+ }
2174
+ else if (state === "prompt" || state === "idle") {
2175
+ if (previousActivity !== "idle") {
2176
+ await setActivity("idle");
2177
+ await setAttention("normal");
2178
+ }
2179
+ const config = loadConfig().notifications;
2180
+ if (config.enabled && config.onComplete && previousActivity === "running") {
2181
+ await postLiveProjectServiceJsonOrLocal(projectRoot, "/notify", {
2182
+ title: tool,
2183
+ subtitle: "Command complete",
2184
+ message: "Shell returned to a prompt.",
2185
+ sessionId,
2186
+ kind: "task_done",
2187
+ }, () => ({
2188
+ ok: true,
2189
+ notification: addNotification({
2190
+ title: tool,
2191
+ subtitle: "Command complete",
2192
+ body: "Shell returned to a prompt.",
2193
+ sessionId,
2194
+ kind: "task_done",
2195
+ }),
2196
+ }));
2197
+ }
2198
+ }
2199
+ else {
2200
+ throw new Error(`Unsupported shell hook state: ${state}`);
2201
+ }
2202
+ if (opts.json) {
2203
+ console.log(JSON.stringify(result));
2204
+ return;
2205
+ }
2206
+ console.log("OK");
2207
+ });
2009
2208
  program
2010
2209
  .command("list-notifications")
2011
2210
  .description("List project notifications")