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/daemon.js
CHANGED
|
@@ -1,19 +1,92 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve as pathResolve } from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { writeJsonAtomic } from "./atomic-write.js";
|
|
6
|
-
import { getDaemonInfoPath, getDaemonStatePath, getProjectIdFor } from "./paths.js";
|
|
6
|
+
import { getDaemonInfoPath, getDaemonStatePath, getDaemonStdioLogPath, getProjectIdFor, getProjectServiceStdioLogPathFor, } from "./paths.js";
|
|
7
7
|
import { listDesktopProjects } from "./project-scanner.js";
|
|
8
8
|
import { loadMetadataEndpoint } from "./metadata-store.js";
|
|
9
9
|
import { requestJson } from "./http-client.js";
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
import { getLoggingConfig, log } from "./debug.js";
|
|
11
|
+
import { RelayClient } from "./relay-client.js";
|
|
12
|
+
import { loadCredentials, setRemoteEnabled } from "./credentials.js";
|
|
13
|
+
import { assertRemoteAccessAllowed, parseRemoteActor } from "./remote-access.js";
|
|
14
|
+
const DEFAULT_DAEMON_PORT = 43190;
|
|
15
|
+
const DEFAULT_DAEMON_HOST = "127.0.0.1";
|
|
12
16
|
const DAEMON_STARTUP_TIMEOUT_MS = 10_000;
|
|
13
17
|
const PROJECT_SERVICE_STARTUP_GRACE_MS = 15_000;
|
|
18
|
+
const PROJECT_SERVICE_TERM_GRACE_MS = 2_000;
|
|
19
|
+
const PROJECT_SERVICE_KILL_GRACE_MS = 3_000;
|
|
20
|
+
const PROJECT_SERVICE_EXIT_POLL_MS = 50;
|
|
21
|
+
const PROXY_TIMEOUT_MS = 10_000;
|
|
22
|
+
// `::1` is intentionally excluded — building http://::1:port is invalid (IPv6
|
|
23
|
+
// needs brackets) and metadata services bind to 127.0.0.1 anyway.
|
|
24
|
+
const PROXY_ALLOWED_HOSTS = new Set(["127.0.0.1", "localhost"]);
|
|
25
|
+
const CORS_ALLOWED_ORIGINS = new Set([
|
|
26
|
+
"http://localhost:8081",
|
|
27
|
+
"http://127.0.0.1:8081",
|
|
28
|
+
"http://localhost:8091",
|
|
29
|
+
"http://127.0.0.1:8091",
|
|
30
|
+
"http://localhost:43192",
|
|
31
|
+
"http://127.0.0.1:43192",
|
|
32
|
+
]);
|
|
33
|
+
export function getDaemonHost() {
|
|
34
|
+
const host = process.env.AIMUX_DAEMON_HOST?.trim();
|
|
35
|
+
const resolved = host || DEFAULT_DAEMON_HOST;
|
|
36
|
+
if (resolved !== "127.0.0.1" && resolved !== "localhost") {
|
|
37
|
+
throw new Error(`AIMUX_DAEMON_HOST must be loopback (127.0.0.1 or localhost), got ${resolved}`);
|
|
38
|
+
}
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
export function getDaemonPort() {
|
|
42
|
+
const raw = process.env.AIMUX_DAEMON_PORT?.trim();
|
|
43
|
+
if (!raw)
|
|
44
|
+
return DEFAULT_DAEMON_PORT;
|
|
45
|
+
const port = Number(raw);
|
|
46
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
47
|
+
throw new Error(`AIMUX_DAEMON_PORT must be an integer between 1 and 65535, got ${raw}`);
|
|
48
|
+
}
|
|
49
|
+
return port;
|
|
50
|
+
}
|
|
51
|
+
function getDaemonBaseUrl(port = getDaemonPort()) {
|
|
52
|
+
return `http://${getDaemonHost()}:${port}`;
|
|
53
|
+
}
|
|
14
54
|
function ensureParent(path) {
|
|
15
55
|
mkdirSync(dirname(path), { recursive: true });
|
|
16
56
|
}
|
|
57
|
+
function loggingChildEnv() {
|
|
58
|
+
const logging = getLoggingConfig();
|
|
59
|
+
if (!logging.enabled)
|
|
60
|
+
return process.env;
|
|
61
|
+
return {
|
|
62
|
+
...process.env,
|
|
63
|
+
AIMUX_LOG: "1",
|
|
64
|
+
AIMUX_LOG_LEVEL: logging.level,
|
|
65
|
+
AIMUX_LOG_CATEGORIES: logging.categories.join(","),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function loggingChildStdio(path) {
|
|
69
|
+
const logging = getLoggingConfig();
|
|
70
|
+
if (!logging.enabled)
|
|
71
|
+
return { stdio: "ignore", close: () => { } };
|
|
72
|
+
try {
|
|
73
|
+
ensureParent(path);
|
|
74
|
+
const stdout = openSync(path, "a");
|
|
75
|
+
const stderr = openSync(path, "a");
|
|
76
|
+
let closed = false;
|
|
77
|
+
const close = () => {
|
|
78
|
+
if (closed)
|
|
79
|
+
return;
|
|
80
|
+
closed = true;
|
|
81
|
+
closeSync(stdout);
|
|
82
|
+
closeSync(stderr);
|
|
83
|
+
};
|
|
84
|
+
return { stdio: ["ignore", stdout, stderr], close };
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return { stdio: "ignore", close: () => { } };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
17
90
|
function saveJson(path, value) {
|
|
18
91
|
try {
|
|
19
92
|
writeJsonAtomic(path, value);
|
|
@@ -64,6 +137,32 @@ function send(res, status, body) {
|
|
|
64
137
|
res.setHeader("connection", "close");
|
|
65
138
|
res.end(payload);
|
|
66
139
|
}
|
|
140
|
+
function setCorsHeaders(req, res) {
|
|
141
|
+
const origin = req.headers.origin;
|
|
142
|
+
if (origin && !isAllowedCorsOrigin(origin))
|
|
143
|
+
return false;
|
|
144
|
+
if (origin) {
|
|
145
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
146
|
+
res.setHeader("Vary", "Origin");
|
|
147
|
+
}
|
|
148
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
149
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
function isAllowedCorsOrigin(origin) {
|
|
153
|
+
if (CORS_ALLOWED_ORIGINS.has(origin))
|
|
154
|
+
return true;
|
|
155
|
+
try {
|
|
156
|
+
const parsed = new URL(origin);
|
|
157
|
+
return parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function rejectCors(res) {
|
|
164
|
+
send(res, 403, { ok: false, error: "origin not allowed" });
|
|
165
|
+
}
|
|
67
166
|
export function loadDaemonInfo() {
|
|
68
167
|
const info = loadJson(getDaemonInfoPath(), null);
|
|
69
168
|
if (!info)
|
|
@@ -75,6 +174,11 @@ export async function stopDaemon(signal = "SIGTERM") {
|
|
|
75
174
|
if (!info)
|
|
76
175
|
return null;
|
|
77
176
|
const state = loadDaemonState();
|
|
177
|
+
log.info("stopping daemon", "daemon", {
|
|
178
|
+
pid: info.pid,
|
|
179
|
+
signal,
|
|
180
|
+
projectCount: Object.keys(state.projects).length,
|
|
181
|
+
});
|
|
78
182
|
for (const entry of Object.values(state.projects)) {
|
|
79
183
|
try {
|
|
80
184
|
process.kill(entry.pid, signal);
|
|
@@ -123,7 +227,7 @@ export async function requestDaemonJson(path, init) {
|
|
|
123
227
|
if (!info) {
|
|
124
228
|
throw new Error("aimux daemon is not running");
|
|
125
229
|
}
|
|
126
|
-
const { status, json } = await requestJson(
|
|
230
|
+
const { status, json } = await requestJson(`${getDaemonBaseUrl(info.port)}${path}`, {
|
|
127
231
|
method: init?.method,
|
|
128
232
|
headers: init?.headers,
|
|
129
233
|
body: init?.body,
|
|
@@ -140,29 +244,49 @@ export async function ensureDaemonRunning() {
|
|
|
140
244
|
await requestDaemonJson("/health");
|
|
141
245
|
return existing;
|
|
142
246
|
}
|
|
143
|
-
catch {
|
|
247
|
+
catch (error) {
|
|
248
|
+
log.warn("stored daemon info failed health check", "daemon", {
|
|
249
|
+
pid: existing.pid,
|
|
250
|
+
error: error instanceof Error ? error.message : String(error),
|
|
251
|
+
});
|
|
144
252
|
clearFile(getDaemonInfoPath());
|
|
145
253
|
}
|
|
146
254
|
}
|
|
147
255
|
try {
|
|
148
|
-
const { status, json } = await requestJson(
|
|
256
|
+
const { status, json } = await requestJson(`${getDaemonBaseUrl()}/health`);
|
|
149
257
|
if (status >= 200 && status < 300 && json?.ok !== false && typeof json?.pid === "number") {
|
|
150
258
|
const adopted = {
|
|
151
259
|
pid: json.pid,
|
|
152
|
-
port: typeof json?.port === "number" ? json.port :
|
|
260
|
+
port: typeof json?.port === "number" ? json.port : getDaemonPort(),
|
|
153
261
|
startedAt: loadDaemonInfo()?.startedAt ?? new Date().toISOString(),
|
|
154
262
|
updatedAt: new Date().toISOString(),
|
|
155
263
|
};
|
|
156
264
|
saveJson(getDaemonInfoPath(), adopted);
|
|
265
|
+
log.info("adopted existing daemon on default port", "daemon", { ...adopted });
|
|
157
266
|
return adopted;
|
|
158
267
|
}
|
|
159
268
|
}
|
|
160
|
-
catch {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
log.debug("default daemon health probe failed", "daemon", {
|
|
271
|
+
error: error instanceof Error ? error.message : String(error),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const stdio = loggingChildStdio(getDaemonStdioLogPath());
|
|
275
|
+
let child;
|
|
276
|
+
try {
|
|
277
|
+
child = spawn(process.execPath, [process.argv[1], "daemon", "run"], {
|
|
278
|
+
detached: true,
|
|
279
|
+
env: loggingChildEnv(),
|
|
280
|
+
stdio: stdio.stdio,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
stdio.close();
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
child.once("exit", stdio.close);
|
|
165
288
|
child.unref();
|
|
289
|
+
log.info("spawned daemon", "daemon", { pid: child.pid });
|
|
166
290
|
const deadline = Date.now() + DAEMON_STARTUP_TIMEOUT_MS;
|
|
167
291
|
while (Date.now() < deadline) {
|
|
168
292
|
const info = loadDaemonInfo();
|
|
@@ -203,14 +327,16 @@ export async function projectServiceStatus(projectRoot) {
|
|
|
203
327
|
}
|
|
204
328
|
export class AimuxDaemon {
|
|
205
329
|
server = null;
|
|
330
|
+
relayClient = null;
|
|
206
331
|
children = new Map();
|
|
332
|
+
projectEnsurePromises = new Map();
|
|
207
333
|
state = loadDaemonState();
|
|
208
334
|
async start() {
|
|
209
335
|
if (this.server)
|
|
210
336
|
return;
|
|
211
337
|
saveJson(getDaemonInfoPath(), {
|
|
212
338
|
pid: process.pid,
|
|
213
|
-
port:
|
|
339
|
+
port: getDaemonPort(),
|
|
214
340
|
startedAt: new Date().toISOString(),
|
|
215
341
|
updatedAt: new Date().toISOString(),
|
|
216
342
|
});
|
|
@@ -222,16 +348,57 @@ export class AimuxDaemon {
|
|
|
222
348
|
});
|
|
223
349
|
});
|
|
224
350
|
});
|
|
351
|
+
const host = getDaemonHost();
|
|
352
|
+
const port = getDaemonPort();
|
|
225
353
|
await new Promise((resolve, reject) => {
|
|
226
354
|
this.server.once("error", reject);
|
|
227
|
-
this.server.listen(
|
|
355
|
+
this.server.listen(port, host, () => {
|
|
228
356
|
this.server?.off("error", reject);
|
|
229
357
|
resolve();
|
|
230
358
|
});
|
|
231
359
|
});
|
|
232
360
|
this.refreshState();
|
|
361
|
+
log.info("daemon started", "daemon", { pid: process.pid, host, port });
|
|
362
|
+
this.connectRelayIfConfigured();
|
|
363
|
+
}
|
|
364
|
+
// Resolve relay config from stored credentials (`aimux login`), with env-var
|
|
365
|
+
// overrides for advanced/CI use. Connects only when remote access is enabled.
|
|
366
|
+
connectRelayIfConfigured(options = {}) {
|
|
367
|
+
const status = this.relayClient?.getStatus().status;
|
|
368
|
+
if (!options.force && this.relayClient && status !== "auth_failed" && status !== "disconnected")
|
|
369
|
+
return;
|
|
370
|
+
if (this.relayClient) {
|
|
371
|
+
this.relayClient.disconnect();
|
|
372
|
+
this.relayClient = null;
|
|
373
|
+
}
|
|
374
|
+
const creds = loadCredentials();
|
|
375
|
+
const relayUrl = process.env.AIMUX_RELAY_URL ?? creds?.relayUrl;
|
|
376
|
+
const relayToken = process.env.AIMUX_RELAY_TOKEN ?? creds?.token;
|
|
377
|
+
const hasEnvOverride = Boolean(process.env.AIMUX_RELAY_URL || process.env.AIMUX_RELAY_TOKEN);
|
|
378
|
+
const enabled = hasEnvOverride ? Boolean(relayUrl && relayToken) : Boolean(creds?.remoteEnabled);
|
|
379
|
+
if (relayUrl && relayToken && enabled) {
|
|
380
|
+
this.relayClient = new RelayClient(relayUrl, relayToken, this);
|
|
381
|
+
this.relayClient.connect();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
getRelayStatus() {
|
|
385
|
+
return this.relayClient?.getStatus() ?? { status: "off" };
|
|
386
|
+
}
|
|
387
|
+
enableRelay() {
|
|
388
|
+
setRemoteEnabled(true);
|
|
389
|
+
this.connectRelayIfConfigured({ force: true });
|
|
390
|
+
return this.getRelayStatus();
|
|
391
|
+
}
|
|
392
|
+
disableRelay() {
|
|
393
|
+
setRemoteEnabled(false);
|
|
394
|
+
this.relayClient?.disconnect();
|
|
395
|
+
this.relayClient = null;
|
|
396
|
+
return { status: "off" };
|
|
233
397
|
}
|
|
234
398
|
stop() {
|
|
399
|
+
log.info("daemon stopping child services", "daemon", { projectCount: Object.keys(this.state.projects).length });
|
|
400
|
+
this.relayClient?.disconnect();
|
|
401
|
+
this.relayClient = null;
|
|
235
402
|
for (const entry of Object.values(this.state.projects)) {
|
|
236
403
|
try {
|
|
237
404
|
process.kill(entry.pid, "SIGTERM");
|
|
@@ -267,16 +434,25 @@ export class AimuxDaemon {
|
|
|
267
434
|
saveJson(getDaemonStatePath(), this.state);
|
|
268
435
|
saveJson(getDaemonInfoPath(), {
|
|
269
436
|
pid: process.pid,
|
|
270
|
-
port:
|
|
437
|
+
port: getDaemonPort(),
|
|
271
438
|
startedAt: loadDaemonInfo()?.startedAt ?? new Date().toISOString(),
|
|
272
439
|
updatedAt: new Date().toISOString(),
|
|
273
440
|
});
|
|
274
441
|
}
|
|
275
442
|
spawnProjectService(projectRoot, projectId) {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
443
|
+
const stdio = loggingChildStdio(getProjectServiceStdioLogPathFor(projectRoot));
|
|
444
|
+
let child;
|
|
445
|
+
try {
|
|
446
|
+
child = spawn(process.execPath, [process.argv[1], "__project-service-internal"], {
|
|
447
|
+
cwd: projectRoot,
|
|
448
|
+
env: loggingChildEnv(),
|
|
449
|
+
stdio: stdio.stdio,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
stdio.close();
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
280
456
|
this.children.set(projectId, child);
|
|
281
457
|
const now = new Date().toISOString();
|
|
282
458
|
const state = {
|
|
@@ -287,8 +463,23 @@ export class AimuxDaemon {
|
|
|
287
463
|
updatedAt: now,
|
|
288
464
|
};
|
|
289
465
|
this.state.projects[projectId] = state;
|
|
290
|
-
|
|
291
|
-
|
|
466
|
+
log.info("spawned project service", "daemon", {
|
|
467
|
+
projectId,
|
|
468
|
+
projectRoot,
|
|
469
|
+
pid: state.pid,
|
|
470
|
+
});
|
|
471
|
+
child.on("exit", (code, signal) => {
|
|
472
|
+
stdio.close();
|
|
473
|
+
log.warn("project service exited", "daemon", {
|
|
474
|
+
projectId,
|
|
475
|
+
projectRoot,
|
|
476
|
+
pid: state.pid,
|
|
477
|
+
code,
|
|
478
|
+
signal,
|
|
479
|
+
});
|
|
480
|
+
if (this.children.get(projectId) === child) {
|
|
481
|
+
this.children.delete(projectId);
|
|
482
|
+
}
|
|
292
483
|
const current = this.state.projects[projectId];
|
|
293
484
|
if (current?.pid === state.pid) {
|
|
294
485
|
delete this.state.projects[projectId];
|
|
@@ -301,6 +492,18 @@ export class AimuxDaemon {
|
|
|
301
492
|
async ensureProject(projectRoot) {
|
|
302
493
|
const resolvedRoot = pathResolve(projectRoot);
|
|
303
494
|
const projectId = getProjectIdFor(resolvedRoot);
|
|
495
|
+
const existingEnsure = this.projectEnsurePromises.get(projectId);
|
|
496
|
+
if (existingEnsure)
|
|
497
|
+
return existingEnsure;
|
|
498
|
+
const ensure = this.ensureProjectUnlocked(resolvedRoot, projectId).finally(() => {
|
|
499
|
+
if (this.projectEnsurePromises.get(projectId) === ensure) {
|
|
500
|
+
this.projectEnsurePromises.delete(projectId);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
this.projectEnsurePromises.set(projectId, ensure);
|
|
504
|
+
return ensure;
|
|
505
|
+
}
|
|
506
|
+
async ensureProjectUnlocked(resolvedRoot, projectId) {
|
|
304
507
|
const existing = this.state.projects[projectId];
|
|
305
508
|
if (existing && isPidAlive(existing.pid)) {
|
|
306
509
|
const startedAtMs = Date.parse(existing.startedAt);
|
|
@@ -319,13 +522,12 @@ export class AimuxDaemon {
|
|
|
319
522
|
if (withinStartupGrace) {
|
|
320
523
|
return refreshExisting();
|
|
321
524
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
this.
|
|
328
|
-
return this.spawnProjectService(resolvedRoot, projectId);
|
|
525
|
+
log.warn("project service missing metadata endpoint after startup grace", "daemon", {
|
|
526
|
+
projectId,
|
|
527
|
+
projectRoot: resolvedRoot,
|
|
528
|
+
pid: existing.pid,
|
|
529
|
+
});
|
|
530
|
+
return this.replaceProjectServiceAfterExit(resolvedRoot, projectId, existing, refreshExisting);
|
|
329
531
|
}
|
|
330
532
|
try {
|
|
331
533
|
const { status, json } = await requestJson(`http://${endpoint.host}:${endpoint.port}/health`, {
|
|
@@ -335,22 +537,95 @@ export class AimuxDaemon {
|
|
|
335
537
|
throw new Error(json?.error || `health request failed: ${status}`);
|
|
336
538
|
}
|
|
337
539
|
}
|
|
338
|
-
catch {
|
|
540
|
+
catch (error) {
|
|
339
541
|
if (withinStartupGrace) {
|
|
340
542
|
return refreshExisting();
|
|
341
543
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return this.
|
|
544
|
+
log.warn("project service health check failed", "daemon", {
|
|
545
|
+
projectId,
|
|
546
|
+
projectRoot: resolvedRoot,
|
|
547
|
+
pid: existing.pid,
|
|
548
|
+
error: error instanceof Error ? error.message : String(error),
|
|
549
|
+
});
|
|
550
|
+
return this.replaceProjectServiceAfterExit(resolvedRoot, projectId, existing, refreshExisting);
|
|
349
551
|
}
|
|
350
552
|
return refreshExisting();
|
|
351
553
|
}
|
|
352
554
|
return this.spawnProjectService(resolvedRoot, projectId);
|
|
353
555
|
}
|
|
556
|
+
async replaceProjectServiceAfterExit(resolvedRoot, projectId, existing, refreshExisting) {
|
|
557
|
+
const stopped = await this.terminateProjectService(projectId, existing);
|
|
558
|
+
if (!stopped) {
|
|
559
|
+
log.warn("project service did not exit before replacement deadline", "daemon", {
|
|
560
|
+
projectId,
|
|
561
|
+
projectRoot: resolvedRoot,
|
|
562
|
+
pid: existing.pid,
|
|
563
|
+
});
|
|
564
|
+
return refreshExisting();
|
|
565
|
+
}
|
|
566
|
+
const current = this.state.projects[projectId];
|
|
567
|
+
if (current?.pid === existing.pid) {
|
|
568
|
+
delete this.state.projects[projectId];
|
|
569
|
+
}
|
|
570
|
+
const child = this.children.get(projectId);
|
|
571
|
+
if (child?.pid === existing.pid) {
|
|
572
|
+
this.children.delete(projectId);
|
|
573
|
+
}
|
|
574
|
+
this.refreshState();
|
|
575
|
+
return this.spawnProjectService(resolvedRoot, projectId);
|
|
576
|
+
}
|
|
577
|
+
async terminateProjectService(projectId, existing) {
|
|
578
|
+
const child = this.children.get(projectId);
|
|
579
|
+
log.info("terminating project service", "daemon", {
|
|
580
|
+
projectId,
|
|
581
|
+
projectRoot: existing.projectRoot,
|
|
582
|
+
pid: existing.pid,
|
|
583
|
+
});
|
|
584
|
+
try {
|
|
585
|
+
process.kill(existing.pid, "SIGTERM");
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
if (await this.waitForProjectServiceExit(existing.pid, child, PROJECT_SERVICE_TERM_GRACE_MS)) {
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
try {
|
|
594
|
+
process.kill(existing.pid, "SIGKILL");
|
|
595
|
+
}
|
|
596
|
+
catch {
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
599
|
+
return this.waitForProjectServiceExit(existing.pid, child, PROJECT_SERVICE_KILL_GRACE_MS);
|
|
600
|
+
}
|
|
601
|
+
async waitForProjectServiceExit(pid, child, timeoutMs) {
|
|
602
|
+
if (!isPidAlive(pid))
|
|
603
|
+
return true;
|
|
604
|
+
return new Promise((resolve) => {
|
|
605
|
+
let settled = false;
|
|
606
|
+
const cleanups = [];
|
|
607
|
+
const finish = (value) => {
|
|
608
|
+
if (settled)
|
|
609
|
+
return;
|
|
610
|
+
settled = true;
|
|
611
|
+
for (const cleanup of cleanups)
|
|
612
|
+
cleanup();
|
|
613
|
+
resolve(value);
|
|
614
|
+
};
|
|
615
|
+
const onExit = () => finish(true);
|
|
616
|
+
if (child) {
|
|
617
|
+
child.once("exit", onExit);
|
|
618
|
+
cleanups.push(() => child.off("exit", onExit));
|
|
619
|
+
}
|
|
620
|
+
const interval = setInterval(() => {
|
|
621
|
+
if (!isPidAlive(pid))
|
|
622
|
+
finish(true);
|
|
623
|
+
}, PROJECT_SERVICE_EXIT_POLL_MS);
|
|
624
|
+
cleanups.push(() => clearInterval(interval));
|
|
625
|
+
const timeout = setTimeout(() => finish(!isPidAlive(pid)), timeoutMs);
|
|
626
|
+
cleanups.push(() => clearTimeout(timeout));
|
|
627
|
+
});
|
|
628
|
+
}
|
|
354
629
|
stopProject(projectRoot) {
|
|
355
630
|
const projectId = getProjectIdFor(pathResolve(projectRoot));
|
|
356
631
|
const existing = this.state.projects[projectId];
|
|
@@ -361,18 +636,34 @@ export class AimuxDaemon {
|
|
|
361
636
|
}
|
|
362
637
|
catch { }
|
|
363
638
|
delete this.state.projects[projectId];
|
|
364
|
-
this.children.
|
|
639
|
+
if (this.children.get(projectId)?.pid === existing.pid) {
|
|
640
|
+
this.children.delete(projectId);
|
|
641
|
+
}
|
|
365
642
|
this.refreshState();
|
|
366
643
|
return existing;
|
|
367
644
|
}
|
|
368
|
-
async
|
|
645
|
+
async routeRequest(method, path, body, headers) {
|
|
369
646
|
this.refreshState();
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
647
|
+
const routeUrl = new URL(path, getDaemonBaseUrl());
|
|
648
|
+
const pathname = routeUrl.pathname;
|
|
649
|
+
const actor = parseRemoteActor(headers);
|
|
650
|
+
const access = assertRemoteAccessAllowed(actor, method, pathname, routeUrl.searchParams);
|
|
651
|
+
if (!access.ok) {
|
|
652
|
+
return { status: access.status ?? 403, body: { ok: false, error: access.error ?? "remote access denied" } };
|
|
653
|
+
}
|
|
654
|
+
if (method === "GET" && pathname === "/health") {
|
|
655
|
+
return { status: 200, body: { ok: true, pid: process.pid, port: getDaemonPort() } };
|
|
656
|
+
}
|
|
657
|
+
if (method === "GET" && pathname === "/relay/status") {
|
|
658
|
+
return { status: 200, body: { ok: true, relay: this.getRelayStatus() } };
|
|
659
|
+
}
|
|
660
|
+
if (method === "POST" && pathname === "/relay/enable") {
|
|
661
|
+
return { status: 200, body: { ok: true, relay: this.enableRelay() } };
|
|
374
662
|
}
|
|
375
|
-
if (
|
|
663
|
+
if (method === "POST" && pathname === "/relay/disable") {
|
|
664
|
+
return { status: 200, body: { ok: true, relay: this.disableRelay() } };
|
|
665
|
+
}
|
|
666
|
+
if (method === "GET" && pathname === "/projects") {
|
|
376
667
|
const liveById = this.state.projects;
|
|
377
668
|
const projects = listDesktopProjects().map((project) => ({
|
|
378
669
|
...project,
|
|
@@ -380,36 +671,70 @@ export class AimuxDaemon {
|
|
|
380
671
|
serviceAlive: Boolean(liveById[project.id]),
|
|
381
672
|
serviceEndpoint: project.path ? loadMetadataEndpoint(project.path) : null,
|
|
382
673
|
}));
|
|
383
|
-
|
|
384
|
-
return;
|
|
674
|
+
return { status: 200, body: { ok: true, projects } };
|
|
385
675
|
}
|
|
386
|
-
if (
|
|
387
|
-
const projectId = decodeURIComponent(
|
|
676
|
+
if (method === "GET" && pathname.startsWith("/projects/")) {
|
|
677
|
+
const projectId = decodeURIComponent(pathname.slice("/projects/".length));
|
|
388
678
|
const project = this.state.projects[projectId] ?? null;
|
|
389
|
-
|
|
390
|
-
return;
|
|
679
|
+
return { status: 200, body: { ok: true, project } };
|
|
391
680
|
}
|
|
392
|
-
if (
|
|
393
|
-
const
|
|
394
|
-
if (!
|
|
395
|
-
|
|
396
|
-
return;
|
|
681
|
+
if (method === "POST" && pathname === "/projects/ensure") {
|
|
682
|
+
const b = body;
|
|
683
|
+
if (!b?.projectRoot) {
|
|
684
|
+
return { status: 400, body: { ok: false, error: "projectRoot is required" } };
|
|
397
685
|
}
|
|
398
|
-
const project = await this.ensureProject(
|
|
399
|
-
|
|
400
|
-
return;
|
|
686
|
+
const project = await this.ensureProject(b.projectRoot);
|
|
687
|
+
return { status: 200, body: { ok: true, project } };
|
|
401
688
|
}
|
|
402
|
-
if (
|
|
403
|
-
const
|
|
404
|
-
if (!
|
|
405
|
-
|
|
406
|
-
|
|
689
|
+
if (method === "POST" && pathname === "/projects/stop") {
|
|
690
|
+
const b = body;
|
|
691
|
+
if (!b?.projectRoot) {
|
|
692
|
+
return { status: 400, body: { ok: false, error: "projectRoot is required" } };
|
|
693
|
+
}
|
|
694
|
+
const project = this.stopProject(b.projectRoot);
|
|
695
|
+
return { status: 200, body: { ok: true, project } };
|
|
696
|
+
}
|
|
697
|
+
const proxyMatch = pathname.match(/^\/proxy\/([^/]+)\/(\d+)(\/.*)/);
|
|
698
|
+
if (proxyMatch) {
|
|
699
|
+
const [, host, portStr, subPath] = proxyMatch;
|
|
700
|
+
if (!PROXY_ALLOWED_HOSTS.has(host)) {
|
|
701
|
+
return { status: 403, body: { ok: false, error: "proxy host not allowed" } };
|
|
702
|
+
}
|
|
703
|
+
try {
|
|
704
|
+
const { status, json } = await requestJson(`http://${host}:${portStr}${subPath}${routeUrl.search}`, {
|
|
705
|
+
method,
|
|
706
|
+
headers,
|
|
707
|
+
body: body !== undefined ? body : undefined,
|
|
708
|
+
timeoutMs: PROXY_TIMEOUT_MS,
|
|
709
|
+
});
|
|
710
|
+
return { status, body: json };
|
|
711
|
+
}
|
|
712
|
+
catch (err) {
|
|
713
|
+
const message = err instanceof Error ? err.message : "Proxy error";
|
|
714
|
+
const status = /timed? out|timeout/i.test(message) ? 504 : 502;
|
|
715
|
+
return { status, body: { ok: false, error: message } };
|
|
407
716
|
}
|
|
408
|
-
|
|
409
|
-
|
|
717
|
+
}
|
|
718
|
+
return { status: 404, body: { ok: false, error: "not found" } };
|
|
719
|
+
}
|
|
720
|
+
async handle(req, res) {
|
|
721
|
+
// No auth on the HTTP server: the daemon binds to 127.0.0.1, and remote
|
|
722
|
+
// app requests come in over the relay (which performs Clerk/HS256 verify
|
|
723
|
+
// before forwarding) and call routeRequest() directly in-process. Local
|
|
724
|
+
// CLI is trusted with the daemon's user.
|
|
725
|
+
if (!setCorsHeaders(req, res)) {
|
|
726
|
+
rejectCors(res);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (req.method === "OPTIONS") {
|
|
730
|
+
res.statusCode = 204;
|
|
731
|
+
res.end();
|
|
410
732
|
return;
|
|
411
733
|
}
|
|
412
|
-
|
|
734
|
+
const url = new URL(req.url ?? "/", getDaemonBaseUrl());
|
|
735
|
+
const body = req.method === "POST" ? await readJson(req) : undefined;
|
|
736
|
+
const result = await this.routeRequest(req.method ?? "GET", `${url.pathname}${url.search}`, body);
|
|
737
|
+
send(res, result.status, result.body);
|
|
413
738
|
}
|
|
414
739
|
}
|
|
415
740
|
//# sourceMappingURL=daemon.js.map
|