@launchsecure/launch-kit 0.0.34 → 0.0.36
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/dist/server/cli.js +277 -33
- package/dist/server/init-entry.js +741 -230
- package/dist/server/launch-bot-entry.js +4078 -0
- package/dist/server/orbit-entry.js +969 -136
- package/dist/server/radar-docker-init-entry.js +326 -32
- package/dist/server/rover-entry.js +624 -124
- package/package.json +4 -3
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/SKILL.md +53 -22
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/briefs.mjs +152 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/kickoff/SKILL.md +167 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/orbit/SKILL.md +41 -9
|
@@ -34,6 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
34
34
|
));
|
|
35
35
|
|
|
36
36
|
// src/server/cf-ingress.ts
|
|
37
|
+
function serviceLabel(s) {
|
|
38
|
+
return s.label ?? s.name;
|
|
39
|
+
}
|
|
37
40
|
async function cf(opts) {
|
|
38
41
|
const res = await fetch(`${CF_API_BASE}${opts.path}`, {
|
|
39
42
|
method: opts.method,
|
|
@@ -113,7 +116,7 @@ async function fetchConnectorToken(input, tunnelId) {
|
|
|
113
116
|
}
|
|
114
117
|
async function setIngressConfig(input, tunnelId) {
|
|
115
118
|
const ingress = input.services.map((s) => ({
|
|
116
|
-
hostname: `${s
|
|
119
|
+
hostname: `${serviceLabel(s)}.${input.zone.name}`,
|
|
117
120
|
service: `http://localhost:${s.port}`
|
|
118
121
|
}));
|
|
119
122
|
ingress.push({ service: "http_status:404" });
|
|
@@ -128,7 +131,7 @@ async function setIngressConfig(input, tunnelId) {
|
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
async function ensureDnsRecord(input, tunnelId, service) {
|
|
131
|
-
const fqdn = `${service
|
|
134
|
+
const fqdn = `${serviceLabel(service)}.${input.zone.name}`;
|
|
132
135
|
const target = `${tunnelId}.cfargotunnel.com`;
|
|
133
136
|
const existing = await cf({
|
|
134
137
|
apiToken: input.apiToken,
|
|
@@ -172,7 +175,7 @@ async function provisionIngress(input) {
|
|
|
172
175
|
await setIngressConfig(input, tunnelId);
|
|
173
176
|
await Promise.all(input.services.map((s) => ensureDnsRecord(input, tunnelId, s)));
|
|
174
177
|
const hostnames = {};
|
|
175
|
-
for (const s of input.services) hostnames[s.name] = `${s
|
|
178
|
+
for (const s of input.services) hostnames[s.name] = `${serviceLabel(s)}.${input.zone.name}`;
|
|
176
179
|
return { tunnelId, connectorToken, hostnames };
|
|
177
180
|
}
|
|
178
181
|
var import_node_fs, import_node_path, CF_API_BASE, CF_ERR_DNS_RECORD_EXISTS;
|
|
@@ -222,6 +225,17 @@ async function ensureDocker(opts = {}) {
|
|
|
222
225
|
const initial = detectDocker();
|
|
223
226
|
if (initial.daemonAlive) return initial;
|
|
224
227
|
const platform = process.platform;
|
|
228
|
+
if (initial.cliPresent && canStartDaemon(platform)) {
|
|
229
|
+
if (await confirm(opts, "Docker is installed but its daemon isn't running. Start it now?")) {
|
|
230
|
+
const started = await startDockerDaemon(platform);
|
|
231
|
+
if (started.daemonAlive) return started;
|
|
232
|
+
console.log(
|
|
233
|
+
`
|
|
234
|
+
Docker did not become ready within ${Math.round(DAEMON_START_TIMEOUT_MS / 1e3)}s. Open Docker manually and re-run setup.`
|
|
235
|
+
);
|
|
236
|
+
throw new Error("Docker daemon did not start in time.");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
225
239
|
if (!opts.autoInstall) {
|
|
226
240
|
printManualInstructions(initial, platform);
|
|
227
241
|
throw new Error("Docker is not ready. Install + start it, then re-run `launch-rover setup`.");
|
|
@@ -250,6 +264,40 @@ async function ensureDocker(opts = {}) {
|
|
|
250
264
|
`Auto-install is not supported on ${platform}. Install Docker Desktop manually, then re-run setup.`
|
|
251
265
|
);
|
|
252
266
|
}
|
|
267
|
+
function canStartDaemon(platform) {
|
|
268
|
+
if (platform === "darwin") return true;
|
|
269
|
+
if (platform === "linux") return commandExists("systemctl");
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
async function startDockerDaemon(platform) {
|
|
273
|
+
if (platform === "darwin") {
|
|
274
|
+
runAndPrint("open", ["-a", "Docker"]);
|
|
275
|
+
} else if (platform === "linux") {
|
|
276
|
+
runAndPrint("sudo", ["systemctl", "start", "docker"]);
|
|
277
|
+
} else {
|
|
278
|
+
throw new Error(`Cannot start the Docker daemon on ${platform}.`);
|
|
279
|
+
}
|
|
280
|
+
console.log("Waiting for the Docker daemon to come up\u2026");
|
|
281
|
+
const deadline = DAEMON_START_TIMEOUT_MS;
|
|
282
|
+
let waited = 0;
|
|
283
|
+
while (waited < deadline) {
|
|
284
|
+
await (0, import_promises2.setTimeout)(DAEMON_POLL_INTERVAL_MS);
|
|
285
|
+
waited += DAEMON_POLL_INTERVAL_MS;
|
|
286
|
+
const probe = detectDocker();
|
|
287
|
+
if (probe.daemonAlive) {
|
|
288
|
+
console.log(`Docker daemon is up (after ~${Math.round(waited / 1e3)}s).`);
|
|
289
|
+
return probe;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return detectDocker();
|
|
293
|
+
}
|
|
294
|
+
function commandExists(cmd) {
|
|
295
|
+
const probe = (0, import_node_child_process.spawnSync)(process.platform === "win32" ? "where" : "which", [cmd], {
|
|
296
|
+
stdio: "ignore",
|
|
297
|
+
timeout: 1500
|
|
298
|
+
});
|
|
299
|
+
return probe.status === 0;
|
|
300
|
+
}
|
|
253
301
|
function printManualInstructions(detection, platform) {
|
|
254
302
|
console.log("\n\u2500\u2500 Docker is required to run launch-rover \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
255
303
|
if (!detection.cliPresent) {
|
|
@@ -305,13 +353,16 @@ function runAndPrint(cmd, args) {
|
|
|
305
353
|
throw new Error(`${cmd} exited with code ${result.status ?? "signal"}`);
|
|
306
354
|
}
|
|
307
355
|
}
|
|
308
|
-
var import_node_child_process, import_promises, import_node_process;
|
|
356
|
+
var import_node_child_process, import_promises, import_node_process, import_promises2, DAEMON_START_TIMEOUT_MS, DAEMON_POLL_INTERVAL_MS;
|
|
309
357
|
var init_docker_install = __esm({
|
|
310
358
|
"src/server/rover/docker-install.ts"() {
|
|
311
359
|
"use strict";
|
|
312
360
|
import_node_child_process = require("node:child_process");
|
|
313
361
|
import_promises = require("node:readline/promises");
|
|
314
362
|
import_node_process = require("node:process");
|
|
363
|
+
import_promises2 = require("node:timers/promises");
|
|
364
|
+
DAEMON_START_TIMEOUT_MS = 6e4;
|
|
365
|
+
DAEMON_POLL_INTERVAL_MS = 2e3;
|
|
315
366
|
}
|
|
316
367
|
});
|
|
317
368
|
|
|
@@ -439,18 +490,18 @@ var init_mcp_client = __esm({
|
|
|
439
490
|
var state_exports = {};
|
|
440
491
|
__export(state_exports, {
|
|
441
492
|
clearRoverState: () => clearRoverState,
|
|
442
|
-
|
|
493
|
+
listRoverIds: () => listRoverIds,
|
|
443
494
|
loadRoverState: () => loadRoverState,
|
|
444
495
|
saveRoverState: () => saveRoverState
|
|
445
496
|
});
|
|
446
497
|
function stateDir() {
|
|
447
498
|
return (0, import_node_path2.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR, "rover");
|
|
448
499
|
}
|
|
449
|
-
function statePath(
|
|
450
|
-
return (0, import_node_path2.join)(stateDir(), `${
|
|
500
|
+
function statePath(roverId) {
|
|
501
|
+
return (0, import_node_path2.join)(stateDir(), `${roverId}.json`);
|
|
451
502
|
}
|
|
452
|
-
function loadRoverState(
|
|
453
|
-
const p = statePath(
|
|
503
|
+
function loadRoverState(roverId) {
|
|
504
|
+
const p = statePath(roverId);
|
|
454
505
|
if (!(0, import_node_fs2.existsSync)(p)) return null;
|
|
455
506
|
try {
|
|
456
507
|
const data = JSON.parse((0, import_node_fs2.readFileSync)(p, "utf-8"));
|
|
@@ -462,23 +513,23 @@ function loadRoverState(orgSlug) {
|
|
|
462
513
|
}
|
|
463
514
|
function saveRoverState(state) {
|
|
464
515
|
(0, import_node_fs2.mkdirSync)(stateDir(), { recursive: true });
|
|
465
|
-
const final = statePath(state.
|
|
516
|
+
const final = statePath(state.roverId);
|
|
466
517
|
const tmp = `${final}.tmp`;
|
|
467
518
|
(0, import_node_fs2.writeFileSync)(tmp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
468
519
|
(0, import_node_fs2.renameSync)(tmp, final);
|
|
469
520
|
}
|
|
470
|
-
function clearRoverState(
|
|
521
|
+
function clearRoverState(roverId) {
|
|
471
522
|
try {
|
|
472
|
-
(0, import_node_fs2.unlinkSync)(statePath(
|
|
523
|
+
(0, import_node_fs2.unlinkSync)(statePath(roverId));
|
|
473
524
|
} catch {
|
|
474
525
|
}
|
|
475
526
|
}
|
|
476
|
-
function
|
|
527
|
+
function listRoverIds() {
|
|
477
528
|
const dir = stateDir();
|
|
478
529
|
if (!(0, import_node_fs2.existsSync)(dir)) return [];
|
|
479
530
|
try {
|
|
480
531
|
const fs = require("node:fs");
|
|
481
|
-
return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5));
|
|
532
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.includes(".tunnel.")).map((f) => f.slice(0, -5));
|
|
482
533
|
} catch {
|
|
483
534
|
return [];
|
|
484
535
|
}
|
|
@@ -514,7 +565,8 @@ async function registerOrRefresh(params) {
|
|
|
514
565
|
secret,
|
|
515
566
|
tunnelUrl: params.tunnelUrl,
|
|
516
567
|
installedAt: params.existing?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
517
|
-
daemonPort: params.daemonPort
|
|
568
|
+
daemonPort: params.daemonPort,
|
|
569
|
+
installPat: params.installPat
|
|
518
570
|
};
|
|
519
571
|
saveRoverState(state);
|
|
520
572
|
return {
|
|
@@ -530,6 +582,139 @@ var init_registration = __esm({
|
|
|
530
582
|
}
|
|
531
583
|
});
|
|
532
584
|
|
|
585
|
+
// package.json
|
|
586
|
+
var require_package = __commonJS({
|
|
587
|
+
"package.json"(exports2, module2) {
|
|
588
|
+
module2.exports = {
|
|
589
|
+
name: "@launchsecure/launch-kit",
|
|
590
|
+
version: "0.0.36",
|
|
591
|
+
description: "LaunchSecure toolkit \u2014 launch-sequencer (pipeline runner + terminal bridge), launch-radar (feedback webhook receiver), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup). launch-pod is the container image these run inside.",
|
|
592
|
+
license: "MIT",
|
|
593
|
+
author: "LaunchSecure - AutomateWithUs",
|
|
594
|
+
homepage: "https://automatewith.us",
|
|
595
|
+
repository: {
|
|
596
|
+
type: "git",
|
|
597
|
+
url: "https://github.com/launchsecure/launchsecure-v2",
|
|
598
|
+
directory: "packages/cli"
|
|
599
|
+
},
|
|
600
|
+
keywords: [
|
|
601
|
+
"launchsecure",
|
|
602
|
+
"launch-kit",
|
|
603
|
+
"launch-chart",
|
|
604
|
+
"launch-pod",
|
|
605
|
+
"project-graph",
|
|
606
|
+
"launch-deck",
|
|
607
|
+
"launch-kit-beacon",
|
|
608
|
+
"feedback",
|
|
609
|
+
"bug-report",
|
|
610
|
+
"screenshot",
|
|
611
|
+
"web-component",
|
|
612
|
+
"launch-recall",
|
|
613
|
+
"playground",
|
|
614
|
+
"visual",
|
|
615
|
+
"mcp",
|
|
616
|
+
"pipeline",
|
|
617
|
+
"ai",
|
|
618
|
+
"cli",
|
|
619
|
+
"file-watcher",
|
|
620
|
+
"backup",
|
|
621
|
+
"recovery"
|
|
622
|
+
],
|
|
623
|
+
exports: {
|
|
624
|
+
"./beacon": {
|
|
625
|
+
types: "./dist/beacon/types/index.d.ts",
|
|
626
|
+
import: "./dist/beacon/beacon.mjs",
|
|
627
|
+
require: "./dist/beacon/beacon.umd.js"
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
engines: {
|
|
631
|
+
node: ">=18.0.0"
|
|
632
|
+
},
|
|
633
|
+
publishConfig: {
|
|
634
|
+
access: "public"
|
|
635
|
+
},
|
|
636
|
+
bin: {
|
|
637
|
+
"launch-kit": "./dist/server/init-entry.js",
|
|
638
|
+
"launch-sequencer": "./dist/server/cli.js",
|
|
639
|
+
"launch-radar": "./dist/server/launch-radar-entry.js",
|
|
640
|
+
"launch-chart": "./dist/server/graph-mcp-entry.js",
|
|
641
|
+
"launch-deck": "./dist/server/deck-mcp-entry.js",
|
|
642
|
+
"launch-recall": "./dist/server/recall-entry.js",
|
|
643
|
+
"launch-orbit": "./dist/server/orbit-entry.js",
|
|
644
|
+
"launch-course": "./dist/server/course-entry.js",
|
|
645
|
+
"launch-beacon": "./dist/server/beacon-monitor-entry.js",
|
|
646
|
+
"launch-rover": "./dist/server/rover-entry.js",
|
|
647
|
+
"launch-bot": "./dist/server/launch-bot-entry.js"
|
|
648
|
+
},
|
|
649
|
+
scripts: {
|
|
650
|
+
build: "pnpm build:client && pnpm build:chart-client && pnpm build:deck-client && pnpm build:council-client && pnpm build:beacon && pnpm build:server",
|
|
651
|
+
"build:beacon": "vite build --config vite.beacon.config.ts && tsc -p tsconfig.beacon.json --emitDeclarationOnly --outDir dist/beacon/types",
|
|
652
|
+
"test:beacon": "vitest run --config vite.beacon.config.ts",
|
|
653
|
+
"test:radar": "vitest run --config vite.radar.config.ts",
|
|
654
|
+
"test:chart": "vitest run --config vite.chart.test.config.ts",
|
|
655
|
+
"build:deck-client": "vite build --config vite.deck.config.ts",
|
|
656
|
+
"build:council-client": "vite build --config vite.council.config.ts",
|
|
657
|
+
"build:client": "vite build",
|
|
658
|
+
"build:chart-client": "vite build --config vite.chart.config.ts",
|
|
659
|
+
"build:server": "esbuild src/server/cli.ts src/server/fb-wizard.ts src/server/graph-mcp-entry.ts src/server/chart-serve.ts src/server/deck-mcp-entry.ts src/server/deck-serve.ts src/server/council-entry.ts src/server/council-serve.ts src/server/recall-entry.ts src/server/init-entry.ts src/server/orbit-entry.ts src/server/course-entry.ts src/server/beacon-monitor-entry.ts src/server/parse-worker-entry.ts src/server/radar-teardown-entry.ts src/server/radar-docker-init-entry.ts src/server/launch-radar-entry.ts src/server/rover-entry.ts src/server/launch-bot-entry.ts --bundle --platform=node --target=node18 --outdir=dist/server --external:node-pty --external:ws --external:typescript --external:web-tree-sitter --external:tree-sitter-typescript --external:cloudflared --external:pg --external:pg-native --external:pgsql-parser --external:libpg-query && rm -rf dist/server/public && cp -r ../claude-code-web/src/public dist/server/public && rm -rf dist/server/graph/queries && mkdir -p dist/server/graph && cp -r src/server/graph/queries dist/server/graph/queries",
|
|
660
|
+
"dev:client": "vite",
|
|
661
|
+
"dev:deck-serve": "cd ../.. && tsx watch packages/cli/src/server/deck-mcp-entry.ts serve",
|
|
662
|
+
"dev:chart": "pnpm build:server && pnpm build:chart-client && node dist/server/graph-mcp-entry.js serve",
|
|
663
|
+
"dev:server": "pnpm build:server && node dist/server/cli.js",
|
|
664
|
+
dev: 'pnpm build:server && concurrently -k -n client,server -c cyan,magenta "vite" "node dist/server/cli.js"',
|
|
665
|
+
prepublishOnly: "pnpm build"
|
|
666
|
+
},
|
|
667
|
+
files: [
|
|
668
|
+
"dist",
|
|
669
|
+
"prompts",
|
|
670
|
+
"scaffolds"
|
|
671
|
+
],
|
|
672
|
+
dependencies: {
|
|
673
|
+
"cacheable-lookup": "^7.0.0",
|
|
674
|
+
cloudflared: "^0.7.1",
|
|
675
|
+
"html-to-image": "^1.11.13",
|
|
676
|
+
"node-pty": "^1.2.0-beta.12",
|
|
677
|
+
pg: "^8.13.0",
|
|
678
|
+
"pgsql-parser": "^17.9.15",
|
|
679
|
+
"tree-sitter": "^0.21.1",
|
|
680
|
+
"tree-sitter-typescript": "^0.23.2",
|
|
681
|
+
typescript: "^5.5.0",
|
|
682
|
+
undici: "^6.25.0",
|
|
683
|
+
"web-tree-sitter": "^0.24.7",
|
|
684
|
+
ws: "^8.18.0"
|
|
685
|
+
},
|
|
686
|
+
devDependencies: {
|
|
687
|
+
"@launchsecure/claude-code-web": "workspace:*",
|
|
688
|
+
"@launchsecure/ui": "workspace:*",
|
|
689
|
+
"@types/node": "^20.0.0",
|
|
690
|
+
"@types/pg": "^8.11.10",
|
|
691
|
+
"@types/react": "^18.3.12",
|
|
692
|
+
"@types/react-dom": "^18.3.1",
|
|
693
|
+
"@types/ws": "^8.5.10",
|
|
694
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
695
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
696
|
+
"@xterm/addon-web-links": "^0.11.0",
|
|
697
|
+
"@xterm/xterm": "^5.5.0",
|
|
698
|
+
"@xyflow/react": "^12.10.2",
|
|
699
|
+
autoprefixer: "^10.4.27",
|
|
700
|
+
concurrently: "^9.2.1",
|
|
701
|
+
esbuild: "^0.28.0",
|
|
702
|
+
"happy-dom": "^20.8.8",
|
|
703
|
+
marked: "^15.0.0",
|
|
704
|
+
mermaid: "^11.4.0",
|
|
705
|
+
postcss: "^8.5.8",
|
|
706
|
+
react: "^18.3.1",
|
|
707
|
+
"react-dom": "^18.3.1",
|
|
708
|
+
"react-force-graph-2d": "^1.25.5",
|
|
709
|
+
"react-router-dom": "^6.28.0",
|
|
710
|
+
tailwindcss: "^3.4.19",
|
|
711
|
+
vite: "^5.4.11",
|
|
712
|
+
vitest: "^1.6.0"
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
|
|
533
718
|
// ../../node_modules/.pnpm/cacheable-lookup@7.0.0/node_modules/cacheable-lookup/source/index.js
|
|
534
719
|
var import_node_dns, import_node_util, import_node_os2, AsyncResolver, kCacheableLookupCreateConnection, kCacheableLookupInstance, kExpires, supportsALL, verifyAgent, map4to6, getIfaceInfo, isIterable, ignoreNoResultErrors, ttl, all, all4, all6, CacheableLookup;
|
|
535
720
|
var init_source = __esm({
|
|
@@ -11455,7 +11640,7 @@ var require_mock_interceptor = __commonJS({
|
|
|
11455
11640
|
var require_mock_client = __commonJS({
|
|
11456
11641
|
"../../node_modules/.pnpm/undici@6.25.0/node_modules/undici/lib/mock/mock-client.js"(exports2, module2) {
|
|
11457
11642
|
"use strict";
|
|
11458
|
-
var { promisify:
|
|
11643
|
+
var { promisify: promisify3 } = require("node:util");
|
|
11459
11644
|
var Client = require_client();
|
|
11460
11645
|
var { buildMockDispatch } = require_mock_utils();
|
|
11461
11646
|
var {
|
|
@@ -11495,7 +11680,7 @@ var require_mock_client = __commonJS({
|
|
|
11495
11680
|
return new MockInterceptor(opts, this[kDispatches]);
|
|
11496
11681
|
}
|
|
11497
11682
|
async [kClose]() {
|
|
11498
|
-
await
|
|
11683
|
+
await promisify3(this[kOriginalClose])();
|
|
11499
11684
|
this[kConnected] = 0;
|
|
11500
11685
|
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
|
|
11501
11686
|
}
|
|
@@ -11508,7 +11693,7 @@ var require_mock_client = __commonJS({
|
|
|
11508
11693
|
var require_mock_pool = __commonJS({
|
|
11509
11694
|
"../../node_modules/.pnpm/undici@6.25.0/node_modules/undici/lib/mock/mock-pool.js"(exports2, module2) {
|
|
11510
11695
|
"use strict";
|
|
11511
|
-
var { promisify:
|
|
11696
|
+
var { promisify: promisify3 } = require("node:util");
|
|
11512
11697
|
var Pool = require_pool();
|
|
11513
11698
|
var { buildMockDispatch } = require_mock_utils();
|
|
11514
11699
|
var {
|
|
@@ -11548,7 +11733,7 @@ var require_mock_pool = __commonJS({
|
|
|
11548
11733
|
return new MockInterceptor(opts, this[kDispatches]);
|
|
11549
11734
|
}
|
|
11550
11735
|
async [kClose]() {
|
|
11551
|
-
await
|
|
11736
|
+
await promisify3(this[kOriginalClose])();
|
|
11552
11737
|
this[kConnected] = 0;
|
|
11553
11738
|
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
|
|
11554
11739
|
}
|
|
@@ -19288,17 +19473,17 @@ function createTunnel(opts) {
|
|
|
19288
19473
|
const exhaustive = opts.provider;
|
|
19289
19474
|
throw new Error(`[tunnel] unknown provider: ${String(exhaustive)}`);
|
|
19290
19475
|
}
|
|
19291
|
-
var import_node_fs3, import_node_events,
|
|
19476
|
+
var import_node_fs3, import_node_events, import_promises3, import_undici, import_cloudflared, dnsResolver, dnsCache, dnsResilientDispatcher, BACKOFF_MIN_MS, BACKOFF_MAX_MS, SELFTEST_INTERVAL_MS, SELFTEST_TIMEOUT_MS, NAMED_FATAL_PATTERNS, CloudflaredTunnel;
|
|
19292
19477
|
var init_tunnel = __esm({
|
|
19293
19478
|
"src/server/tunnel/index.ts"() {
|
|
19294
19479
|
"use strict";
|
|
19295
19480
|
import_node_fs3 = require("node:fs");
|
|
19296
19481
|
import_node_events = require("node:events");
|
|
19297
|
-
|
|
19482
|
+
import_promises3 = require("node:dns/promises");
|
|
19298
19483
|
init_source();
|
|
19299
19484
|
import_undici = __toESM(require_undici());
|
|
19300
19485
|
import_cloudflared = require("cloudflared");
|
|
19301
|
-
dnsResolver = new
|
|
19486
|
+
dnsResolver = new import_promises3.Resolver();
|
|
19302
19487
|
dnsResolver.setServers(["1.1.1.1", "8.8.8.8"]);
|
|
19303
19488
|
dnsCache = new CacheableLookup({ resolver: dnsResolver });
|
|
19304
19489
|
dnsResilientDispatcher = new import_undici.Agent({
|
|
@@ -19530,6 +19715,262 @@ var init_hmac = __esm({
|
|
|
19530
19715
|
}
|
|
19531
19716
|
});
|
|
19532
19717
|
|
|
19718
|
+
// src/server/rover/pods.ts
|
|
19719
|
+
async function podsUp(state, body) {
|
|
19720
|
+
if (!state.installPat) {
|
|
19721
|
+
throw new PodError(
|
|
19722
|
+
"rover state is missing the install PAT \u2014 re-run `launch-rover setup` to refresh.",
|
|
19723
|
+
500
|
|
19724
|
+
);
|
|
19725
|
+
}
|
|
19726
|
+
if (!body || typeof body !== "object") {
|
|
19727
|
+
throw new PodError("body must be a JSON object");
|
|
19728
|
+
}
|
|
19729
|
+
if (typeof body.projectSlug !== "string" || !DNS_SLUG_RE.test(body.projectSlug)) {
|
|
19730
|
+
throw new PodError("projectSlug must be a DNS-safe string (a-z, 0-9, -)");
|
|
19731
|
+
}
|
|
19732
|
+
if (typeof body.claudeCredentialsB64 !== "string" || body.claudeCredentialsB64.length < 16) {
|
|
19733
|
+
throw new PodError("claudeCredentialsB64 is required (base64-encoded Claude credentials JSON)");
|
|
19734
|
+
}
|
|
19735
|
+
const image = body.image ?? DEFAULT_IMAGE;
|
|
19736
|
+
const containerName = POD_NAME_PREFIX + body.projectSlug;
|
|
19737
|
+
const workspaceVol = `${VOLUME_PREFIX}${body.projectSlug}-workspace`;
|
|
19738
|
+
const claudeVol = `${VOLUME_PREFIX}${body.projectSlug}-claude`;
|
|
19739
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19740
|
+
await stopAndRemove(containerName);
|
|
19741
|
+
const dockerConfigDir = body.skipPull ? null : setupDockerConfig();
|
|
19742
|
+
try {
|
|
19743
|
+
if (!body.skipPull) {
|
|
19744
|
+
await dockerRun(
|
|
19745
|
+
["pull", image],
|
|
19746
|
+
dockerConfigDir ? { DOCKER_CONFIG: dockerConfigDir } : {}
|
|
19747
|
+
);
|
|
19748
|
+
}
|
|
19749
|
+
const runArgs = [
|
|
19750
|
+
"run",
|
|
19751
|
+
"-d",
|
|
19752
|
+
"--name",
|
|
19753
|
+
containerName,
|
|
19754
|
+
"--restart",
|
|
19755
|
+
"unless-stopped",
|
|
19756
|
+
// Lets the container reach the host's loopback. Built-in on Docker
|
|
19757
|
+
// Desktop (macOS/Windows); explicit on Linux. Required when LS runs
|
|
19758
|
+
// on the host as a dev server (LS_SERVER_URL=http://localhost:…).
|
|
19759
|
+
"--add-host",
|
|
19760
|
+
"host.docker.internal:host-gateway",
|
|
19761
|
+
"--label",
|
|
19762
|
+
`${MANAGED_LABEL}=true`,
|
|
19763
|
+
"--label",
|
|
19764
|
+
`launch-rover.projectSlug=${body.projectSlug}`,
|
|
19765
|
+
"--label",
|
|
19766
|
+
`launch-rover.createdAt=${createdAt}`,
|
|
19767
|
+
"-v",
|
|
19768
|
+
`${workspaceVol}:/workspace`,
|
|
19769
|
+
"-v",
|
|
19770
|
+
`${claudeVol}:/home/launchpod/.claude`
|
|
19771
|
+
];
|
|
19772
|
+
if (body.podId) runArgs.push("--label", `launch-rover.podId=${body.podId}`);
|
|
19773
|
+
if (body.name) runArgs.push("--label", `launch-rover.name=${body.name}`);
|
|
19774
|
+
const envVars = {
|
|
19775
|
+
CLAUDE_CREDENTIALS_B64: body.claudeCredentialsB64,
|
|
19776
|
+
LS_PAT: state.installPat,
|
|
19777
|
+
LS_ORG_SLUG: state.orgSlug,
|
|
19778
|
+
LS_PROJECT_SLUG: body.projectSlug,
|
|
19779
|
+
LS_SERVER_URL: rewriteLocalhostForContainer(state.serverUrl)
|
|
19780
|
+
};
|
|
19781
|
+
if (body.services && Array.isArray(body.services)) {
|
|
19782
|
+
envVars.LAUNCHKIT_SERVICES = JSON.stringify(body.services);
|
|
19783
|
+
}
|
|
19784
|
+
if (body.cfBaseDomain) envVars.LAUNCHKIT_CF_BASE_DOMAIN = body.cfBaseDomain;
|
|
19785
|
+
if (body.env && typeof body.env === "object") {
|
|
19786
|
+
for (const [k, v] of Object.entries(body.env)) {
|
|
19787
|
+
if (typeof v === "string") envVars[k] = v;
|
|
19788
|
+
}
|
|
19789
|
+
}
|
|
19790
|
+
for (const [k, v] of Object.entries(envVars)) {
|
|
19791
|
+
runArgs.push("-e", `${k}=${v}`);
|
|
19792
|
+
}
|
|
19793
|
+
runArgs.push(image);
|
|
19794
|
+
const containerId = (await dockerRun(runArgs)).stdout.trim();
|
|
19795
|
+
return { podId: body.podId ?? null, projectSlug: body.projectSlug, containerId, image, createdAt };
|
|
19796
|
+
} finally {
|
|
19797
|
+
if (dockerConfigDir) (0, import_node_fs4.rmSync)(dockerConfigDir, { recursive: true, force: true });
|
|
19798
|
+
}
|
|
19799
|
+
}
|
|
19800
|
+
async function podsDown(_state, body) {
|
|
19801
|
+
if (!body || typeof body !== "object") {
|
|
19802
|
+
throw new PodError("body must be a JSON object");
|
|
19803
|
+
}
|
|
19804
|
+
if (typeof body.projectSlug !== "string" || !DNS_SLUG_RE.test(body.projectSlug)) {
|
|
19805
|
+
throw new PodError("projectSlug must be a DNS-safe string (a-z, 0-9, -)");
|
|
19806
|
+
}
|
|
19807
|
+
const mode = body.mode ?? "stop";
|
|
19808
|
+
if (mode !== "stop" && mode !== "remove" && mode !== "purge") {
|
|
19809
|
+
throw new PodError(`mode must be "stop" | "remove" | "purge" (got "${mode}")`);
|
|
19810
|
+
}
|
|
19811
|
+
const containerName = POD_NAME_PREFIX + body.projectSlug;
|
|
19812
|
+
const stopped = await dockerStopIfExists(containerName);
|
|
19813
|
+
let removed = false;
|
|
19814
|
+
let volumesRemoved = false;
|
|
19815
|
+
if (mode === "remove" || mode === "purge") {
|
|
19816
|
+
removed = await dockerRmIfExists(containerName);
|
|
19817
|
+
}
|
|
19818
|
+
if (mode === "purge") {
|
|
19819
|
+
const workspaceVol = `${VOLUME_PREFIX}${body.projectSlug}-workspace`;
|
|
19820
|
+
const claudeVol = `${VOLUME_PREFIX}${body.projectSlug}-claude`;
|
|
19821
|
+
const a = await dockerVolumeRmIfExists(workspaceVol);
|
|
19822
|
+
const b = await dockerVolumeRmIfExists(claudeVol);
|
|
19823
|
+
volumesRemoved = a || b;
|
|
19824
|
+
}
|
|
19825
|
+
return { projectSlug: body.projectSlug, mode, stopped, removed, volumesRemoved };
|
|
19826
|
+
}
|
|
19827
|
+
async function podsList(_state) {
|
|
19828
|
+
const { stdout: stdout2 } = await dockerRun([
|
|
19829
|
+
"ps",
|
|
19830
|
+
"-a",
|
|
19831
|
+
"--filter",
|
|
19832
|
+
`label=${MANAGED_LABEL}=true`,
|
|
19833
|
+
"--format",
|
|
19834
|
+
"{{json .}}"
|
|
19835
|
+
]);
|
|
19836
|
+
const pods = [];
|
|
19837
|
+
for (const line of stdout2.split("\n")) {
|
|
19838
|
+
const trimmed = line.trim();
|
|
19839
|
+
if (!trimmed) continue;
|
|
19840
|
+
let row;
|
|
19841
|
+
try {
|
|
19842
|
+
row = JSON.parse(trimmed);
|
|
19843
|
+
} catch {
|
|
19844
|
+
continue;
|
|
19845
|
+
}
|
|
19846
|
+
pods.push(parsePsRow(row));
|
|
19847
|
+
}
|
|
19848
|
+
return { pods };
|
|
19849
|
+
}
|
|
19850
|
+
async function stopAndRemove(containerName) {
|
|
19851
|
+
await dockerStopIfExists(containerName);
|
|
19852
|
+
await dockerRmIfExists(containerName);
|
|
19853
|
+
}
|
|
19854
|
+
async function dockerStopIfExists(containerName) {
|
|
19855
|
+
try {
|
|
19856
|
+
await dockerRun(["stop", containerName]);
|
|
19857
|
+
return true;
|
|
19858
|
+
} catch (err) {
|
|
19859
|
+
if (isNoSuchContainer(err)) return false;
|
|
19860
|
+
throw err;
|
|
19861
|
+
}
|
|
19862
|
+
}
|
|
19863
|
+
async function dockerRmIfExists(containerName) {
|
|
19864
|
+
try {
|
|
19865
|
+
await dockerRun(["rm", containerName]);
|
|
19866
|
+
return true;
|
|
19867
|
+
} catch (err) {
|
|
19868
|
+
if (isNoSuchContainer(err)) return false;
|
|
19869
|
+
throw err;
|
|
19870
|
+
}
|
|
19871
|
+
}
|
|
19872
|
+
async function dockerVolumeRmIfExists(volumeName) {
|
|
19873
|
+
try {
|
|
19874
|
+
await dockerRun(["volume", "rm", volumeName]);
|
|
19875
|
+
return true;
|
|
19876
|
+
} catch (err) {
|
|
19877
|
+
if (isNoSuchVolume(err)) return false;
|
|
19878
|
+
throw err;
|
|
19879
|
+
}
|
|
19880
|
+
}
|
|
19881
|
+
function isNoSuchContainer(err) {
|
|
19882
|
+
const e = err;
|
|
19883
|
+
const text = `${e?.stderr ?? ""}${e?.message ?? ""}`.toLowerCase();
|
|
19884
|
+
return text.includes("no such container");
|
|
19885
|
+
}
|
|
19886
|
+
function isNoSuchVolume(err) {
|
|
19887
|
+
const e = err;
|
|
19888
|
+
const text = `${e?.stderr ?? ""}${e?.message ?? ""}`.toLowerCase();
|
|
19889
|
+
return text.includes("no such volume") || text.includes("get no such volume");
|
|
19890
|
+
}
|
|
19891
|
+
async function dockerRun(args, envOverlay = {}) {
|
|
19892
|
+
try {
|
|
19893
|
+
const { stdout: stdout2, stderr } = await execFileAsync("docker", args, {
|
|
19894
|
+
env: { ...process.env, ...envOverlay },
|
|
19895
|
+
maxBuffer: 32 * 1024 * 1024
|
|
19896
|
+
});
|
|
19897
|
+
return { stdout: stdout2, stderr };
|
|
19898
|
+
} catch (err) {
|
|
19899
|
+
const e = err;
|
|
19900
|
+
throw new PodError(
|
|
19901
|
+
`docker ${args[0]} failed: ${(e.stderr || e.message || "").trim()}`,
|
|
19902
|
+
502
|
|
19903
|
+
);
|
|
19904
|
+
}
|
|
19905
|
+
}
|
|
19906
|
+
function setupDockerConfig() {
|
|
19907
|
+
const token = process.env.GHCR_PULL_TOKEN;
|
|
19908
|
+
if (!token) {
|
|
19909
|
+
console.log(
|
|
19910
|
+
"[rover] no GHCR_PULL_TOKEN set \u2014 pulling image anonymously (succeeds only if the image is public)"
|
|
19911
|
+
);
|
|
19912
|
+
return null;
|
|
19913
|
+
}
|
|
19914
|
+
const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path3.join)((0, import_node_os3.tmpdir)(), "launch-rover-docker-"));
|
|
19915
|
+
const auth = Buffer.from(`${GHCR_USERNAME}:${token}`, "utf-8").toString("base64");
|
|
19916
|
+
const config = { auths: { [GHCR_REGISTRY]: { auth } } };
|
|
19917
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path3.join)(dir, "config.json"), JSON.stringify(config), { mode: 384 });
|
|
19918
|
+
return dir;
|
|
19919
|
+
}
|
|
19920
|
+
function parsePsRow(row) {
|
|
19921
|
+
const labels = parseLabels(row.Labels);
|
|
19922
|
+
return {
|
|
19923
|
+
podId: labels["launch-rover.podId"] ?? null,
|
|
19924
|
+
projectSlug: labels["launch-rover.projectSlug"] ?? null,
|
|
19925
|
+
name: labels["launch-rover.name"] ?? null,
|
|
19926
|
+
containerName: row.Names ?? "",
|
|
19927
|
+
containerId: row.ID ?? "",
|
|
19928
|
+
image: row.Image ?? "",
|
|
19929
|
+
state: row.State ?? "",
|
|
19930
|
+
status: row.Status ?? "",
|
|
19931
|
+
createdAt: labels["launch-rover.createdAt"] ?? row.CreatedAt ?? null
|
|
19932
|
+
};
|
|
19933
|
+
}
|
|
19934
|
+
function rewriteLocalhostForContainer(url) {
|
|
19935
|
+
return url.replace(/^(https?:\/\/)(localhost|127\.0\.0\.1)(?=[:\/]|$)/i, "$1host.docker.internal");
|
|
19936
|
+
}
|
|
19937
|
+
function parseLabels(raw) {
|
|
19938
|
+
const out = {};
|
|
19939
|
+
if (!raw) return out;
|
|
19940
|
+
for (const pair of raw.split(",")) {
|
|
19941
|
+
const eq = pair.indexOf("=");
|
|
19942
|
+
if (eq <= 0) continue;
|
|
19943
|
+
out[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
19944
|
+
}
|
|
19945
|
+
return out;
|
|
19946
|
+
}
|
|
19947
|
+
var import_node_child_process2, import_node_fs4, import_node_os3, import_node_path3, import_node_util2, execFileAsync, DEFAULT_IMAGE, GHCR_REGISTRY, GHCR_USERNAME, MANAGED_LABEL, POD_NAME_PREFIX, VOLUME_PREFIX, DNS_SLUG_RE, PodError;
|
|
19948
|
+
var init_pods = __esm({
|
|
19949
|
+
"src/server/rover/pods.ts"() {
|
|
19950
|
+
"use strict";
|
|
19951
|
+
import_node_child_process2 = require("node:child_process");
|
|
19952
|
+
import_node_fs4 = require("node:fs");
|
|
19953
|
+
import_node_os3 = require("node:os");
|
|
19954
|
+
import_node_path3 = require("node:path");
|
|
19955
|
+
import_node_util2 = require("node:util");
|
|
19956
|
+
execFileAsync = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
19957
|
+
DEFAULT_IMAGE = "ghcr.io/launchsecure/launch-pod:latest";
|
|
19958
|
+
GHCR_REGISTRY = "ghcr.io";
|
|
19959
|
+
GHCR_USERNAME = process.env.GHCR_PULL_USERNAME ?? "ghcr-pull";
|
|
19960
|
+
MANAGED_LABEL = "launch-rover.managed";
|
|
19961
|
+
POD_NAME_PREFIX = "launch-pod-";
|
|
19962
|
+
VOLUME_PREFIX = "lp-";
|
|
19963
|
+
DNS_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
19964
|
+
PodError = class extends Error {
|
|
19965
|
+
constructor(message, httpStatus = 400) {
|
|
19966
|
+
super(message);
|
|
19967
|
+
this.name = "PodError";
|
|
19968
|
+
this.httpStatus = httpStatus;
|
|
19969
|
+
}
|
|
19970
|
+
};
|
|
19971
|
+
}
|
|
19972
|
+
});
|
|
19973
|
+
|
|
19533
19974
|
// src/server/rover/daemon.ts
|
|
19534
19975
|
var daemon_exports = {};
|
|
19535
19976
|
__export(daemon_exports, {
|
|
@@ -19537,33 +19978,33 @@ __export(daemon_exports, {
|
|
|
19537
19978
|
startDaemon: () => startDaemon
|
|
19538
19979
|
});
|
|
19539
19980
|
function lockPath() {
|
|
19540
|
-
return (0,
|
|
19981
|
+
return (0, import_node_path4.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR, ROVER_LOCK_FILENAME);
|
|
19541
19982
|
}
|
|
19542
19983
|
function readRoverLock() {
|
|
19543
19984
|
const p = lockPath();
|
|
19544
|
-
if (!(0,
|
|
19985
|
+
if (!(0, import_node_fs5.existsSync)(p)) return null;
|
|
19545
19986
|
try {
|
|
19546
|
-
return JSON.parse((0,
|
|
19987
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(p, "utf-8"));
|
|
19547
19988
|
} catch {
|
|
19548
19989
|
return null;
|
|
19549
19990
|
}
|
|
19550
19991
|
}
|
|
19551
19992
|
function writeLock(lock) {
|
|
19552
|
-
const dir = (0,
|
|
19553
|
-
(0,
|
|
19554
|
-
(0,
|
|
19993
|
+
const dir = (0, import_node_path4.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR);
|
|
19994
|
+
(0, import_node_fs5.mkdirSync)(dir, { recursive: true });
|
|
19995
|
+
(0, import_node_fs5.writeFileSync)(lockPath(), JSON.stringify(lock, null, 2) + "\n", "utf-8");
|
|
19555
19996
|
}
|
|
19556
19997
|
function clearLock() {
|
|
19557
19998
|
try {
|
|
19558
|
-
(0,
|
|
19999
|
+
(0, import_node_fs5.unlinkSync)(lockPath());
|
|
19559
20000
|
} catch {
|
|
19560
20001
|
}
|
|
19561
20002
|
}
|
|
19562
20003
|
async function startDaemon(opts) {
|
|
19563
|
-
const state = loadRoverState(opts.
|
|
20004
|
+
const state = loadRoverState(opts.roverId);
|
|
19564
20005
|
if (!state) {
|
|
19565
20006
|
throw new Error(
|
|
19566
|
-
`No rover state for
|
|
20007
|
+
`No rover state for id "${opts.roverId}". Run \`launch-rover setup\` first.`
|
|
19567
20008
|
);
|
|
19568
20009
|
}
|
|
19569
20010
|
const port = opts.port ?? state.daemonPort;
|
|
@@ -19588,7 +20029,8 @@ async function startDaemon(opts) {
|
|
|
19588
20029
|
cwd: process.cwd(),
|
|
19589
20030
|
url: state.tunnelUrl,
|
|
19590
20031
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19591
|
-
|
|
20032
|
+
roverId: state.roverId,
|
|
20033
|
+
orgSlug: state.orgSlug
|
|
19592
20034
|
};
|
|
19593
20035
|
writeLock(lock);
|
|
19594
20036
|
console.log(`[rover] listening on 0.0.0.0:${port} (tunnel: ${state.tunnelUrl})`);
|
|
@@ -19633,7 +20075,33 @@ async function handleRequest(req, res, state) {
|
|
|
19633
20075
|
console.warn(`[rover] ${method} ${path} \u2192 401 (${failure})`);
|
|
19634
20076
|
return json(res, 401, { error: "invalid_signature" });
|
|
19635
20077
|
}
|
|
19636
|
-
|
|
20078
|
+
let parsed = {};
|
|
20079
|
+
if (rawBody.length > 0) {
|
|
20080
|
+
try {
|
|
20081
|
+
parsed = JSON.parse(rawBody);
|
|
20082
|
+
} catch {
|
|
20083
|
+
return json(res, 400, { error: "malformed_json" });
|
|
20084
|
+
}
|
|
20085
|
+
}
|
|
20086
|
+
try {
|
|
20087
|
+
if (path === "/pods/up") {
|
|
20088
|
+
const result2 = await podsUp(state, parsed);
|
|
20089
|
+
return json(res, 200, result2);
|
|
20090
|
+
}
|
|
20091
|
+
if (path === "/pods/down") {
|
|
20092
|
+
const result2 = await podsDown(state, parsed);
|
|
20093
|
+
return json(res, 200, result2);
|
|
20094
|
+
}
|
|
20095
|
+
const result = await podsList(state);
|
|
20096
|
+
return json(res, 200, result);
|
|
20097
|
+
} catch (err) {
|
|
20098
|
+
if (err instanceof PodError) {
|
|
20099
|
+
console.warn(`[rover] ${method} ${path} \u2192 ${err.httpStatus} (${err.message})`);
|
|
20100
|
+
return json(res, err.httpStatus, { error: err.message });
|
|
20101
|
+
}
|
|
20102
|
+
console.error(`[rover] ${method} ${path} crashed:`, err);
|
|
20103
|
+
return json(res, 500, { error: err instanceof Error ? err.message : "internal_error" });
|
|
20104
|
+
}
|
|
19637
20105
|
}
|
|
19638
20106
|
return json(res, 404, { error: "not_found", path });
|
|
19639
20107
|
}
|
|
@@ -19651,7 +20119,7 @@ function readBody(req) {
|
|
|
19651
20119
|
});
|
|
19652
20120
|
}
|
|
19653
20121
|
async function sendHeartbeat(state) {
|
|
19654
|
-
const payload = JSON.stringify({
|
|
20122
|
+
const payload = JSON.stringify({ roverId: state.roverId });
|
|
19655
20123
|
const { header } = buildSignatureHeader(state.secret, payload);
|
|
19656
20124
|
const url = new URL("/api/rover/heartbeat", state.serverUrl);
|
|
19657
20125
|
await postJson(url, payload, { "X-LS-Signature": header });
|
|
@@ -19690,18 +20158,19 @@ function postJson(url, body, extra) {
|
|
|
19690
20158
|
req.end();
|
|
19691
20159
|
});
|
|
19692
20160
|
}
|
|
19693
|
-
var
|
|
20161
|
+
var import_node_fs5, import_node_http2, import_node_http3, import_node_https2, import_node_os4, import_node_path4, ROVER_LOCK_FILENAME;
|
|
19694
20162
|
var init_daemon = __esm({
|
|
19695
20163
|
"src/server/rover/daemon.ts"() {
|
|
19696
20164
|
"use strict";
|
|
19697
|
-
|
|
20165
|
+
import_node_fs5 = require("node:fs");
|
|
19698
20166
|
import_node_http2 = require("node:http");
|
|
19699
20167
|
import_node_http3 = require("node:http");
|
|
19700
20168
|
import_node_https2 = require("node:https");
|
|
19701
|
-
|
|
19702
|
-
|
|
20169
|
+
import_node_os4 = require("node:os");
|
|
20170
|
+
import_node_path4 = require("node:path");
|
|
19703
20171
|
init_launch_kit_paths();
|
|
19704
20172
|
init_hmac();
|
|
20173
|
+
init_pods();
|
|
19705
20174
|
init_state();
|
|
19706
20175
|
ROVER_LOCK_FILENAME = "launch-rover.lock";
|
|
19707
20176
|
}
|
|
@@ -19748,7 +20217,7 @@ function printUsage() {
|
|
|
19748
20217
|
"Usage: launch-rover setup --pat=<PAT> --org=<orgSlug> [options]",
|
|
19749
20218
|
"",
|
|
19750
20219
|
"Required:",
|
|
19751
|
-
" --pat=<ls_pat_...> Setup PAT issued by LS (
|
|
20220
|
+
" --pat=<ls_pat_...> Setup PAT issued by LS (rover:setup scope)",
|
|
19752
20221
|
" --org=<slug> Organization slug the rover belongs to",
|
|
19753
20222
|
"",
|
|
19754
20223
|
"Optional:",
|
|
@@ -19756,15 +20225,27 @@ function printUsage() {
|
|
|
19756
20225
|
" --name=<host-name> Human-readable rover name (default: hostname)",
|
|
19757
20226
|
" --port=<n> Local daemon port (default: 52749)",
|
|
19758
20227
|
" --auto-install Allow setup to install Docker via brew / get-docker.sh",
|
|
19759
|
-
" --yes / -y Skip confirmation prompts (assume yes)",
|
|
20228
|
+
" --yes / -y Skip confirmation prompts (assume yes). Also auto-starts",
|
|
20229
|
+
" an installed-but-stopped Docker daemon without prompting.",
|
|
19760
20230
|
" --detach Spawn the daemon as a detached process and exit",
|
|
19761
20231
|
""
|
|
19762
20232
|
].join("\n")
|
|
19763
20233
|
);
|
|
19764
20234
|
}
|
|
20235
|
+
function ensureNodeVersion() {
|
|
20236
|
+
const raw = process.versions.node;
|
|
20237
|
+
const major = Number.parseInt(raw.split(".")[0] ?? "", 10);
|
|
20238
|
+
if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) {
|
|
20239
|
+
throw new Error(
|
|
20240
|
+
`launch-rover requires Node >= ${MIN_NODE_MAJOR} (running ${raw}). Upgrade Node, then re-run \`launch-rover setup\`.`
|
|
20241
|
+
);
|
|
20242
|
+
}
|
|
20243
|
+
console.log(`[rover] node ok: v${raw}`);
|
|
20244
|
+
}
|
|
19765
20245
|
async function runSetup(argv) {
|
|
19766
20246
|
const args = parseArgs(argv);
|
|
19767
20247
|
console.log(`[rover] setup org=${args.orgSlug} server=${args.serverUrl}`);
|
|
20248
|
+
ensureNodeVersion();
|
|
19768
20249
|
await ensureDocker({ autoInstall: args.autoInstall, yes: args.yes });
|
|
19769
20250
|
const dockerInfo = detectDocker();
|
|
19770
20251
|
console.log(`[rover] docker ready: ${dockerInfo.version ?? "unknown"}`);
|
|
@@ -19773,26 +20254,33 @@ async function runSetup(argv) {
|
|
|
19773
20254
|
pat: args.pat,
|
|
19774
20255
|
orgSlug: args.orgSlug
|
|
19775
20256
|
});
|
|
19776
|
-
console.log("[rover] fetching
|
|
20257
|
+
console.log("[rover] fetching rover config from LS\u2026");
|
|
19777
20258
|
const lsConfig = await mcp.call("rover_get_config", {});
|
|
19778
|
-
if (!lsConfig.
|
|
20259
|
+
if (!lsConfig.id) {
|
|
19779
20260
|
throw new Error(
|
|
19780
|
-
"LS rover config
|
|
20261
|
+
"LS rover config did not return an id. PAT may not be bound to a specific rover \u2014 create the rover in LS Organization \u2192 Rovers and re-issue the install command."
|
|
19781
20262
|
);
|
|
19782
20263
|
}
|
|
19783
|
-
|
|
19784
|
-
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
|
|
19788
|
-
|
|
19789
|
-
const
|
|
19790
|
-
const
|
|
19791
|
-
console.log(`[rover]
|
|
20264
|
+
if (!lsConfig.cfApiToken || !lsConfig.cfDomainName) {
|
|
20265
|
+
throw new Error(
|
|
20266
|
+
"LS rover config is missing Cloudflare credentials. Connect a Cloudflare integration and set the rover's domain in LS Organization \u2192 Rovers."
|
|
20267
|
+
);
|
|
20268
|
+
}
|
|
20269
|
+
console.log(`[rover] LS rover "${lsConfig.name}" (id: ${lsConfig.id}, status: ${lsConfig.status})`);
|
|
20270
|
+
const roverId = lsConfig.id;
|
|
20271
|
+
const zone = await resolveZone(lsConfig.cfApiToken, lsConfig.cfDomainName);
|
|
20272
|
+
console.log(`[rover] resolved CF zone: ${zone.name} (account ${zone.accountId})`);
|
|
20273
|
+
const stateDir2 = (0, import_node_path5.join)((0, import_node_os5.homedir)(), LAUNCHSECURE_DIR, "rover");
|
|
20274
|
+
(0, import_node_fs6.mkdirSync)(stateDir2, { recursive: true });
|
|
20275
|
+
const tunnelStateFile = (0, import_node_path5.join)(stateDir2, `${roverId}.tunnel.json`);
|
|
20276
|
+
const idSuffix = roverId.replace(/[^a-z0-9]/gi, "").toLowerCase().slice(-8);
|
|
20277
|
+
const subdomain = `rover-${args.orgSlug.replace(/[^a-z0-9-]/gi, "-").toLowerCase().slice(0, 30)}-${idSuffix}`;
|
|
20278
|
+
const tunnelName = `launch-rover-${roverId}`;
|
|
20279
|
+
console.log(`[rover] provisioning CF tunnel ${tunnelName} \u2192 ${subdomain}.${zone.name}`);
|
|
19792
20280
|
const ingress = await provisionIngress({
|
|
19793
20281
|
apiToken: lsConfig.cfApiToken,
|
|
19794
|
-
accountId:
|
|
19795
|
-
zone: { id:
|
|
20282
|
+
accountId: zone.accountId,
|
|
20283
|
+
zone: { id: zone.id, name: zone.name },
|
|
19796
20284
|
tunnelName,
|
|
19797
20285
|
services: [{ name: subdomain, port: args.port }],
|
|
19798
20286
|
stateFile: tunnelStateFile
|
|
@@ -19803,18 +20291,19 @@ async function runSetup(argv) {
|
|
|
19803
20291
|
}
|
|
19804
20292
|
const tunnelUrl = `https://${hostname}`;
|
|
19805
20293
|
console.log(`[rover] tunnel public URL: ${tunnelUrl}`);
|
|
19806
|
-
const existing = loadRoverState(
|
|
20294
|
+
const existing = loadRoverState(roverId);
|
|
19807
20295
|
const outcome = await registerOrRefresh({
|
|
19808
20296
|
mcp,
|
|
19809
20297
|
orgSlug: args.orgSlug,
|
|
19810
20298
|
serverUrl: args.serverUrl,
|
|
19811
20299
|
tunnelUrl,
|
|
19812
20300
|
daemonPort: args.port,
|
|
20301
|
+
installPat: args.pat,
|
|
19813
20302
|
hostInfo: {
|
|
19814
|
-
platform: (0,
|
|
19815
|
-
hostname: args.name ?? (0,
|
|
20303
|
+
platform: (0, import_node_os5.platform)(),
|
|
20304
|
+
hostname: args.name ?? (0, import_node_os5.hostname)(),
|
|
19816
20305
|
dockerVersion: dockerInfo.version ?? void 0,
|
|
19817
|
-
kitVersion:
|
|
20306
|
+
kitVersion: KIT_VERSION
|
|
19818
20307
|
},
|
|
19819
20308
|
existing
|
|
19820
20309
|
});
|
|
@@ -19825,30 +20314,39 @@ async function runSetup(argv) {
|
|
|
19825
20314
|
console.log("");
|
|
19826
20315
|
if (outcome.approvalStatus === "pending_approval") {
|
|
19827
20316
|
console.log("\u2713 Rover is live and registered, awaiting admin approval in LS.");
|
|
19828
|
-
console.log(` ${args.serverUrl}/${args.orgSlug}/
|
|
20317
|
+
console.log(` ${args.serverUrl}/${args.orgSlug}/rovers`);
|
|
19829
20318
|
} else if (outcome.approvalStatus === "approved") {
|
|
19830
20319
|
console.log("\u2713 Rover is live and already approved. Heartbeats will start within 60s.");
|
|
19831
20320
|
} else {
|
|
19832
20321
|
console.log(`! Rover state: ${outcome.approvalStatus}.`);
|
|
19833
20322
|
}
|
|
19834
20323
|
}
|
|
19835
|
-
async function
|
|
19836
|
-
const res = await fetch(
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
|
|
19840
|
-
|
|
19841
|
-
|
|
19842
|
-
|
|
20324
|
+
async function resolveZone(apiToken, domainName) {
|
|
20325
|
+
const res = await fetch(
|
|
20326
|
+
`https://api.cloudflare.com/client/v4/zones?name=${encodeURIComponent(domainName)}`,
|
|
20327
|
+
{
|
|
20328
|
+
headers: {
|
|
20329
|
+
Authorization: `Bearer ${apiToken}`,
|
|
20330
|
+
Accept: "application/json"
|
|
20331
|
+
},
|
|
20332
|
+
signal: AbortSignal.timeout(1e4)
|
|
20333
|
+
}
|
|
20334
|
+
);
|
|
19843
20335
|
if (!res.ok) {
|
|
19844
20336
|
throw new Error(`CF zone lookup failed: HTTP ${res.status}`);
|
|
19845
20337
|
}
|
|
19846
20338
|
const body = await res.json();
|
|
19847
|
-
if (!body.success
|
|
20339
|
+
if (!body.success) {
|
|
19848
20340
|
const reason = body.errors?.[0]?.message ?? "unknown";
|
|
19849
20341
|
throw new Error(`CF zone lookup failed: ${reason}`);
|
|
19850
20342
|
}
|
|
19851
|
-
|
|
20343
|
+
const zone = body.result?.[0];
|
|
20344
|
+
if (!zone?.id || !zone.account?.id) {
|
|
20345
|
+
throw new Error(
|
|
20346
|
+
`No Cloudflare zone found for "${domainName}". The API token may lack Zone:Read, or the domain isn't on this Cloudflare account.`
|
|
20347
|
+
);
|
|
20348
|
+
}
|
|
20349
|
+
return { id: zone.id, name: zone.name, accountId: zone.account.id };
|
|
19852
20350
|
}
|
|
19853
20351
|
async function launchDaemon(args, ingress, state) {
|
|
19854
20352
|
if (!args.detach) {
|
|
@@ -19863,11 +20361,11 @@ async function launchDaemon(args, ingress, state) {
|
|
|
19863
20361
|
});
|
|
19864
20362
|
void tunnel.start();
|
|
19865
20363
|
const { startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
19866
|
-
await startDaemon2({
|
|
20364
|
+
await startDaemon2({ roverId: state.roverId, port: args.port });
|
|
19867
20365
|
return;
|
|
19868
20366
|
}
|
|
19869
20367
|
const argv0 = process.argv[1] ?? "launch-rover";
|
|
19870
|
-
const child = (0,
|
|
20368
|
+
const child = (0, import_node_child_process3.spawn)(process.execPath, [argv0, "serve", `--rover=${state.roverId}`], {
|
|
19871
20369
|
detached: true,
|
|
19872
20370
|
stdio: "ignore",
|
|
19873
20371
|
env: {
|
|
@@ -19879,25 +20377,27 @@ async function launchDaemon(args, ingress, state) {
|
|
|
19879
20377
|
child.unref();
|
|
19880
20378
|
console.log(`[rover] daemon spawned (pid ${child.pid}, detached)`);
|
|
19881
20379
|
}
|
|
19882
|
-
function runSetupReset(
|
|
19883
|
-
clearRoverState(
|
|
20380
|
+
function runSetupReset(roverId) {
|
|
20381
|
+
clearRoverState(roverId);
|
|
19884
20382
|
}
|
|
19885
|
-
var
|
|
20383
|
+
var import_node_os5, import_node_child_process3, import_node_path5, import_node_fs6, KIT_VERSION, DEFAULT_SERVER, DEFAULT_DAEMON_PORT, MIN_NODE_MAJOR;
|
|
19886
20384
|
var init_setup = __esm({
|
|
19887
20385
|
"src/server/rover/setup.ts"() {
|
|
19888
20386
|
"use strict";
|
|
19889
|
-
|
|
19890
|
-
|
|
19891
|
-
|
|
19892
|
-
|
|
20387
|
+
import_node_os5 = require("node:os");
|
|
20388
|
+
import_node_child_process3 = require("node:child_process");
|
|
20389
|
+
import_node_path5 = require("node:path");
|
|
20390
|
+
import_node_fs6 = require("node:fs");
|
|
19893
20391
|
init_cf_ingress();
|
|
19894
20392
|
init_launch_kit_paths();
|
|
19895
20393
|
init_docker_install();
|
|
19896
20394
|
init_mcp_client();
|
|
19897
20395
|
init_registration();
|
|
19898
20396
|
init_state();
|
|
20397
|
+
KIT_VERSION = require_package().version;
|
|
19899
20398
|
DEFAULT_SERVER = "https://launchsecure-v2.vercel.app";
|
|
19900
20399
|
DEFAULT_DAEMON_PORT = 52749;
|
|
20400
|
+
MIN_NODE_MAJOR = 18;
|
|
19901
20401
|
}
|
|
19902
20402
|
});
|
|
19903
20403
|
|
|
@@ -19907,41 +20407,41 @@ __export(teardown_exports, {
|
|
|
19907
20407
|
runStatus: () => runStatus,
|
|
19908
20408
|
runTeardown: () => runTeardown
|
|
19909
20409
|
});
|
|
19910
|
-
function parseArgs2(argv,
|
|
19911
|
-
let
|
|
20410
|
+
function parseArgs2(argv, requireRover = true) {
|
|
20411
|
+
let roverId;
|
|
19912
20412
|
let force = false;
|
|
19913
20413
|
for (const arg of argv) {
|
|
19914
|
-
if (arg.startsWith("--
|
|
20414
|
+
if (arg.startsWith("--rover=")) roverId = arg.slice("--rover=".length);
|
|
19915
20415
|
else if (arg === "--force" || arg === "-f") force = true;
|
|
19916
20416
|
else if (arg === "--help" || arg === "-h") return null;
|
|
19917
20417
|
else throw new Error(`Unknown argument: ${arg}`);
|
|
19918
20418
|
}
|
|
19919
|
-
if (!
|
|
19920
|
-
const known =
|
|
20419
|
+
if (!roverId) {
|
|
20420
|
+
const known = listRoverIds();
|
|
19921
20421
|
if (known.length === 1) {
|
|
19922
|
-
|
|
19923
|
-
} else if (
|
|
20422
|
+
roverId = known[0];
|
|
20423
|
+
} else if (requireRover) {
|
|
19924
20424
|
const hint = known.length === 0 ? "no rovers found" : `multiple rovers found: ${known.join(", ")}`;
|
|
19925
|
-
throw new Error(`--
|
|
20425
|
+
throw new Error(`--rover=<id> is required (${hint})`);
|
|
19926
20426
|
} else {
|
|
19927
20427
|
return null;
|
|
19928
20428
|
}
|
|
19929
20429
|
}
|
|
19930
|
-
return {
|
|
20430
|
+
return { roverId, force };
|
|
19931
20431
|
}
|
|
19932
20432
|
async function runTeardown(argv) {
|
|
19933
20433
|
const parsed = parseArgs2(argv);
|
|
19934
20434
|
if (!parsed) {
|
|
19935
|
-
console.log("Usage: launch-rover teardown --
|
|
20435
|
+
console.log("Usage: launch-rover teardown --rover=<id> [--force]");
|
|
19936
20436
|
return;
|
|
19937
20437
|
}
|
|
19938
|
-
const state = loadRoverState(parsed.
|
|
20438
|
+
const state = loadRoverState(parsed.roverId);
|
|
19939
20439
|
if (!state) {
|
|
19940
|
-
console.log(`[rover] no local state for
|
|
20440
|
+
console.log(`[rover] no local state for rover "${parsed.roverId}" \u2014 nothing to tear down.`);
|
|
19941
20441
|
return;
|
|
19942
20442
|
}
|
|
19943
20443
|
const lock = readRoverLock();
|
|
19944
|
-
if (lock && lock.
|
|
20444
|
+
if (lock && lock.roverId === parsed.roverId) {
|
|
19945
20445
|
if (isPidAlive(lock.pid)) {
|
|
19946
20446
|
console.log(`[rover] stopping daemon (pid ${lock.pid})\u2026`);
|
|
19947
20447
|
try {
|
|
@@ -19950,7 +20450,7 @@ async function runTeardown(argv) {
|
|
|
19950
20450
|
}
|
|
19951
20451
|
const deadline = Date.now() + 3e3;
|
|
19952
20452
|
while (Date.now() < deadline && isPidAlive(lock.pid)) {
|
|
19953
|
-
await
|
|
20453
|
+
await sleep2(100);
|
|
19954
20454
|
}
|
|
19955
20455
|
if (isPidAlive(lock.pid) && parsed.force) {
|
|
19956
20456
|
console.log("[rover] daemon did not exit, sending SIGKILL");
|
|
@@ -19961,44 +20461,43 @@ async function runTeardown(argv) {
|
|
|
19961
20461
|
}
|
|
19962
20462
|
}
|
|
19963
20463
|
}
|
|
19964
|
-
clearRoverState(parsed.
|
|
19965
|
-
const tunnelStateFile = (0,
|
|
19966
|
-
(0,
|
|
20464
|
+
clearRoverState(parsed.roverId);
|
|
20465
|
+
const tunnelStateFile = (0, import_node_path6.join)(
|
|
20466
|
+
(0, import_node_os6.homedir)(),
|
|
19967
20467
|
LAUNCHSECURE_DIR,
|
|
19968
20468
|
"rover",
|
|
19969
|
-
`${parsed.
|
|
20469
|
+
`${parsed.roverId}.tunnel.json`
|
|
19970
20470
|
);
|
|
19971
|
-
if ((0,
|
|
20471
|
+
if ((0, import_node_fs7.existsSync)(tunnelStateFile)) {
|
|
19972
20472
|
try {
|
|
19973
|
-
(0,
|
|
20473
|
+
(0, import_node_fs7.unlinkSync)(tunnelStateFile);
|
|
19974
20474
|
} catch {
|
|
19975
20475
|
}
|
|
19976
20476
|
}
|
|
19977
|
-
console.log(`[rover] teardown complete for
|
|
20477
|
+
console.log(`[rover] teardown complete for rover "${parsed.roverId}".`);
|
|
19978
20478
|
console.log(" \u2192 To fully reset on the LS side (revoke install PAT, allow re-create), use the");
|
|
19979
|
-
console.log(` "Reset" button at ${state.serverUrl}/${
|
|
20479
|
+
console.log(` "Reset" button at ${state.serverUrl}/${state.orgSlug}/rovers`);
|
|
19980
20480
|
}
|
|
19981
20481
|
function runStatus(argv) {
|
|
19982
20482
|
const parsed = parseArgs2(
|
|
19983
20483
|
argv,
|
|
19984
|
-
/*
|
|
20484
|
+
/* requireRover */
|
|
19985
20485
|
false
|
|
19986
20486
|
);
|
|
19987
|
-
const
|
|
19988
|
-
if (
|
|
20487
|
+
const rovers = parsed ? [parsed.roverId] : listRoverIds();
|
|
20488
|
+
if (rovers.length === 0) {
|
|
19989
20489
|
console.log("[rover] no rovers configured on this host.");
|
|
19990
20490
|
return;
|
|
19991
20491
|
}
|
|
19992
|
-
for (const
|
|
19993
|
-
const state = loadRoverState(
|
|
20492
|
+
for (const roverId of rovers) {
|
|
20493
|
+
const state = loadRoverState(roverId);
|
|
19994
20494
|
if (!state) {
|
|
19995
|
-
console.log(`[rover] ${
|
|
20495
|
+
console.log(`[rover] ${roverId}: no state file`);
|
|
19996
20496
|
continue;
|
|
19997
20497
|
}
|
|
19998
20498
|
const lock = readRoverLock();
|
|
19999
|
-
const live = lock && lock.
|
|
20000
|
-
console.log(`\u2500\u2500 ${orgSlug} \u2500\u2500`);
|
|
20001
|
-
console.log(` rover id: ${state.roverId}`);
|
|
20499
|
+
const live = lock && lock.roverId === roverId && isPidAlive(lock.pid);
|
|
20500
|
+
console.log(`\u2500\u2500 ${roverId} (${state.orgSlug}) \u2500\u2500`);
|
|
20002
20501
|
console.log(` org id: ${state.organizationId}`);
|
|
20003
20502
|
console.log(` server: ${state.serverUrl}`);
|
|
20004
20503
|
console.log(` tunnel url: ${state.tunnelUrl}`);
|
|
@@ -20019,16 +20518,16 @@ function isPidAlive(pid) {
|
|
|
20019
20518
|
return false;
|
|
20020
20519
|
}
|
|
20021
20520
|
}
|
|
20022
|
-
function
|
|
20521
|
+
function sleep2(ms) {
|
|
20023
20522
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20024
20523
|
}
|
|
20025
|
-
var
|
|
20524
|
+
var import_node_fs7, import_node_os6, import_node_path6;
|
|
20026
20525
|
var init_teardown = __esm({
|
|
20027
20526
|
"src/server/rover/teardown.ts"() {
|
|
20028
20527
|
"use strict";
|
|
20029
|
-
|
|
20030
|
-
|
|
20031
|
-
|
|
20528
|
+
import_node_fs7 = require("node:fs");
|
|
20529
|
+
import_node_os6 = require("node:os");
|
|
20530
|
+
import_node_path6 = require("node:path");
|
|
20032
20531
|
init_launch_kit_paths();
|
|
20033
20532
|
init_daemon();
|
|
20034
20533
|
init_state();
|
|
@@ -20049,9 +20548,10 @@ function printUsage2() {
|
|
|
20049
20548
|
" setup --pat=<PAT> --org=<slug> [options]",
|
|
20050
20549
|
" install Docker, provision CF tunnel,",
|
|
20051
20550
|
" register with LaunchSecure, start daemon",
|
|
20052
|
-
" serve [--
|
|
20053
|
-
"
|
|
20054
|
-
"
|
|
20551
|
+
" serve [--rover=<id>] start the rover HTTP daemon (foreground).",
|
|
20552
|
+
" With one local rover, --rover is optional.",
|
|
20553
|
+
" status [--rover=<id>] print state + daemon liveness",
|
|
20554
|
+
" teardown --rover=<id> stop daemon and clear local state",
|
|
20055
20555
|
" (does NOT delete the LS RoverInstance \u2014 use",
|
|
20056
20556
|
" the Reset button in LS for that)",
|
|
20057
20557
|
"",
|
|
@@ -20074,21 +20574,21 @@ async function main() {
|
|
|
20074
20574
|
return;
|
|
20075
20575
|
}
|
|
20076
20576
|
case "serve": {
|
|
20077
|
-
const
|
|
20078
|
-
if (!
|
|
20079
|
-
const {
|
|
20080
|
-
const known =
|
|
20577
|
+
const roverId = argv.find((a) => a.startsWith("--rover="))?.slice("--rover=".length);
|
|
20578
|
+
if (!roverId) {
|
|
20579
|
+
const { listRoverIds: listRoverIds2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
20580
|
+
const known = listRoverIds2();
|
|
20081
20581
|
if (known.length !== 1) {
|
|
20082
20582
|
throw new Error(
|
|
20083
|
-
`serve needs --
|
|
20583
|
+
`serve needs --rover=<id>${known.length ? ` (known: ${known.join(", ")})` : " (no rovers configured)"}`
|
|
20084
20584
|
);
|
|
20085
20585
|
}
|
|
20086
20586
|
const { startDaemon: startDaemon3 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
20087
|
-
await startDaemon3({
|
|
20587
|
+
await startDaemon3({ roverId: known[0] });
|
|
20088
20588
|
return;
|
|
20089
20589
|
}
|
|
20090
20590
|
const { startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
20091
|
-
await startDaemon2({
|
|
20591
|
+
await startDaemon2({ roverId });
|
|
20092
20592
|
return;
|
|
20093
20593
|
}
|
|
20094
20594
|
case "status": {
|