aimux-cli 0.1.16 → 0.1.18
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 +184 -67
- package/bin/aimux-dev +10 -0
- package/dist/alert-display.d.ts +21 -0
- package/dist/alert-display.js +86 -0
- package/dist/alert-display.js.map +1 -0
- package/dist/attachment-store.d.ts +0 -7
- package/dist/attachment-store.js +2 -86
- package/dist/attachment-store.js.map +1 -1
- package/dist/builtin-metadata-watchers.js +4 -4
- package/dist/builtin-metadata-watchers.js.map +1 -1
- package/dist/claude-hooks.d.ts +1 -0
- package/dist/claude-hooks.js +25 -0
- package/dist/claude-hooks.js.map +1 -1
- package/dist/config.d.ts +19 -13
- package/dist/config.js +28 -14
- package/dist/config.js.map +1 -1
- package/dist/connection-targets.d.ts +8 -0
- package/dist/connection-targets.js +28 -0
- package/dist/connection-targets.js.map +1 -0
- package/dist/credentials.d.ts +12 -0
- package/dist/credentials.js +49 -0
- package/dist/credentials.js.map +1 -0
- package/dist/daemon.d.ts +23 -0
- package/dist/daemon.js +391 -66
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/index.d.ts +13 -10
- package/dist/dashboard/index.js +3 -26
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/order.d.ts +22 -0
- package/dist/dashboard/order.js +55 -0
- package/dist/dashboard/order.js.map +1 -0
- package/dist/dashboard/pending-actions.d.ts +39 -10
- package/dist/dashboard/pending-actions.js +166 -36
- package/dist/dashboard/pending-actions.js.map +1 -1
- package/dist/dashboard/quick-jump.d.ts +2 -1
- package/dist/dashboard/quick-jump.js +7 -4
- package/dist/dashboard/quick-jump.js.map +1 -1
- package/dist/dashboard/session-actions.d.ts +4 -4
- package/dist/dashboard/session-actions.js +1 -1
- package/dist/dashboard/session-actions.js.map +1 -1
- package/dist/dashboard/session-registry.d.ts +4 -3
- package/dist/dashboard/session-registry.js +16 -50
- package/dist/dashboard/session-registry.js.map +1 -1
- package/dist/dashboard/state.d.ts +1 -1
- package/dist/dashboard/state.js.map +1 -1
- package/dist/dashboard/ui-state-store.d.ts +16 -1
- package/dist/dashboard/ui-state-store.js +73 -2
- package/dist/dashboard/ui-state-store.js.map +1 -1
- package/dist/debug-state.d.ts +97 -0
- package/dist/debug-state.js +541 -0
- package/dist/debug-state.js.map +1 -0
- package/dist/debug.d.ts +38 -0
- package/dist/debug.js +219 -15
- package/dist/debug.js.map +1 -1
- package/dist/default-plugins/gh-pr-context.d.ts +2 -1
- package/dist/default-plugins/gh-pr-context.js +17 -11
- package/dist/default-plugins/gh-pr-context.js.map +1 -1
- package/dist/default-plugins/transcript-length.js +15 -2
- package/dist/default-plugins/transcript-length.js.map +1 -1
- package/dist/fast-control.js +37 -19
- package/dist/fast-control.js.map +1 -1
- package/dist/http-client.js +31 -2
- package/dist/http-client.js.map +1 -1
- package/dist/local-ui-server.d.ts +22 -0
- package/dist/local-ui-server.js +186 -0
- package/dist/local-ui-server.js.map +1 -0
- package/dist/login-flow.d.ts +7 -0
- package/dist/login-flow.js +120 -0
- package/dist/login-flow.js.map +1 -0
- package/dist/main.js +821 -151
- package/dist/main.js.map +1 -1
- package/dist/managed-launch-env.js +14 -0
- package/dist/managed-launch-env.js.map +1 -1
- package/dist/metadata-server.d.ts +36 -36
- package/dist/metadata-server.js +638 -137
- package/dist/metadata-server.js.map +1 -1
- package/dist/metadata-store.d.ts +4 -1
- package/dist/metadata-store.js +30 -2
- package/dist/metadata-store.js.map +1 -1
- package/dist/multiplexer/agent-io-methods.d.ts +2 -10
- package/dist/multiplexer/agent-io-methods.js +12 -43
- package/dist/multiplexer/agent-io-methods.js.map +1 -1
- package/dist/multiplexer/archives.js +8 -9
- package/dist/multiplexer/archives.js.map +1 -1
- package/dist/multiplexer/dashboard-control.js +45 -13
- package/dist/multiplexer/dashboard-control.js.map +1 -1
- package/dist/multiplexer/dashboard-interaction.d.ts +8 -2
- package/dist/multiplexer/dashboard-interaction.js +187 -28
- package/dist/multiplexer/dashboard-interaction.js.map +1 -1
- package/dist/multiplexer/dashboard-model.d.ts +10 -3
- package/dist/multiplexer/dashboard-model.js +417 -35
- package/dist/multiplexer/dashboard-model.js.map +1 -1
- package/dist/multiplexer/dashboard-ops.d.ts +9 -7
- package/dist/multiplexer/dashboard-ops.js +178 -68
- package/dist/multiplexer/dashboard-ops.js.map +1 -1
- package/dist/multiplexer/dashboard-state-methods.d.ts +2 -1
- package/dist/multiplexer/dashboard-state-methods.js +3 -2
- package/dist/multiplexer/dashboard-state-methods.js.map +1 -1
- package/dist/multiplexer/dashboard-tail-methods.d.ts +22 -10
- package/dist/multiplexer/dashboard-tail-methods.js +164 -47
- package/dist/multiplexer/dashboard-tail-methods.js.map +1 -1
- package/dist/multiplexer/dashboard-view-methods.d.ts +1 -1
- package/dist/multiplexer/dashboard-view-methods.js +23 -8
- package/dist/multiplexer/dashboard-view-methods.js.map +1 -1
- package/dist/multiplexer/graveyard-view-model.d.ts +9 -1
- package/dist/multiplexer/graveyard-view-model.js +39 -0
- package/dist/multiplexer/graveyard-view-model.js.map +1 -1
- package/dist/multiplexer/index.d.ts +15 -12
- package/dist/multiplexer/index.js +64 -43
- package/dist/multiplexer/index.js.map +1 -1
- package/dist/multiplexer/notifications.js +107 -24
- package/dist/multiplexer/notifications.js.map +1 -1
- package/dist/multiplexer/persistence-methods.d.ts +31 -4
- package/dist/multiplexer/persistence-methods.js +304 -308
- package/dist/multiplexer/persistence-methods.js.map +1 -1
- package/dist/multiplexer/runtime-lifecycle-methods.d.ts +8 -10
- package/dist/multiplexer/runtime-lifecycle-methods.js +104 -86
- package/dist/multiplexer/runtime-lifecycle-methods.js.map +1 -1
- package/dist/multiplexer/runtime-state.d.ts +8 -10
- package/dist/multiplexer/runtime-state.js +82 -145
- package/dist/multiplexer/runtime-state.js.map +1 -1
- package/dist/multiplexer/runtime-sync.d.ts +2 -10
- package/dist/multiplexer/runtime-sync.js +3 -18
- package/dist/multiplexer/runtime-sync.js.map +1 -1
- package/dist/multiplexer/service-state-snapshot.d.ts +2 -4
- package/dist/multiplexer/service-state-snapshot.js +4 -51
- package/dist/multiplexer/service-state-snapshot.js.map +1 -1
- package/dist/multiplexer/services.d.ts +1 -0
- package/dist/multiplexer/services.js +55 -5
- package/dist/multiplexer/services.js.map +1 -1
- package/dist/multiplexer/session-capture.d.ts +1 -0
- package/dist/multiplexer/session-capture.js +24 -0
- package/dist/multiplexer/session-capture.js.map +1 -0
- package/dist/multiplexer/session-launch.d.ts +4 -1
- package/dist/multiplexer/session-launch.js +152 -63
- package/dist/multiplexer/session-launch.js.map +1 -1
- package/dist/multiplexer/session-runtime-core.d.ts +8 -20
- package/dist/multiplexer/session-runtime-core.js +40 -135
- package/dist/multiplexer/session-runtime-core.js.map +1 -1
- package/dist/multiplexer/subscreens.js +10 -3
- package/dist/multiplexer/subscreens.js.map +1 -1
- package/dist/multiplexer/worktree-graveyard.d.ts +0 -1
- package/dist/multiplexer/worktree-graveyard.js +15 -16
- package/dist/multiplexer/worktree-graveyard.js.map +1 -1
- package/dist/multiplexer/worktrees.js +96 -40
- package/dist/multiplexer/worktrees.js.map +1 -1
- package/dist/notification-context.js +8 -4
- package/dist/notification-context.js.map +1 -1
- package/dist/notifications.js +163 -101
- package/dist/notifications.js.map +1 -1
- package/dist/notify.d.ts +4 -0
- package/dist/notify.js +14 -0
- package/dist/notify.js.map +1 -1
- package/dist/paths.d.ts +32 -7
- package/dist/paths.js +82 -58
- package/dist/paths.js.map +1 -1
- package/dist/pending-actions.d.ts +5 -0
- package/dist/pending-actions.js +14 -0
- package/dist/pending-actions.js.map +1 -0
- package/dist/plugin-runtime.js +9 -2
- package/dist/plugin-runtime.js.map +1 -1
- package/dist/project-events.d.ts +1 -10
- package/dist/project-events.js +0 -10
- package/dist/project-events.js.map +1 -1
- package/dist/project-scanner.d.ts +2 -3
- package/dist/project-scanner.js +58 -129
- package/dist/project-scanner.js.map +1 -1
- package/dist/project-service-manifest.d.ts +1 -3
- package/dist/project-service-manifest.js +1 -3
- package/dist/project-service-manifest.js.map +1 -1
- package/dist/relay-client.d.ts +30 -0
- package/dist/relay-client.js +191 -0
- package/dist/relay-client.js.map +1 -0
- package/dist/remote-access.d.ts +16 -0
- package/dist/remote-access.js +91 -0
- package/dist/remote-access.js.map +1 -0
- package/dist/runtime-core/exchange-derived.d.ts +2 -0
- package/dist/runtime-core/exchange-derived.js +154 -0
- package/dist/runtime-core/exchange-derived.js.map +1 -0
- package/dist/runtime-core/exchange-import.d.ts +24 -0
- package/dist/runtime-core/exchange-import.js +318 -0
- package/dist/runtime-core/exchange-import.js.map +1 -0
- package/dist/runtime-core/exchange-store.d.ts +157 -0
- package/dist/runtime-core/exchange-store.js +453 -0
- package/dist/runtime-core/exchange-store.js.map +1 -0
- package/dist/runtime-core/topology-services.d.ts +38 -0
- package/dist/runtime-core/topology-services.js +171 -0
- package/dist/runtime-core/topology-services.js.map +1 -0
- package/dist/runtime-core/topology-sessions.d.ts +52 -0
- package/dist/runtime-core/topology-sessions.js +239 -0
- package/dist/runtime-core/topology-sessions.js.map +1 -0
- package/dist/runtime-core/topology-store.d.ts +171 -0
- package/dist/runtime-core/topology-store.js +420 -0
- package/dist/runtime-core/topology-store.js.map +1 -0
- package/dist/runtime-core/topology-worktrees.d.ts +60 -0
- package/dist/runtime-core/topology-worktrees.js +200 -0
- package/dist/runtime-core/topology-worktrees.js.map +1 -0
- package/dist/runtime-migration.d.ts +69 -0
- package/dist/runtime-migration.js +399 -0
- package/dist/runtime-migration.js.map +1 -0
- package/dist/session-bootstrap.d.ts +8 -6
- package/dist/session-bootstrap.js +51 -158
- package/dist/session-bootstrap.js.map +1 -1
- package/dist/session-runtime.d.ts +2 -0
- package/dist/session-runtime.js +1 -0
- package/dist/session-runtime.js.map +1 -1
- package/dist/session-semantics.d.ts +12 -4
- package/dist/session-semantics.js +14 -0
- package/dist/session-semantics.js.map +1 -1
- package/dist/shell-hooks.js +32 -10
- package/dist/shell-hooks.js.map +1 -1
- package/dist/shell-state.d.ts +2 -0
- package/dist/shell-state.js +26 -1
- package/dist/shell-state.js.map +1 -1
- package/dist/statusline-model.d.ts +10 -2
- package/dist/statusline-model.js +106 -30
- package/dist/statusline-model.js.map +1 -1
- package/dist/task-workflow.d.ts +6 -9
- package/dist/task-workflow.js +37 -84
- package/dist/task-workflow.js.map +1 -1
- package/dist/tasks.d.ts +6 -33
- package/dist/tasks.js +46 -88
- package/dist/tasks.js.map +1 -1
- package/dist/team.d.ts +29 -0
- package/dist/team.js +40 -0
- package/dist/team.js.map +1 -1
- package/dist/threads.d.ts +6 -35
- package/dist/threads.js +89 -98
- package/dist/threads.js.map +1 -1
- package/dist/tmux/inbox-popup.js +37 -15
- package/dist/tmux/inbox-popup.js.map +1 -1
- package/dist/tmux/runtime-manager.d.ts +3 -0
- package/dist/tmux/runtime-manager.js +21 -4
- package/dist/tmux/runtime-manager.js.map +1 -1
- package/dist/tmux/statusline.js +49 -9
- package/dist/tmux/statusline.js.map +1 -1
- package/dist/tmux/window-open.js +1 -2
- package/dist/tmux/window-open.js.map +1 -1
- package/dist/tool-output-watchers.d.ts +0 -18
- package/dist/tool-output-watchers.js +0 -322
- package/dist/tool-output-watchers.js.map +1 -1
- package/dist/tui/screens/dashboard-renderers.js +37 -25
- package/dist/tui/screens/dashboard-renderers.js.map +1 -1
- package/dist/tui/screens/overlay-renderers.d.ts +2 -0
- package/dist/tui/screens/overlay-renderers.js +37 -1
- package/dist/tui/screens/overlay-renderers.js.map +1 -1
- package/dist/tui/screens/subscreen-renderers.js +7 -0
- package/dist/tui/screens/subscreen-renderers.js.map +1 -1
- package/dist/worktree.js +17 -0
- package/dist/worktree.js.map +1 -1
- package/dist-ui/_expo/static/css/web-30453ede1678c16acb08b97e83e8646d.css +1 -0
- package/dist-ui/_expo/static/js/web/entry-477c745b2adc79367a4380ecf07d9ff6.js +14620 -0
- package/dist-ui/assets/assets/images/icon.a5413dcd2e811c9f2317d01a28118d8a.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
- package/dist-ui/assets/node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
- package/dist-ui/assets/node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
- package/dist-ui/favicon.ico +0 -0
- package/dist-ui/index.html +38 -0
- package/dist-ui/metadata.json +1 -0
- package/package.json +28 -12
- package/dist/agent-message-parts.d.ts +0 -17
- package/dist/agent-message-parts.js +0 -31
- package/dist/agent-message-parts.js.map +0 -1
- package/dist/instance-directory.d.ts +0 -32
- package/dist/instance-directory.js +0 -82
- package/dist/instance-directory.js.map +0 -1
- package/dist/instance-registry.d.ts +0 -39
- package/dist/instance-registry.js +0 -208
- package/dist/instance-registry.js.map +0 -1
- package/dist/multiplexer/session-actions.d.ts +0 -40
- package/dist/multiplexer/session-actions.js +0 -110
- package/dist/multiplexer/session-actions.js.map +0 -1
- package/dist/orchestration-dispatcher.d.ts +0 -25
- package/dist/orchestration-dispatcher.js +0 -59
- package/dist/orchestration-dispatcher.js.map +0 -1
- package/dist/session-input-operations.d.ts +0 -19
- package/dist/session-input-operations.js +0 -46
- package/dist/session-input-operations.js.map +0 -1
- package/dist/session-message-history.d.ts +0 -27
- package/dist/session-message-history.js +0 -105
- package/dist/session-message-history.js.map +0 -1
- package/dist/task-dispatcher.d.ts +0 -64
- package/dist/task-dispatcher.js +0 -213
- package/dist/task-dispatcher.js.map +0 -1
package/dist/main.js
CHANGED
|
@@ -5,18 +5,21 @@ 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 } from "./config.js";
|
|
9
|
-
import { initPaths, getHistoryDir,
|
|
8
|
+
import { initProject, loadConfig } from "./config.js";
|
|
9
|
+
import { initPaths, getHistoryDir, getContextDir, getProjectId, getRepoRoot, getDaemonLogPath, getProjectLogPath, getProjectStateDirFor, getRuntimeTopologyPath, } from "./paths.js";
|
|
10
10
|
import { loadTeamConfig, saveTeamConfig, getDefaultTeamConfig } from "./team.js";
|
|
11
|
-
import {
|
|
11
|
+
import { 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, resolveProjectServiceEndpoint as resolveStoredProjectServiceEndpoint, updateSessionMetadata, clearSessionLogs, removeMetadataEndpoint, } from "./metadata-store.js";
|
|
14
|
+
import { loadMetadataEndpoint, resolveProjectServiceEndpoint as resolveStoredProjectServiceEndpoint, updateSessionMetadata, clearSessionLogs, loadMetadataState, removeMetadataEndpoint, } from "./metadata-store.js";
|
|
15
|
+
import { contextualizeAlertInput, metadataDisplayContext } from "./alert-display.js";
|
|
15
16
|
import { AgentTracker } from "./agent-tracker.js";
|
|
16
|
-
import { AimuxDaemon, ensureDaemonRunning, ensureProjectService, loadDaemonInfo, loadDaemonState, projectServiceStatus, requestDaemonJson, stopDaemon, stopProjectService, } from "./daemon.js";
|
|
17
|
+
import { AimuxDaemon, ensureDaemonRunning, ensureProjectService, getDaemonHost, getDaemonPort, loadDaemonInfo, loadDaemonState, projectServiceStatus, requestDaemonJson, stopDaemon, stopProjectService, } from "./daemon.js";
|
|
17
18
|
import { getProjectServiceManifest, manifestsMatch } from "./project-service-manifest.js";
|
|
18
19
|
import { createThread, listThreadSummaries, markThreadSeen, readMessages, readThread, setThreadStatus, } from "./threads.js";
|
|
19
20
|
import { sendDirectMessage, sendThreadMessage } from "./orchestration.js";
|
|
21
|
+
import { runLoginFlow } from "./login-flow.js";
|
|
22
|
+
import { clearCredentials, loadCredentials, setRemoteEnabled } from "./credentials.js";
|
|
20
23
|
import { acceptHandoff, approveReview, acceptTask, assignTask, blockTask, completeHandoff, completeTask, reopenTask, requestTaskChanges, sendHandoff, } from "./orchestration-actions.js";
|
|
21
24
|
import { readAllTasks, readTask } from "./tasks.js";
|
|
22
25
|
import { clearNotifications, listNotifications, markNotificationsRead, upsertNotification, unreadNotificationCount, } from "./notifications.js";
|
|
@@ -25,11 +28,18 @@ import { parseClaudeHookPayload, summarizeClaudeNotification, summarizeClaudeSto
|
|
|
25
28
|
import { requestJson } from "./http-client.js";
|
|
26
29
|
import { runTmuxSwitcher } from "./tmux/switcher.js";
|
|
27
30
|
import { runTmuxInboxPopup } from "./tmux/inbox-popup.js";
|
|
31
|
+
import { buildDebugStateReport, renderDebugStateReport } from "./debug-state.js";
|
|
28
32
|
import { getDashboardCommandSpec } from "./dashboard/command-spec.js";
|
|
29
33
|
import { findLiveDashboardTarget, openDashboardTarget, pruneDashboardArtifacts, resolveDashboardTarget, } from "./dashboard/targets.js";
|
|
30
34
|
import { invalidateTmuxStatuslineArtifacts } from "./tmux/statusline-cache.js";
|
|
31
35
|
import { loadStatusline, renderTmuxStatuslineFromData } from "./tmux/statusline.js";
|
|
32
36
|
import { persistProjectRuntimeSnapshotsBeforeTmuxStop } from "./multiplexer/service-state-snapshot.js";
|
|
37
|
+
import { configureLogging, log, resolveLoggingRuntimeConfig } from "./debug.js";
|
|
38
|
+
import { createRuntimeTopologyStore } from "./runtime-core/topology-store.js";
|
|
39
|
+
import { listTopologySessionStates } from "./runtime-core/topology-sessions.js";
|
|
40
|
+
import { listTopologyWorktreeGraveyard, listTopologyWorktreeGraveyardPaths, } from "./runtime-core/topology-worktrees.js";
|
|
41
|
+
import { buildRuntimeMigrationReport, importRuntimeMigration, renderRuntimeMigrationImportResult, renderRuntimeMigrationReport, renderRuntimeMigrationRollbackResult, rollbackRuntimeMigration, } from "./runtime-migration.js";
|
|
42
|
+
import { DEFAULT_LOCAL_UI_HOST, DEFAULT_LOCAL_UI_PORT, openUrlInBrowser, startLocalUiServer, } from "./local-ui-server.js";
|
|
33
43
|
const program = new Command();
|
|
34
44
|
class ProjectServiceVersionError extends Error {
|
|
35
45
|
projectRoot;
|
|
@@ -61,6 +71,7 @@ function renderProjectServiceVersionHelp(error) {
|
|
|
61
71
|
}
|
|
62
72
|
async function restartStaleControlPlane(projectRoot) {
|
|
63
73
|
console.error(`aimux: restarting stale daemon-managed control plane for ${projectRoot}...`);
|
|
74
|
+
log.warn("restarting stale control plane", "runtime", { projectRoot });
|
|
64
75
|
await stopDaemon();
|
|
65
76
|
removeMetadataEndpoint(projectRoot);
|
|
66
77
|
await ensureDaemonRunning();
|
|
@@ -94,9 +105,21 @@ async function waitForVerifiedProjectService(projectRoot, opts) {
|
|
|
94
105
|
const health = await fetchProjectServiceHealth(endpoint);
|
|
95
106
|
lastServiceInfo = health.serviceInfo ?? null;
|
|
96
107
|
if (manifestsMatch(expected, health.serviceInfo)) {
|
|
108
|
+
log.info("project service verified", "runtime", {
|
|
109
|
+
projectRoot,
|
|
110
|
+
endpoint,
|
|
111
|
+
pid: health.pid,
|
|
112
|
+
elapsedMs: Date.now() - startedAt,
|
|
113
|
+
});
|
|
97
114
|
return { endpoint, health };
|
|
98
115
|
}
|
|
99
116
|
lastError = `project service manifest mismatch: expected ${JSON.stringify(expected)} actual ${JSON.stringify(health.serviceInfo ?? null)}`;
|
|
117
|
+
log.warn("project service manifest mismatch", "runtime", {
|
|
118
|
+
projectRoot,
|
|
119
|
+
endpoint,
|
|
120
|
+
expected,
|
|
121
|
+
actual: health.serviceInfo ?? null,
|
|
122
|
+
});
|
|
100
123
|
}
|
|
101
124
|
catch (error) {
|
|
102
125
|
lastError = error instanceof Error ? error.message : String(error);
|
|
@@ -106,6 +129,11 @@ async function waitForVerifiedProjectService(projectRoot, opts) {
|
|
|
106
129
|
lastError.includes("ECONNRESET") ||
|
|
107
130
|
lastError.includes("socket hang up"))) {
|
|
108
131
|
respawnAttempted = true;
|
|
132
|
+
log.warn("respawning project service after connection failure", "runtime", {
|
|
133
|
+
projectRoot,
|
|
134
|
+
endpoint,
|
|
135
|
+
error: lastError,
|
|
136
|
+
});
|
|
109
137
|
removeMetadataEndpoint(projectRoot);
|
|
110
138
|
await ensureProjectService(projectRoot);
|
|
111
139
|
}
|
|
@@ -118,6 +146,7 @@ async function waitForVerifiedProjectService(projectRoot, opts) {
|
|
|
118
146
|
}
|
|
119
147
|
else if (!respawnAttempted && Date.now() - missingEndpointSince >= 1000) {
|
|
120
148
|
respawnAttempted = true;
|
|
149
|
+
log.warn("respawning project service after missing endpoint", "runtime", { projectRoot });
|
|
121
150
|
await stopProjectService(projectRoot);
|
|
122
151
|
removeMetadataEndpoint(projectRoot);
|
|
123
152
|
await ensureProjectService(projectRoot);
|
|
@@ -160,7 +189,7 @@ function rewriteLocalStatuslineArtifacts(projectRoot, tmux, dashboardSessionName
|
|
|
160
189
|
if (dashboardSessionName) {
|
|
161
190
|
writeStatusFile(`bottom-dashboard-${dashboardSessionName}.txt`, dashboardBottom);
|
|
162
191
|
}
|
|
163
|
-
for (const entry of data.sessions ?? []) {
|
|
192
|
+
for (const entry of [...(data.sessions ?? []), ...(data.teammates ?? [])]) {
|
|
164
193
|
if (!entry.tmuxWindowId)
|
|
165
194
|
continue;
|
|
166
195
|
const renderOptions = {
|
|
@@ -216,34 +245,74 @@ async function postProjectServiceJsonOrLocal(path, body, fallback) {
|
|
|
216
245
|
return fallback();
|
|
217
246
|
}
|
|
218
247
|
}
|
|
248
|
+
async function getProjectServiceJsonOrLocal(path, fallback) {
|
|
249
|
+
try {
|
|
250
|
+
return await getProjectServiceJson(path);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return fallback();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
219
256
|
function exitAfterOpen() {
|
|
220
257
|
process.exit(0);
|
|
221
258
|
}
|
|
222
259
|
async function postLiveProjectServiceJsonOrLocal(projectRoot, path, body, fallback) {
|
|
260
|
+
let endpoint;
|
|
223
261
|
try {
|
|
224
|
-
|
|
225
|
-
if (!endpoint) {
|
|
226
|
-
return fallback();
|
|
227
|
-
}
|
|
228
|
-
const { status, json } = await requestJson(`http://${endpoint.host}:${endpoint.port}${path}`, {
|
|
229
|
-
method: "POST",
|
|
230
|
-
headers: { "content-type": "application/json" },
|
|
231
|
-
body,
|
|
232
|
-
});
|
|
233
|
-
if (status < 200 || status >= 300 || json?.ok === false) {
|
|
234
|
-
throw new Error(json?.error || `request failed: ${status}`);
|
|
235
|
-
}
|
|
236
|
-
return json;
|
|
262
|
+
endpoint = await resolveProjectServiceEndpoint(projectRoot);
|
|
237
263
|
}
|
|
238
264
|
catch {
|
|
239
265
|
return fallback();
|
|
240
266
|
}
|
|
267
|
+
if (!endpoint) {
|
|
268
|
+
return fallback();
|
|
269
|
+
}
|
|
270
|
+
const { status, json } = await requestJson(`http://${endpoint.host}:${endpoint.port}${path}`, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: { "content-type": "application/json" },
|
|
273
|
+
body,
|
|
274
|
+
});
|
|
275
|
+
if (status === 404 || status === 405 || status === 501) {
|
|
276
|
+
return fallback();
|
|
277
|
+
}
|
|
278
|
+
if (status < 200 || status >= 300 || json?.ok === false) {
|
|
279
|
+
throw new Error(json?.error || `request failed: ${status}`);
|
|
280
|
+
}
|
|
281
|
+
return json;
|
|
282
|
+
}
|
|
283
|
+
async function getLiveProjectServiceJsonOrLocal(projectRoot, path, fallback) {
|
|
284
|
+
let endpoint;
|
|
285
|
+
try {
|
|
286
|
+
endpoint = await resolveProjectServiceEndpoint(projectRoot);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return fallback();
|
|
290
|
+
}
|
|
291
|
+
if (!endpoint) {
|
|
292
|
+
return fallback();
|
|
293
|
+
}
|
|
294
|
+
let status;
|
|
295
|
+
let json;
|
|
296
|
+
try {
|
|
297
|
+
({ status, json } = await requestJson(`http://${endpoint.host}:${endpoint.port}${path}`, {
|
|
298
|
+
method: "GET",
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
return fallback();
|
|
303
|
+
}
|
|
304
|
+
if (status === 404 || status === 405 || status === 501) {
|
|
305
|
+
return fallback();
|
|
306
|
+
}
|
|
307
|
+
if (status < 200 || status >= 300 || json?.ok === false) {
|
|
308
|
+
throw new Error(json?.error || `request failed: ${status}`);
|
|
309
|
+
}
|
|
310
|
+
return json;
|
|
241
311
|
}
|
|
242
312
|
async function resolveClaudeHookSessionId(explicitSessionId, payloadSessionId) {
|
|
243
313
|
if (!payloadSessionId)
|
|
244
314
|
return explicitSessionId;
|
|
245
|
-
const
|
|
246
|
-
const match = state?.sessions.find((session) => session.backendSessionId === payloadSessionId);
|
|
315
|
+
const match = listTopologySessionStates().find((session) => session.backendSessionId === payloadSessionId);
|
|
247
316
|
return match?.id ?? explicitSessionId;
|
|
248
317
|
}
|
|
249
318
|
async function resolveProjectServiceEndpoint(projectRoot = resolveProjectRoot(process.cwd())) {
|
|
@@ -371,6 +440,69 @@ function ensureTmuxAvailable(tmux) {
|
|
|
371
440
|
process.exit(1);
|
|
372
441
|
}
|
|
373
442
|
}
|
|
443
|
+
function commandPath(command) {
|
|
444
|
+
const names = [];
|
|
445
|
+
let current = command;
|
|
446
|
+
while (current) {
|
|
447
|
+
const name = current.name();
|
|
448
|
+
if (name)
|
|
449
|
+
names.unshift(name);
|
|
450
|
+
current = current.parent ?? null;
|
|
451
|
+
}
|
|
452
|
+
return names;
|
|
453
|
+
}
|
|
454
|
+
function loggingProcessKind(command) {
|
|
455
|
+
const names = commandPath(command);
|
|
456
|
+
if (names.at(-1) === "__project-service-internal")
|
|
457
|
+
return "project-service";
|
|
458
|
+
if (names.at(-2) === "daemon" && names.at(-1) === "run")
|
|
459
|
+
return "daemon";
|
|
460
|
+
return "cli";
|
|
461
|
+
}
|
|
462
|
+
function configureLoggingForCommand(command) {
|
|
463
|
+
const processKind = loggingProcessKind(command);
|
|
464
|
+
const config = loadConfig();
|
|
465
|
+
const path = processKind === "daemon" ? getDaemonLogPath() : getProjectLogPath();
|
|
466
|
+
const cli = program.opts();
|
|
467
|
+
const resolved = resolveLoggingRuntimeConfig({
|
|
468
|
+
config: config.logging,
|
|
469
|
+
env: process.env,
|
|
470
|
+
cli,
|
|
471
|
+
path,
|
|
472
|
+
processKind,
|
|
473
|
+
projectId: getProjectId(),
|
|
474
|
+
projectRoot: getRepoRoot(),
|
|
475
|
+
});
|
|
476
|
+
configureLogging(resolved);
|
|
477
|
+
log.info("logging configured", "logging", {
|
|
478
|
+
path: resolved.path,
|
|
479
|
+
level: resolved.level,
|
|
480
|
+
categories: resolved.categories,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
function parseLineCount(value) {
|
|
484
|
+
const parsed = Number.parseInt(value ?? "80", 10);
|
|
485
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 80;
|
|
486
|
+
}
|
|
487
|
+
function parsePortOption(value, fallback) {
|
|
488
|
+
const parsed = Number.parseInt(value ?? String(fallback), 10);
|
|
489
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65_535) {
|
|
490
|
+
throw new Error(`Port must be an integer between 1 and 65535, got ${value}`);
|
|
491
|
+
}
|
|
492
|
+
return parsed;
|
|
493
|
+
}
|
|
494
|
+
function selectedLogPath(opts) {
|
|
495
|
+
return opts.daemon ? getDaemonLogPath() : getProjectLogPath();
|
|
496
|
+
}
|
|
497
|
+
function readLastLogLines(path, lines) {
|
|
498
|
+
if (!existsSync(path))
|
|
499
|
+
return "";
|
|
500
|
+
const content = readFileSync(path, "utf-8");
|
|
501
|
+
const allLines = content.split(/\r?\n/);
|
|
502
|
+
if (allLines.at(-1) === "")
|
|
503
|
+
allLines.pop();
|
|
504
|
+
return allLines.slice(-lines).join("\n");
|
|
505
|
+
}
|
|
374
506
|
const pkgJsonPath = pathJoin(pathDirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
375
507
|
const pkgVersion = JSON.parse(readFileSync(pkgJsonPath, "utf8")).version;
|
|
376
508
|
program
|
|
@@ -382,11 +514,27 @@ program
|
|
|
382
514
|
.option("--resume", "Resume previous sessions using native tool resume")
|
|
383
515
|
.option("--restore", "Start fresh sessions with injected history context")
|
|
384
516
|
.option("--tmux-dashboard-internal", "Internal tmux dashboard entrypoint")
|
|
517
|
+
.option("--debug", "Enable debug logging for this process")
|
|
518
|
+
.option("--trace", "Enable trace logging for this process")
|
|
519
|
+
.option("--log-level <level>", "Enable logging at level: error|warn|info|debug|trace")
|
|
520
|
+
.option("--log-category <categories>", "Comma-separated log categories to include")
|
|
385
521
|
.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
522
|
+
const names = commandPath(actionCommand);
|
|
523
|
+
const isMigrationAudit = names.at(-2) === "migration" && names.at(-1) === "audit";
|
|
524
|
+
if (isMigrationAudit) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
386
527
|
const opts = typeof actionCommand?.opts === "function" ? actionCommand.opts() : {};
|
|
387
|
-
const requestedProject = typeof opts.project === "string"
|
|
528
|
+
const requestedProject = typeof opts.project === "string"
|
|
529
|
+
? opts.project
|
|
530
|
+
: typeof opts.projectRoot === "string"
|
|
531
|
+
? opts.projectRoot
|
|
532
|
+
: typeof opts["project-root"] === "string"
|
|
533
|
+
? opts["project-root"]
|
|
534
|
+
: undefined;
|
|
388
535
|
const projectRoot = requestedProject ? resolveProjectRoot(pathResolve(requestedProject)) : undefined;
|
|
389
536
|
await initPaths(projectRoot);
|
|
537
|
+
configureLoggingForCommand(actionCommand);
|
|
390
538
|
})
|
|
391
539
|
.action(async (tool, args, opts) => {
|
|
392
540
|
const originalCwd = process.cwd();
|
|
@@ -447,11 +595,19 @@ program
|
|
|
447
595
|
process.on("SIGTERM", shutdown);
|
|
448
596
|
process.on("uncaughtException", (err) => {
|
|
449
597
|
cleanupAll();
|
|
598
|
+
log.error("uncaught exception", "runtime", {
|
|
599
|
+
error: err instanceof Error ? err.message : String(err),
|
|
600
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
601
|
+
});
|
|
450
602
|
console.error(err);
|
|
451
603
|
process.exit(1);
|
|
452
604
|
});
|
|
453
605
|
process.on("unhandledRejection", (reason) => {
|
|
454
606
|
cleanupAll();
|
|
607
|
+
log.error("unhandled rejection", "runtime", {
|
|
608
|
+
error: reason instanceof Error ? reason.message : String(reason),
|
|
609
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
610
|
+
});
|
|
455
611
|
console.error(reason);
|
|
456
612
|
process.exit(1);
|
|
457
613
|
});
|
|
@@ -533,8 +689,11 @@ program
|
|
|
533
689
|
try {
|
|
534
690
|
if (sessionId) {
|
|
535
691
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
536
|
-
|
|
537
|
-
const result = await
|
|
692
|
+
await ensureDaemonProjectReady(projectRoot);
|
|
693
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/agents/stop", { sessionId }, () => {
|
|
694
|
+
const mux = new Multiplexer();
|
|
695
|
+
return mux.stopAgent(sessionId);
|
|
696
|
+
});
|
|
538
697
|
if (opts.json) {
|
|
539
698
|
console.log(JSON.stringify({
|
|
540
699
|
ok: true,
|
|
@@ -557,6 +716,7 @@ program
|
|
|
557
716
|
projectServiceStopped: result.projectServiceStopped,
|
|
558
717
|
tmuxSessionsKilled: result.tmuxSessionsKilled,
|
|
559
718
|
}, null, 2));
|
|
719
|
+
process.exitCode = 1;
|
|
560
720
|
return;
|
|
561
721
|
}
|
|
562
722
|
console.log(`Stopped project runtime for ${projectRoot}`);
|
|
@@ -608,6 +768,47 @@ program
|
|
|
608
768
|
const hostCmd = program
|
|
609
769
|
.command("host")
|
|
610
770
|
.description("Advanced compatibility wrappers for legacy daemon-managed project services");
|
|
771
|
+
program
|
|
772
|
+
.command("ui")
|
|
773
|
+
.description("Run the first-party local web UI from the built app bundle")
|
|
774
|
+
.option("--host <host>", "Loopback host to bind", DEFAULT_LOCAL_UI_HOST)
|
|
775
|
+
.option("--port <port>", "Local UI port", String(DEFAULT_LOCAL_UI_PORT))
|
|
776
|
+
.option("--daemon-url <url>", "Daemon URL for the UI to call")
|
|
777
|
+
.option("--no-daemon", "Do not ensure the local daemon before serving")
|
|
778
|
+
.option("--open", "Open the UI in the default browser")
|
|
779
|
+
.action(async (opts) => {
|
|
780
|
+
try {
|
|
781
|
+
const shouldEnsureDaemon = opts.daemon !== false;
|
|
782
|
+
const daemonInfo = shouldEnsureDaemon ? await ensureDaemonRunning() : null;
|
|
783
|
+
const daemonUrl = opts.daemonUrl?.trim() || `http://${getDaemonHost()}:${daemonInfo?.port ?? getDaemonPort()}`;
|
|
784
|
+
const server = await startLocalUiServer({
|
|
785
|
+
host: opts.host,
|
|
786
|
+
port: parsePortOption(opts.port, DEFAULT_LOCAL_UI_PORT),
|
|
787
|
+
config: {
|
|
788
|
+
connectionMode: "local",
|
|
789
|
+
daemonUrl,
|
|
790
|
+
},
|
|
791
|
+
});
|
|
792
|
+
console.log(`aimux UI: ${server.url}`);
|
|
793
|
+
console.log(`Daemon: ${daemonUrl}`);
|
|
794
|
+
console.log("Press Ctrl-C to stop.");
|
|
795
|
+
if (opts.open) {
|
|
796
|
+
openUrlInBrowser(server.url);
|
|
797
|
+
}
|
|
798
|
+
const shutdown = async () => {
|
|
799
|
+
await server.close();
|
|
800
|
+
process.exit(0);
|
|
801
|
+
};
|
|
802
|
+
process.on("SIGINT", () => void shutdown());
|
|
803
|
+
process.on("SIGTERM", () => void shutdown());
|
|
804
|
+
await new Promise(() => { });
|
|
805
|
+
}
|
|
806
|
+
catch (err) {
|
|
807
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
808
|
+
console.error(`Error: ${msg}`);
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
611
812
|
program
|
|
612
813
|
.command("serve")
|
|
613
814
|
.description("Advanced: ensure the legacy daemon-backed project control service is running")
|
|
@@ -720,27 +921,22 @@ hostCmd
|
|
|
720
921
|
console.log(`Restarted project service for ${dashboardSession.sessionName}`);
|
|
721
922
|
});
|
|
722
923
|
hostCmd
|
|
723
|
-
.command("
|
|
724
|
-
.description("
|
|
725
|
-
.
|
|
726
|
-
.
|
|
727
|
-
.
|
|
728
|
-
.option("--submit", "Submit after writing the input")
|
|
729
|
-
.action(async (sessionId, data, opts) => {
|
|
924
|
+
.command("topology")
|
|
925
|
+
.description("Show the runtime topology YAML path or parsed contents")
|
|
926
|
+
.option("--json", "Emit parsed topology JSON")
|
|
927
|
+
.option("--raw", "Print raw YAML contents")
|
|
928
|
+
.action(async (opts) => {
|
|
730
929
|
await initPaths();
|
|
731
|
-
const
|
|
732
|
-
if (
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const result = await postProjectServiceJson("/agents/input", {
|
|
736
|
-
sessionId,
|
|
737
|
-
data: payload,
|
|
738
|
-
submit: opts.submit === true,
|
|
739
|
-
});
|
|
740
|
-
if (result.accepted === false) {
|
|
741
|
-
throw new Error(result.error || `agent input failed for ${result.sessionId}`);
|
|
930
|
+
const path = getRuntimeTopologyPath();
|
|
931
|
+
if (opts.json) {
|
|
932
|
+
console.log(JSON.stringify(createRuntimeTopologyStore(path).read(), null, 2));
|
|
933
|
+
return;
|
|
742
934
|
}
|
|
743
|
-
|
|
935
|
+
if (opts.raw) {
|
|
936
|
+
console.log(readFileSync(path, "utf-8"));
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
console.log(path);
|
|
744
940
|
});
|
|
745
941
|
hostCmd
|
|
746
942
|
.command("agent-read")
|
|
@@ -930,9 +1126,20 @@ daemonCmd
|
|
|
930
1126
|
.action(async (opts) => {
|
|
931
1127
|
const info = loadDaemonInfo();
|
|
932
1128
|
const state = loadDaemonState();
|
|
1129
|
+
let relay = { status: "off" };
|
|
1130
|
+
if (info) {
|
|
1131
|
+
try {
|
|
1132
|
+
const result = await requestDaemonJson("/relay/status");
|
|
1133
|
+
relay = result.relay;
|
|
1134
|
+
}
|
|
1135
|
+
catch {
|
|
1136
|
+
// Relay status unavailable — leave as off.
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
933
1139
|
const payload = {
|
|
934
1140
|
daemon: info,
|
|
935
1141
|
projects: Object.values(state.projects),
|
|
1142
|
+
relay,
|
|
936
1143
|
};
|
|
937
1144
|
if (opts.json) {
|
|
938
1145
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -944,6 +1151,13 @@ daemonCmd
|
|
|
944
1151
|
}
|
|
945
1152
|
console.log(`Daemon pid=${info.pid} port=${info.port}`);
|
|
946
1153
|
console.log(`Managed projects: ${Object.keys(state.projects).length}`);
|
|
1154
|
+
const r = relay;
|
|
1155
|
+
if (r.status && r.status !== "off") {
|
|
1156
|
+
console.log(`Relay: ${r.status}${r.relayUrl ? ` (${r.relayUrl})` : ""}`);
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
console.log("Relay: off");
|
|
1160
|
+
}
|
|
947
1161
|
});
|
|
948
1162
|
daemonCmd
|
|
949
1163
|
.command("projects")
|
|
@@ -1003,11 +1217,19 @@ program
|
|
|
1003
1217
|
process.on("SIGTERM", shutdown);
|
|
1004
1218
|
process.on("uncaughtException", (err) => {
|
|
1005
1219
|
cleanupAll();
|
|
1220
|
+
log.error("project service uncaught exception", "runtime", {
|
|
1221
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1222
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
1223
|
+
});
|
|
1006
1224
|
console.error(err);
|
|
1007
1225
|
process.exit(1);
|
|
1008
1226
|
});
|
|
1009
1227
|
process.on("unhandledRejection", (reason) => {
|
|
1010
1228
|
cleanupAll();
|
|
1229
|
+
log.error("project service unhandled rejection", "runtime", {
|
|
1230
|
+
error: reason instanceof Error ? reason.message : String(reason),
|
|
1231
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
1232
|
+
});
|
|
1011
1233
|
console.error(reason);
|
|
1012
1234
|
process.exit(1);
|
|
1013
1235
|
});
|
|
@@ -1041,15 +1263,8 @@ projectsCmd
|
|
|
1041
1263
|
return;
|
|
1042
1264
|
}
|
|
1043
1265
|
for (const project of projects) {
|
|
1044
|
-
const liveBadge = project.
|
|
1266
|
+
const liveBadge = project.serviceAlive ? "live" : "idle";
|
|
1045
1267
|
console.log(`${project.name} ${liveBadge} ${project.path}`);
|
|
1046
|
-
if (project.sessions.length === 0)
|
|
1047
|
-
continue;
|
|
1048
|
-
for (const session of project.sessions) {
|
|
1049
|
-
const label = session.label ? ` ${session.label}` : "";
|
|
1050
|
-
const headline = session.headline ? ` - ${session.headline}` : "";
|
|
1051
|
-
console.log(` ${session.id} ${session.tool} ${session.status}${label}${headline}`);
|
|
1052
|
-
}
|
|
1053
1268
|
}
|
|
1054
1269
|
});
|
|
1055
1270
|
program
|
|
@@ -1075,6 +1290,166 @@ program
|
|
|
1075
1290
|
llmCompact(sessionIds);
|
|
1076
1291
|
console.log(`Done. Summary written to ${getContextDir()}/summary.md`);
|
|
1077
1292
|
});
|
|
1293
|
+
program
|
|
1294
|
+
.command("login")
|
|
1295
|
+
.description("Sign in to enable remote access via aimux.app")
|
|
1296
|
+
.option("--web-app-url <url>", "Override the web app URL")
|
|
1297
|
+
// No --relay-url here: the token is minted by whichever relay the web app
|
|
1298
|
+
// points at, so a CLI override would just store a relay URL that rejects
|
|
1299
|
+
// the resulting token (different RELAY_TOKEN_SECRET).
|
|
1300
|
+
.action(async (opts) => {
|
|
1301
|
+
try {
|
|
1302
|
+
const { userId } = await runLoginFlow({ webAppUrl: opts.webAppUrl });
|
|
1303
|
+
let relayStatus = null;
|
|
1304
|
+
let relayError = null;
|
|
1305
|
+
if (loadDaemonInfo()) {
|
|
1306
|
+
try {
|
|
1307
|
+
const result = await requestDaemonJson("/relay/enable", { method: "POST" });
|
|
1308
|
+
const relay = result.relay;
|
|
1309
|
+
relayStatus = relay.status ?? "unknown";
|
|
1310
|
+
relayError = relay.lastError ?? null;
|
|
1311
|
+
}
|
|
1312
|
+
catch (err) {
|
|
1313
|
+
relayError = err instanceof Error ? err.message : String(err);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
console.log(`\n✓ Logged in as ${userId}`);
|
|
1317
|
+
if (relayStatus) {
|
|
1318
|
+
console.log(`Remote access is enabled (connection: ${relayStatus}).`);
|
|
1319
|
+
if (relayError)
|
|
1320
|
+
console.log(`Last error: ${relayError}`);
|
|
1321
|
+
}
|
|
1322
|
+
else {
|
|
1323
|
+
console.log("Remote access is enabled. The daemon will connect on next start.");
|
|
1324
|
+
if (relayError)
|
|
1325
|
+
console.log(`Daemon refresh failed: ${relayError}`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
catch (err) {
|
|
1329
|
+
console.error(`Login failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
program
|
|
1334
|
+
.command("logout")
|
|
1335
|
+
.description("Clear stored credentials and disable remote access")
|
|
1336
|
+
.action(async () => {
|
|
1337
|
+
// If the daemon is running it already has the credential loaded into
|
|
1338
|
+
// memory; tell it to disconnect before we yank the file so the running
|
|
1339
|
+
// process stops talking to the relay immediately (best-effort — we
|
|
1340
|
+
// ignore failures since the daemon may not be up).
|
|
1341
|
+
if (loadDaemonInfo()) {
|
|
1342
|
+
try {
|
|
1343
|
+
await requestDaemonJson("/relay/disable", { method: "POST" });
|
|
1344
|
+
}
|
|
1345
|
+
catch {
|
|
1346
|
+
// daemon offline or refused; the file removal below still kills
|
|
1347
|
+
// future startup, so this isn't fatal.
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
const result = clearCredentials();
|
|
1351
|
+
if (result === "cleared")
|
|
1352
|
+
console.log("✓ Logged out. Remote access disabled.");
|
|
1353
|
+
else if (result === "none")
|
|
1354
|
+
console.log("Not logged in.");
|
|
1355
|
+
else {
|
|
1356
|
+
console.error("Failed to remove credentials file — check permissions.");
|
|
1357
|
+
process.exitCode = 1;
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1360
|
+
program
|
|
1361
|
+
.command("whoami")
|
|
1362
|
+
.description("Show the current remote-access login status")
|
|
1363
|
+
.option("--json", "Emit JSON")
|
|
1364
|
+
.action((opts) => {
|
|
1365
|
+
const creds = loadCredentials();
|
|
1366
|
+
if (opts.json) {
|
|
1367
|
+
console.log(JSON.stringify(creds
|
|
1368
|
+
? { loggedIn: true, userId: creds.userId, relayUrl: creds.relayUrl, remoteEnabled: creds.remoteEnabled }
|
|
1369
|
+
: { loggedIn: false }, null, 2));
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (!creds) {
|
|
1373
|
+
console.log("Not logged in. Run `aimux login` to enable remote access.");
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
console.log(`Logged in as ${creds.userId}`);
|
|
1377
|
+
console.log(`Relay: ${creds.relayUrl}`);
|
|
1378
|
+
console.log(`Remote access: ${creds.remoteEnabled ? "enabled" : "disabled"}`);
|
|
1379
|
+
});
|
|
1380
|
+
const remoteCmd = program.command("remote").description("Manage remote access via the relay");
|
|
1381
|
+
const securityCmd = program.command("security").description("Manage aimux security controls");
|
|
1382
|
+
remoteCmd
|
|
1383
|
+
.command("status")
|
|
1384
|
+
.description("Show relay connection status")
|
|
1385
|
+
.option("--json", "Emit JSON")
|
|
1386
|
+
.action(async (opts) => {
|
|
1387
|
+
const creds = loadCredentials();
|
|
1388
|
+
let relay = { status: "off" };
|
|
1389
|
+
if (loadDaemonInfo()) {
|
|
1390
|
+
try {
|
|
1391
|
+
const result = await requestDaemonJson("/relay/status");
|
|
1392
|
+
relay = result.relay;
|
|
1393
|
+
}
|
|
1394
|
+
catch {
|
|
1395
|
+
// Daemon is not reachable — fall back to credential state.
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (opts.json) {
|
|
1399
|
+
console.log(JSON.stringify({ loggedIn: Boolean(creds), relay }, null, 2));
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
if (!creds) {
|
|
1403
|
+
console.log("Not logged in. Run `aimux login` to enable remote access.");
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
const r = relay;
|
|
1407
|
+
console.log(`Remote access: ${creds.remoteEnabled ? "enabled" : "disabled"}`);
|
|
1408
|
+
console.log(`Relay: ${creds.relayUrl}`);
|
|
1409
|
+
console.log(`Connection: ${r.status ?? "unknown"}`);
|
|
1410
|
+
if (r.lastError)
|
|
1411
|
+
console.log(`Last error: ${r.lastError}`);
|
|
1412
|
+
});
|
|
1413
|
+
remoteCmd
|
|
1414
|
+
.command("enable")
|
|
1415
|
+
.description("Enable remote access and connect to the relay")
|
|
1416
|
+
.action(async () => {
|
|
1417
|
+
if (!loadCredentials()) {
|
|
1418
|
+
console.error("Not logged in. Run `aimux login` first.");
|
|
1419
|
+
process.exit(1);
|
|
1420
|
+
}
|
|
1421
|
+
await ensureDaemonRunning();
|
|
1422
|
+
const result = await requestDaemonJson("/relay/enable", { method: "POST" });
|
|
1423
|
+
const r = result.relay;
|
|
1424
|
+
console.log(`✓ Remote access enabled (connection: ${r.status ?? "unknown"})`);
|
|
1425
|
+
});
|
|
1426
|
+
remoteCmd
|
|
1427
|
+
.command("disable")
|
|
1428
|
+
.description("Disable remote access and disconnect from the relay")
|
|
1429
|
+
.action(async () => {
|
|
1430
|
+
if (loadDaemonInfo()) {
|
|
1431
|
+
await requestDaemonJson("/relay/disable", { method: "POST" });
|
|
1432
|
+
console.log("✓ Remote access disabled. Daemon disconnected from relay.");
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
setRemoteEnabled(false);
|
|
1436
|
+
console.log("✓ Remote access disabled.");
|
|
1437
|
+
});
|
|
1438
|
+
securityCmd
|
|
1439
|
+
.command("unlock")
|
|
1440
|
+
.description("Clear relay security lockdown after re-authenticating")
|
|
1441
|
+
.option("--web-app-url <url>", "Override the web app URL")
|
|
1442
|
+
.action(async (opts) => {
|
|
1443
|
+
try {
|
|
1444
|
+
const { userId } = await runLoginFlow({ webAppUrl: opts.webAppUrl, action: "security-unlock" });
|
|
1445
|
+
console.log(`\n✓ Security unlocked for ${userId}`);
|
|
1446
|
+
console.log("Remote access is enabled with a fresh daemon token. Restart the daemon to reconnect immediately.");
|
|
1447
|
+
}
|
|
1448
|
+
catch (err) {
|
|
1449
|
+
console.error(`Security unlock failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1450
|
+
process.exit(1);
|
|
1451
|
+
}
|
|
1452
|
+
});
|
|
1078
1453
|
async function prepareProjectContext(requestedProject) {
|
|
1079
1454
|
const requestedPath = pathResolve(requestedProject ?? process.cwd());
|
|
1080
1455
|
const projectRoot = resolveProjectRoot(requestedPath);
|
|
@@ -1082,9 +1457,13 @@ async function prepareProjectContext(requestedProject) {
|
|
|
1082
1457
|
process.chdir(projectRoot);
|
|
1083
1458
|
return projectRoot;
|
|
1084
1459
|
}
|
|
1085
|
-
function
|
|
1460
|
+
function listVisibleLocalWorktrees(projectRoot) {
|
|
1461
|
+
const graveyardPaths = listTopologyWorktreeGraveyardPaths();
|
|
1462
|
+
return listWorktrees(projectRoot).filter((worktree) => !graveyardPaths.has(worktree.path));
|
|
1463
|
+
}
|
|
1464
|
+
function printWorktrees(projectRoot, worktreesInput) {
|
|
1086
1465
|
try {
|
|
1087
|
-
const worktrees = listWorktrees(projectRoot);
|
|
1466
|
+
const worktrees = worktreesInput ?? listWorktrees(projectRoot);
|
|
1088
1467
|
if (worktrees.length === 0) {
|
|
1089
1468
|
console.log("No worktrees found.");
|
|
1090
1469
|
return;
|
|
@@ -1101,9 +1480,55 @@ function printWorktrees(projectRoot) {
|
|
|
1101
1480
|
process.exit(1);
|
|
1102
1481
|
}
|
|
1103
1482
|
}
|
|
1483
|
+
function printGraveyard(input) {
|
|
1484
|
+
const entries = Array.isArray(input.entries) ? input.entries : [];
|
|
1485
|
+
const worktrees = Array.isArray(input.worktrees) ? input.worktrees : [];
|
|
1486
|
+
if (entries.length === 0 && worktrees.length === 0) {
|
|
1487
|
+
console.log("Graveyard is empty.");
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (worktrees.length > 0) {
|
|
1491
|
+
console.log("Worktrees");
|
|
1492
|
+
console.log("Name".padEnd(30) + "Branch".padEnd(35) + "Path");
|
|
1493
|
+
console.log("-".repeat(95));
|
|
1494
|
+
for (const worktree of worktrees) {
|
|
1495
|
+
console.log(String(worktree.name ?? "?").padEnd(30) +
|
|
1496
|
+
String(worktree.branch ?? "").padEnd(35) +
|
|
1497
|
+
String(worktree.path ?? "?"));
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (entries.length > 0) {
|
|
1501
|
+
if (worktrees.length > 0)
|
|
1502
|
+
console.log("");
|
|
1503
|
+
console.log("Agents");
|
|
1504
|
+
console.log("ID".padEnd(25) + "Tool".padEnd(15) + "Backend Session ID");
|
|
1505
|
+
console.log("-".repeat(70));
|
|
1506
|
+
for (const session of entries) {
|
|
1507
|
+
console.log(String(session.id ?? "?").padEnd(25) +
|
|
1508
|
+
String(session.command ?? session.tool ?? "?").padEnd(15) +
|
|
1509
|
+
String(session.backendSessionId ?? "(none)"));
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1104
1513
|
const worktreeCmd = program.command("worktree").description("Manage git worktrees");
|
|
1105
|
-
|
|
1106
|
-
|
|
1514
|
+
async function ensureDaemonProjectReadyForFallback(projectRoot) {
|
|
1515
|
+
try {
|
|
1516
|
+
await ensureDaemonProjectReady(projectRoot);
|
|
1517
|
+
}
|
|
1518
|
+
catch (err) {
|
|
1519
|
+
if (err instanceof ProjectServiceVersionError) {
|
|
1520
|
+
throw err;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
worktreeCmd.action(async () => {
|
|
1525
|
+
const projectRoot = await prepareProjectContext();
|
|
1526
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
1527
|
+
const result = await getLiveProjectServiceJsonOrLocal(projectRoot, "/worktrees", () => ({
|
|
1528
|
+
ok: true,
|
|
1529
|
+
worktrees: listVisibleLocalWorktrees(projectRoot),
|
|
1530
|
+
}));
|
|
1531
|
+
printWorktrees(projectRoot, result.worktrees ?? []);
|
|
1107
1532
|
});
|
|
1108
1533
|
const threadCmd = program.command("thread").description("Inspect and manage orchestration threads");
|
|
1109
1534
|
program
|
|
@@ -1111,8 +1536,9 @@ program
|
|
|
1111
1536
|
.description("Alias for thread list")
|
|
1112
1537
|
.option("--session <sessionId>", "Filter to threads involving a session")
|
|
1113
1538
|
.option("--json", "Emit JSON")
|
|
1114
|
-
.action((opts) => {
|
|
1115
|
-
const
|
|
1539
|
+
.action(async (opts) => {
|
|
1540
|
+
const query = opts.session ? `?session=${encodeURIComponent(opts.session)}` : "";
|
|
1541
|
+
const summaries = await getProjectServiceJsonOrLocal(`/threads${query}`, () => listThreadSummaries(opts.session));
|
|
1116
1542
|
if (opts.json) {
|
|
1117
1543
|
console.log(JSON.stringify(summaries, null, 2));
|
|
1118
1544
|
return;
|
|
@@ -1136,8 +1562,9 @@ threadCmd
|
|
|
1136
1562
|
.description("List orchestration threads")
|
|
1137
1563
|
.option("--session <sessionId>", "Filter to threads involving a session")
|
|
1138
1564
|
.option("--json", "Emit JSON")
|
|
1139
|
-
.action((opts) => {
|
|
1140
|
-
const
|
|
1565
|
+
.action(async (opts) => {
|
|
1566
|
+
const query = opts.session ? `?session=${encodeURIComponent(opts.session)}` : "";
|
|
1567
|
+
const summaries = await getProjectServiceJsonOrLocal(`/threads${query}`, () => listThreadSummaries(opts.session));
|
|
1141
1568
|
if (opts.json) {
|
|
1142
1569
|
console.log(JSON.stringify(summaries, null, 2));
|
|
1143
1570
|
return;
|
|
@@ -1161,13 +1588,18 @@ threadCmd
|
|
|
1161
1588
|
.description("Show a thread and its messages")
|
|
1162
1589
|
.argument("<threadId>")
|
|
1163
1590
|
.option("--json", "Emit JSON")
|
|
1164
|
-
.action((threadId, opts) => {
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1591
|
+
.action(async (threadId, opts) => {
|
|
1592
|
+
const detail = await getProjectServiceJsonOrLocal(`/threads/${encodeURIComponent(threadId)}`, () => {
|
|
1593
|
+
const thread = readThread(threadId);
|
|
1594
|
+
if (!thread)
|
|
1595
|
+
return null;
|
|
1596
|
+
return { thread, messages: readMessages(threadId) };
|
|
1597
|
+
});
|
|
1598
|
+
if (!detail?.thread) {
|
|
1167
1599
|
console.error(`aimux: thread not found: ${threadId}`);
|
|
1168
1600
|
process.exit(1);
|
|
1169
1601
|
}
|
|
1170
|
-
const messages =
|
|
1602
|
+
const { thread, messages } = detail;
|
|
1171
1603
|
if (opts.json) {
|
|
1172
1604
|
console.log(JSON.stringify({ thread, messages }, null, 2));
|
|
1173
1605
|
return;
|
|
@@ -1193,18 +1625,25 @@ threadCmd
|
|
|
1193
1625
|
.requiredOption("--from <sessionId>", "Creating session")
|
|
1194
1626
|
.requiredOption("--participants <ids>", "Comma-separated participant session ids")
|
|
1195
1627
|
.option("--kind <kind>", "conversation|task|review|handoff|user", "conversation")
|
|
1196
|
-
.action((opts) => {
|
|
1628
|
+
.action(async (opts) => {
|
|
1197
1629
|
const participants = opts.participants
|
|
1198
1630
|
.split(",")
|
|
1199
1631
|
.map((value) => value.trim())
|
|
1200
1632
|
.filter(Boolean);
|
|
1201
|
-
const
|
|
1633
|
+
const result = await postProjectServiceJsonOrLocal("/threads/open", {
|
|
1202
1634
|
title: opts.title,
|
|
1635
|
+
from: opts.from,
|
|
1636
|
+
participants,
|
|
1203
1637
|
kind: opts.kind ?? "conversation",
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1638
|
+
}, () => ({
|
|
1639
|
+
thread: createThread({
|
|
1640
|
+
title: opts.title,
|
|
1641
|
+
kind: opts.kind ?? "conversation",
|
|
1642
|
+
createdBy: opts.from,
|
|
1643
|
+
participants: [...new Set([opts.from, ...participants])],
|
|
1644
|
+
}),
|
|
1645
|
+
}));
|
|
1646
|
+
console.log(result.thread.id);
|
|
1208
1647
|
});
|
|
1209
1648
|
threadCmd
|
|
1210
1649
|
.command("send")
|
|
@@ -1214,36 +1653,46 @@ threadCmd
|
|
|
1214
1653
|
.requiredOption("--from <sessionId>", "Sending session")
|
|
1215
1654
|
.option("--to <ids>", "Comma-separated recipient session ids")
|
|
1216
1655
|
.option("--kind <kind>", "request|reply|status|decision|handoff|note", "note")
|
|
1217
|
-
.action((threadId, body, opts) => {
|
|
1218
|
-
const thread = readThread(threadId);
|
|
1219
|
-
if (!thread) {
|
|
1220
|
-
console.error(`aimux: thread not found: ${threadId}`);
|
|
1221
|
-
process.exit(1);
|
|
1222
|
-
}
|
|
1656
|
+
.action(async (threadId, body, opts) => {
|
|
1223
1657
|
const to = opts.to
|
|
1224
1658
|
?.split(",")
|
|
1225
1659
|
.map((value) => value.trim())
|
|
1226
1660
|
.filter(Boolean);
|
|
1227
|
-
const
|
|
1661
|
+
const result = await postProjectServiceJsonOrLocal("/threads/send", {
|
|
1228
1662
|
threadId,
|
|
1229
1663
|
from: opts.from,
|
|
1230
1664
|
to,
|
|
1231
1665
|
kind: opts.kind ?? "note",
|
|
1232
1666
|
body,
|
|
1233
|
-
})
|
|
1234
|
-
|
|
1667
|
+
}, () => {
|
|
1668
|
+
if (!readThread(threadId)) {
|
|
1669
|
+
console.error(`aimux: thread not found: ${threadId}`);
|
|
1670
|
+
process.exit(1);
|
|
1671
|
+
}
|
|
1672
|
+
return sendThreadMessage({
|
|
1673
|
+
threadId,
|
|
1674
|
+
from: opts.from,
|
|
1675
|
+
to,
|
|
1676
|
+
kind: opts.kind ?? "note",
|
|
1677
|
+
body,
|
|
1678
|
+
});
|
|
1679
|
+
});
|
|
1680
|
+
console.log(result.message.id);
|
|
1235
1681
|
});
|
|
1236
1682
|
threadCmd
|
|
1237
1683
|
.command("mark-seen")
|
|
1238
1684
|
.description("Mark a thread as seen for a participant")
|
|
1239
1685
|
.argument("<threadId>")
|
|
1240
1686
|
.requiredOption("--session <sessionId>", "Participant session id")
|
|
1241
|
-
.action((threadId, opts) => {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1687
|
+
.action(async (threadId, opts) => {
|
|
1688
|
+
await postProjectServiceJsonOrLocal("/threads/mark-seen", { threadId, session: opts.session }, () => {
|
|
1689
|
+
const thread = markThreadSeen(threadId, opts.session);
|
|
1690
|
+
if (!thread) {
|
|
1691
|
+
console.error(`aimux: thread not found: ${threadId}`);
|
|
1692
|
+
process.exit(1);
|
|
1693
|
+
}
|
|
1694
|
+
return { ok: true, thread };
|
|
1695
|
+
});
|
|
1247
1696
|
console.log("ok");
|
|
1248
1697
|
});
|
|
1249
1698
|
threadCmd
|
|
@@ -1383,7 +1832,11 @@ handoffCmd
|
|
|
1383
1832
|
catch {
|
|
1384
1833
|
const result = sendHandoff({
|
|
1385
1834
|
from: opts.from ?? "user",
|
|
1386
|
-
to: to
|
|
1835
|
+
to: to?.length
|
|
1836
|
+
? to
|
|
1837
|
+
: [opts.assignee, opts.tool]
|
|
1838
|
+
.map((value) => value?.trim())
|
|
1839
|
+
.filter((value) => Boolean(value)),
|
|
1387
1840
|
body,
|
|
1388
1841
|
title: opts.title,
|
|
1389
1842
|
worktreePath: opts.worktree,
|
|
@@ -1453,10 +1906,20 @@ taskCmd
|
|
|
1453
1906
|
.option("--session <sessionId>", "Filter to tasks assigned to or created by a session")
|
|
1454
1907
|
.option("--status <status>", "Filter by task status")
|
|
1455
1908
|
.option("--json", "Emit JSON")
|
|
1456
|
-
.action((opts) => {
|
|
1457
|
-
const
|
|
1458
|
-
|
|
1459
|
-
.
|
|
1909
|
+
.action(async (opts) => {
|
|
1910
|
+
const params = new URLSearchParams();
|
|
1911
|
+
if (opts.session)
|
|
1912
|
+
params.set("session", opts.session);
|
|
1913
|
+
if (opts.status)
|
|
1914
|
+
params.set("status", opts.status);
|
|
1915
|
+
const query = params.toString();
|
|
1916
|
+
const result = await getProjectServiceJsonOrLocal(`/tasks${query ? `?${query}` : ""}`, () => ({
|
|
1917
|
+
ok: true,
|
|
1918
|
+
tasks: readAllTasks()
|
|
1919
|
+
.filter((task) => !opts.session || task.assignedTo === opts.session || task.assignedBy === opts.session)
|
|
1920
|
+
.filter((task) => !opts.status || task.status === opts.status),
|
|
1921
|
+
}));
|
|
1922
|
+
const tasks = Array.isArray(result.tasks) ? result.tasks : [];
|
|
1460
1923
|
if (opts.json) {
|
|
1461
1924
|
console.log(JSON.stringify({ tasks }, null, 2));
|
|
1462
1925
|
return;
|
|
@@ -1477,14 +1940,23 @@ taskCmd
|
|
|
1477
1940
|
.description("Show an orchestrated task")
|
|
1478
1941
|
.argument("<taskId>")
|
|
1479
1942
|
.option("--json", "Emit JSON")
|
|
1480
|
-
.action((taskId, opts) => {
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1943
|
+
.action(async (taskId, opts) => {
|
|
1944
|
+
const detail = await getProjectServiceJsonOrLocal(`/tasks/${encodeURIComponent(taskId)}`, () => {
|
|
1945
|
+
const task = readTask(taskId);
|
|
1946
|
+
if (!task)
|
|
1947
|
+
return null;
|
|
1948
|
+
return {
|
|
1949
|
+
ok: true,
|
|
1950
|
+
task,
|
|
1951
|
+
thread: task.threadId ? readThread(task.threadId) : undefined,
|
|
1952
|
+
messages: task.threadId ? readMessages(task.threadId) : [],
|
|
1953
|
+
};
|
|
1954
|
+
});
|
|
1955
|
+
if (!detail?.task) {
|
|
1483
1956
|
console.error(`aimux: task not found: ${taskId}`);
|
|
1484
1957
|
process.exit(1);
|
|
1485
1958
|
}
|
|
1486
|
-
const
|
|
1487
|
-
const messages = task.threadId ? readMessages(task.threadId) : [];
|
|
1959
|
+
const { task, thread, messages } = detail;
|
|
1488
1960
|
if (opts.json) {
|
|
1489
1961
|
console.log(JSON.stringify({ task, thread, messages }, null, 2));
|
|
1490
1962
|
return;
|
|
@@ -1745,12 +2217,17 @@ worktreeCmd
|
|
|
1745
2217
|
.option("--json", "Emit JSON")
|
|
1746
2218
|
.action(async (opts) => {
|
|
1747
2219
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
1748
|
-
|
|
2220
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
2221
|
+
const result = await getLiveProjectServiceJsonOrLocal(projectRoot, "/worktrees", () => ({
|
|
2222
|
+
ok: true,
|
|
2223
|
+
worktrees: listVisibleLocalWorktrees(projectRoot),
|
|
2224
|
+
}));
|
|
2225
|
+
const worktrees = result.worktrees ?? [];
|
|
1749
2226
|
if (opts.json) {
|
|
1750
2227
|
console.log(JSON.stringify(worktrees, null, 2));
|
|
1751
2228
|
return;
|
|
1752
2229
|
}
|
|
1753
|
-
printWorktrees(projectRoot);
|
|
2230
|
+
printWorktrees(projectRoot, worktrees);
|
|
1754
2231
|
});
|
|
1755
2232
|
worktreeCmd
|
|
1756
2233
|
.command("create <name>")
|
|
@@ -1760,7 +2237,12 @@ worktreeCmd
|
|
|
1760
2237
|
.action(async (name, opts) => {
|
|
1761
2238
|
try {
|
|
1762
2239
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
1763
|
-
|
|
2240
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
2241
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/worktrees/create", { name }, () => {
|
|
2242
|
+
const mux = new Multiplexer();
|
|
2243
|
+
return mux.createDesktopWorktree(name);
|
|
2244
|
+
});
|
|
2245
|
+
const createdPath = result.path;
|
|
1764
2246
|
if (opts.json) {
|
|
1765
2247
|
console.log(JSON.stringify({
|
|
1766
2248
|
ok: true,
|
|
@@ -1778,6 +2260,114 @@ worktreeCmd
|
|
|
1778
2260
|
process.exit(1);
|
|
1779
2261
|
}
|
|
1780
2262
|
});
|
|
2263
|
+
worktreeCmd
|
|
2264
|
+
.command("remove <path>")
|
|
2265
|
+
.description("Remove a git worktree")
|
|
2266
|
+
.option("--project <path>", "Project path")
|
|
2267
|
+
.option("--json", "Emit JSON")
|
|
2268
|
+
.action(async (targetPath, opts) => {
|
|
2269
|
+
try {
|
|
2270
|
+
const inputCwd = process.cwd();
|
|
2271
|
+
const resolvedPath = pathResolve(inputCwd, targetPath);
|
|
2272
|
+
const projectRoot = await prepareProjectContext(opts.project);
|
|
2273
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
2274
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/worktrees/remove", { path: resolvedPath }, () => {
|
|
2275
|
+
const mux = new Multiplexer();
|
|
2276
|
+
return mux.removeDesktopWorktree(resolvedPath);
|
|
2277
|
+
});
|
|
2278
|
+
if (opts.json) {
|
|
2279
|
+
console.log(JSON.stringify({ ok: true, projectRoot, path: result.path, status: result.status }, null, 2));
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
console.log(`${result.status === "removing" ? "removing" : "removed"} ${result.path}`);
|
|
2283
|
+
}
|
|
2284
|
+
catch (err) {
|
|
2285
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2286
|
+
console.error(`Error: ${msg}`);
|
|
2287
|
+
process.exit(1);
|
|
2288
|
+
}
|
|
2289
|
+
});
|
|
2290
|
+
worktreeCmd
|
|
2291
|
+
.command("graveyard <path>")
|
|
2292
|
+
.description("Move a worktree to the graveyard without deleting the checkout")
|
|
2293
|
+
.option("--project <path>", "Project path")
|
|
2294
|
+
.option("--json", "Emit JSON")
|
|
2295
|
+
.action(async (targetPath, opts) => {
|
|
2296
|
+
try {
|
|
2297
|
+
const inputCwd = process.cwd();
|
|
2298
|
+
const resolvedPath = pathResolve(inputCwd, targetPath);
|
|
2299
|
+
const projectRoot = await prepareProjectContext(opts.project);
|
|
2300
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
2301
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/worktrees/graveyard", { path: resolvedPath }, () => {
|
|
2302
|
+
const mux = new Multiplexer();
|
|
2303
|
+
return mux.graveyardDesktopWorktree(resolvedPath);
|
|
2304
|
+
});
|
|
2305
|
+
if (opts.json) {
|
|
2306
|
+
console.log(JSON.stringify({ ok: true, projectRoot, path: result.path, status: result.status }, null, 2));
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
console.log(`graveyarded ${result.path}`);
|
|
2310
|
+
}
|
|
2311
|
+
catch (err) {
|
|
2312
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2313
|
+
console.error(`Error: ${msg}`);
|
|
2314
|
+
process.exit(1);
|
|
2315
|
+
}
|
|
2316
|
+
});
|
|
2317
|
+
worktreeCmd
|
|
2318
|
+
.command("resurrect <path>")
|
|
2319
|
+
.description("Restore a graveyarded worktree to the active worktree list")
|
|
2320
|
+
.option("--project <path>", "Project path")
|
|
2321
|
+
.option("--json", "Emit JSON")
|
|
2322
|
+
.action(async (targetPath, opts) => {
|
|
2323
|
+
try {
|
|
2324
|
+
const inputCwd = process.cwd();
|
|
2325
|
+
const resolvedPath = pathResolve(inputCwd, targetPath);
|
|
2326
|
+
const projectRoot = await prepareProjectContext(opts.project);
|
|
2327
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
2328
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/graveyard/worktrees/resurrect", { path: resolvedPath }, () => {
|
|
2329
|
+
const mux = new Multiplexer();
|
|
2330
|
+
return mux.resurrectGraveyardWorktree(resolvedPath);
|
|
2331
|
+
});
|
|
2332
|
+
if (opts.json) {
|
|
2333
|
+
console.log(JSON.stringify({ ok: true, projectRoot, path: result.path, status: result.status }, null, 2));
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
console.log(`resurrected ${result.path}`);
|
|
2337
|
+
}
|
|
2338
|
+
catch (err) {
|
|
2339
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2340
|
+
console.error(`Error: ${msg}`);
|
|
2341
|
+
process.exit(1);
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
worktreeCmd
|
|
2345
|
+
.command("delete-graveyard <path>")
|
|
2346
|
+
.description("Permanently delete a graveyarded worktree entry")
|
|
2347
|
+
.option("--project <path>", "Project path")
|
|
2348
|
+
.option("--json", "Emit JSON")
|
|
2349
|
+
.action(async (targetPath, opts) => {
|
|
2350
|
+
try {
|
|
2351
|
+
const inputCwd = process.cwd();
|
|
2352
|
+
const resolvedPath = pathResolve(inputCwd, targetPath);
|
|
2353
|
+
const projectRoot = await prepareProjectContext(opts.project);
|
|
2354
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
2355
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/graveyard/worktrees/delete", { path: resolvedPath }, () => {
|
|
2356
|
+
const mux = new Multiplexer();
|
|
2357
|
+
return mux.deleteGraveyardWorktree(resolvedPath);
|
|
2358
|
+
});
|
|
2359
|
+
if (opts.json) {
|
|
2360
|
+
console.log(JSON.stringify({ ok: true, projectRoot, path: result.path, status: result.status }, null, 2));
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
console.log(`deleted ${result.path}`);
|
|
2364
|
+
}
|
|
2365
|
+
catch (err) {
|
|
2366
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2367
|
+
console.error(`Error: ${msg}`);
|
|
2368
|
+
process.exit(1);
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
1781
2371
|
program
|
|
1782
2372
|
.command("spawn")
|
|
1783
2373
|
.description("Spawn a fresh agent session using the same flow as the dashboard")
|
|
@@ -1870,27 +2460,26 @@ graveyardCmd
|
|
|
1870
2460
|
.option("--project <path>", "Project path")
|
|
1871
2461
|
.option("--json", "Emit JSON")
|
|
1872
2462
|
.action(async (opts) => {
|
|
1873
|
-
await prepareProjectContext(opts.project);
|
|
1874
|
-
|
|
2463
|
+
const projectRoot = await prepareProjectContext(opts.project);
|
|
2464
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
1875
2465
|
try {
|
|
1876
|
-
const graveyard =
|
|
2466
|
+
const graveyard = await getLiveProjectServiceJsonOrLocal(projectRoot, "/graveyard", () => ({
|
|
2467
|
+
ok: true,
|
|
2468
|
+
entries: listTopologySessionStates({ statuses: ["graveyard"] }),
|
|
2469
|
+
worktrees: listTopologyWorktreeGraveyard(),
|
|
2470
|
+
}));
|
|
1877
2471
|
if (opts.json) {
|
|
1878
|
-
console.log(JSON.stringify(
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
console.log("Graveyard is empty.");
|
|
2472
|
+
console.log(JSON.stringify({
|
|
2473
|
+
entries: Array.isArray(graveyard.entries) ? graveyard.entries : [],
|
|
2474
|
+
worktrees: Array.isArray(graveyard.worktrees) ? graveyard.worktrees : [],
|
|
2475
|
+
}, null, 2));
|
|
1883
2476
|
return;
|
|
1884
2477
|
}
|
|
1885
|
-
|
|
1886
|
-
console.log("-".repeat(70));
|
|
1887
|
-
for (const s of graveyard) {
|
|
1888
|
-
console.log((s.id ?? "?").padEnd(25) + (s.command ?? s.tool ?? "?").padEnd(15) + (s.backendSessionId ?? "(none)"));
|
|
1889
|
-
}
|
|
2478
|
+
printGraveyard(graveyard);
|
|
1890
2479
|
}
|
|
1891
2480
|
catch {
|
|
1892
2481
|
if (opts.json) {
|
|
1893
|
-
console.log(
|
|
2482
|
+
console.log(JSON.stringify({ entries: [], worktrees: [] }, null, 2));
|
|
1894
2483
|
return;
|
|
1895
2484
|
}
|
|
1896
2485
|
console.log("Graveyard is empty.");
|
|
@@ -1904,8 +2493,11 @@ graveyardCmd
|
|
|
1904
2493
|
.action(async (id, opts) => {
|
|
1905
2494
|
try {
|
|
1906
2495
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
1907
|
-
|
|
1908
|
-
const result = await
|
|
2496
|
+
await ensureDaemonProjectReady(projectRoot);
|
|
2497
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/agents/kill", { sessionId: id }, () => {
|
|
2498
|
+
const mux = new Multiplexer();
|
|
2499
|
+
return mux.sendAgentToGraveyard(id);
|
|
2500
|
+
});
|
|
1909
2501
|
if (opts.json) {
|
|
1910
2502
|
console.log(JSON.stringify({
|
|
1911
2503
|
ok: true,
|
|
@@ -1930,44 +2522,23 @@ graveyardCmd
|
|
|
1930
2522
|
.option("--project <path>", "Project path")
|
|
1931
2523
|
.option("--json", "Emit JSON")
|
|
1932
2524
|
.action(async (id, opts) => {
|
|
1933
|
-
await prepareProjectContext(opts.project);
|
|
1934
|
-
const graveyardPath = getGraveyardPath();
|
|
1935
|
-
if (!existsSync(graveyardPath)) {
|
|
1936
|
-
console.error("Graveyard is empty.");
|
|
1937
|
-
process.exit(1);
|
|
1938
|
-
}
|
|
1939
2525
|
try {
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
}
|
|
1946
|
-
const restored = graveyard.splice(idx, 1)[0];
|
|
1947
|
-
writeFileSync(graveyardPath, JSON.stringify(graveyard, null, 2) + "\n");
|
|
1948
|
-
const statePath = getStatePath();
|
|
1949
|
-
let state = {
|
|
1950
|
-
savedAt: new Date().toISOString(),
|
|
1951
|
-
cwd: process.cwd(),
|
|
1952
|
-
sessions: [],
|
|
1953
|
-
};
|
|
1954
|
-
if (existsSync(statePath)) {
|
|
1955
|
-
try {
|
|
1956
|
-
state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
1957
|
-
}
|
|
1958
|
-
catch { }
|
|
1959
|
-
}
|
|
1960
|
-
state.sessions.push(restored);
|
|
1961
|
-
writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
|
|
2526
|
+
const projectRoot = await prepareProjectContext(opts.project);
|
|
2527
|
+
await ensureDaemonProjectReady(projectRoot);
|
|
2528
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/graveyard/resurrect", { sessionId: id }, () => {
|
|
2529
|
+
const mux = new Multiplexer();
|
|
2530
|
+
return mux.resurrectGraveyardSession(id);
|
|
2531
|
+
});
|
|
1962
2532
|
if (opts.json) {
|
|
1963
2533
|
console.log(JSON.stringify({
|
|
1964
2534
|
ok: true,
|
|
1965
|
-
|
|
1966
|
-
|
|
2535
|
+
projectRoot,
|
|
2536
|
+
sessionId: result.sessionId,
|
|
2537
|
+
status: result.status,
|
|
1967
2538
|
}, null, 2));
|
|
1968
2539
|
return;
|
|
1969
2540
|
}
|
|
1970
|
-
console.log(`
|
|
2541
|
+
console.log(`resurrected ${result.sessionId}`);
|
|
1971
2542
|
}
|
|
1972
2543
|
catch (err) {
|
|
1973
2544
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2062,8 +2633,11 @@ program
|
|
|
2062
2633
|
.action(async (sessionId, opts) => {
|
|
2063
2634
|
try {
|
|
2064
2635
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
2065
|
-
|
|
2066
|
-
const result = await
|
|
2636
|
+
await ensureDaemonProjectReady(projectRoot);
|
|
2637
|
+
const result = await postLiveProjectServiceJsonOrLocal(projectRoot, "/agents/kill", { sessionId }, () => {
|
|
2638
|
+
const mux = new Multiplexer();
|
|
2639
|
+
return mux.sendAgentToGraveyard(sessionId);
|
|
2640
|
+
});
|
|
2067
2641
|
if (opts.json) {
|
|
2068
2642
|
console.log(JSON.stringify({
|
|
2069
2643
|
ok: true,
|
|
@@ -2115,6 +2689,75 @@ program
|
|
|
2115
2689
|
const statuslineCmd = program.command("statusline").description("Manage Claude Code statusline integration");
|
|
2116
2690
|
const doctorCmd = program.command("doctor").description("Inspect aimux runtime state");
|
|
2117
2691
|
const repairCmd = program.command("repair").description("Repair the current project runtime in place");
|
|
2692
|
+
program
|
|
2693
|
+
.command("debug-state <target>")
|
|
2694
|
+
.description("Read-only debug snapshot for one session, service, backend session, or worktree")
|
|
2695
|
+
.action((target) => {
|
|
2696
|
+
const report = buildDebugStateReport({ cwd: process.cwd(), target });
|
|
2697
|
+
console.log(renderDebugStateReport(report));
|
|
2698
|
+
});
|
|
2699
|
+
const migrationCmd = program
|
|
2700
|
+
.command("migration")
|
|
2701
|
+
.description("Explicit runtime-core migration audit, import, and rollback tooling");
|
|
2702
|
+
migrationCmd
|
|
2703
|
+
.command("audit")
|
|
2704
|
+
.description("Inspect legacy runtime artifacts without mutating project state")
|
|
2705
|
+
.option("--project <path>", "Project path", process.cwd())
|
|
2706
|
+
.action((opts) => {
|
|
2707
|
+
const projectRoot = resolveProjectRoot(pathResolve(opts.project));
|
|
2708
|
+
console.log(renderRuntimeMigrationReport(buildRuntimeMigrationReport({ cwd: projectRoot })));
|
|
2709
|
+
});
|
|
2710
|
+
migrationCmd
|
|
2711
|
+
.command("import")
|
|
2712
|
+
.description("Import legacy exchange artifacts into runtime-exchange.yaml with a rollback manifest")
|
|
2713
|
+
.option("--project <path>", "Project path", process.cwd())
|
|
2714
|
+
.action(async (opts) => {
|
|
2715
|
+
const projectRoot = resolveProjectRoot(pathResolve(opts.project));
|
|
2716
|
+
await initPaths(projectRoot);
|
|
2717
|
+
console.log(renderRuntimeMigrationImportResult(importRuntimeMigration({ cwd: projectRoot })));
|
|
2718
|
+
});
|
|
2719
|
+
migrationCmd
|
|
2720
|
+
.command("rollback <manifest>")
|
|
2721
|
+
.description("Restore files recorded by a runtime migration manifest")
|
|
2722
|
+
.action((manifest) => {
|
|
2723
|
+
console.log(renderRuntimeMigrationRollbackResult(rollbackRuntimeMigration(pathResolve(manifest))));
|
|
2724
|
+
});
|
|
2725
|
+
const logsCmd = program.command("logs").description("Inspect persistent aimux logs");
|
|
2726
|
+
logsCmd
|
|
2727
|
+
.command("path")
|
|
2728
|
+
.description("Print the active log file path")
|
|
2729
|
+
.option("--daemon", "Show the global daemon log path")
|
|
2730
|
+
.option("--project <path>", "Project path")
|
|
2731
|
+
.action((opts) => {
|
|
2732
|
+
console.log(selectedLogPath(opts));
|
|
2733
|
+
});
|
|
2734
|
+
logsCmd
|
|
2735
|
+
.command("tail")
|
|
2736
|
+
.description("Print recent log lines")
|
|
2737
|
+
.option("--daemon", "Tail the global daemon log")
|
|
2738
|
+
.option("--project <path>", "Project path")
|
|
2739
|
+
.option("-n, --lines <number>", "Number of lines to print", "80")
|
|
2740
|
+
.action((opts) => {
|
|
2741
|
+
const path = selectedLogPath(opts);
|
|
2742
|
+
const output = readLastLogLines(path, parseLineCount(opts.lines));
|
|
2743
|
+
if (output) {
|
|
2744
|
+
console.log(output);
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
console.error(`No log entries at ${path}`);
|
|
2748
|
+
process.exit(1);
|
|
2749
|
+
});
|
|
2750
|
+
logsCmd
|
|
2751
|
+
.command("clear")
|
|
2752
|
+
.description("Clear the active log file")
|
|
2753
|
+
.option("--daemon", "Clear the global daemon log")
|
|
2754
|
+
.option("--project <path>", "Project path")
|
|
2755
|
+
.action((opts) => {
|
|
2756
|
+
const path = selectedLogPath(opts);
|
|
2757
|
+
mkdirSync(pathDirname(path), { recursive: true });
|
|
2758
|
+
writeFileSync(path, "");
|
|
2759
|
+
console.log(`Cleared ${path}`);
|
|
2760
|
+
});
|
|
2118
2761
|
doctorCmd
|
|
2119
2762
|
.command("tmux")
|
|
2120
2763
|
.description("Inspect managed tmux runtime state")
|
|
@@ -2234,20 +2877,29 @@ program
|
|
|
2234
2877
|
force: true,
|
|
2235
2878
|
}, () => {
|
|
2236
2879
|
const kind = (opts.kind?.trim() || "notification");
|
|
2237
|
-
const
|
|
2880
|
+
const sessionId = opts.session?.trim() || undefined;
|
|
2881
|
+
const context = sessionId ? metadataDisplayContext(loadMetadataState().sessions[sessionId]) : undefined;
|
|
2882
|
+
const alert = contextualizeAlertInput({
|
|
2883
|
+
kind,
|
|
2884
|
+
sessionId,
|
|
2238
2885
|
title,
|
|
2886
|
+
message: [opts.subtitle?.trim(), body].filter(Boolean).join(" — "),
|
|
2887
|
+
forceNotify: true,
|
|
2888
|
+
}, context);
|
|
2889
|
+
const notification = upsertNotification({
|
|
2890
|
+
title: alert.title,
|
|
2239
2891
|
subtitle: opts.subtitle?.trim() || undefined,
|
|
2240
2892
|
body,
|
|
2241
|
-
sessionId
|
|
2893
|
+
sessionId,
|
|
2242
2894
|
kind,
|
|
2243
2895
|
});
|
|
2244
2896
|
notifyAlert({
|
|
2245
2897
|
type: "alert",
|
|
2246
2898
|
kind,
|
|
2247
2899
|
projectId: getProjectId(),
|
|
2248
|
-
sessionId
|
|
2249
|
-
title,
|
|
2250
|
-
message:
|
|
2900
|
+
sessionId,
|
|
2901
|
+
title: alert.title,
|
|
2902
|
+
message: alert.message,
|
|
2251
2903
|
ts: notification.createdAt,
|
|
2252
2904
|
forceNotify: true,
|
|
2253
2905
|
});
|
|
@@ -2273,6 +2925,9 @@ program
|
|
|
2273
2925
|
const payload = parseClaudeHookPayload(rawInput);
|
|
2274
2926
|
const sessionId = await resolveClaudeHookSessionId(opts.session, payload.session_id);
|
|
2275
2927
|
const result = { ok: true, action, sessionId };
|
|
2928
|
+
if (payload.session_id) {
|
|
2929
|
+
result.backendSessionId = payload.session_id;
|
|
2930
|
+
}
|
|
2276
2931
|
const setActivity = async (activity) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-activity", { session: sessionId, activity }, () => metadataTracker.setActivity(sessionId, activity, projectRoot));
|
|
2277
2932
|
const setAttention = async (attention) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-attention", { session: sessionId, attention }, () => metadataTracker.setAttention(sessionId, attention, projectRoot));
|
|
2278
2933
|
const emitEvent = async (kind, message, tone) => postLiveProjectServiceJsonOrLocal(projectRoot, "/event", { session: sessionId, event: { kind, message, tone } }, () => metadataTracker.emit(sessionId, { kind, message, tone }, projectRoot));
|
|
@@ -2280,6 +2935,21 @@ program
|
|
|
2280
2935
|
ok: true,
|
|
2281
2936
|
cleared: clearNotifications({ sessionId }),
|
|
2282
2937
|
}));
|
|
2938
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path.trim() : "";
|
|
2939
|
+
if (transcriptPath) {
|
|
2940
|
+
const context = { transcriptPath };
|
|
2941
|
+
await postLiveProjectServiceJsonOrLocal(projectRoot, "/set-context", { session: sessionId, context }, () => {
|
|
2942
|
+
updateSessionMetadata(sessionId, (current) => ({
|
|
2943
|
+
...current,
|
|
2944
|
+
context: {
|
|
2945
|
+
...(current.context ?? {}),
|
|
2946
|
+
...context,
|
|
2947
|
+
},
|
|
2948
|
+
}), projectRoot);
|
|
2949
|
+
return { ok: true };
|
|
2950
|
+
});
|
|
2951
|
+
result.transcriptPath = transcriptPath;
|
|
2952
|
+
}
|
|
2283
2953
|
switch (action) {
|
|
2284
2954
|
case "session-start":
|
|
2285
2955
|
case "active":
|