aimux-cli 0.1.15 → 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 +824 -152
- 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 +31 -15
- 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,20 +440,101 @@ 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
|
+
}
|
|
506
|
+
const pkgJsonPath = pathJoin(pathDirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
507
|
+
const pkgVersion = JSON.parse(readFileSync(pkgJsonPath, "utf8")).version;
|
|
374
508
|
program
|
|
375
509
|
.name("aimux")
|
|
376
510
|
.description("Native CLI agent multiplexer")
|
|
377
|
-
.version(
|
|
511
|
+
.version(pkgVersion)
|
|
378
512
|
.argument("[tool]", "Tool to run (e.g. claude, codex, aider)")
|
|
379
513
|
.argument("[args...]", "Arguments to pass to the tool")
|
|
380
514
|
.option("--resume", "Resume previous sessions using native tool resume")
|
|
381
515
|
.option("--restore", "Start fresh sessions with injected history context")
|
|
382
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")
|
|
383
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
|
+
}
|
|
384
527
|
const opts = typeof actionCommand?.opts === "function" ? actionCommand.opts() : {};
|
|
385
|
-
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;
|
|
386
535
|
const projectRoot = requestedProject ? resolveProjectRoot(pathResolve(requestedProject)) : undefined;
|
|
387
536
|
await initPaths(projectRoot);
|
|
537
|
+
configureLoggingForCommand(actionCommand);
|
|
388
538
|
})
|
|
389
539
|
.action(async (tool, args, opts) => {
|
|
390
540
|
const originalCwd = process.cwd();
|
|
@@ -445,11 +595,19 @@ program
|
|
|
445
595
|
process.on("SIGTERM", shutdown);
|
|
446
596
|
process.on("uncaughtException", (err) => {
|
|
447
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
|
+
});
|
|
448
602
|
console.error(err);
|
|
449
603
|
process.exit(1);
|
|
450
604
|
});
|
|
451
605
|
process.on("unhandledRejection", (reason) => {
|
|
452
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
|
+
});
|
|
453
611
|
console.error(reason);
|
|
454
612
|
process.exit(1);
|
|
455
613
|
});
|
|
@@ -531,8 +689,11 @@ program
|
|
|
531
689
|
try {
|
|
532
690
|
if (sessionId) {
|
|
533
691
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
534
|
-
|
|
535
|
-
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
|
+
});
|
|
536
697
|
if (opts.json) {
|
|
537
698
|
console.log(JSON.stringify({
|
|
538
699
|
ok: true,
|
|
@@ -555,6 +716,7 @@ program
|
|
|
555
716
|
projectServiceStopped: result.projectServiceStopped,
|
|
556
717
|
tmuxSessionsKilled: result.tmuxSessionsKilled,
|
|
557
718
|
}, null, 2));
|
|
719
|
+
process.exitCode = 1;
|
|
558
720
|
return;
|
|
559
721
|
}
|
|
560
722
|
console.log(`Stopped project runtime for ${projectRoot}`);
|
|
@@ -606,6 +768,47 @@ program
|
|
|
606
768
|
const hostCmd = program
|
|
607
769
|
.command("host")
|
|
608
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
|
+
});
|
|
609
812
|
program
|
|
610
813
|
.command("serve")
|
|
611
814
|
.description("Advanced: ensure the legacy daemon-backed project control service is running")
|
|
@@ -718,27 +921,22 @@ hostCmd
|
|
|
718
921
|
console.log(`Restarted project service for ${dashboardSession.sessionName}`);
|
|
719
922
|
});
|
|
720
923
|
hostCmd
|
|
721
|
-
.command("
|
|
722
|
-
.description("
|
|
723
|
-
.
|
|
724
|
-
.
|
|
725
|
-
.
|
|
726
|
-
.option("--submit", "Submit after writing the input")
|
|
727
|
-
.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) => {
|
|
728
929
|
await initPaths();
|
|
729
|
-
const
|
|
730
|
-
if (
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const result = await postProjectServiceJson("/agents/input", {
|
|
734
|
-
sessionId,
|
|
735
|
-
data: payload,
|
|
736
|
-
submit: opts.submit === true,
|
|
737
|
-
});
|
|
738
|
-
if (result.accepted === false) {
|
|
739
|
-
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;
|
|
740
934
|
}
|
|
741
|
-
|
|
935
|
+
if (opts.raw) {
|
|
936
|
+
console.log(readFileSync(path, "utf-8"));
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
console.log(path);
|
|
742
940
|
});
|
|
743
941
|
hostCmd
|
|
744
942
|
.command("agent-read")
|
|
@@ -928,9 +1126,20 @@ daemonCmd
|
|
|
928
1126
|
.action(async (opts) => {
|
|
929
1127
|
const info = loadDaemonInfo();
|
|
930
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
|
+
}
|
|
931
1139
|
const payload = {
|
|
932
1140
|
daemon: info,
|
|
933
1141
|
projects: Object.values(state.projects),
|
|
1142
|
+
relay,
|
|
934
1143
|
};
|
|
935
1144
|
if (opts.json) {
|
|
936
1145
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -942,6 +1151,13 @@ daemonCmd
|
|
|
942
1151
|
}
|
|
943
1152
|
console.log(`Daemon pid=${info.pid} port=${info.port}`);
|
|
944
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
|
+
}
|
|
945
1161
|
});
|
|
946
1162
|
daemonCmd
|
|
947
1163
|
.command("projects")
|
|
@@ -1001,11 +1217,19 @@ program
|
|
|
1001
1217
|
process.on("SIGTERM", shutdown);
|
|
1002
1218
|
process.on("uncaughtException", (err) => {
|
|
1003
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
|
+
});
|
|
1004
1224
|
console.error(err);
|
|
1005
1225
|
process.exit(1);
|
|
1006
1226
|
});
|
|
1007
1227
|
process.on("unhandledRejection", (reason) => {
|
|
1008
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
|
+
});
|
|
1009
1233
|
console.error(reason);
|
|
1010
1234
|
process.exit(1);
|
|
1011
1235
|
});
|
|
@@ -1039,15 +1263,8 @@ projectsCmd
|
|
|
1039
1263
|
return;
|
|
1040
1264
|
}
|
|
1041
1265
|
for (const project of projects) {
|
|
1042
|
-
const liveBadge = project.
|
|
1266
|
+
const liveBadge = project.serviceAlive ? "live" : "idle";
|
|
1043
1267
|
console.log(`${project.name} ${liveBadge} ${project.path}`);
|
|
1044
|
-
if (project.sessions.length === 0)
|
|
1045
|
-
continue;
|
|
1046
|
-
for (const session of project.sessions) {
|
|
1047
|
-
const label = session.label ? ` ${session.label}` : "";
|
|
1048
|
-
const headline = session.headline ? ` - ${session.headline}` : "";
|
|
1049
|
-
console.log(` ${session.id} ${session.tool} ${session.status}${label}${headline}`);
|
|
1050
|
-
}
|
|
1051
1268
|
}
|
|
1052
1269
|
});
|
|
1053
1270
|
program
|
|
@@ -1073,6 +1290,166 @@ program
|
|
|
1073
1290
|
llmCompact(sessionIds);
|
|
1074
1291
|
console.log(`Done. Summary written to ${getContextDir()}/summary.md`);
|
|
1075
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
|
+
});
|
|
1076
1453
|
async function prepareProjectContext(requestedProject) {
|
|
1077
1454
|
const requestedPath = pathResolve(requestedProject ?? process.cwd());
|
|
1078
1455
|
const projectRoot = resolveProjectRoot(requestedPath);
|
|
@@ -1080,9 +1457,13 @@ async function prepareProjectContext(requestedProject) {
|
|
|
1080
1457
|
process.chdir(projectRoot);
|
|
1081
1458
|
return projectRoot;
|
|
1082
1459
|
}
|
|
1083
|
-
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) {
|
|
1084
1465
|
try {
|
|
1085
|
-
const worktrees = listWorktrees(projectRoot);
|
|
1466
|
+
const worktrees = worktreesInput ?? listWorktrees(projectRoot);
|
|
1086
1467
|
if (worktrees.length === 0) {
|
|
1087
1468
|
console.log("No worktrees found.");
|
|
1088
1469
|
return;
|
|
@@ -1099,9 +1480,55 @@ function printWorktrees(projectRoot) {
|
|
|
1099
1480
|
process.exit(1);
|
|
1100
1481
|
}
|
|
1101
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
|
+
}
|
|
1102
1513
|
const worktreeCmd = program.command("worktree").description("Manage git worktrees");
|
|
1103
|
-
|
|
1104
|
-
|
|
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 ?? []);
|
|
1105
1532
|
});
|
|
1106
1533
|
const threadCmd = program.command("thread").description("Inspect and manage orchestration threads");
|
|
1107
1534
|
program
|
|
@@ -1109,8 +1536,9 @@ program
|
|
|
1109
1536
|
.description("Alias for thread list")
|
|
1110
1537
|
.option("--session <sessionId>", "Filter to threads involving a session")
|
|
1111
1538
|
.option("--json", "Emit JSON")
|
|
1112
|
-
.action((opts) => {
|
|
1113
|
-
const
|
|
1539
|
+
.action(async (opts) => {
|
|
1540
|
+
const query = opts.session ? `?session=${encodeURIComponent(opts.session)}` : "";
|
|
1541
|
+
const summaries = await getProjectServiceJsonOrLocal(`/threads${query}`, () => listThreadSummaries(opts.session));
|
|
1114
1542
|
if (opts.json) {
|
|
1115
1543
|
console.log(JSON.stringify(summaries, null, 2));
|
|
1116
1544
|
return;
|
|
@@ -1134,8 +1562,9 @@ threadCmd
|
|
|
1134
1562
|
.description("List orchestration threads")
|
|
1135
1563
|
.option("--session <sessionId>", "Filter to threads involving a session")
|
|
1136
1564
|
.option("--json", "Emit JSON")
|
|
1137
|
-
.action((opts) => {
|
|
1138
|
-
const
|
|
1565
|
+
.action(async (opts) => {
|
|
1566
|
+
const query = opts.session ? `?session=${encodeURIComponent(opts.session)}` : "";
|
|
1567
|
+
const summaries = await getProjectServiceJsonOrLocal(`/threads${query}`, () => listThreadSummaries(opts.session));
|
|
1139
1568
|
if (opts.json) {
|
|
1140
1569
|
console.log(JSON.stringify(summaries, null, 2));
|
|
1141
1570
|
return;
|
|
@@ -1159,13 +1588,18 @@ threadCmd
|
|
|
1159
1588
|
.description("Show a thread and its messages")
|
|
1160
1589
|
.argument("<threadId>")
|
|
1161
1590
|
.option("--json", "Emit JSON")
|
|
1162
|
-
.action((threadId, opts) => {
|
|
1163
|
-
const
|
|
1164
|
-
|
|
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) {
|
|
1165
1599
|
console.error(`aimux: thread not found: ${threadId}`);
|
|
1166
1600
|
process.exit(1);
|
|
1167
1601
|
}
|
|
1168
|
-
const messages =
|
|
1602
|
+
const { thread, messages } = detail;
|
|
1169
1603
|
if (opts.json) {
|
|
1170
1604
|
console.log(JSON.stringify({ thread, messages }, null, 2));
|
|
1171
1605
|
return;
|
|
@@ -1191,18 +1625,25 @@ threadCmd
|
|
|
1191
1625
|
.requiredOption("--from <sessionId>", "Creating session")
|
|
1192
1626
|
.requiredOption("--participants <ids>", "Comma-separated participant session ids")
|
|
1193
1627
|
.option("--kind <kind>", "conversation|task|review|handoff|user", "conversation")
|
|
1194
|
-
.action((opts) => {
|
|
1628
|
+
.action(async (opts) => {
|
|
1195
1629
|
const participants = opts.participants
|
|
1196
1630
|
.split(",")
|
|
1197
1631
|
.map((value) => value.trim())
|
|
1198
1632
|
.filter(Boolean);
|
|
1199
|
-
const
|
|
1633
|
+
const result = await postProjectServiceJsonOrLocal("/threads/open", {
|
|
1200
1634
|
title: opts.title,
|
|
1635
|
+
from: opts.from,
|
|
1636
|
+
participants,
|
|
1201
1637
|
kind: opts.kind ?? "conversation",
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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);
|
|
1206
1647
|
});
|
|
1207
1648
|
threadCmd
|
|
1208
1649
|
.command("send")
|
|
@@ -1212,36 +1653,46 @@ threadCmd
|
|
|
1212
1653
|
.requiredOption("--from <sessionId>", "Sending session")
|
|
1213
1654
|
.option("--to <ids>", "Comma-separated recipient session ids")
|
|
1214
1655
|
.option("--kind <kind>", "request|reply|status|decision|handoff|note", "note")
|
|
1215
|
-
.action((threadId, body, opts) => {
|
|
1216
|
-
const thread = readThread(threadId);
|
|
1217
|
-
if (!thread) {
|
|
1218
|
-
console.error(`aimux: thread not found: ${threadId}`);
|
|
1219
|
-
process.exit(1);
|
|
1220
|
-
}
|
|
1656
|
+
.action(async (threadId, body, opts) => {
|
|
1221
1657
|
const to = opts.to
|
|
1222
1658
|
?.split(",")
|
|
1223
1659
|
.map((value) => value.trim())
|
|
1224
1660
|
.filter(Boolean);
|
|
1225
|
-
const
|
|
1661
|
+
const result = await postProjectServiceJsonOrLocal("/threads/send", {
|
|
1226
1662
|
threadId,
|
|
1227
1663
|
from: opts.from,
|
|
1228
1664
|
to,
|
|
1229
1665
|
kind: opts.kind ?? "note",
|
|
1230
1666
|
body,
|
|
1231
|
-
})
|
|
1232
|
-
|
|
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);
|
|
1233
1681
|
});
|
|
1234
1682
|
threadCmd
|
|
1235
1683
|
.command("mark-seen")
|
|
1236
1684
|
.description("Mark a thread as seen for a participant")
|
|
1237
1685
|
.argument("<threadId>")
|
|
1238
1686
|
.requiredOption("--session <sessionId>", "Participant session id")
|
|
1239
|
-
.action((threadId, opts) => {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
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
|
+
});
|
|
1245
1696
|
console.log("ok");
|
|
1246
1697
|
});
|
|
1247
1698
|
threadCmd
|
|
@@ -1381,7 +1832,11 @@ handoffCmd
|
|
|
1381
1832
|
catch {
|
|
1382
1833
|
const result = sendHandoff({
|
|
1383
1834
|
from: opts.from ?? "user",
|
|
1384
|
-
to: to
|
|
1835
|
+
to: to?.length
|
|
1836
|
+
? to
|
|
1837
|
+
: [opts.assignee, opts.tool]
|
|
1838
|
+
.map((value) => value?.trim())
|
|
1839
|
+
.filter((value) => Boolean(value)),
|
|
1385
1840
|
body,
|
|
1386
1841
|
title: opts.title,
|
|
1387
1842
|
worktreePath: opts.worktree,
|
|
@@ -1451,10 +1906,20 @@ taskCmd
|
|
|
1451
1906
|
.option("--session <sessionId>", "Filter to tasks assigned to or created by a session")
|
|
1452
1907
|
.option("--status <status>", "Filter by task status")
|
|
1453
1908
|
.option("--json", "Emit JSON")
|
|
1454
|
-
.action((opts) => {
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
.
|
|
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 : [];
|
|
1458
1923
|
if (opts.json) {
|
|
1459
1924
|
console.log(JSON.stringify({ tasks }, null, 2));
|
|
1460
1925
|
return;
|
|
@@ -1475,14 +1940,23 @@ taskCmd
|
|
|
1475
1940
|
.description("Show an orchestrated task")
|
|
1476
1941
|
.argument("<taskId>")
|
|
1477
1942
|
.option("--json", "Emit JSON")
|
|
1478
|
-
.action((taskId, opts) => {
|
|
1479
|
-
const
|
|
1480
|
-
|
|
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) {
|
|
1481
1956
|
console.error(`aimux: task not found: ${taskId}`);
|
|
1482
1957
|
process.exit(1);
|
|
1483
1958
|
}
|
|
1484
|
-
const
|
|
1485
|
-
const messages = task.threadId ? readMessages(task.threadId) : [];
|
|
1959
|
+
const { task, thread, messages } = detail;
|
|
1486
1960
|
if (opts.json) {
|
|
1487
1961
|
console.log(JSON.stringify({ task, thread, messages }, null, 2));
|
|
1488
1962
|
return;
|
|
@@ -1743,12 +2217,17 @@ worktreeCmd
|
|
|
1743
2217
|
.option("--json", "Emit JSON")
|
|
1744
2218
|
.action(async (opts) => {
|
|
1745
2219
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
1746
|
-
|
|
2220
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
2221
|
+
const result = await getLiveProjectServiceJsonOrLocal(projectRoot, "/worktrees", () => ({
|
|
2222
|
+
ok: true,
|
|
2223
|
+
worktrees: listVisibleLocalWorktrees(projectRoot),
|
|
2224
|
+
}));
|
|
2225
|
+
const worktrees = result.worktrees ?? [];
|
|
1747
2226
|
if (opts.json) {
|
|
1748
2227
|
console.log(JSON.stringify(worktrees, null, 2));
|
|
1749
2228
|
return;
|
|
1750
2229
|
}
|
|
1751
|
-
printWorktrees(projectRoot);
|
|
2230
|
+
printWorktrees(projectRoot, worktrees);
|
|
1752
2231
|
});
|
|
1753
2232
|
worktreeCmd
|
|
1754
2233
|
.command("create <name>")
|
|
@@ -1758,7 +2237,12 @@ worktreeCmd
|
|
|
1758
2237
|
.action(async (name, opts) => {
|
|
1759
2238
|
try {
|
|
1760
2239
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
1761
|
-
|
|
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;
|
|
1762
2246
|
if (opts.json) {
|
|
1763
2247
|
console.log(JSON.stringify({
|
|
1764
2248
|
ok: true,
|
|
@@ -1776,6 +2260,114 @@ worktreeCmd
|
|
|
1776
2260
|
process.exit(1);
|
|
1777
2261
|
}
|
|
1778
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
|
+
});
|
|
1779
2371
|
program
|
|
1780
2372
|
.command("spawn")
|
|
1781
2373
|
.description("Spawn a fresh agent session using the same flow as the dashboard")
|
|
@@ -1868,27 +2460,26 @@ graveyardCmd
|
|
|
1868
2460
|
.option("--project <path>", "Project path")
|
|
1869
2461
|
.option("--json", "Emit JSON")
|
|
1870
2462
|
.action(async (opts) => {
|
|
1871
|
-
await prepareProjectContext(opts.project);
|
|
1872
|
-
|
|
2463
|
+
const projectRoot = await prepareProjectContext(opts.project);
|
|
2464
|
+
await ensureDaemonProjectReadyForFallback(projectRoot);
|
|
1873
2465
|
try {
|
|
1874
|
-
const graveyard =
|
|
2466
|
+
const graveyard = await getLiveProjectServiceJsonOrLocal(projectRoot, "/graveyard", () => ({
|
|
2467
|
+
ok: true,
|
|
2468
|
+
entries: listTopologySessionStates({ statuses: ["graveyard"] }),
|
|
2469
|
+
worktrees: listTopologyWorktreeGraveyard(),
|
|
2470
|
+
}));
|
|
1875
2471
|
if (opts.json) {
|
|
1876
|
-
console.log(JSON.stringify(
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
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));
|
|
1881
2476
|
return;
|
|
1882
2477
|
}
|
|
1883
|
-
|
|
1884
|
-
console.log("-".repeat(70));
|
|
1885
|
-
for (const s of graveyard) {
|
|
1886
|
-
console.log((s.id ?? "?").padEnd(25) + (s.command ?? s.tool ?? "?").padEnd(15) + (s.backendSessionId ?? "(none)"));
|
|
1887
|
-
}
|
|
2478
|
+
printGraveyard(graveyard);
|
|
1888
2479
|
}
|
|
1889
2480
|
catch {
|
|
1890
2481
|
if (opts.json) {
|
|
1891
|
-
console.log(
|
|
2482
|
+
console.log(JSON.stringify({ entries: [], worktrees: [] }, null, 2));
|
|
1892
2483
|
return;
|
|
1893
2484
|
}
|
|
1894
2485
|
console.log("Graveyard is empty.");
|
|
@@ -1902,8 +2493,11 @@ graveyardCmd
|
|
|
1902
2493
|
.action(async (id, opts) => {
|
|
1903
2494
|
try {
|
|
1904
2495
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
1905
|
-
|
|
1906
|
-
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
|
+
});
|
|
1907
2501
|
if (opts.json) {
|
|
1908
2502
|
console.log(JSON.stringify({
|
|
1909
2503
|
ok: true,
|
|
@@ -1928,44 +2522,23 @@ graveyardCmd
|
|
|
1928
2522
|
.option("--project <path>", "Project path")
|
|
1929
2523
|
.option("--json", "Emit JSON")
|
|
1930
2524
|
.action(async (id, opts) => {
|
|
1931
|
-
await prepareProjectContext(opts.project);
|
|
1932
|
-
const graveyardPath = getGraveyardPath();
|
|
1933
|
-
if (!existsSync(graveyardPath)) {
|
|
1934
|
-
console.error("Graveyard is empty.");
|
|
1935
|
-
process.exit(1);
|
|
1936
|
-
}
|
|
1937
2525
|
try {
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
}
|
|
1944
|
-
const restored = graveyard.splice(idx, 1)[0];
|
|
1945
|
-
writeFileSync(graveyardPath, JSON.stringify(graveyard, null, 2) + "\n");
|
|
1946
|
-
const statePath = getStatePath();
|
|
1947
|
-
let state = {
|
|
1948
|
-
savedAt: new Date().toISOString(),
|
|
1949
|
-
cwd: process.cwd(),
|
|
1950
|
-
sessions: [],
|
|
1951
|
-
};
|
|
1952
|
-
if (existsSync(statePath)) {
|
|
1953
|
-
try {
|
|
1954
|
-
state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
1955
|
-
}
|
|
1956
|
-
catch { }
|
|
1957
|
-
}
|
|
1958
|
-
state.sessions.push(restored);
|
|
1959
|
-
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
|
+
});
|
|
1960
2532
|
if (opts.json) {
|
|
1961
2533
|
console.log(JSON.stringify({
|
|
1962
2534
|
ok: true,
|
|
1963
|
-
|
|
1964
|
-
|
|
2535
|
+
projectRoot,
|
|
2536
|
+
sessionId: result.sessionId,
|
|
2537
|
+
status: result.status,
|
|
1965
2538
|
}, null, 2));
|
|
1966
2539
|
return;
|
|
1967
2540
|
}
|
|
1968
|
-
console.log(`
|
|
2541
|
+
console.log(`resurrected ${result.sessionId}`);
|
|
1969
2542
|
}
|
|
1970
2543
|
catch (err) {
|
|
1971
2544
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2060,8 +2633,11 @@ program
|
|
|
2060
2633
|
.action(async (sessionId, opts) => {
|
|
2061
2634
|
try {
|
|
2062
2635
|
const projectRoot = await prepareProjectContext(opts.project);
|
|
2063
|
-
|
|
2064
|
-
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
|
+
});
|
|
2065
2641
|
if (opts.json) {
|
|
2066
2642
|
console.log(JSON.stringify({
|
|
2067
2643
|
ok: true,
|
|
@@ -2113,6 +2689,75 @@ program
|
|
|
2113
2689
|
const statuslineCmd = program.command("statusline").description("Manage Claude Code statusline integration");
|
|
2114
2690
|
const doctorCmd = program.command("doctor").description("Inspect aimux runtime state");
|
|
2115
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
|
+
});
|
|
2116
2761
|
doctorCmd
|
|
2117
2762
|
.command("tmux")
|
|
2118
2763
|
.description("Inspect managed tmux runtime state")
|
|
@@ -2232,20 +2877,29 @@ program
|
|
|
2232
2877
|
force: true,
|
|
2233
2878
|
}, () => {
|
|
2234
2879
|
const kind = (opts.kind?.trim() || "notification");
|
|
2235
|
-
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,
|
|
2236
2885
|
title,
|
|
2886
|
+
message: [opts.subtitle?.trim(), body].filter(Boolean).join(" — "),
|
|
2887
|
+
forceNotify: true,
|
|
2888
|
+
}, context);
|
|
2889
|
+
const notification = upsertNotification({
|
|
2890
|
+
title: alert.title,
|
|
2237
2891
|
subtitle: opts.subtitle?.trim() || undefined,
|
|
2238
2892
|
body,
|
|
2239
|
-
sessionId
|
|
2893
|
+
sessionId,
|
|
2240
2894
|
kind,
|
|
2241
2895
|
});
|
|
2242
2896
|
notifyAlert({
|
|
2243
2897
|
type: "alert",
|
|
2244
2898
|
kind,
|
|
2245
2899
|
projectId: getProjectId(),
|
|
2246
|
-
sessionId
|
|
2247
|
-
title,
|
|
2248
|
-
message:
|
|
2900
|
+
sessionId,
|
|
2901
|
+
title: alert.title,
|
|
2902
|
+
message: alert.message,
|
|
2249
2903
|
ts: notification.createdAt,
|
|
2250
2904
|
forceNotify: true,
|
|
2251
2905
|
});
|
|
@@ -2271,6 +2925,9 @@ program
|
|
|
2271
2925
|
const payload = parseClaudeHookPayload(rawInput);
|
|
2272
2926
|
const sessionId = await resolveClaudeHookSessionId(opts.session, payload.session_id);
|
|
2273
2927
|
const result = { ok: true, action, sessionId };
|
|
2928
|
+
if (payload.session_id) {
|
|
2929
|
+
result.backendSessionId = payload.session_id;
|
|
2930
|
+
}
|
|
2274
2931
|
const setActivity = async (activity) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-activity", { session: sessionId, activity }, () => metadataTracker.setActivity(sessionId, activity, projectRoot));
|
|
2275
2932
|
const setAttention = async (attention) => postLiveProjectServiceJsonOrLocal(projectRoot, "/set-attention", { session: sessionId, attention }, () => metadataTracker.setAttention(sessionId, attention, projectRoot));
|
|
2276
2933
|
const emitEvent = async (kind, message, tone) => postLiveProjectServiceJsonOrLocal(projectRoot, "/event", { session: sessionId, event: { kind, message, tone } }, () => metadataTracker.emit(sessionId, { kind, message, tone }, projectRoot));
|
|
@@ -2278,6 +2935,21 @@ program
|
|
|
2278
2935
|
ok: true,
|
|
2279
2936
|
cleared: clearNotifications({ sessionId }),
|
|
2280
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
|
+
}
|
|
2281
2953
|
switch (action) {
|
|
2282
2954
|
case "session-start":
|
|
2283
2955
|
case "active":
|