aimux-cli 0.1.9 → 0.1.11

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 (57) hide show
  1. package/dist/builtin-metadata-watchers.js +2 -103
  2. package/dist/builtin-metadata-watchers.js.map +1 -1
  3. package/dist/config.d.ts +12 -0
  4. package/dist/config.js +9 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/daemon.js +45 -10
  7. package/dist/daemon.js.map +1 -1
  8. package/dist/dashboard/targets.js +3 -1
  9. package/dist/dashboard/targets.js.map +1 -1
  10. package/dist/default-plugins/transcript-length.d.ts +6 -0
  11. package/dist/default-plugins/transcript-length.js +84 -0
  12. package/dist/default-plugins/transcript-length.js.map +1 -0
  13. package/dist/http-client.js +1 -0
  14. package/dist/http-client.js.map +1 -1
  15. package/dist/main.js +80 -83
  16. package/dist/main.js.map +1 -1
  17. package/dist/metadata-server.d.ts +1 -0
  18. package/dist/metadata-server.js +17 -0
  19. package/dist/metadata-server.js.map +1 -1
  20. package/dist/metadata-store.d.ts +10 -0
  21. package/dist/metadata-store.js +1 -1
  22. package/dist/metadata-store.js.map +1 -1
  23. package/dist/multiplexer/dashboard-interaction.js +0 -1
  24. package/dist/multiplexer/dashboard-interaction.js.map +1 -1
  25. package/dist/multiplexer/dashboard-model.js +43 -8
  26. package/dist/multiplexer/dashboard-model.js.map +1 -1
  27. package/dist/multiplexer/index.js +2 -24
  28. package/dist/multiplexer/index.js.map +1 -1
  29. package/dist/multiplexer/persistence-methods.js +5 -2
  30. package/dist/multiplexer/persistence-methods.js.map +1 -1
  31. package/dist/multiplexer/runtime-state.js +0 -1
  32. package/dist/multiplexer/runtime-state.js.map +1 -1
  33. package/dist/multiplexer/runtime-sync.js +0 -1
  34. package/dist/multiplexer/runtime-sync.js.map +1 -1
  35. package/dist/multiplexer/session-launch.js +13 -26
  36. package/dist/multiplexer/session-launch.js.map +1 -1
  37. package/dist/plugin-runtime.d.ts +11 -2
  38. package/dist/plugin-runtime.js +111 -42
  39. package/dist/plugin-runtime.js.map +1 -1
  40. package/dist/session-bootstrap.d.ts +7 -4
  41. package/dist/session-bootstrap.js +183 -112
  42. package/dist/session-bootstrap.js.map +1 -1
  43. package/dist/shell-hooks.js +37 -15
  44. package/dist/shell-hooks.js.map +1 -1
  45. package/dist/shell-state.d.ts +28 -0
  46. package/dist/shell-state.js +51 -0
  47. package/dist/shell-state.js.map +1 -0
  48. package/dist/statusline-model.d.ts +12 -0
  49. package/dist/statusline-model.js.map +1 -1
  50. package/dist/tmux/runtime-manager.js +9 -2
  51. package/dist/tmux/runtime-manager.js.map +1 -1
  52. package/dist/tmux/statusline.js +35 -4
  53. package/dist/tmux/statusline.js.map +1 -1
  54. package/dist/worktree.d.ts +1 -0
  55. package/dist/worktree.js +59 -35
  56. package/dist/worktree.js.map +1 -1
  57. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import { Command } from "commander";
2
- import { existsSync, readFileSync, writeFileSync, readdirSync, copyFileSync, mkdirSync, chmodSync } from "node:fs";
2
+ import { existsSync, readFileSync, writeFileSync, readdirSync, copyFileSync, mkdirSync, chmodSync, renameSync, } 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";
6
6
  import { Multiplexer } from "./multiplexer/index.js";
7
7
  import { llmCompact } from "./context/compactor.js";
8
- import { initProject, loadConfig } from "./config.js";
9
- import { initPaths, getHistoryDir, getGraveyardPath, getStatePath, getContextDir, getProjectId } from "./paths.js";
8
+ import { initProject } from "./config.js";
9
+ import { initPaths, getHistoryDir, getGraveyardPath, getStatePath, getContextDir, getProjectId, getProjectStateDirFor, } 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
13
  import { buildTmuxDoctorReport, renderTmuxDoctorReport, renderTmuxRepairResult, repairTmuxRuntime, } from "./tmux/doctor.js";
