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.
- package/README.md +20 -19
- package/dist/agent-tracker.js +15 -13
- package/dist/agent-tracker.js.map +1 -1
- package/dist/context/context-bridge.js +8 -1
- package/dist/context/context-bridge.js.map +1 -1
- package/dist/daemon.js +8 -1
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-pending-actions.js +1 -1
- package/dist/dashboard-pending-actions.js.map +1 -1
- package/dist/dashboard-session-actions.d.ts +4 -4
- package/dist/dashboard-session-actions.js.map +1 -1
- package/dist/dashboard.d.ts +2 -2
- package/dist/key-parser.js +7 -0
- package/dist/key-parser.js.map +1 -1
- package/dist/main.js +248 -49
- package/dist/main.js.map +1 -1
- package/dist/metadata-server.d.ts +18 -0
- package/dist/metadata-server.js +23 -0
- package/dist/metadata-server.js.map +1 -1
- package/dist/multiplexer.d.ts +14 -0
- package/dist/multiplexer.js +329 -41
- package/dist/multiplexer.js.map +1 -1
- package/dist/notification-context.d.ts +1 -0
- package/dist/notification-context.js +18 -1
- package/dist/notification-context.js.map +1 -1
- package/dist/notify.js +19 -1
- package/dist/notify.js.map +1 -1
- package/dist/plugin-runtime.js +8 -2
- package/dist/plugin-runtime.js.map +1 -1
- package/dist/project-events.d.ts +2 -0
- package/dist/project-events.js +1 -0
- package/dist/project-events.js.map +1 -1
- package/dist/project-scanner.js +14 -3
- package/dist/project-scanner.js.map +1 -1
- package/dist/shell-hooks.d.ts +27 -0
- package/dist/shell-hooks.js +137 -0
- package/dist/shell-hooks.js.map +1 -0
- package/dist/tmux-doctor.d.ts +10 -0
- package/dist/tmux-doctor.js +62 -0
- package/dist/tmux-doctor.js.map +1 -1
- package/dist/tmux-runtime-manager.d.ts +10 -0
- package/dist/tmux-runtime-manager.js +106 -22
- package/dist/tmux-runtime-manager.js.map +1 -1
- package/dist/tmux-statusline.js +1 -1
- package/dist/tmux-statusline.js.map +1 -1
- package/dist/tool-output-watchers.js +128 -33
- 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
|
@@ -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
|
|
49
|
-
|
|
50
|
-
` aimux daemon project-ensure --project ${quotedProject}`,
|
|
48
|
+
"Restart the project runtime, then retry:",
|
|
49
|
+
" aimux restart-runtime --open",
|
|
51
50
|
"",
|
|
52
|
-
"
|
|
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
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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
|
|
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
|
|
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
|
-
|
|
1908
|
-
|
|
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
|
|
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")
|