aimux-cli 0.1.2 → 0.1.4

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 (45) 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 +227 -41
  16. package/dist/main.js.map +1 -1
  17. package/dist/metadata-server.d.ts +18 -0
  18. package/dist/metadata-server.js +22 -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 +12 -0
  25. package/dist/notification-context.js.map +1 -1
  26. package/dist/plugin-runtime.js +8 -2
  27. package/dist/plugin-runtime.js.map +1 -1
  28. package/dist/project-scanner.js +14 -3
  29. package/dist/project-scanner.js.map +1 -1
  30. package/dist/shell-hooks.d.ts +27 -0
  31. package/dist/shell-hooks.js +137 -0
  32. package/dist/shell-hooks.js.map +1 -0
  33. package/dist/tmux-doctor.d.ts +10 -0
  34. package/dist/tmux-doctor.js +62 -0
  35. package/dist/tmux-doctor.js.map +1 -1
  36. package/dist/tmux-runtime-manager.d.ts +8 -0
  37. package/dist/tmux-runtime-manager.js +51 -12
  38. package/dist/tmux-runtime-manager.js.map +1 -1
  39. package/dist/tmux-statusline.js +1 -1
  40. package/dist/tmux-statusline.js.map +1 -1
  41. package/dist/tool-output-watchers.js +128 -33
  42. package/dist/tool-output-watchers.js.map +1 -1
  43. package/dist/tui/screens/dashboard-renderers.js +2 -1
  44. package/dist/tui/screens/dashboard-renderers.js.map +1 -1
  45. 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";
8
+ import { initProject, loadConfig } from "./config.js";
9
9
  import { initPaths, getHistoryDir, getGraveyardPath, getStatePath, getContextDir } 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";
@@ -37,7 +37,6 @@ class ProjectServiceVersionError extends Error {
37
37
  }
38
38
  }
