@opengsd/gsd-pi 1.9.0-dev.e10b60d4 → 1.10.0
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 +10 -10
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +24 -4
- package/dist/resources/extensions/gsd/auto/run-unit.js +19 -1
- package/dist/resources/extensions/gsd/auto-start.js +29 -9
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +7 -3
- package/dist/resources/extensions/gsd/auto.js +12 -1
- package/dist/resources/extensions/gsd/doctor-engine-checks.js +21 -2
- package/dist/resources/extensions/gsd/flat-phase-migration.js +12 -0
- package/dist/resources/extensions/gsd/gsd-db.js +75 -31
- package/dist/resources/extensions/gsd/worktree-state-projection.js +20 -18
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
- package/dist/web/standalone/.next/server/chunks/1991.js +2 -2
- package/dist/web/standalone/.next/server/chunks/2842.js +4 -4
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -1
- package/dist/web/standalone/.next/server/chunks/8357.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +3 -3
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-49f565245e1e4afe.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/{layout-4ae2d68984392bbf.js → layout-b35cbfff38aaf4cf.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-bc4c723c6a19c6f0.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{main-app-90d1d8d5e5d2dc6b.js → main-app-590a74400e35f685.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ec3eaffc9785ba48.js +1 -0
- package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/integrations/hermes/open_gsd_hermes/gsd_client.py +1 -1
- package/integrations/hermes/pyproject.toml +1 -1
- package/package.json +6 -6
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/gsd-cloud/README.md +9 -3
- package/packages/gsd-cloud/dist/background-cli.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/background-cli.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/background-cli.test.js +248 -0
- package/packages/gsd-cloud/dist/background-cli.test.js.map +1 -0
- package/packages/gsd-cloud/dist/cli.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cli.js +121 -15
- package/packages/gsd-cloud/dist/cli.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts +1 -1
- package/packages/gsd-cloud/dist/cloud-config.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-config.js +7 -1
- package/packages/gsd-cloud/dist/cloud-config.js.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts +1 -3
- package/packages/gsd-cloud/dist/cloud-runtime.d.ts.map +1 -1
- package/packages/gsd-cloud/dist/cloud-runtime.js +9 -7
- package/packages/gsd-cloud/dist/cloud-runtime.js.map +1 -1
- package/packages/gsd-cloud/dist/runtime-process.d.ts +27 -0
- package/packages/gsd-cloud/dist/runtime-process.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.js +272 -0
- package/packages/gsd-cloud/dist/runtime-process.js.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts +2 -0
- package/packages/gsd-cloud/dist/runtime-process.test.d.ts.map +1 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js +97 -0
- package/packages/gsd-cloud/dist/runtime-process.test.js.map +1 -0
- package/packages/gsd-cloud/package.json +2 -2
- package/packages/mcp-server/dist/cli-runner.d.ts +3 -0
- package/packages/mcp-server/dist/cli-runner.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli-runner.js +119 -33
- package/packages/mcp-server/dist/cli-runner.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +17 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +17 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +54 -0
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +48 -4
- package/src/resources/extensions/gsd/auto/run-unit.ts +20 -1
- package/src/resources/extensions/gsd/auto-start.ts +31 -8
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +10 -3
- package/src/resources/extensions/gsd/auto.ts +13 -1
- package/src/resources/extensions/gsd/doctor-engine-checks.ts +27 -2
- package/src/resources/extensions/gsd/flat-phase-migration.ts +14 -0
- package/src/resources/extensions/gsd/gsd-db.ts +85 -33
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +156 -0
- package/src/resources/extensions/gsd/tests/auto-start-index-lock.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +74 -1
- package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +83 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +8 -9
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +32 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +60 -0
- package/src/resources/extensions/gsd/worktree-state-projection.ts +21 -24
- package/dist/web/standalone/.next/static/chunks/app/_not-found/page-a6fb1847f67f167c.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-72a856634ad14c10.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-1115d46ac61b9823.js +0 -1
- /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → EAhnhYMt5UVOiG9-iPDjc}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{V9MAVyYVPboQUB9KTTAMi → EAhnhYMt5UVOiG9-iPDjc}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// Project/App: Open GSD
|
|
2
|
+
// File Purpose: Acceptance coverage for the detached gsd-cloud runtime lifecycle.
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync } from "node:fs";
|
|
5
|
+
import { createServer } from "node:http";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { test } from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { WebSocketServer } from "ws";
|
|
12
|
+
import { saveCloudConfig } from "./cloud-config.js";
|
|
13
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
+
const cliPath = join(packageRoot, "bin", "gsd-cloud.js");
|
|
15
|
+
function runCli(args, cwd, timeoutMs = 12_000, extraEnv = {}) {
|
|
16
|
+
return new Promise((resolveResult, reject) => {
|
|
17
|
+
const child = spawn(process.execPath, [cliPath, ...args], {
|
|
18
|
+
cwd,
|
|
19
|
+
env: { ...process.env, GSD_CLOUD_PROJECTS: undefined, ...extraEnv },
|
|
20
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
21
|
+
});
|
|
22
|
+
let stdout = "";
|
|
23
|
+
let stderr = "";
|
|
24
|
+
child.stdout.setEncoding("utf8");
|
|
25
|
+
child.stderr.setEncoding("utf8");
|
|
26
|
+
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
27
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
28
|
+
const timeout = setTimeout(() => {
|
|
29
|
+
child.kill("SIGTERM");
|
|
30
|
+
reject(new Error(`gsd-cloud did not return within ${timeoutMs}ms`));
|
|
31
|
+
}, timeoutMs);
|
|
32
|
+
child.once("error", (error) => {
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
reject(error);
|
|
35
|
+
});
|
|
36
|
+
child.once("exit", (code) => {
|
|
37
|
+
clearTimeout(timeout);
|
|
38
|
+
resolveResult({ code, stdout, stderr });
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
// Spawn a long-lived CLI process (e.g. `--foreground`) without waiting for exit,
|
|
43
|
+
// so the test can inspect its state and then stop it.
|
|
44
|
+
function spawnForegroundCli(args, cwd) {
|
|
45
|
+
const child = spawn(process.execPath, [cliPath, ...args], {
|
|
46
|
+
cwd,
|
|
47
|
+
env: { ...process.env, GSD_CLOUD_PROJECTS: undefined },
|
|
48
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
49
|
+
});
|
|
50
|
+
const exited = new Promise((resolveExit) => {
|
|
51
|
+
child.once("exit", (code) => resolveExit(code));
|
|
52
|
+
});
|
|
53
|
+
return { child, exited };
|
|
54
|
+
}
|
|
55
|
+
async function createTestGateway(deviceFlow = false) {
|
|
56
|
+
let baseUrl = "";
|
|
57
|
+
const server = createServer((req, res) => {
|
|
58
|
+
if (!deviceFlow) {
|
|
59
|
+
res.writeHead(404).end();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
res.setHeader("content-type", "application/json");
|
|
63
|
+
if (req.url === "/api/device/code") {
|
|
64
|
+
res.end(JSON.stringify({
|
|
65
|
+
userCode: "TEST-CODE",
|
|
66
|
+
deviceCode: "test-device",
|
|
67
|
+
verificationUriComplete: `${baseUrl}/approve-device?code=TEST-CODE`,
|
|
68
|
+
expiresIn: 30,
|
|
69
|
+
}));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (req.url === "/api/device/token") {
|
|
73
|
+
res.end(JSON.stringify({
|
|
74
|
+
status: "approved",
|
|
75
|
+
token: "test",
|
|
76
|
+
runtimeId: "fixture-runtime",
|
|
77
|
+
gateway_url: baseUrl,
|
|
78
|
+
}));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
res.writeHead(404).end();
|
|
82
|
+
});
|
|
83
|
+
const wss = new WebSocketServer({ server });
|
|
84
|
+
await new Promise((resolveListen, reject) => {
|
|
85
|
+
server.once("error", reject);
|
|
86
|
+
server.listen(0, "127.0.0.1", resolveListen);
|
|
87
|
+
});
|
|
88
|
+
const address = server.address();
|
|
89
|
+
assert.ok(address && typeof address === "object");
|
|
90
|
+
baseUrl = `http://127.0.0.1:${address.port}`;
|
|
91
|
+
let helloTimeout;
|
|
92
|
+
const hello = new Promise((resolveHello, reject) => {
|
|
93
|
+
helloTimeout = setTimeout(() => reject(new Error("background runtime did not advertise its project")), 8_000);
|
|
94
|
+
wss.once("connection", (socket) => {
|
|
95
|
+
socket.once("message", (data) => {
|
|
96
|
+
clearTimeout(helloTimeout);
|
|
97
|
+
resolveHello(JSON.parse(data.toString("utf8")));
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
baseUrl,
|
|
103
|
+
hello,
|
|
104
|
+
close: async () => {
|
|
105
|
+
clearTimeout(helloTimeout);
|
|
106
|
+
for (const client of wss.clients)
|
|
107
|
+
client.terminate();
|
|
108
|
+
await new Promise((resolveClose) => wss.close(() => server.close(() => resolveClose())));
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
test("connect returns while a background runtime advertises the selected project", async () => {
|
|
113
|
+
const root = mkdtempSync(join(tmpdir(), "gsd-cloud-background-"));
|
|
114
|
+
const projectDir = join(root, "project");
|
|
115
|
+
const expectedProjectDir = join(realpathSync(root), "project");
|
|
116
|
+
const configPath = join(root, "daemon.yaml");
|
|
117
|
+
mkdirSync(join(projectDir, ".gsd"), { recursive: true });
|
|
118
|
+
const gateway = await createTestGateway();
|
|
119
|
+
saveCloudConfig(configPath, {
|
|
120
|
+
gateway_url: gateway.baseUrl,
|
|
121
|
+
device_token: "test",
|
|
122
|
+
runtime_id: "fixture-runtime",
|
|
123
|
+
enabled: true,
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
const connect = await runCli(["connect", "--config", configPath], projectDir);
|
|
127
|
+
assert.equal(connect.code, 0, connect.stderr);
|
|
128
|
+
assert.match(connect.stdout, /connected in the background/i);
|
|
129
|
+
const message = await gateway.hello;
|
|
130
|
+
assert.equal(message.type, "hello");
|
|
131
|
+
const projects = message.projects;
|
|
132
|
+
assert.equal(projects.length, 1);
|
|
133
|
+
assert.equal(projects[0]?.path, expectedProjectDir);
|
|
134
|
+
const status = await runCli(["status", "--config", configPath], projectDir);
|
|
135
|
+
assert.equal(status.code, 0, status.stderr);
|
|
136
|
+
const statusBody = JSON.parse(status.stdout);
|
|
137
|
+
assert.equal(statusBody.background?.running, true);
|
|
138
|
+
assert.equal(typeof statusBody.background?.pid, "number");
|
|
139
|
+
assert.deepEqual(statusBody.projects, [expectedProjectDir]);
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
await runCli(["disconnect", "--config", configPath], projectDir).catch(() => undefined);
|
|
143
|
+
await gateway.close();
|
|
144
|
+
rmSync(root, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
test("login returns after approval and keeps the selected project connected", async () => {
|
|
148
|
+
const root = mkdtempSync(join(tmpdir(), "gsd-cloud-login-background-"));
|
|
149
|
+
const projectDir = join(root, "project");
|
|
150
|
+
const expectedProjectDir = join(realpathSync(root), "project");
|
|
151
|
+
const configPath = join(root, "daemon.yaml");
|
|
152
|
+
mkdirSync(join(projectDir, ".gsd"), { recursive: true });
|
|
153
|
+
const gateway = await createTestGateway(true);
|
|
154
|
+
try {
|
|
155
|
+
const login = await runCli([
|
|
156
|
+
"login",
|
|
157
|
+
"--gateway", gateway.baseUrl,
|
|
158
|
+
"--config", configPath,
|
|
159
|
+
], projectDir, 12_000);
|
|
160
|
+
assert.equal(login.code, 0, login.stderr);
|
|
161
|
+
assert.match(login.stdout, /Authorization approved!/);
|
|
162
|
+
assert.match(login.stdout, /connected in the background/i);
|
|
163
|
+
const message = await gateway.hello;
|
|
164
|
+
const projects = message.projects;
|
|
165
|
+
assert.deepEqual(projects.map((project) => project.path), [expectedProjectDir]);
|
|
166
|
+
const disconnect = await runCli(["disconnect", "--config", configPath], projectDir);
|
|
167
|
+
assert.equal(disconnect.code, 0, disconnect.stderr);
|
|
168
|
+
const status = await runCli(["status", "--config", configPath], projectDir);
|
|
169
|
+
const statusBody = JSON.parse(status.stdout);
|
|
170
|
+
assert.equal(statusBody.configured, false);
|
|
171
|
+
assert.equal(statusBody.background?.running, false);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
await runCli(["disconnect", "--config", configPath], projectDir).catch(() => undefined);
|
|
175
|
+
await gateway.close();
|
|
176
|
+
rmSync(root, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
test("advertised project paths are canonicalized through symlinks", async () => {
|
|
180
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-cloud-symlink-")));
|
|
181
|
+
const realProject = join(root, "real-project");
|
|
182
|
+
mkdirSync(join(realProject, ".gsd"), { recursive: true });
|
|
183
|
+
const linkProject = join(root, "link-project");
|
|
184
|
+
symlinkSync(realProject, linkProject);
|
|
185
|
+
const configPath = join(root, "daemon.yaml");
|
|
186
|
+
const gateway = await createTestGateway();
|
|
187
|
+
saveCloudConfig(configPath, {
|
|
188
|
+
gateway_url: gateway.baseUrl,
|
|
189
|
+
device_token: "test",
|
|
190
|
+
runtime_id: "fixture-runtime",
|
|
191
|
+
enabled: true,
|
|
192
|
+
});
|
|
193
|
+
try {
|
|
194
|
+
// Point the runtime at the symlink; the advertised path must be the real dir.
|
|
195
|
+
const connect = await runCli(["connect", "--config", configPath], realProject, 5_000, { GSD_CLOUD_PROJECTS: linkProject });
|
|
196
|
+
assert.equal(connect.code, 0, connect.stderr);
|
|
197
|
+
const message = await gateway.hello;
|
|
198
|
+
const projects = message.projects;
|
|
199
|
+
assert.equal(projects.length, 1);
|
|
200
|
+
assert.equal(projects[0]?.path, realProject);
|
|
201
|
+
const status = await runCli(["status", "--config", configPath], realProject);
|
|
202
|
+
const statusBody = JSON.parse(status.stdout);
|
|
203
|
+
assert.deepEqual(statusBody.projects, [realProject]);
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
await runCli(["disconnect", "--config", configPath], realProject).catch(() => undefined);
|
|
207
|
+
await gateway.close();
|
|
208
|
+
rmSync(root, { recursive: true, force: true });
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
test("a foreground runtime registers its PID so disconnect can stop it", async () => {
|
|
212
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "gsd-cloud-foreground-")));
|
|
213
|
+
const projectDir = join(root, "project");
|
|
214
|
+
const configPath = join(root, "daemon.yaml");
|
|
215
|
+
mkdirSync(join(projectDir, ".gsd"), { recursive: true });
|
|
216
|
+
const gateway = await createTestGateway();
|
|
217
|
+
saveCloudConfig(configPath, {
|
|
218
|
+
gateway_url: gateway.baseUrl,
|
|
219
|
+
device_token: "test",
|
|
220
|
+
runtime_id: "fixture-runtime",
|
|
221
|
+
enabled: true,
|
|
222
|
+
});
|
|
223
|
+
const foreground = spawnForegroundCli(["connect", "--foreground", "--config", configPath], projectDir);
|
|
224
|
+
try {
|
|
225
|
+
// Once the foreground runtime advertises its project it is fully connected.
|
|
226
|
+
await gateway.hello;
|
|
227
|
+
// The foreground session must record its own PID in the shared runtime state
|
|
228
|
+
// so status can see it and a later disconnect can find and stop it.
|
|
229
|
+
const status = await runCli(["status", "--config", configPath], projectDir);
|
|
230
|
+
const statusBody = JSON.parse(status.stdout);
|
|
231
|
+
assert.equal(statusBody.background?.running, true);
|
|
232
|
+
assert.equal(statusBody.background?.pid, foreground.child.pid);
|
|
233
|
+
const disconnect = await runCli(["disconnect", "--config", configPath], projectDir);
|
|
234
|
+
assert.equal(disconnect.code, 0, disconnect.stderr);
|
|
235
|
+
// disconnect must have terminated the foreground process.
|
|
236
|
+
const exitCode = await foreground.exited;
|
|
237
|
+
assert.equal(exitCode, 0);
|
|
238
|
+
const afterStatus = await runCli(["status", "--config", configPath], projectDir);
|
|
239
|
+
const afterBody = JSON.parse(afterStatus.stdout);
|
|
240
|
+
assert.equal(afterBody.background?.running, false);
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
foreground.child.kill("SIGKILL");
|
|
244
|
+
await gateway.close();
|
|
245
|
+
rmSync(root, { recursive: true, force: true });
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
//# sourceMappingURL=background-cli.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"background-cli.test.js","sourceRoot":"","sources":["../src/background-cli.test.ts"],"names":[],"mappings":"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","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Acceptance coverage for the detached gsd-cloud runtime lifecycle.\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync } from \"node:fs\";\nimport { createServer } from \"node:http\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { test } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { WebSocketServer } from \"ws\";\nimport { saveCloudConfig } from \"./cloud-config.js\";\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), \"..\");\nconst cliPath = join(packageRoot, \"bin\", \"gsd-cloud.js\");\n\ninterface CliResult {\n code: number | null;\n stdout: string;\n stderr: string;\n}\n\ninterface TestGateway {\n baseUrl: string;\n hello: Promise<Record<string, unknown>>;\n close: () => Promise<void>;\n}\n\nfunction runCli(\n args: string[],\n cwd: string,\n timeoutMs = 12_000,\n extraEnv: Record<string, string | undefined> = {},\n): Promise<CliResult> {\n return new Promise((resolveResult, reject) => {\n const child = spawn(process.execPath, [cliPath, ...args], {\n cwd,\n env: { ...process.env, GSD_CLOUD_PROJECTS: undefined, ...extraEnv },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n let stdout = \"\";\n let stderr = \"\";\n child.stdout.setEncoding(\"utf8\");\n child.stderr.setEncoding(\"utf8\");\n child.stdout.on(\"data\", (chunk: string) => { stdout += chunk; });\n child.stderr.on(\"data\", (chunk: string) => { stderr += chunk; });\n\n const timeout = setTimeout(() => {\n child.kill(\"SIGTERM\");\n reject(new Error(`gsd-cloud did not return within ${timeoutMs}ms`));\n }, timeoutMs);\n child.once(\"error\", (error) => {\n clearTimeout(timeout);\n reject(error);\n });\n child.once(\"exit\", (code) => {\n clearTimeout(timeout);\n resolveResult({ code, stdout, stderr });\n });\n });\n}\n\ninterface ForegroundCli {\n child: ChildProcess;\n exited: Promise<number | null>;\n}\n\n// Spawn a long-lived CLI process (e.g. `--foreground`) without waiting for exit,\n// so the test can inspect its state and then stop it.\nfunction spawnForegroundCli(args: string[], cwd: string): ForegroundCli {\n const child = spawn(process.execPath, [cliPath, ...args], {\n cwd,\n env: { ...process.env, GSD_CLOUD_PROJECTS: undefined },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const exited = new Promise<number | null>((resolveExit) => {\n child.once(\"exit\", (code) => resolveExit(code));\n });\n return { child, exited };\n}\n\nasync function createTestGateway(deviceFlow = false): Promise<TestGateway> {\n let baseUrl = \"\";\n const server = createServer((req, res) => {\n if (!deviceFlow) {\n res.writeHead(404).end();\n return;\n }\n res.setHeader(\"content-type\", \"application/json\");\n if (req.url === \"/api/device/code\") {\n res.end(JSON.stringify({\n userCode: \"TEST-CODE\",\n deviceCode: \"test-device\",\n verificationUriComplete: `${baseUrl}/approve-device?code=TEST-CODE`,\n expiresIn: 30,\n }));\n return;\n }\n if (req.url === \"/api/device/token\") {\n res.end(JSON.stringify({\n status: \"approved\",\n token: \"test\",\n runtimeId: \"fixture-runtime\",\n gateway_url: baseUrl,\n }));\n return;\n }\n res.writeHead(404).end();\n });\n const wss = new WebSocketServer({ server });\n await new Promise<void>((resolveListen, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", resolveListen);\n });\n const address = server.address();\n assert.ok(address && typeof address === \"object\");\n baseUrl = `http://127.0.0.1:${address.port}`;\n\n let helloTimeout: ReturnType<typeof setTimeout>;\n const hello = new Promise<Record<string, unknown>>((resolveHello, reject) => {\n helloTimeout = setTimeout(() => reject(new Error(\"background runtime did not advertise its project\")), 8_000);\n wss.once(\"connection\", (socket) => {\n socket.once(\"message\", (data) => {\n clearTimeout(helloTimeout);\n resolveHello(JSON.parse(data.toString(\"utf8\")) as Record<string, unknown>);\n });\n });\n });\n\n return {\n baseUrl,\n hello,\n close: async () => {\n clearTimeout(helloTimeout);\n for (const client of wss.clients) client.terminate();\n await new Promise<void>((resolveClose) => wss.close(() => server.close(() => resolveClose())));\n },\n };\n}\n\ntest(\"connect returns while a background runtime advertises the selected project\", async () => {\n const root = mkdtempSync(join(tmpdir(), \"gsd-cloud-background-\"));\n const projectDir = join(root, \"project\");\n const expectedProjectDir = join(realpathSync(root), \"project\");\n const configPath = join(root, \"daemon.yaml\");\n mkdirSync(join(projectDir, \".gsd\"), { recursive: true });\n\n const gateway = await createTestGateway();\n\n saveCloudConfig(configPath, {\n gateway_url: gateway.baseUrl,\n device_token: \"test\",\n runtime_id: \"fixture-runtime\",\n enabled: true,\n });\n\n try {\n const connect = await runCli([\"connect\", \"--config\", configPath], projectDir);\n assert.equal(connect.code, 0, connect.stderr);\n assert.match(connect.stdout, /connected in the background/i);\n\n const message = await gateway.hello;\n assert.equal(message.type, \"hello\");\n const projects = message.projects as Array<{ path: string }>;\n assert.equal(projects.length, 1);\n assert.equal(projects[0]?.path, expectedProjectDir);\n\n const status = await runCli([\"status\", \"--config\", configPath], projectDir);\n assert.equal(status.code, 0, status.stderr);\n const statusBody = JSON.parse(status.stdout) as {\n background?: { running?: boolean; pid?: number | null };\n projects?: string[];\n };\n assert.equal(statusBody.background?.running, true);\n assert.equal(typeof statusBody.background?.pid, \"number\");\n assert.deepEqual(statusBody.projects, [expectedProjectDir]);\n } finally {\n await runCli([\"disconnect\", \"--config\", configPath], projectDir).catch(() => undefined);\n await gateway.close();\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"login returns after approval and keeps the selected project connected\", async () => {\n const root = mkdtempSync(join(tmpdir(), \"gsd-cloud-login-background-\"));\n const projectDir = join(root, \"project\");\n const expectedProjectDir = join(realpathSync(root), \"project\");\n const configPath = join(root, \"daemon.yaml\");\n mkdirSync(join(projectDir, \".gsd\"), { recursive: true });\n const gateway = await createTestGateway(true);\n\n try {\n const login = await runCli([\n \"login\",\n \"--gateway\", gateway.baseUrl,\n \"--config\", configPath,\n ], projectDir, 12_000);\n assert.equal(login.code, 0, login.stderr);\n assert.match(login.stdout, /Authorization approved!/);\n assert.match(login.stdout, /connected in the background/i);\n\n const message = await gateway.hello;\n const projects = message.projects as Array<{ path: string }>;\n assert.deepEqual(projects.map((project) => project.path), [expectedProjectDir]);\n\n const disconnect = await runCli([\"disconnect\", \"--config\", configPath], projectDir);\n assert.equal(disconnect.code, 0, disconnect.stderr);\n const status = await runCli([\"status\", \"--config\", configPath], projectDir);\n const statusBody = JSON.parse(status.stdout) as {\n configured?: boolean;\n background?: { running?: boolean };\n };\n assert.equal(statusBody.configured, false);\n assert.equal(statusBody.background?.running, false);\n } finally {\n await runCli([\"disconnect\", \"--config\", configPath], projectDir).catch(() => undefined);\n await gateway.close();\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"advertised project paths are canonicalized through symlinks\", async () => {\n const root = realpathSync(mkdtempSync(join(tmpdir(), \"gsd-cloud-symlink-\")));\n const realProject = join(root, \"real-project\");\n mkdirSync(join(realProject, \".gsd\"), { recursive: true });\n const linkProject = join(root, \"link-project\");\n symlinkSync(realProject, linkProject);\n const configPath = join(root, \"daemon.yaml\");\n const gateway = await createTestGateway();\n\n saveCloudConfig(configPath, {\n gateway_url: gateway.baseUrl,\n device_token: \"test\",\n runtime_id: \"fixture-runtime\",\n enabled: true,\n });\n\n try {\n // Point the runtime at the symlink; the advertised path must be the real dir.\n const connect = await runCli(\n [\"connect\", \"--config\", configPath],\n realProject,\n 5_000,\n { GSD_CLOUD_PROJECTS: linkProject },\n );\n assert.equal(connect.code, 0, connect.stderr);\n\n const message = await gateway.hello;\n const projects = message.projects as Array<{ path: string }>;\n assert.equal(projects.length, 1);\n assert.equal(projects[0]?.path, realProject);\n\n const status = await runCli([\"status\", \"--config\", configPath], realProject);\n const statusBody = JSON.parse(status.stdout) as { projects?: string[] };\n assert.deepEqual(statusBody.projects, [realProject]);\n } finally {\n await runCli([\"disconnect\", \"--config\", configPath], realProject).catch(() => undefined);\n await gateway.close();\n rmSync(root, { recursive: true, force: true });\n }\n});\n\ntest(\"a foreground runtime registers its PID so disconnect can stop it\", async () => {\n const root = realpathSync(mkdtempSync(join(tmpdir(), \"gsd-cloud-foreground-\")));\n const projectDir = join(root, \"project\");\n const configPath = join(root, \"daemon.yaml\");\n mkdirSync(join(projectDir, \".gsd\"), { recursive: true });\n const gateway = await createTestGateway();\n\n saveCloudConfig(configPath, {\n gateway_url: gateway.baseUrl,\n device_token: \"test\",\n runtime_id: \"fixture-runtime\",\n enabled: true,\n });\n\n const foreground = spawnForegroundCli([\"connect\", \"--foreground\", \"--config\", configPath], projectDir);\n try {\n // Once the foreground runtime advertises its project it is fully connected.\n await gateway.hello;\n\n // The foreground session must record its own PID in the shared runtime state\n // so status can see it and a later disconnect can find and stop it.\n const status = await runCli([\"status\", \"--config\", configPath], projectDir);\n const statusBody = JSON.parse(status.stdout) as {\n background?: { running?: boolean; pid?: number | null };\n };\n assert.equal(statusBody.background?.running, true);\n assert.equal(statusBody.background?.pid, foreground.child.pid);\n\n const disconnect = await runCli([\"disconnect\", \"--config\", configPath], projectDir);\n assert.equal(disconnect.code, 0, disconnect.stderr);\n\n // disconnect must have terminated the foreground process.\n const exitCode = await foreground.exited;\n assert.equal(exitCode, 0);\n\n const afterStatus = await runCli([\"status\", \"--config\", configPath], projectDir);\n const afterBody = JSON.parse(afterStatus.stdout) as { background?: { running?: boolean } };\n assert.equal(afterBody.background?.running, false);\n } finally {\n foreground.child.kill(\"SIGKILL\");\n await gateway.close();\n rmSync(root, { recursive: true, force: true });\n }\n});\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA8BA,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE;IAC7D,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkIhB;AA0GD,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA+BtD"}
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
// no dependency on any Daemon or Orchestrator class; the only cloud behaviour is
|
|
6
6
|
// the WS relay client driving the local GSD runtime through the Executor seam.
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
|
+
import { realpathSync } from "node:fs";
|
|
9
|
+
import { delimiter, resolve } from "node:path";
|
|
8
10
|
import { resolveConfigPath, loadConfig } from "./config.js";
|
|
9
11
|
import { Logger } from "./logger.js";
|
|
10
12
|
import { clearCloudConfig, exchangePairingCode, redactedCloudStatus, saveCloudConfig, } from "./cloud-config.js";
|
|
11
13
|
import { runDeviceFlow } from "./device-flow.js";
|
|
12
14
|
import { CloudRuntime } from "./cloud-runtime.js";
|
|
13
15
|
import { selectExecutor } from "./executors/index.js";
|
|
16
|
+
import { backgroundRuntimeStatus, clearRuntimeState, startBackgroundRuntime, stopBackgroundRuntime, writeRuntimeState, } from "./runtime-process.js";
|
|
14
17
|
export async function handleCloudCommand(argv, opts) {
|
|
15
18
|
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
16
19
|
process.stdout.write(formatUsage(opts.binaryName));
|
|
@@ -25,6 +28,7 @@ export async function handleCloudCommand(argv, opts) {
|
|
|
25
28
|
code: { type: "string" },
|
|
26
29
|
"runtime-name": { type: "string" },
|
|
27
30
|
verbose: { type: "boolean", short: "v", default: false },
|
|
31
|
+
foreground: { type: "boolean", default: false },
|
|
28
32
|
help: { type: "boolean", short: "h", default: false },
|
|
29
33
|
},
|
|
30
34
|
strict: true,
|
|
@@ -35,12 +39,18 @@ export async function handleCloudCommand(argv, opts) {
|
|
|
35
39
|
}
|
|
36
40
|
const configPath = resolveConfigPath(values.config);
|
|
37
41
|
if (command === "status") {
|
|
38
|
-
|
|
42
|
+
const config = loadConfig(configPath);
|
|
43
|
+
process.stdout.write(`${JSON.stringify({
|
|
44
|
+
...redactedCloudStatus(config),
|
|
45
|
+
projects: config.projects.scan_roots,
|
|
46
|
+
background: backgroundRuntimeStatus(configPath),
|
|
47
|
+
}, null, 2)}\n`);
|
|
39
48
|
return;
|
|
40
49
|
}
|
|
41
50
|
if (command === "disconnect") {
|
|
51
|
+
await stopBackgroundRuntime(configPath);
|
|
42
52
|
clearCloudConfig(configPath);
|
|
43
|
-
process.stdout.write(`${opts.binaryName}:
|
|
53
|
+
process.stdout.write(`${opts.binaryName}: background runtime stopped and cloud credentials removed.\n`);
|
|
44
54
|
return;
|
|
45
55
|
}
|
|
46
56
|
if (command === "login") {
|
|
@@ -54,15 +64,23 @@ export async function handleCloudCommand(argv, opts) {
|
|
|
54
64
|
runtimeName,
|
|
55
65
|
binaryName: opts.binaryName,
|
|
56
66
|
});
|
|
67
|
+
const projectDirs = selectedProjectDirs([]);
|
|
57
68
|
const config = saveCloudConfig(configPath, {
|
|
58
69
|
gateway_url: gatewayUrl,
|
|
59
70
|
device_token: deviceToken,
|
|
60
71
|
runtime_id: runtimeId,
|
|
61
72
|
...(runtimeName ? { runtime_name: runtimeName } : {}),
|
|
62
73
|
enabled: true,
|
|
63
|
-
});
|
|
74
|
+
}, projectDirs);
|
|
64
75
|
process.stdout.write(`${opts.binaryName}: cloud runtime ${runtimeId} paired — connecting...\n`);
|
|
65
|
-
|
|
76
|
+
if (values.foreground) {
|
|
77
|
+
await stopBackgroundRuntime(configPath);
|
|
78
|
+
await runCloudRuntime(config, opts.binaryName, values.verbose, projectDirs, {
|
|
79
|
+
registerConfigPath: configPath,
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
await startAndReportBackgroundRuntime(configPath, projectDirs, opts.binaryName, values.verbose);
|
|
66
84
|
return;
|
|
67
85
|
}
|
|
68
86
|
if (command === "pair") {
|
|
@@ -86,11 +104,33 @@ export async function handleCloudCommand(argv, opts) {
|
|
|
86
104
|
return;
|
|
87
105
|
}
|
|
88
106
|
if (command === "connect") {
|
|
107
|
+
let config = loadConfig(configPath);
|
|
108
|
+
if (!config.cloud?.device_token || !config.cloud.runtime_id) {
|
|
109
|
+
throw new Error("cloud runtime is not paired; run `login` first");
|
|
110
|
+
}
|
|
111
|
+
const projectDirs = selectedProjectDirs(config.projects.scan_roots);
|
|
112
|
+
config = saveCloudConfig(configPath, config.cloud, projectDirs);
|
|
113
|
+
if (values.foreground) {
|
|
114
|
+
await stopBackgroundRuntime(configPath);
|
|
115
|
+
await runCloudRuntime(config, opts.binaryName, values.verbose, projectDirs, {
|
|
116
|
+
registerConfigPath: configPath,
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await startAndReportBackgroundRuntime(configPath, projectDirs, opts.binaryName, values.verbose);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (command === "_run") {
|
|
89
124
|
const config = loadConfig(configPath);
|
|
90
125
|
if (!config.cloud?.device_token || !config.cloud.runtime_id) {
|
|
91
126
|
throw new Error("cloud runtime is not paired; run `login` first");
|
|
92
127
|
}
|
|
93
|
-
|
|
128
|
+
const projectDirs = selectedProjectDirs(config.projects.scan_roots);
|
|
129
|
+
await runCloudRuntime(config, opts.binaryName, values.verbose, projectDirs, {
|
|
130
|
+
onConnected: () => {
|
|
131
|
+
process.send?.({ type: "ready" });
|
|
132
|
+
},
|
|
133
|
+
});
|
|
94
134
|
return;
|
|
95
135
|
}
|
|
96
136
|
throw new Error(`Unknown cloud runtime command: ${command}`);
|
|
@@ -100,7 +140,7 @@ export async function handleCloudCommand(argv, opts) {
|
|
|
100
140
|
* "daemon" for the standalone agent: one CloudRuntime + one Executor, no Discord,
|
|
101
141
|
* no scanner, no session-manager class.
|
|
102
142
|
*/
|
|
103
|
-
async function runCloudRuntime(config, binaryName, verbose) {
|
|
143
|
+
async function runCloudRuntime(config, binaryName, verbose, projectDirs, opts = {}) {
|
|
104
144
|
if (!config.cloud)
|
|
105
145
|
throw new Error("cloud runtime is not configured");
|
|
106
146
|
if (config.cloud.enabled === false) {
|
|
@@ -111,12 +151,29 @@ async function runCloudRuntime(config, binaryName, verbose) {
|
|
|
111
151
|
level: config.log.level,
|
|
112
152
|
verbose,
|
|
113
153
|
});
|
|
114
|
-
const executor = selectExecutor(logger);
|
|
154
|
+
const executor = selectExecutor(logger, { projectDirs });
|
|
115
155
|
const runtime = new CloudRuntime(config.cloud, executor, logger);
|
|
116
|
-
|
|
156
|
+
// A foreground session owns the runtime itself, so it records its own PID in
|
|
157
|
+
// the shared state file. That lets a later `connect`/`disconnect` find and
|
|
158
|
+
// stop it, just like a detached background runtime. Background `_run` children
|
|
159
|
+
// do not register here; their launcher records the child PID instead.
|
|
160
|
+
if (opts.registerConfigPath) {
|
|
161
|
+
writeRuntimeState(opts.registerConfigPath, process.pid, projectDirs);
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
await runtime.start();
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (opts.registerConfigPath)
|
|
168
|
+
clearRuntimeState(opts.registerConfigPath);
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
opts.onConnected?.();
|
|
117
172
|
process.stdout.write(`${binaryName}: connected to ${config.cloud.gateway_url}. Press Ctrl+C to stop.\n`);
|
|
118
173
|
await new Promise((resolve) => {
|
|
119
174
|
const shutdown = () => {
|
|
175
|
+
if (opts.registerConfigPath)
|
|
176
|
+
clearRuntimeState(opts.registerConfigPath);
|
|
120
177
|
runtime.stop();
|
|
121
178
|
void Promise.resolve(executor.close?.()).finally(() => {
|
|
122
179
|
void logger.close().finally(() => resolve());
|
|
@@ -126,21 +183,69 @@ async function runCloudRuntime(config, binaryName, verbose) {
|
|
|
126
183
|
process.once("SIGTERM", shutdown);
|
|
127
184
|
});
|
|
128
185
|
}
|
|
186
|
+
async function startAndReportBackgroundRuntime(configPath, projectDirs, binaryName, verbose = false) {
|
|
187
|
+
const binaryPath = process.argv[1];
|
|
188
|
+
if (!binaryPath)
|
|
189
|
+
throw new Error("could not resolve the gsd-cloud executable path");
|
|
190
|
+
const status = await startBackgroundRuntime({ binaryPath, configPath, projectDirs, verbose });
|
|
191
|
+
process.stdout.write(`${binaryName}: connected in the background (PID ${status.pid}).\n`);
|
|
192
|
+
for (const project of projectDirs)
|
|
193
|
+
process.stdout.write(`${binaryName}: project ${project}\n`);
|
|
194
|
+
process.stdout.write(`${binaryName}: logs ${status.log_file}\n`);
|
|
195
|
+
}
|
|
196
|
+
function selectedProjectDirs(savedProjectDirs) {
|
|
197
|
+
const configured = process.env["GSD_CLOUD_PROJECTS"];
|
|
198
|
+
if (configured?.trim()) {
|
|
199
|
+
const configuredPaths = uniqueResolvedPaths(configured.split(delimiter));
|
|
200
|
+
if (configuredPaths.length > 0)
|
|
201
|
+
return configuredPaths;
|
|
202
|
+
}
|
|
203
|
+
if (savedProjectDirs.length > 0) {
|
|
204
|
+
const savedPaths = uniqueResolvedPaths(savedProjectDirs);
|
|
205
|
+
if (savedPaths.length > 0)
|
|
206
|
+
return savedPaths;
|
|
207
|
+
}
|
|
208
|
+
return [canonicalizePath(process.cwd())];
|
|
209
|
+
}
|
|
210
|
+
function uniqueResolvedPaths(paths) {
|
|
211
|
+
const resolved = paths
|
|
212
|
+
.map((path) => path.trim())
|
|
213
|
+
.filter(Boolean)
|
|
214
|
+
.map((path) => canonicalizePath(path));
|
|
215
|
+
return [...new Set(resolved)];
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Resolve a project path to its canonical, symlink-free absolute form. The
|
|
219
|
+
* executor advertises project paths via `resolve()` alone, so canonicalizing
|
|
220
|
+
* here keeps the saved `scan_roots` and the advertised hello paths identical to
|
|
221
|
+
* the real directory (e.g. when the cwd or GSD_CLOUD_PROJECTS points through a
|
|
222
|
+
* symlink). Falls back to the plain resolved path when the target does not yet
|
|
223
|
+
* exist on disk.
|
|
224
|
+
*/
|
|
225
|
+
function canonicalizePath(path) {
|
|
226
|
+
const resolved = resolve(path);
|
|
227
|
+
try {
|
|
228
|
+
return realpathSync(resolved);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return resolved;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
129
234
|
export function formatUsage(binaryName) {
|
|
130
|
-
return `Usage: ${binaryName} login [--gateway <url>] [--runtime-name <name>] [--config <path>]
|
|
235
|
+
return `Usage: ${binaryName} login [--gateway <url>] [--runtime-name <name>] [--config <path>] [--foreground]
|
|
131
236
|
${binaryName} status [--config <path>]
|
|
132
237
|
${binaryName} pair --gateway <url> --code <code> [--runtime-name <name>] [--config <path>]
|
|
133
|
-
${binaryName} connect [--config <path>] [--verbose]
|
|
238
|
+
${binaryName} connect [--config <path>] [--verbose] [--foreground]
|
|
134
239
|
${binaryName} disconnect [--config <path>]
|
|
135
240
|
|
|
136
241
|
Commands:
|
|
137
|
-
login (Recommended) Browser-based pairing — opens an approval URL
|
|
138
|
-
|
|
139
|
-
|
|
242
|
+
login (Recommended) Browser-based pairing — opens an approval URL, then
|
|
243
|
+
connects the current project in the background. Defaults to the
|
|
244
|
+
public GSD Cloud gateway.
|
|
140
245
|
status Show current cloud runtime configuration and connection status.
|
|
141
246
|
pair Exchange a pairing code for a device token (headless/CI environments).
|
|
142
|
-
connect
|
|
143
|
-
disconnect
|
|
247
|
+
connect Start a background connection using saved credentials and projects.
|
|
248
|
+
disconnect Stop the background runtime and remove local cloud credentials.
|
|
144
249
|
|
|
145
250
|
Options:
|
|
146
251
|
--config <path> Path to YAML config file (default: ~/.gsd/daemon.yaml)
|
|
@@ -148,6 +253,7 @@ Options:
|
|
|
148
253
|
--code <code> Pairing code from the gateway (pair only)
|
|
149
254
|
--runtime-name <name> Friendly name for this local GSD runtime
|
|
150
255
|
--verbose Print log entries to stderr in addition to the log file
|
|
256
|
+
--foreground Keep login/connect attached to this terminal (debugging)
|
|
151
257
|
--help Show this help message and exit
|
|
152
258
|
|
|
153
259
|
Environment:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,kFAAkF;AAClF,EAAE;AACF,kFAAkF;AAClF,iFAAiF;AACjF,+EAA+E;AAE/E,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAc,EAAE,IAExD;IACC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QAC3B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACnB,OAAO,EAAE;YACP,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;YACtC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAC3B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACxB,cAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YAClC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;YACxD,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE;SACtD;QACD,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEpD,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QAClG,OAAO;IACT,CAAC;IAED,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,yCAAyC,CAAC,CAAC;QAClF,OAAO;IACT,CAAC;IAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,WAAW,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QAC3C,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,aAAa,CAAC;YACjE,UAAU,EAAE,MAAM,CAAC,OAAO;YAC1B,UAAU;YACV,WAAW;YACX,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,EAAE;YACzC,WAAW,EAAE,UAAU;YACvB,YAAY,EAAE,WAAW;YACzB,UAAU,EAAE,SAAS;YACrB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,mBAAmB,SAAS,2BAA2B,CAAC,CAAC;QAChG,MAAM,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/D,OAAO;IACT,CAAC;IAED,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QACD,MAAM,WAAW,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC;YACvC,UAAU,EAAE,MAAM,CAAC,OAAO;YAC1B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,WAAW;SACZ,CAAC,CAAC;QACH,eAAe,CAAC,UAAU,EAAE;YAC1B,WAAW,EAAE,MAAM,CAAC,OAAO;YAC3B,YAAY,EAAE,MAAM,CAAC,WAAW;YAChC,UAAU,EAAE,MAAM,CAAC,SAAS;YAC5B,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QACH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,0BAA0B,MAAM,CAAC,SAAS,KAAK,CAAC,CAAC;QACxF,OAAO;IACT,CAAC;IAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,YAAY,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QACD,MAAM,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAC/D,OAAO;IACT,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,eAAe,CAAC,MAAoB,EAAE,UAAkB,EAAE,OAAgB;IACvF,IAAI,CAAC,MAAM,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACtE,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAC;IAC/F,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI;QACzB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK;QACvB,OAAO;KACR,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,UAAU,kBAAkB,MAAM,CAAC,KAAK,CAAC,WAAW,2BAA2B,CAAC,CAAC;IAEzG,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAClC,MAAM,QAAQ,GAAG,GAAG,EAAE;YACpB,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;gBACpD,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/C,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,OAAO,UAAU,UAAU;SACpB,UAAU;SACV,UAAU;SACV,UAAU;SACV,UAAU;;;;;;;;;;;;;;;;;;;;;;;;CAwBlB,CAAC;AACF,CAAC","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Standalone gsd-cloud CLI — login / status / connect / disconnect.\n//\n// Wires device-flow + CloudRuntime + a selected Executor adapter DIRECTLY. It has\n// no dependency on any Daemon or Orchestrator class; the only cloud behaviour is\n// the WS relay client driving the local GSD runtime through the Executor seam.\n\nimport { parseArgs } from \"node:util\";\nimport { resolveConfigPath, loadConfig } from \"./config.js\";\nimport { Logger } from \"./logger.js\";\nimport {\n clearCloudConfig,\n exchangePairingCode,\n redactedCloudStatus,\n saveCloudConfig,\n} from \"./cloud-config.js\";\nimport { runDeviceFlow } from \"./device-flow.js\";\nimport { CloudRuntime } from \"./cloud-runtime.js\";\nimport { selectExecutor } from \"./executors/index.js\";\nimport type { DaemonConfig } from \"./types.js\";\n\nexport async function handleCloudCommand(argv: string[], opts: {\n binaryName: string;\n}): Promise<void> {\n if (argv[0] === \"--help\" || argv[0] === \"-h\") {\n process.stdout.write(formatUsage(opts.binaryName));\n process.exit(0);\n }\n\n const command = argv[0];\n const { values } = parseArgs({\n args: argv.slice(1),\n options: {\n config: { type: \"string\", short: \"c\" },\n gateway: { type: \"string\" },\n code: { type: \"string\" },\n \"runtime-name\": { type: \"string\" },\n verbose: { type: \"boolean\", short: \"v\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n strict: true,\n });\n\n if (values.help || !command) {\n process.stdout.write(formatUsage(opts.binaryName));\n process.exit(0);\n }\n\n const configPath = resolveConfigPath(values.config);\n\n if (command === \"status\") {\n process.stdout.write(`${JSON.stringify(redactedCloudStatus(loadConfig(configPath)), null, 2)}\\n`);\n return;\n }\n\n if (command === \"disconnect\") {\n clearCloudConfig(configPath);\n process.stdout.write(`${opts.binaryName}: cloud runtime disconnected locally.\\n`);\n return;\n }\n\n if (command === \"login\") {\n if (!values.gateway) {\n throw new Error(\"login requires --gateway\");\n }\n const runtimeName = values[\"runtime-name\"];\n const { deviceToken, runtimeId, gatewayUrl } = await runDeviceFlow({\n gatewayUrl: values.gateway,\n configPath,\n runtimeName,\n binaryName: opts.binaryName,\n });\n const config = saveCloudConfig(configPath, {\n gateway_url: gatewayUrl,\n device_token: deviceToken,\n runtime_id: runtimeId,\n ...(runtimeName ? { runtime_name: runtimeName } : {}),\n enabled: true,\n });\n process.stdout.write(`${opts.binaryName}: cloud runtime ${runtimeId} paired — connecting...\\n`);\n await runCloudRuntime(config, opts.binaryName, values.verbose);\n return;\n }\n\n if (command === \"pair\") {\n if (!values.gateway || !values.code) {\n throw new Error(\"pair requires --gateway and --code\");\n }\n const runtimeName = values[\"runtime-name\"];\n const result = await exchangePairingCode({\n gatewayUrl: values.gateway,\n code: values.code,\n runtimeName,\n });\n saveCloudConfig(configPath, {\n gateway_url: values.gateway,\n device_token: result.deviceToken,\n runtime_id: result.runtimeId,\n ...(runtimeName ? { runtime_name: runtimeName } : {}),\n enabled: true,\n });\n process.stdout.write(`${opts.binaryName}: paired cloud runtime ${result.runtimeId}.\\n`);\n return;\n }\n\n if (command === \"connect\") {\n const config = loadConfig(configPath);\n if (!config.cloud?.device_token || !config.cloud.runtime_id) {\n throw new Error(\"cloud runtime is not paired; run `login` first\");\n }\n await runCloudRuntime(config, opts.binaryName, values.verbose);\n return;\n }\n\n throw new Error(`Unknown cloud runtime command: ${command}`);\n}\n\n/**\n * Start the WS relay and block until the process is signalled. This is the whole\n * \"daemon\" for the standalone agent: one CloudRuntime + one Executor, no Discord,\n * no scanner, no session-manager class.\n */\nasync function runCloudRuntime(config: DaemonConfig, binaryName: string, verbose: boolean): Promise<void> {\n if (!config.cloud) throw new Error(\"cloud runtime is not configured\");\n if (config.cloud.enabled === false) {\n throw new Error(\"cloud runtime is disabled in config; set cloud.enabled to true to connect\");\n }\n const logger = new Logger({\n filePath: config.log.file,\n level: config.log.level,\n verbose,\n });\n const executor = selectExecutor(logger);\n const runtime = new CloudRuntime(config.cloud, executor, logger);\n await runtime.start();\n process.stdout.write(`${binaryName}: connected to ${config.cloud.gateway_url}. Press Ctrl+C to stop.\\n`);\n\n await new Promise<void>((resolve) => {\n const shutdown = () => {\n runtime.stop();\n void Promise.resolve(executor.close?.()).finally(() => {\n void logger.close().finally(() => resolve());\n });\n };\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n });\n}\n\nexport function formatUsage(binaryName: string): string {\n return `Usage: ${binaryName} login [--gateway <url>] [--runtime-name <name>] [--config <path>]\n ${binaryName} status [--config <path>]\n ${binaryName} pair --gateway <url> --code <code> [--runtime-name <name>] [--config <path>]\n ${binaryName} connect [--config <path>] [--verbose]\n ${binaryName} disconnect [--config <path>]\n\nCommands:\n login (Recommended) Browser-based pairing — opens an approval URL in the\n terminal, polls for authorization, then auto-connects. Defaults to\n the public GSD Cloud gateway.\n status Show current cloud runtime configuration and connection status.\n pair Exchange a pairing code for a device token (headless/CI environments).\n connect Connect using a previously paired device token.\n disconnect Remove cloud runtime configuration from the local config file.\n\nOptions:\n --config <path> Path to YAML config file (default: ~/.gsd/daemon.yaml)\n --gateway <url> Cloud gateway URL (login defaults to https://cloud.opengsd.net)\n --code <code> Pairing code from the gateway (pair only)\n --runtime-name <name> Friendly name for this local GSD runtime\n --verbose Print log entries to stderr in addition to the log file\n --help Show this help message and exit\n\nEnvironment:\n GSD_CLOUD_PROJECTS Path-delimiter separated project dirs to advertise\n (default: current working directory)\n GSD_CLI_PATH Path to the gsd binary (default: gsd on PATH)\n GSD_CLOUD_EXECUTOR Backend adapter: gsd-pi (default), codex, claude\n`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["// Project/App: Open GSD\n// File Purpose: Standalone gsd-cloud CLI — login / status / connect / disconnect.\n//\n// Wires device-flow + CloudRuntime + a selected Executor adapter DIRECTLY. It has\n// no dependency on any Daemon or Orchestrator class; the only cloud behaviour is\n// the WS relay client driving the local GSD runtime through the Executor seam.\n\nimport { parseArgs } from \"node:util\";\nimport { realpathSync } from \"node:fs\";\nimport { delimiter, resolve } from \"node:path\";\nimport { resolveConfigPath, loadConfig } from \"./config.js\";\nimport { Logger } from \"./logger.js\";\nimport {\n clearCloudConfig,\n exchangePairingCode,\n redactedCloudStatus,\n saveCloudConfig,\n} from \"./cloud-config.js\";\nimport { runDeviceFlow } from \"./device-flow.js\";\nimport { CloudRuntime } from \"./cloud-runtime.js\";\nimport { selectExecutor } from \"./executors/index.js\";\nimport {\n backgroundRuntimeStatus,\n clearRuntimeState,\n startBackgroundRuntime,\n stopBackgroundRuntime,\n writeRuntimeState,\n} from \"./runtime-process.js\";\nimport type { DaemonConfig } from \"./types.js\";\n\nexport async function handleCloudCommand(argv: string[], opts: {\n binaryName: string;\n}): Promise<void> {\n if (argv[0] === \"--help\" || argv[0] === \"-h\") {\n process.stdout.write(formatUsage(opts.binaryName));\n process.exit(0);\n }\n\n const command = argv[0];\n const { values } = parseArgs({\n args: argv.slice(1),\n options: {\n config: { type: \"string\", short: \"c\" },\n gateway: { type: \"string\" },\n code: { type: \"string\" },\n \"runtime-name\": { type: \"string\" },\n verbose: { type: \"boolean\", short: \"v\", default: false },\n foreground: { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n strict: true,\n });\n\n if (values.help || !command) {\n process.stdout.write(formatUsage(opts.binaryName));\n process.exit(0);\n }\n\n const configPath = resolveConfigPath(values.config);\n\n if (command === \"status\") {\n const config = loadConfig(configPath);\n process.stdout.write(`${JSON.stringify({\n ...redactedCloudStatus(config),\n projects: config.projects.scan_roots,\n background: backgroundRuntimeStatus(configPath),\n }, null, 2)}\\n`);\n return;\n }\n\n if (command === \"disconnect\") {\n await stopBackgroundRuntime(configPath);\n clearCloudConfig(configPath);\n process.stdout.write(`${opts.binaryName}: background runtime stopped and cloud credentials removed.\\n`);\n return;\n }\n\n if (command === \"login\") {\n if (!values.gateway) {\n throw new Error(\"login requires --gateway\");\n }\n const runtimeName = values[\"runtime-name\"];\n const { deviceToken, runtimeId, gatewayUrl } = await runDeviceFlow({\n gatewayUrl: values.gateway,\n configPath,\n runtimeName,\n binaryName: opts.binaryName,\n });\n const projectDirs = selectedProjectDirs([]);\n const config = saveCloudConfig(configPath, {\n gateway_url: gatewayUrl,\n device_token: deviceToken,\n runtime_id: runtimeId,\n ...(runtimeName ? { runtime_name: runtimeName } : {}),\n enabled: true,\n }, projectDirs);\n process.stdout.write(`${opts.binaryName}: cloud runtime ${runtimeId} paired — connecting...\\n`);\n if (values.foreground) {\n await stopBackgroundRuntime(configPath);\n await runCloudRuntime(config, opts.binaryName, values.verbose, projectDirs, {\n registerConfigPath: configPath,\n });\n return;\n }\n await startAndReportBackgroundRuntime(configPath, projectDirs, opts.binaryName, values.verbose);\n return;\n }\n\n if (command === \"pair\") {\n if (!values.gateway || !values.code) {\n throw new Error(\"pair requires --gateway and --code\");\n }\n const runtimeName = values[\"runtime-name\"];\n const result = await exchangePairingCode({\n gatewayUrl: values.gateway,\n code: values.code,\n runtimeName,\n });\n saveCloudConfig(configPath, {\n gateway_url: values.gateway,\n device_token: result.deviceToken,\n runtime_id: result.runtimeId,\n ...(runtimeName ? { runtime_name: runtimeName } : {}),\n enabled: true,\n });\n process.stdout.write(`${opts.binaryName}: paired cloud runtime ${result.runtimeId}.\\n`);\n return;\n }\n\n if (command === \"connect\") {\n let config = loadConfig(configPath);\n if (!config.cloud?.device_token || !config.cloud.runtime_id) {\n throw new Error(\"cloud runtime is not paired; run `login` first\");\n }\n const projectDirs = selectedProjectDirs(config.projects.scan_roots);\n config = saveCloudConfig(configPath, config.cloud, projectDirs);\n if (values.foreground) {\n await stopBackgroundRuntime(configPath);\n await runCloudRuntime(config, opts.binaryName, values.verbose, projectDirs, {\n registerConfigPath: configPath,\n });\n return;\n }\n await startAndReportBackgroundRuntime(configPath, projectDirs, opts.binaryName, values.verbose);\n return;\n }\n\n if (command === \"_run\") {\n const config = loadConfig(configPath);\n if (!config.cloud?.device_token || !config.cloud.runtime_id) {\n throw new Error(\"cloud runtime is not paired; run `login` first\");\n }\n const projectDirs = selectedProjectDirs(config.projects.scan_roots);\n await runCloudRuntime(config, opts.binaryName, values.verbose, projectDirs, {\n onConnected: () => {\n process.send?.({ type: \"ready\" });\n },\n });\n return;\n }\n\n throw new Error(`Unknown cloud runtime command: ${command}`);\n}\n\n/**\n * Start the WS relay and block until the process is signalled. This is the whole\n * \"daemon\" for the standalone agent: one CloudRuntime + one Executor, no Discord,\n * no scanner, no session-manager class.\n */\nasync function runCloudRuntime(\n config: DaemonConfig,\n binaryName: string,\n verbose: boolean,\n projectDirs: string[],\n opts: { onConnected?: () => void; registerConfigPath?: string } = {},\n): Promise<void> {\n if (!config.cloud) throw new Error(\"cloud runtime is not configured\");\n if (config.cloud.enabled === false) {\n throw new Error(\"cloud runtime is disabled in config; set cloud.enabled to true to connect\");\n }\n const logger = new Logger({\n filePath: config.log.file,\n level: config.log.level,\n verbose,\n });\n const executor = selectExecutor(logger, { projectDirs });\n const runtime = new CloudRuntime(config.cloud, executor, logger);\n // A foreground session owns the runtime itself, so it records its own PID in\n // the shared state file. That lets a later `connect`/`disconnect` find and\n // stop it, just like a detached background runtime. Background `_run` children\n // do not register here; their launcher records the child PID instead.\n if (opts.registerConfigPath) {\n writeRuntimeState(opts.registerConfigPath, process.pid, projectDirs);\n }\n try {\n await runtime.start();\n } catch (error) {\n if (opts.registerConfigPath) clearRuntimeState(opts.registerConfigPath);\n throw error;\n }\n opts.onConnected?.();\n process.stdout.write(`${binaryName}: connected to ${config.cloud.gateway_url}. Press Ctrl+C to stop.\\n`);\n\n await new Promise<void>((resolve) => {\n const shutdown = () => {\n if (opts.registerConfigPath) clearRuntimeState(opts.registerConfigPath);\n runtime.stop();\n void Promise.resolve(executor.close?.()).finally(() => {\n void logger.close().finally(() => resolve());\n });\n };\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n });\n}\n\nasync function startAndReportBackgroundRuntime(\n configPath: string,\n projectDirs: string[],\n binaryName: string,\n verbose = false,\n): Promise<void> {\n const binaryPath = process.argv[1];\n if (!binaryPath) throw new Error(\"could not resolve the gsd-cloud executable path\");\n const status = await startBackgroundRuntime({ binaryPath, configPath, projectDirs, verbose });\n process.stdout.write(`${binaryName}: connected in the background (PID ${status.pid}).\\n`);\n for (const project of projectDirs) process.stdout.write(`${binaryName}: project ${project}\\n`);\n process.stdout.write(`${binaryName}: logs ${status.log_file}\\n`);\n}\n\nfunction selectedProjectDirs(savedProjectDirs: string[]): string[] {\n const configured = process.env[\"GSD_CLOUD_PROJECTS\"];\n if (configured?.trim()) {\n const configuredPaths = uniqueResolvedPaths(configured.split(delimiter));\n if (configuredPaths.length > 0) return configuredPaths;\n }\n if (savedProjectDirs.length > 0) {\n const savedPaths = uniqueResolvedPaths(savedProjectDirs);\n if (savedPaths.length > 0) return savedPaths;\n }\n return [canonicalizePath(process.cwd())];\n}\n\nfunction uniqueResolvedPaths(paths: string[]): string[] {\n const resolved = paths\n .map((path) => path.trim())\n .filter(Boolean)\n .map((path) => canonicalizePath(path));\n return [...new Set(resolved)];\n}\n\n/**\n * Resolve a project path to its canonical, symlink-free absolute form. The\n * executor advertises project paths via `resolve()` alone, so canonicalizing\n * here keeps the saved `scan_roots` and the advertised hello paths identical to\n * the real directory (e.g. when the cwd or GSD_CLOUD_PROJECTS points through a\n * symlink). Falls back to the plain resolved path when the target does not yet\n * exist on disk.\n */\nfunction canonicalizePath(path: string): string {\n const resolved = resolve(path);\n try {\n return realpathSync(resolved);\n } catch {\n return resolved;\n }\n}\n\nexport function formatUsage(binaryName: string): string {\n return `Usage: ${binaryName} login [--gateway <url>] [--runtime-name <name>] [--config <path>] [--foreground]\n ${binaryName} status [--config <path>]\n ${binaryName} pair --gateway <url> --code <code> [--runtime-name <name>] [--config <path>]\n ${binaryName} connect [--config <path>] [--verbose] [--foreground]\n ${binaryName} disconnect [--config <path>]\n\nCommands:\n login (Recommended) Browser-based pairing — opens an approval URL, then\n connects the current project in the background. Defaults to the\n public GSD Cloud gateway.\n status Show current cloud runtime configuration and connection status.\n pair Exchange a pairing code for a device token (headless/CI environments).\n connect Start a background connection using saved credentials and projects.\n disconnect Stop the background runtime and remove local cloud credentials.\n\nOptions:\n --config <path> Path to YAML config file (default: ~/.gsd/daemon.yaml)\n --gateway <url> Cloud gateway URL (login defaults to https://cloud.opengsd.net)\n --code <code> Pairing code from the gateway (pair only)\n --runtime-name <name> Friendly name for this local GSD runtime\n --verbose Print log entries to stderr in addition to the log file\n --foreground Keep login/connect attached to this terminal (debugging)\n --help Show this help message and exit\n\nEnvironment:\n GSD_CLOUD_PROJECTS Path-delimiter separated project dirs to advertise\n (default: current working directory)\n GSD_CLI_PATH Path to the gsd binary (default: gsd on PATH)\n GSD_CLOUD_EXECUTOR Backend adapter: gsd-pi (default), codex, claude\n`;\n}\n"]}
|
|
@@ -10,7 +10,7 @@ export declare function exchangePairingCode(params: {
|
|
|
10
10
|
runtimeName?: string;
|
|
11
11
|
}): Promise<PairingExchangeResult>;
|
|
12
12
|
export declare function parseCloudGatewayUrl(value: string): URL;
|
|
13
|
-
export declare function saveCloudConfig(configPath: string, nextCloud: NonNullable<DaemonConfig["cloud"]
|
|
13
|
+
export declare function saveCloudConfig(configPath: string, nextCloud: NonNullable<DaemonConfig["cloud"]>, projectDirs?: string[]): DaemonConfig;
|
|
14
14
|
export declare function clearCloudConfig(configPath: string): DaemonConfig;
|
|
15
15
|
export declare function redactedCloudStatus(config: DaemonConfig): Record<string, unknown>;
|
|
16
16
|
export declare function validateGatewayNetworkTarget(url: URL): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-config.d.ts","sourceRoot":"","sources":["../src/cloud-config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAK/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAUjC;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CA2BvD;AAED,wBAAgB,eAAe,
|
|
1
|
+
{"version":3,"file":"cloud-config.d.ts","sourceRoot":"","sources":["../src/cloud-config.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAK/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAUjC;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CA2BvD;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,WAAW,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,EAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,YAAY,CAsBd;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAWjE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWjF;AAeD,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAK3D;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,cAAc,CAkC5D;AAiCD,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,SAAS,GAAE,MAAmC,GAC7C,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAsClC"}
|