14
- import { loadMetadataEndpoint, loadMetadataState, resolveProjectServiceEndpoint as resolveStoredProjectServiceEndpoint, updateSessionMetadata, clearSessionLogs, removeMetadataEndpoint, } from "./metadata-store.js";
14
+ import { loadMetadataEndpoint, 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";
@@ -26,6 +26,7 @@ import { runTmuxSwitcher } from "./tmux/switcher.js";
26
26
  import { getDashboardCommandSpec } from "./dashboard/command-spec.js";
27
27
  import { findLiveDashboardTarget, openDashboardTarget, pruneDashboardArtifacts, resolveDashboardTarget, } from "./dashboard/targets.js";
28
28
  import { invalidateTmuxStatuslineArtifacts } from "./tmux/statusline-cache.js";
29
+ import { loadStatusline, renderTmuxStatuslineFromData } from "./tmux/statusline.js";
29
30
  const program = new Command();
30
31
  class ProjectServiceVersionError extends Error {
31
32
  projectRoot;
@@ -65,7 +66,9 @@ async function restartStaleControlPlane(projectRoot) {
65
66
  pruneDashboardArtifacts(projectRoot, dashboardBuildStamp, new TmuxRuntimeManager());
66
67
  }
67
68
  async function fetchProjectServiceHealth(endpoint) {
68
- const { status, json } = await requestJson(`http://${endpoint.host}:${endpoint.port}/health`);
69
+ const { status, json } = await requestJson(`http://${endpoint.host}:${endpoint.port}/health`, {
70
+ timeoutMs: 1000,
71
+ });
69
72
  if (status < 200 || status >= 300 || json?.ok === false) {
70
73
  throw new Error(json?.error || `health request failed: ${status}`);
71
74
  }
@@ -73,7 +76,9 @@ async function fetchProjectServiceHealth(endpoint) {
73
76
  }
74
77
  async function waitForVerifiedProjectService(projectRoot, opts) {
75
78
  const expected = getProjectServiceManifest();
76
- const deadline = Date.now() + (opts?.timeoutMs ?? 8000);
79
+ const timeoutMs = opts?.timeoutMs ?? 8000;
80
+ const startedAt = Date.now();
81
+ const deadline = startedAt + timeoutMs;
77
82
  let lastError = "project service did not become reachable";
78
83
  let lastServiceInfo = null;
79
84
  let respawnAttempted = false;
@@ -122,12 +127,53 @@ async function waitForVerifiedProjectService(projectRoot, opts) {
122
127
  typeof lastServiceInfo === "object") {
123
128
  throw new ProjectServiceVersionError(lastError, projectRoot, expected, lastServiceInfo);
124
129
  }
125
- throw new Error(`${lastError}${lastServiceInfo ? `; last serviceInfo=${JSON.stringify(lastServiceInfo)}` : ""}`);
130
+ const elapsedMs = Date.now() - startedAt;
131
+ const elapsedSeconds = (elapsedMs / 1000).toFixed(1);
132
+ throw new Error(`project service did not become ready after ${elapsedSeconds}s (budget ${timeoutMs}ms); last error: ${lastError}${lastServiceInfo ? `; last serviceInfo=${JSON.stringify(lastServiceInfo)}` : ""}`);
133
+ }
134
+ function rewriteLocalStatuslineArtifacts(projectRoot, tmux, dashboardSessionName) {
135
+ const data = loadStatusline(projectRoot);
136
+ if (!data)
137
+ return;
138
+ const statusDir = pathJoin(getProjectStateDirFor(projectRoot), "tmux-statusline");
139
+ mkdirSync(statusDir, { recursive: true });
140
+ const writeStatusFile = (name, content) => {
141
+ const filePath = pathJoin(statusDir, name);
142
+ const tmpPath = `${filePath}.tmp`;
143
+ writeFileSync(tmpPath, `${content}\n`);
144
+ renameSync(tmpPath, filePath);
145
+ };
146
+ const dashboardTop = renderTmuxStatuslineFromData(data, projectRoot, "top", {
147
+ currentWindow: "dashboard",
148
+ currentPath: projectRoot,
149
+ });
150
+ const dashboardBottom = renderTmuxStatuslineFromData(data, projectRoot, "bottom", {
151
+ currentWindow: "dashboard",
152
+ currentPath: projectRoot,
153
+ currentSession: dashboardSessionName,
154
+ });
155
+ writeStatusFile("top-dashboard.txt", dashboardTop);
156
+ writeStatusFile("bottom-dashboard.txt", dashboardBottom);
157
+ if (dashboardSessionName) {
158
+ writeStatusFile(`bottom-dashboard-${dashboardSessionName}.txt`, dashboardBottom);
159
+ }
160
+ for (const entry of data.sessions ?? []) {
161
+ if (!entry.tmuxWindowId)
162
+ continue;
163
+ const renderOptions = {
164
+ currentWindow: entry.windowName,
165
+ currentWindowId: entry.tmuxWindowId,
166
+ currentPath: entry.worktreePath ?? projectRoot,
167
+ };
168
+ writeStatusFile(`top-${entry.tmuxWindowId}.txt`, renderTmuxStatuslineFromData(data, projectRoot, "top", renderOptions));
169
+ writeStatusFile(`bottom-${entry.tmuxWindowId}.txt`, renderTmuxStatuslineFromData(data, projectRoot, "bottom", renderOptions));
170
+ }
171
+ tmux.refreshStatus();
126
172
  }
127
- async function postProjectServiceJson(path, body) {
173
+ async function postProjectServiceJson(path, body, options) {
128
174
  let endpoint = await resolveProjectServiceEndpoint();
129
175
  if (!endpoint) {
130
- await ensureProjectService(resolveProjectRoot(process.cwd()));
176
+ await ensureDaemonProjectReady(resolveProjectRoot(process.cwd()));
131
177
  endpoint = await resolveProjectServiceEndpoint();
132
178
  }
133
179
  if (!endpoint) {
@@ -137,6 +183,7 @@ async function postProjectServiceJson(path, body) {
137
183
  method: "POST",
138
184
  headers: { "content-type": "application/json" },
139
185
  body,
186
+ timeoutMs: options?.timeoutMs,
140
187
  });
141
188
  if (status < 200 || status >= 300 || json?.ok === false) {
142
189
  throw new Error(json?.error || `request failed: ${status}`);
@@ -146,7 +193,7 @@ async function postProjectServiceJson(path, body) {
146
193
  async function getProjectServiceJson(path) {
147
194
  let endpoint = await resolveProjectServiceEndpoint();
148
195
  if (!endpoint) {
149
- await ensureProjectService(resolveProjectRoot(process.cwd()));
196
+ await ensureDaemonProjectReady(resolveProjectRoot(process.cwd()));
150
197
  endpoint = await resolveProjectServiceEndpoint();
151
198
  }
152
199
  if (!endpoint) {
@@ -166,6 +213,9 @@ async function postProjectServiceJsonOrLocal(path, body, fallback) {
166
213
  return fallback();
167
214
  }
168
215
  }
216
+ function exitAfterOpen() {
217
+ process.exit(0);
218
+ }
169
219
  async function postLiveProjectServiceJsonOrLocal(projectRoot, path, body, fallback) {
170
220
  try {
171
221
  const endpoint = await resolveProjectServiceEndpoint(projectRoot);
@@ -228,7 +278,13 @@ async function ensureDaemonProjectReady(projectRoot, opts) {
228
278
  throw error;
229
279
  }
230
280
  await restartStaleControlPlane(projectRoot);
231
- await waitForVerifiedProjectService(projectRoot);
281
+ try {
282
+ await waitForVerifiedProjectService(projectRoot, { timeoutMs: 15_000 });
283
+ }
284
+ catch {
285
+ await ensureProjectService(projectRoot);
286
+ await waitForVerifiedProjectService(projectRoot, { timeoutMs: 15_000 });
287
+ }
232
288
  }
233
289
  }
234
290
  async function ensureDaemonProjectSpawned(projectRoot) {
@@ -340,13 +396,13 @@ program
340
396
  insideTmux: tmux.isInsideTmux(),
341
397
  alreadyResolved: true,
342
398
  });
343
- return;
399
+ exitAfterOpen();
344
400
  }
345
401
  }
346
- await ensureDaemonProjectSpawned(projectRoot);
402
+ await ensureDaemonProjectReady(projectRoot);
347
403
  if (!tool && !opts.resume && !opts.restore) {
348
404
  openDashboardTarget(projectRoot, tmux);
349
- return;
405
+ exitAfterOpen();
350
406
  }
351
407
  }
352
408
  const mux = new Multiplexer();
@@ -419,15 +475,19 @@ program
419
475
  try {
420
476
  const originalCwd = process.cwd();
421
477
  const projectRoot = resolveProjectRoot(originalCwd);
422
- await ensureDaemonProjectSpawned(projectRoot);
478
+ await ensureDaemonProjectReady(projectRoot);
423
479
  invalidateTmuxStatuslineArtifacts(projectRoot);
424
480
  const tmux = new TmuxRuntimeManager();
425
481
  ensureTmuxAvailable(tmux);
426
482
  const { dashboardSession, dashboardTarget } = resolveDashboardTarget(projectRoot, tmux, { forceReload: true });
427
- await postProjectServiceJson("/statusline/refresh", { force: true });
483
+ try {
484
+ await postProjectServiceJson("/statusline/refresh", { force: true }, { timeoutMs: 1500 });
485
+ }
486
+ catch { }
487
+ rewriteLocalStatuslineArtifacts(projectRoot, tmux, dashboardSession.sessionName);
428
488
  if (opts.open) {
429
489
  tmux.openTarget(dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
430
- return;
490
+ exitAfterOpen();
431
491
  }
432
492
  console.log(`Reloaded dashboard for ${dashboardSession.sessionName}`);
433
493
  }
@@ -499,7 +559,7 @@ program
499
559
  await initPaths(projectRoot);
500
560
  const result = await restartProjectRuntime(projectRoot, { open: opts.open });
501
561
  if (opts.open)
502
- return;
562
+ exitAfterOpen();
503
563
  if (opts.json) {
504
564
  console.log(JSON.stringify({
505
565
  ok: true,
@@ -1946,14 +2006,14 @@ repairCmd
1946
2006
  .action(async (opts) => {
1947
2007
  const projectRoot = resolveProjectRoot(opts.projectRoot);
1948
2008
  await initPaths(projectRoot);
1949
- await ensureDaemonProjectSpawned(projectRoot);
2009
+ await ensureDaemonProjectReady(projectRoot);
1950
2010
  const tmux = new TmuxRuntimeManager();
1951
2011
  ensureTmuxAvailable(tmux);
1952
2012
  const result = repairTmuxRuntime(tmux, { projectRoot });
1953
2013
  if (opts.open) {
1954
2014
  const { dashboardTarget } = resolveDashboardTarget(projectRoot, tmux);
1955
2015
  tmux.openTarget(dashboardTarget, { insideTmux: tmux.isInsideTmux(), alreadyResolved: true });
1956
- return;
2016
+ exitAfterOpen();
1957
2017
  }
1958
2018
  if (opts.json) {
1959
2019
  console.log(JSON.stringify(result, null, 2));
@@ -2149,69 +2209,6 @@ program
2149
2209
  }
2150
2210
  console.log("OK");
2151
2211
  });
2152
- program
2153
- .command("shell-hook <state>")
2154
- .description("Internal generic shell-state adapter modeled after cmux")
2155
- .requiredOption("--session <sessionId>", "Aimux session id")
2156
- .requiredOption("--project <path>", "Project path")
2157
- .option("--tool <tool>", "Tool label", "shell")
2158
- .option("--json", "Emit JSON output")
2159
- .action(async (state, opts) => {
2160
- const projectRoot = resolveProjectRoot(pathResolve(opts.project));
2161
- await initPaths(projectRoot);
2162
- const sessionId = opts.session.trim();
2163
- const tool = opts.tool?.trim() || "shell";
2164
- const previous = loadMetadataState(projectRoot).sessions[sessionId]?.derived;
2165
- const previousActivity = previous?.activity;
2166
- const result = { ok: true, state, sessionId, tool };
2167
- const setActivity = async (activity) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-activity", { session: sessionId, activity }, () => metadataTracker.setActivity(sessionId, activity, projectRoot));
2168
- const setAttention = async (attention) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-attention", { session: sessionId, attention }, () => metadataTracker.setAttention(sessionId, attention, projectRoot));
2169
- const clearSessionNotifications = async () => postLiveProjectServiceJsonOrLocal(projectRoot, "/notifications/clear", { sessionId }, () => ({
2170
- ok: true,
2171
- cleared: clearNotifications({ sessionId }),
2172
- }));
2173
- if (state === "running" || state === "command" || state === "busy") {
2174
- if (previousActivity !== "running") {
2175
- await clearSessionNotifications();
2176
- await setActivity("running");
2177
- await setAttention("normal");
2178
- await postLiveProjectServiceJsonOrLocal(projectRoot, "/mark-seen", { session: sessionId }, () => metadataTracker.markSeen(sessionId, projectRoot));
2179
- }
2180
- }
2181
- else if (state === "prompt" || state === "idle") {
2182
- if (previousActivity !== "idle") {
2183
- await setActivity("idle");
2184
- await setAttention("normal");
2185
- }
2186
- const config = loadConfig().notifications;
2187
- if (config.enabled && config.onComplete && previousActivity === "running") {
2188
- await postLiveProjectServiceJsonOrLocal(projectRoot, "/notify", {
2189
- title: tool,
2190
- subtitle: "Command complete",
2191
- message: "Shell returned to a prompt.",
2192
- sessionId,
2193
- kind: "task_done",
2194
- }, () => ({
2195
- ok: true,
2196
- notification: addNotification({
2197
- title: tool,
2198
- subtitle: "Command complete",
2199
- body: "Shell returned to a prompt.",
2200
- sessionId,
2201
- kind: "task_done",
2202
- }),
2203
- }));
2204
- }
2205
- }
2206
- else {
2207
- throw new Error(`Unsupported shell hook state: ${state}`);
2208
- }
2209
- if (opts.json) {
2210
- console.log(JSON.stringify(result));
2211
- return;
2212
- }
2213
- console.log("OK");
2214
- });
2215
2212
  program
2216
2213
  .command("list-notifications")
2217
2214
  .description("List project notifications")