39
39
  function renderProjectServiceVersionHelp(error) {
40
- const quotedProject = JSON.stringify(error.projectRoot);
41
40
  const lines = [
42
41
  "aimux: the running project service is from a different local build.",
43
42
  "",
@@ -45,11 +44,11 @@ function renderProjectServiceVersionHelp(error) {
45
44
  `Expected build: ${error.expected.buildStamp}`,
46
45
  `Running build: ${error.actual?.buildStamp ?? "unknown"}`,
47
46
  "",
48
- "Restart the daemon-managed control plane, then retry:",
49
- ` aimux daemon restart`,
50
- ` aimux daemon project-ensure --project ${quotedProject}`,
47
+ "Restart the project runtime, then retry:",
48
+ " aimux restart-runtime --open",
51
49
  "",
52
- "Or just restart the daemon and rerun `aimux` if you only changed this local checkout.",
50
+ "If the mismatch persists, use the advanced daemon restart path:",
51
+ " aimux daemon restart",
53
52
  ];
54
53
  return lines.join("\n");
55
54
  }
@@ -233,6 +232,51 @@ async function ensureDaemonProjectSpawned(projectRoot) {
233
232
  await ensureDaemonRunning();
234
233
  await ensureProjectService(projectRoot);
235
234
  }
235
+ function listManagedProjectSessionNames(tmux, projectRoot) {
236
+ const hostSession = tmux.getProjectSession(projectRoot).sessionName;
237
+ return tmux
238
+ .listSessionNames()
239
+ .filter((sessionName) => sessionName === hostSession || sessionName.startsWith(`${hostSession}-client-`))
240
+ .sort((a, b) => {
241
+ const aIsHost = a === hostSession ? 1 : 0;
242
+ const bIsHost = b === hostSession ? 1 : 0;
243
+ return aIsHost - bIsHost;
244
+ });
245
+ }
246
+ function stopProjectTmuxRuntime(tmux, projectRoot) {
247
+ const killed = [];
248
+ for (const sessionName of listManagedProjectSessionNames(tmux, projectRoot)) {
249
+ if (!tmux.hasSession(sessionName))
250
+ continue;
251
+ tmux.killSession(sessionName);
252
+ killed.push(sessionName);
253
+ }
254
+ return killed;
255
+ }
256
+ async function stopProjectRuntime(projectRoot) {
257
+ const projectService = await stopProjectService(projectRoot);
258
+ removeMetadataEndpoint(projectRoot);
259
+ const tmux = new TmuxRuntimeManager();
260
+ const tmuxSessionsKilled = tmux.isAvailable() ? stopProjectTmuxRuntime(tmux, projectRoot) : [];
261
+ return {
262
+ projectServiceStopped: Boolean(projectService),
263
+ tmuxSessionsKilled,
264
+ };
265
+ }
266
+ async function restartProjectRuntime(projectRoot, opts = {}) {
267
+ await stopProjectRuntime(projectRoot);
268
+ await ensureDaemonProjectSpawned(projectRoot);
269
+ const tmux = new TmuxRuntimeManager();
270
+ ensureTmuxAvailable(tmux);
271
+ const resolved = resolveDashboardTarget(projectRoot, tmux, { forceReload: true });
272
+ if (opts.open) {
273
+ tmux.openTarget(resolved.dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
274
+ }
275
+ return {
276
+ dashboardSessionName: resolved.dashboardSession.sessionName,
277
+ dashboardTarget: resolved.dashboardTarget,
278
+ };
279
+ }
236
280
  function resolveProjectRoot(cwd) {
237
281
  try {
238
282
  return findMainRepo(cwd);
@@ -366,7 +410,7 @@ program
366
410
  });
367
411
  program
368
412
  .command("dashboard-reload")
369
- .description("Force reload the managed tmux dashboard for this project")
413
+ .description("Recreate and optionally reopen the dashboard window only")
370
414
  .option("--open", "Open the dashboard after reloading")
371
415
  .action(async (opts) => {
372
416
  try {
@@ -392,10 +436,93 @@ program
392
436
  process.exit(1);
393
437
  }
394
438
  });
395
- const hostCmd = program.command("host").description("Compatibility wrappers for daemon-managed project services");
439
+ program
440
+ .command("stop [sessionId]")
441
+ .description("Stop the current project runtime, or stop a specific running agent by session ID")
442
+ .option("--project <path>", "Project path")
443
+ .option("--json", "Emit JSON")
444
+ .action(async (sessionId, opts) => {
445
+ try {
446
+ if (sessionId) {
447
+ const projectRoot = await prepareProjectContext(opts.project);
448
+ const mux = new Multiplexer();
449
+ const result = await mux.stopAgent(sessionId);
450
+ if (opts.json) {
451
+ console.log(JSON.stringify({
452
+ ok: true,
453
+ projectRoot,
454
+ sessionId: result.sessionId,
455
+ status: result.status,
456
+ }, null, 2));
457
+ return;
458
+ }
459
+ console.log(`stopped ${result.sessionId}`);
460
+ return;
461
+ }
462
+ const projectRoot = resolveProjectRoot(opts.project ?? process.cwd());
463
+ await initPaths(projectRoot);
464
+ const result = await stopProjectRuntime(projectRoot);
465
+ if (opts.json) {
466
+ console.log(JSON.stringify({
467
+ ok: true,
468
+ projectRoot,
469
+ projectServiceStopped: result.projectServiceStopped,
470
+ tmuxSessionsKilled: result.tmuxSessionsKilled,
471
+ }, null, 2));
472
+ return;
473
+ }
474
+ console.log(`Stopped project runtime for ${projectRoot}`);
475
+ if (result.tmuxSessionsKilled.length > 0) {
476
+ console.log(`Removed tmux sessions: ${result.tmuxSessionsKilled.join(", ")}`);
477
+ }
478
+ }
479
+ catch (err) {
480
+ const msg = err instanceof Error ? err.message : String(err);
481
+ console.error(`Error: ${msg}`);
482
+ process.exit(1);
483
+ }
484
+ });
485
+ program
486
+ .command("restart-runtime")
487
+ .description("Hard restart the current project runtime and rebuild its managed tmux topology")
488
+ .option("--project-root <path>", "Project root", process.cwd())
489
+ .option("--open", "Open the dashboard after restarting the runtime")
490
+ .option("--json", "Emit JSON")
491
+ .action(async (opts) => {
492
+ try {
493
+ const projectRoot = resolveProjectRoot(opts.projectRoot);
494
+ await initPaths(projectRoot);
495
+ const result = await restartProjectRuntime(projectRoot, { open: opts.open });
496
+ if (opts.open)
497
+ return;
498
+ if (opts.json) {
499
+ console.log(JSON.stringify({
500
+ ok: true,
501
+ projectRoot,
502
+ dashboardSession: result.dashboardSessionName,
503
+ dashboardTarget: result.dashboardTarget,
504
+ }, null, 2));
505
+ return;
506
+ }
507
+ console.log(`Restarted project runtime for ${projectRoot}`);
508
+ console.log(`Dashboard: ${result.dashboardSessionName}:${result.dashboardTarget.windowIndex}`);
509
+ }
510
+ catch (err) {
511
+ if (err instanceof ProjectServiceVersionError) {
512
+ console.error(renderProjectServiceVersionHelp(err));
513
+ process.exit(1);
514
+ }
515
+ const msg = err instanceof Error ? err.message : String(err);
516
+ console.error(`Error: ${msg}`);
517
+ process.exit(1);
518
+ }
519
+ });
520
+ const hostCmd = program
521
+ .command("host")
522
+ .description("Advanced compatibility wrappers for legacy daemon-managed project services");
396
523
  program
397
524
  .command("serve")
398
- .description("Ensure the daemon-backed project control service is running")
525
+ .description("Advanced: ensure the legacy daemon-backed project control service is running")
399
526
  .action(async () => {
400
527
  const projectRoot = resolveProjectRoot(process.cwd());
401
528
  if (projectRoot !== process.cwd()) {
@@ -639,7 +766,7 @@ hostCmd
639
766
  hostCmd.action(() => {
640
767
  console.log("`aimux host` is a compatibility alias for daemon-managed project services.");
641
768
  });
642
- const daemonCmd = program.command("daemon").description("Manage the global aimux control-plane daemon");
769
+ const daemonCmd = program.command("daemon").description("Advanced: manage the global aimux control-plane daemon");
643
770
  daemonCmd
644
771
  .command("run")
645
772
  .description("Internal daemon entrypoint")
@@ -1667,33 +1794,6 @@ graveyardCmd
1667
1794
  process.exit(1);
1668
1795
  }
1669
1796
  });
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
1797
  program
1698
1798
  .command("rename <sessionId>")
1699
1799
  .description("Rename an agent label in running or offline state")
@@ -1808,10 +1908,11 @@ program
1808
1908
  });
1809
1909
  // ── Statusline commands ────────────────────────────────────────────
1810
1910
  const statuslineCmd = program.command("statusline").description("Manage Claude Code statusline integration");
1811
- const doctorCmd = program.command("doctor").description("Inspect aimux runtime compatibility");
1911
+ const doctorCmd = program.command("doctor").description("Inspect aimux runtime state");
1912
+ const repairCmd = program.command("repair").description("Repair the current project runtime in place");
1812
1913
  doctorCmd
1813
1914
  .command("tmux")
1814
- .description("Inspect managed tmux session compatibility state")
1915
+ .description("Inspect managed tmux runtime state")
1815
1916
  .option("--project-root <path>", "Project root", process.cwd())
1816
1917
  .option("--session <name>", "Managed tmux session name override")
1817
1918
  .option("--window-id <id>", "Specific tmux window id to inspect")
@@ -1830,6 +1931,28 @@ doctorCmd
1830
1931
  }
1831
1932
  console.log(renderTmuxDoctorReport(report));
1832
1933
  });
1934
+ repairCmd
1935
+ .option("--project-root <path>", "Project root", process.cwd())
1936
+ .option("--open", "Open the repaired dashboard after fixing runtime state")
1937
+ .option("--json", "Emit JSON")
1938
+ .action(async (opts) => {
1939
+ const projectRoot = resolveProjectRoot(opts.projectRoot);
1940
+ await initPaths(projectRoot);
1941
+ await ensureDaemonProjectSpawned(projectRoot);
1942
+ const tmux = new TmuxRuntimeManager();
1943
+ ensureTmuxAvailable(tmux);
1944
+ const result = repairTmuxRuntime(tmux, { projectRoot });
1945
+ if (opts.open) {
1946
+ const { dashboardTarget } = resolveDashboardTarget(projectRoot, tmux);
1947
+ tmux.openTarget(dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
1948
+ return;
1949
+ }
1950
+ if (opts.json) {
1951
+ console.log(JSON.stringify(result, null, 2));
1952
+ return;
1953
+ }
1954
+ console.log(renderTmuxRepairResult(result));
1955
+ });
1833
1956
  const metadataCmd = program.command("metadata").description("Push metadata into aimux tmux status integration");
1834
1957
  const metadataTracker = new AgentTracker();
1835
1958
  metadataCmd
@@ -2006,6 +2129,69 @@ program
2006
2129
  }
2007
2130
  console.log("OK");
2008
2131
  });
2132
+ program
2133
+ .command("shell-hook <state>")
2134
+ .description("Internal generic shell-state adapter modeled after cmux")
2135
+ .requiredOption("--session <sessionId>", "Aimux session id")
2136
+ .requiredOption("--project <path>", "Project path")
2137
+ .option("--tool <tool>", "Tool label", "shell")
2138
+ .option("--json", "Emit JSON output")
2139
+ .action(async (state, opts) => {
2140
+ const projectRoot = resolveProjectRoot(pathResolve(opts.project));
2141
+ await initPaths(projectRoot);
2142
+ const sessionId = opts.session.trim();
2143
+ const tool = opts.tool?.trim() || "shell";
2144
+ const previous = loadMetadataState(projectRoot).sessions[sessionId]?.derived;
2145
+ const previousActivity = previous?.activity;
2146
+ const result = { ok: true, state, sessionId, tool };
2147
+ const setActivity = async (activity) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-activity", { session: sessionId, activity }, () => metadataTracker.setActivity(sessionId, activity, projectRoot));
2148
+ const setAttention = async (attention) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-attention", { session: sessionId, attention }, () => metadataTracker.setAttention(sessionId, attention, projectRoot));
2149
+ const clearSessionNotifications = async () => postLiveProjectServiceJsonOrLocal(projectRoot, "/notifications/clear", { sessionId }, () => ({
2150
+ ok: true,
2151
+ cleared: clearNotifications({ sessionId }),
2152
+ }));
2153
+ if (state === "running" || state === "command" || state === "busy") {
2154
+ if (previousActivity !== "running") {
2155
+ await clearSessionNotifications();
2156
+ await setActivity("running");
2157
+ await setAttention("normal");
2158
+ await postLiveProjectServiceJsonOrLocal(projectRoot, "/mark-seen", { session: sessionId }, () => metadataTracker.markSeen(sessionId, projectRoot));
2159
+ }
2160
+ }
2161
+ else if (state === "prompt" || state === "idle") {
2162
+ if (previousActivity !== "idle") {
2163
+ await setActivity("idle");
2164
+ await setAttention("normal");
2165
+ }
2166
+ const config = loadConfig().notifications;
2167
+ if (config.enabled && config.onComplete && previousActivity === "running") {
2168
+ await postLiveProjectServiceJsonOrLocal(projectRoot, "/notify", {
2169
+ title: tool,
2170
+ subtitle: "Command complete",
2171
+ message: "Shell returned to a prompt.",
2172
+ sessionId,
2173
+ kind: "task_done",
2174
+ }, () => ({
2175
+ ok: true,
2176
+ notification: addNotification({
2177
+ title: tool,
2178
+ subtitle: "Command complete",
2179
+ body: "Shell returned to a prompt.",
2180
+ sessionId,
2181
+ kind: "task_done",
2182
+ }),
2183
+ }));
2184
+ }
2185
+ }
2186
+ else {
2187
+ throw new Error(`Unsupported shell hook state: ${state}`);
2188
+ }
2189
+ if (opts.json) {
2190
+ console.log(JSON.stringify(result));
2191
+ return;
2192
+ }
2193
+ console.log("OK");
2194
+ });
2009
2195
  program
2010
2196
  .command("list-notifications")
2011
2197
  .description("List project notifications")