@launchsecure/launch-kit 0.0.34 → 0.0.35
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 +37 -9
- package/dist/server/council-entry.js +0 -0
- package/dist/server/fb-wizard.js +0 -0
- package/dist/server/init-entry.js +128 -20
- package/dist/server/launch-bot-entry.js +4078 -0
- package/dist/server/orbit-entry.js +123 -26
- package/dist/server/radar-docker-init-entry.js +55 -3
- package/dist/server/radar-entrypoint-entry.js +0 -0
- package/dist/server/radar-teardown-entry.js +0 -0
- package/dist/server/rover-entry.js +547 -114
- package/package.json +24 -24
- 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 +151 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/orbit/SKILL.md +14 -2
- package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +0 -0
- package/scaffolds/recall-hook/scripts/ensure-recall.sh +0 -0
|
@@ -439,18 +439,18 @@ var init_mcp_client = __esm({
|
|
|
439
439
|
var state_exports = {};
|
|
440
440
|
__export(state_exports, {
|
|
441
441
|
clearRoverState: () => clearRoverState,
|
|
442
|
-
|
|
442
|
+
listRoverIds: () => listRoverIds,
|
|
443
443
|
loadRoverState: () => loadRoverState,
|
|
444
444
|
saveRoverState: () => saveRoverState
|
|
445
445
|
});
|
|
446
446
|
function stateDir() {
|
|
447
447
|
return (0, import_node_path2.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR, "rover");
|
|
448
448
|
}
|
|
449
|
-
function statePath(
|
|
450
|
-
return (0, import_node_path2.join)(stateDir(), `${
|
|
449
|
+
function statePath(roverId) {
|
|
450
|
+
return (0, import_node_path2.join)(stateDir(), `${roverId}.json`);
|
|
451
451
|
}
|
|
452
|
-
function loadRoverState(
|
|
453
|
-
const p = statePath(
|
|
452
|
+
function loadRoverState(roverId) {
|
|
453
|
+
const p = statePath(roverId);
|
|
454
454
|
if (!(0, import_node_fs2.existsSync)(p)) return null;
|
|
455
455
|
try {
|
|
456
456
|
const data = JSON.parse((0, import_node_fs2.readFileSync)(p, "utf-8"));
|
|
@@ -462,23 +462,23 @@ function loadRoverState(orgSlug) {
|
|
|
462
462
|
}
|
|
463
463
|
function saveRoverState(state) {
|
|
464
464
|
(0, import_node_fs2.mkdirSync)(stateDir(), { recursive: true });
|
|
465
|
-
const final = statePath(state.
|
|
465
|
+
const final = statePath(state.roverId);
|
|
466
466
|
const tmp = `${final}.tmp`;
|
|
467
467
|
(0, import_node_fs2.writeFileSync)(tmp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
468
468
|
(0, import_node_fs2.renameSync)(tmp, final);
|
|
469
469
|
}
|
|
470
|
-
function clearRoverState(
|
|
470
|
+
function clearRoverState(roverId) {
|
|
471
471
|
try {
|
|
472
|
-
(0, import_node_fs2.unlinkSync)(statePath(
|
|
472
|
+
(0, import_node_fs2.unlinkSync)(statePath(roverId));
|
|
473
473
|
} catch {
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
|
-
function
|
|
476
|
+
function listRoverIds() {
|
|
477
477
|
const dir = stateDir();
|
|
478
478
|
if (!(0, import_node_fs2.existsSync)(dir)) return [];
|
|
479
479
|
try {
|
|
480
480
|
const fs = require("node:fs");
|
|
481
|
-
return fs.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5));
|
|
481
|
+
return fs.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.includes(".tunnel.")).map((f) => f.slice(0, -5));
|
|
482
482
|
} catch {
|
|
483
483
|
return [];
|
|
484
484
|
}
|
|
@@ -514,7 +514,8 @@ async function registerOrRefresh(params) {
|
|
|
514
514
|
secret,
|
|
515
515
|
tunnelUrl: params.tunnelUrl,
|
|
516
516
|
installedAt: params.existing?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
517
|
-
daemonPort: params.daemonPort
|
|
517
|
+
daemonPort: params.daemonPort,
|
|
518
|
+
installPat: params.installPat
|
|
518
519
|
};
|
|
519
520
|
saveRoverState(state);
|
|
520
521
|
return {
|
|
@@ -530,6 +531,139 @@ var init_registration = __esm({
|
|
|
530
531
|
}
|
|
531
532
|
});
|
|
532
533
|
|
|
534
|
+
// package.json
|
|
535
|
+
var require_package = __commonJS({
|
|
536
|
+
"package.json"(exports2, module2) {
|
|
537
|
+
module2.exports = {
|
|
538
|
+
name: "@launchsecure/launch-kit",
|
|
539
|
+
version: "0.0.35",
|
|
540
|
+
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.",
|
|
541
|
+
license: "MIT",
|
|
542
|
+
author: "LaunchSecure - AutomateWithUs",
|
|
543
|
+
homepage: "https://automatewith.us",
|
|
544
|
+
repository: {
|
|
545
|
+
type: "git",
|
|
546
|
+
url: "https://github.com/launchsecure/launchsecure-v2",
|
|
547
|
+
directory: "packages/cli"
|
|
548
|
+
},
|
|
549
|
+
keywords: [
|
|
550
|
+
"launchsecure",
|
|
551
|
+
"launch-kit",
|
|
552
|
+
"launch-chart",
|
|
553
|
+
"launch-pod",
|
|
554
|
+
"project-graph",
|
|
555
|
+
"launch-deck",
|
|
556
|
+
"launch-kit-beacon",
|
|
557
|
+
"feedback",
|
|
558
|
+
"bug-report",
|
|
559
|
+
"screenshot",
|
|
560
|
+
"web-component",
|
|
561
|
+
"launch-recall",
|
|
562
|
+
"playground",
|
|
563
|
+
"visual",
|
|
564
|
+
"mcp",
|
|
565
|
+
"pipeline",
|
|
566
|
+
"ai",
|
|
567
|
+
"cli",
|
|
568
|
+
"file-watcher",
|
|
569
|
+
"backup",
|
|
570
|
+
"recovery"
|
|
571
|
+
],
|
|
572
|
+
exports: {
|
|
573
|
+
"./beacon": {
|
|
574
|
+
types: "./dist/beacon/types/index.d.ts",
|
|
575
|
+
import: "./dist/beacon/beacon.mjs",
|
|
576
|
+
require: "./dist/beacon/beacon.umd.js"
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
engines: {
|
|
580
|
+
node: ">=18.0.0"
|
|
581
|
+
},
|
|
582
|
+
publishConfig: {
|
|
583
|
+
access: "public"
|
|
584
|
+
},
|
|
585
|
+
bin: {
|
|
586
|
+
"launch-kit": "./dist/server/init-entry.js",
|
|
587
|
+
"launch-sequencer": "./dist/server/cli.js",
|
|
588
|
+
"launch-radar": "./dist/server/launch-radar-entry.js",
|
|
589
|
+
"launch-chart": "./dist/server/graph-mcp-entry.js",
|
|
590
|
+
"launch-deck": "./dist/server/deck-mcp-entry.js",
|
|
591
|
+
"launch-recall": "./dist/server/recall-entry.js",
|
|
592
|
+
"launch-orbit": "./dist/server/orbit-entry.js",
|
|
593
|
+
"launch-course": "./dist/server/course-entry.js",
|
|
594
|
+
"launch-beacon": "./dist/server/beacon-monitor-entry.js",
|
|
595
|
+
"launch-rover": "./dist/server/rover-entry.js",
|
|
596
|
+
"launch-bot": "./dist/server/launch-bot-entry.js"
|
|
597
|
+
},
|
|
598
|
+
scripts: {
|
|
599
|
+
build: "pnpm build:client && pnpm build:chart-client && pnpm build:deck-client && pnpm build:council-client && pnpm build:beacon && pnpm build:server",
|
|
600
|
+
"build:beacon": "vite build --config vite.beacon.config.ts && tsc -p tsconfig.beacon.json --emitDeclarationOnly --outDir dist/beacon/types",
|
|
601
|
+
"test:beacon": "vitest run --config vite.beacon.config.ts",
|
|
602
|
+
"test:radar": "vitest run --config vite.radar.config.ts",
|
|
603
|
+
"test:chart": "vitest run --config vite.chart.test.config.ts",
|
|
604
|
+
"build:deck-client": "vite build --config vite.deck.config.ts",
|
|
605
|
+
"build:council-client": "vite build --config vite.council.config.ts",
|
|
606
|
+
"build:client": "vite build",
|
|
607
|
+
"build:chart-client": "vite build --config vite.chart.config.ts",
|
|
608
|
+
"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",
|
|
609
|
+
"dev:client": "vite",
|
|
610
|
+
"dev:deck-serve": "cd ../.. && tsx watch packages/cli/src/server/deck-mcp-entry.ts serve",
|
|
611
|
+
"dev:chart": "pnpm build:server && pnpm build:chart-client && node dist/server/graph-mcp-entry.js serve",
|
|
612
|
+
"dev:server": "pnpm build:server && node dist/server/cli.js",
|
|
613
|
+
dev: 'pnpm build:server && concurrently -k -n client,server -c cyan,magenta "vite" "node dist/server/cli.js"',
|
|
614
|
+
prepublishOnly: "pnpm build"
|
|
615
|
+
},
|
|
616
|
+
files: [
|
|
617
|
+
"dist",
|
|
618
|
+
"prompts",
|
|
619
|
+
"scaffolds"
|
|
620
|
+
],
|
|
621
|
+
dependencies: {
|
|
622
|
+
"cacheable-lookup": "^7.0.0",
|
|
623
|
+
cloudflared: "^0.7.1",
|
|
624
|
+
"html-to-image": "^1.11.13",
|
|
625
|
+
"node-pty": "^1.2.0-beta.12",
|
|
626
|
+
pg: "^8.13.0",
|
|
627
|
+
"pgsql-parser": "^17.9.15",
|
|
628
|
+
"tree-sitter": "^0.21.1",
|
|
629
|
+
"tree-sitter-typescript": "^0.23.2",
|
|
630
|
+
typescript: "^5.5.0",
|
|
631
|
+
undici: "^6.25.0",
|
|
632
|
+
"web-tree-sitter": "^0.24.7",
|
|
633
|
+
ws: "^8.18.0"
|
|
634
|
+
},
|
|
635
|
+
devDependencies: {
|
|
636
|
+
"@launchsecure/claude-code-web": "workspace:*",
|
|
637
|
+
"@launchsecure/ui": "workspace:*",
|
|
638
|
+
"@types/node": "^20.0.0",
|
|
639
|
+
"@types/pg": "^8.11.10",
|
|
640
|
+
"@types/react": "^18.3.12",
|
|
641
|
+
"@types/react-dom": "^18.3.1",
|
|
642
|
+
"@types/ws": "^8.5.10",
|
|
643
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
644
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
645
|
+
"@xterm/addon-web-links": "^0.11.0",
|
|
646
|
+
"@xterm/xterm": "^5.5.0",
|
|
647
|
+
"@xyflow/react": "^12.10.2",
|
|
648
|
+
autoprefixer: "^10.4.27",
|
|
649
|
+
concurrently: "^9.2.1",
|
|
650
|
+
esbuild: "^0.28.0",
|
|
651
|
+
"happy-dom": "^20.8.8",
|
|
652
|
+
marked: "^15.0.0",
|
|
653
|
+
mermaid: "^11.4.0",
|
|
654
|
+
postcss: "^8.5.8",
|
|
655
|
+
react: "^18.3.1",
|
|
656
|
+
"react-dom": "^18.3.1",
|
|
657
|
+
"react-force-graph-2d": "^1.25.5",
|
|
658
|
+
"react-router-dom": "^6.28.0",
|
|
659
|
+
tailwindcss: "^3.4.19",
|
|
660
|
+
vite: "^5.4.11",
|
|
661
|
+
vitest: "^1.6.0"
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
533
667
|
// ../../node_modules/.pnpm/cacheable-lookup@7.0.0/node_modules/cacheable-lookup/source/index.js
|
|
534
668
|
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
669
|
var init_source = __esm({
|
|
@@ -11455,7 +11589,7 @@ var require_mock_interceptor = __commonJS({
|
|
|
11455
11589
|
var require_mock_client = __commonJS({
|
|
11456
11590
|
"../../node_modules/.pnpm/undici@6.25.0/node_modules/undici/lib/mock/mock-client.js"(exports2, module2) {
|
|
11457
11591
|
"use strict";
|
|
11458
|
-
var { promisify:
|
|
11592
|
+
var { promisify: promisify3 } = require("node:util");
|
|
11459
11593
|
var Client = require_client();
|
|
11460
11594
|
var { buildMockDispatch } = require_mock_utils();
|
|
11461
11595
|
var {
|
|
@@ -11495,7 +11629,7 @@ var require_mock_client = __commonJS({
|
|
|
11495
11629
|
return new MockInterceptor(opts, this[kDispatches]);
|
|
11496
11630
|
}
|
|
11497
11631
|
async [kClose]() {
|
|
11498
|
-
await
|
|
11632
|
+
await promisify3(this[kOriginalClose])();
|
|
11499
11633
|
this[kConnected] = 0;
|
|
11500
11634
|
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
|
|
11501
11635
|
}
|
|
@@ -11508,7 +11642,7 @@ var require_mock_client = __commonJS({
|
|
|
11508
11642
|
var require_mock_pool = __commonJS({
|
|
11509
11643
|
"../../node_modules/.pnpm/undici@6.25.0/node_modules/undici/lib/mock/mock-pool.js"(exports2, module2) {
|
|
11510
11644
|
"use strict";
|
|
11511
|
-
var { promisify:
|
|
11645
|
+
var { promisify: promisify3 } = require("node:util");
|
|
11512
11646
|
var Pool = require_pool();
|
|
11513
11647
|
var { buildMockDispatch } = require_mock_utils();
|
|
11514
11648
|
var {
|
|
@@ -11548,7 +11682,7 @@ var require_mock_pool = __commonJS({
|
|
|
11548
11682
|
return new MockInterceptor(opts, this[kDispatches]);
|
|
11549
11683
|
}
|
|
11550
11684
|
async [kClose]() {
|
|
11551
|
-
await
|
|
11685
|
+
await promisify3(this[kOriginalClose])();
|
|
11552
11686
|
this[kConnected] = 0;
|
|
11553
11687
|
this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
|
|
11554
11688
|
}
|
|
@@ -19530,6 +19664,259 @@ var init_hmac = __esm({
|
|
|
19530
19664
|
}
|
|
19531
19665
|
});
|
|
19532
19666
|
|
|
19667
|
+
// src/server/rover/pods.ts
|
|
19668
|
+
async function podsUp(state, body) {
|
|
19669
|
+
if (!state.installPat) {
|
|
19670
|
+
throw new PodError(
|
|
19671
|
+
"rover state is missing the install PAT \u2014 re-run `launch-rover setup` to refresh.",
|
|
19672
|
+
500
|
|
19673
|
+
);
|
|
19674
|
+
}
|
|
19675
|
+
if (!body || typeof body !== "object") {
|
|
19676
|
+
throw new PodError("body must be a JSON object");
|
|
19677
|
+
}
|
|
19678
|
+
if (typeof body.projectSlug !== "string" || !DNS_SLUG_RE.test(body.projectSlug)) {
|
|
19679
|
+
throw new PodError("projectSlug must be a DNS-safe string (a-z, 0-9, -)");
|
|
19680
|
+
}
|
|
19681
|
+
if (typeof body.claudeCredentialsB64 !== "string" || body.claudeCredentialsB64.length < 16) {
|
|
19682
|
+
throw new PodError("claudeCredentialsB64 is required (base64-encoded Claude credentials JSON)");
|
|
19683
|
+
}
|
|
19684
|
+
const image = body.image ?? DEFAULT_IMAGE;
|
|
19685
|
+
const containerName = POD_NAME_PREFIX + body.projectSlug;
|
|
19686
|
+
const workspaceVol = `${VOLUME_PREFIX}${body.projectSlug}-workspace`;
|
|
19687
|
+
const claudeVol = `${VOLUME_PREFIX}${body.projectSlug}-claude`;
|
|
19688
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
19689
|
+
await stopAndRemove(containerName);
|
|
19690
|
+
const dockerConfigDir = body.skipPull ? null : setupDockerConfig();
|
|
19691
|
+
try {
|
|
19692
|
+
if (dockerConfigDir) {
|
|
19693
|
+
await dockerRun(["pull", image], { DOCKER_CONFIG: dockerConfigDir });
|
|
19694
|
+
}
|
|
19695
|
+
const runArgs = [
|
|
19696
|
+
"run",
|
|
19697
|
+
"-d",
|
|
19698
|
+
"--name",
|
|
19699
|
+
containerName,
|
|
19700
|
+
"--restart",
|
|
19701
|
+
"unless-stopped",
|
|
19702
|
+
// Lets the container reach the host's loopback. Built-in on Docker
|
|
19703
|
+
// Desktop (macOS/Windows); explicit on Linux. Required when LS runs
|
|
19704
|
+
// on the host as a dev server (LS_SERVER_URL=http://localhost:…).
|
|
19705
|
+
"--add-host",
|
|
19706
|
+
"host.docker.internal:host-gateway",
|
|
19707
|
+
"--label",
|
|
19708
|
+
`${MANAGED_LABEL}=true`,
|
|
19709
|
+
"--label",
|
|
19710
|
+
`launch-rover.projectSlug=${body.projectSlug}`,
|
|
19711
|
+
"--label",
|
|
19712
|
+
`launch-rover.createdAt=${createdAt}`,
|
|
19713
|
+
"-v",
|
|
19714
|
+
`${workspaceVol}:/workspace`,
|
|
19715
|
+
"-v",
|
|
19716
|
+
`${claudeVol}:/home/launchpod/.claude`
|
|
19717
|
+
];
|
|
19718
|
+
if (body.podId) runArgs.push("--label", `launch-rover.podId=${body.podId}`);
|
|
19719
|
+
if (body.name) runArgs.push("--label", `launch-rover.name=${body.name}`);
|
|
19720
|
+
const envVars = {
|
|
19721
|
+
CLAUDE_CREDENTIALS_B64: body.claudeCredentialsB64,
|
|
19722
|
+
LS_PAT: state.installPat,
|
|
19723
|
+
LS_ORG_SLUG: state.orgSlug,
|
|
19724
|
+
LS_PROJECT_SLUG: body.projectSlug,
|
|
19725
|
+
LS_SERVER_URL: rewriteLocalhostForContainer(state.serverUrl)
|
|
19726
|
+
};
|
|
19727
|
+
if (body.services && Array.isArray(body.services)) {
|
|
19728
|
+
envVars.LAUNCHKIT_SERVICES = JSON.stringify(body.services);
|
|
19729
|
+
}
|
|
19730
|
+
if (body.cfBaseDomain) envVars.LAUNCHKIT_CF_BASE_DOMAIN = body.cfBaseDomain;
|
|
19731
|
+
if (body.env && typeof body.env === "object") {
|
|
19732
|
+
for (const [k, v] of Object.entries(body.env)) {
|
|
19733
|
+
if (typeof v === "string") envVars[k] = v;
|
|
19734
|
+
}
|
|
19735
|
+
}
|
|
19736
|
+
for (const [k, v] of Object.entries(envVars)) {
|
|
19737
|
+
runArgs.push("-e", `${k}=${v}`);
|
|
19738
|
+
}
|
|
19739
|
+
runArgs.push(image);
|
|
19740
|
+
const containerId = (await dockerRun(runArgs)).stdout.trim();
|
|
19741
|
+
return { podId: body.podId ?? null, projectSlug: body.projectSlug, containerId, image, createdAt };
|
|
19742
|
+
} finally {
|
|
19743
|
+
if (dockerConfigDir) (0, import_node_fs4.rmSync)(dockerConfigDir, { recursive: true, force: true });
|
|
19744
|
+
}
|
|
19745
|
+
}
|
|
19746
|
+
async function podsDown(_state, body) {
|
|
19747
|
+
if (!body || typeof body !== "object") {
|
|
19748
|
+
throw new PodError("body must be a JSON object");
|
|
19749
|
+
}
|
|
19750
|
+
if (typeof body.projectSlug !== "string" || !DNS_SLUG_RE.test(body.projectSlug)) {
|
|
19751
|
+
throw new PodError("projectSlug must be a DNS-safe string (a-z, 0-9, -)");
|
|
19752
|
+
}
|
|
19753
|
+
const mode = body.mode ?? "stop";
|
|
19754
|
+
if (mode !== "stop" && mode !== "remove" && mode !== "purge") {
|
|
19755
|
+
throw new PodError(`mode must be "stop" | "remove" | "purge" (got "${mode}")`);
|
|
19756
|
+
}
|
|
19757
|
+
const containerName = POD_NAME_PREFIX + body.projectSlug;
|
|
19758
|
+
const stopped = await dockerStopIfExists(containerName);
|
|
19759
|
+
let removed = false;
|
|
19760
|
+
let volumesRemoved = false;
|
|
19761
|
+
if (mode === "remove" || mode === "purge") {
|
|
19762
|
+
removed = await dockerRmIfExists(containerName);
|
|
19763
|
+
}
|
|
19764
|
+
if (mode === "purge") {
|
|
19765
|
+
const workspaceVol = `${VOLUME_PREFIX}${body.projectSlug}-workspace`;
|
|
19766
|
+
const claudeVol = `${VOLUME_PREFIX}${body.projectSlug}-claude`;
|
|
19767
|
+
const a = await dockerVolumeRmIfExists(workspaceVol);
|
|
19768
|
+
const b = await dockerVolumeRmIfExists(claudeVol);
|
|
19769
|
+
volumesRemoved = a || b;
|
|
19770
|
+
}
|
|
19771
|
+
return { projectSlug: body.projectSlug, mode, stopped, removed, volumesRemoved };
|
|
19772
|
+
}
|
|
19773
|
+
async function podsList(_state) {
|
|
19774
|
+
const { stdout: stdout2 } = await dockerRun([
|
|
19775
|
+
"ps",
|
|
19776
|
+
"-a",
|
|
19777
|
+
"--filter",
|
|
19778
|
+
`label=${MANAGED_LABEL}=true`,
|
|
19779
|
+
"--format",
|
|
19780
|
+
"{{json .}}"
|
|
19781
|
+
]);
|
|
19782
|
+
const pods = [];
|
|
19783
|
+
for (const line of stdout2.split("\n")) {
|
|
19784
|
+
const trimmed = line.trim();
|
|
19785
|
+
if (!trimmed) continue;
|
|
19786
|
+
let row;
|
|
19787
|
+
try {
|
|
19788
|
+
row = JSON.parse(trimmed);
|
|
19789
|
+
} catch {
|
|
19790
|
+
continue;
|
|
19791
|
+
}
|
|
19792
|
+
pods.push(parsePsRow(row));
|
|
19793
|
+
}
|
|
19794
|
+
return { pods };
|
|
19795
|
+
}
|
|
19796
|
+
async function stopAndRemove(containerName) {
|
|
19797
|
+
await dockerStopIfExists(containerName);
|
|
19798
|
+
await dockerRmIfExists(containerName);
|
|
19799
|
+
}
|
|
19800
|
+
async function dockerStopIfExists(containerName) {
|
|
19801
|
+
try {
|
|
19802
|
+
await dockerRun(["stop", containerName]);
|
|
19803
|
+
return true;
|
|
19804
|
+
} catch (err) {
|
|
19805
|
+
if (isNoSuchContainer(err)) return false;
|
|
19806
|
+
throw err;
|
|
19807
|
+
}
|
|
19808
|
+
}
|
|
19809
|
+
async function dockerRmIfExists(containerName) {
|
|
19810
|
+
try {
|
|
19811
|
+
await dockerRun(["rm", containerName]);
|
|
19812
|
+
return true;
|
|
19813
|
+
} catch (err) {
|
|
19814
|
+
if (isNoSuchContainer(err)) return false;
|
|
19815
|
+
throw err;
|
|
19816
|
+
}
|
|
19817
|
+
}
|
|
19818
|
+
async function dockerVolumeRmIfExists(volumeName) {
|
|
19819
|
+
try {
|
|
19820
|
+
await dockerRun(["volume", "rm", volumeName]);
|
|
19821
|
+
return true;
|
|
19822
|
+
} catch (err) {
|
|
19823
|
+
if (isNoSuchVolume(err)) return false;
|
|
19824
|
+
throw err;
|
|
19825
|
+
}
|
|
19826
|
+
}
|
|
19827
|
+
function isNoSuchContainer(err) {
|
|
19828
|
+
const e = err;
|
|
19829
|
+
const text = `${e?.stderr ?? ""}${e?.message ?? ""}`.toLowerCase();
|
|
19830
|
+
return text.includes("no such container");
|
|
19831
|
+
}
|
|
19832
|
+
function isNoSuchVolume(err) {
|
|
19833
|
+
const e = err;
|
|
19834
|
+
const text = `${e?.stderr ?? ""}${e?.message ?? ""}`.toLowerCase();
|
|
19835
|
+
return text.includes("no such volume") || text.includes("get no such volume");
|
|
19836
|
+
}
|
|
19837
|
+
async function dockerRun(args, envOverlay = {}) {
|
|
19838
|
+
try {
|
|
19839
|
+
const { stdout: stdout2, stderr } = await execFileAsync("docker", args, {
|
|
19840
|
+
env: { ...process.env, ...envOverlay },
|
|
19841
|
+
maxBuffer: 32 * 1024 * 1024
|
|
19842
|
+
});
|
|
19843
|
+
return { stdout: stdout2, stderr };
|
|
19844
|
+
} catch (err) {
|
|
19845
|
+
const e = err;
|
|
19846
|
+
throw new PodError(
|
|
19847
|
+
`docker ${args[0]} failed: ${(e.stderr || e.message || "").trim()}`,
|
|
19848
|
+
502
|
|
19849
|
+
);
|
|
19850
|
+
}
|
|
19851
|
+
}
|
|
19852
|
+
function setupDockerConfig() {
|
|
19853
|
+
const token = process.env.GHCR_PULL_TOKEN;
|
|
19854
|
+
if (!token) {
|
|
19855
|
+
throw new PodError(
|
|
19856
|
+
"GHCR_PULL_TOKEN is not set in the rover's environment. Add it to .env.local and restart the rover daemon. (Will be replaced by per-call token from LS later.)",
|
|
19857
|
+
500
|
|
19858
|
+
);
|
|
19859
|
+
}
|
|
19860
|
+
const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path3.join)((0, import_node_os3.tmpdir)(), "launch-rover-docker-"));
|
|
19861
|
+
const auth = Buffer.from(`${GHCR_USERNAME}:${token}`, "utf-8").toString("base64");
|
|
19862
|
+
const config = { auths: { [GHCR_REGISTRY]: { auth } } };
|
|
19863
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path3.join)(dir, "config.json"), JSON.stringify(config), { mode: 384 });
|
|
19864
|
+
return dir;
|
|
19865
|
+
}
|
|
19866
|
+
function parsePsRow(row) {
|
|
19867
|
+
const labels = parseLabels(row.Labels);
|
|
19868
|
+
return {
|
|
19869
|
+
podId: labels["launch-rover.podId"] ?? null,
|
|
19870
|
+
projectSlug: labels["launch-rover.projectSlug"] ?? null,
|
|
19871
|
+
name: labels["launch-rover.name"] ?? null,
|
|
19872
|
+
containerName: row.Names ?? "",
|
|
19873
|
+
containerId: row.ID ?? "",
|
|
19874
|
+
image: row.Image ?? "",
|
|
19875
|
+
state: row.State ?? "",
|
|
19876
|
+
status: row.Status ?? "",
|
|
19877
|
+
createdAt: labels["launch-rover.createdAt"] ?? row.CreatedAt ?? null
|
|
19878
|
+
};
|
|
19879
|
+
}
|
|
19880
|
+
function rewriteLocalhostForContainer(url) {
|
|
19881
|
+
return url.replace(/^(https?:\/\/)(localhost|127\.0\.0\.1)(?=[:\/]|$)/i, "$1host.docker.internal");
|
|
19882
|
+
}
|
|
19883
|
+
function parseLabels(raw) {
|
|
19884
|
+
const out = {};
|
|
19885
|
+
if (!raw) return out;
|
|
19886
|
+
for (const pair of raw.split(",")) {
|
|
19887
|
+
const eq = pair.indexOf("=");
|
|
19888
|
+
if (eq <= 0) continue;
|
|
19889
|
+
out[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
|
19890
|
+
}
|
|
19891
|
+
return out;
|
|
19892
|
+
}
|
|
19893
|
+
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;
|
|
19894
|
+
var init_pods = __esm({
|
|
19895
|
+
"src/server/rover/pods.ts"() {
|
|
19896
|
+
"use strict";
|
|
19897
|
+
import_node_child_process2 = require("node:child_process");
|
|
19898
|
+
import_node_fs4 = require("node:fs");
|
|
19899
|
+
import_node_os3 = require("node:os");
|
|
19900
|
+
import_node_path3 = require("node:path");
|
|
19901
|
+
import_node_util2 = require("node:util");
|
|
19902
|
+
execFileAsync = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
19903
|
+
DEFAULT_IMAGE = "ghcr.io/launchsecure/launch-pod:latest";
|
|
19904
|
+
GHCR_REGISTRY = "ghcr.io";
|
|
19905
|
+
GHCR_USERNAME = process.env.GHCR_PULL_USERNAME ?? "ghcr-pull";
|
|
19906
|
+
MANAGED_LABEL = "launch-rover.managed";
|
|
19907
|
+
POD_NAME_PREFIX = "launch-pod-";
|
|
19908
|
+
VOLUME_PREFIX = "lp-";
|
|
19909
|
+
DNS_SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
19910
|
+
PodError = class extends Error {
|
|
19911
|
+
constructor(message, httpStatus = 400) {
|
|
19912
|
+
super(message);
|
|
19913
|
+
this.name = "PodError";
|
|
19914
|
+
this.httpStatus = httpStatus;
|
|
19915
|
+
}
|
|
19916
|
+
};
|
|
19917
|
+
}
|
|
19918
|
+
});
|
|
19919
|
+
|
|
19533
19920
|
// src/server/rover/daemon.ts
|
|
19534
19921
|
var daemon_exports = {};
|
|
19535
19922
|
__export(daemon_exports, {
|
|
@@ -19537,33 +19924,33 @@ __export(daemon_exports, {
|
|
|
19537
19924
|
startDaemon: () => startDaemon
|
|
19538
19925
|
});
|
|
19539
19926
|
function lockPath() {
|
|
19540
|
-
return (0,
|
|
19927
|
+
return (0, import_node_path4.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR, ROVER_LOCK_FILENAME);
|
|
19541
19928
|
}
|
|
19542
19929
|
function readRoverLock() {
|
|
19543
19930
|
const p = lockPath();
|
|
19544
|
-
if (!(0,
|
|
19931
|
+
if (!(0, import_node_fs5.existsSync)(p)) return null;
|
|
19545
19932
|
try {
|
|
19546
|
-
return JSON.parse((0,
|
|
19933
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(p, "utf-8"));
|
|
19547
19934
|
} catch {
|
|
19548
19935
|
return null;
|
|
19549
19936
|
}
|
|
19550
19937
|
}
|
|
19551
19938
|
function writeLock(lock) {
|
|
19552
|
-
const dir = (0,
|
|
19553
|
-
(0,
|
|
19554
|
-
(0,
|
|
19939
|
+
const dir = (0, import_node_path4.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR);
|
|
19940
|
+
(0, import_node_fs5.mkdirSync)(dir, { recursive: true });
|
|
19941
|
+
(0, import_node_fs5.writeFileSync)(lockPath(), JSON.stringify(lock, null, 2) + "\n", "utf-8");
|
|
19555
19942
|
}
|
|
19556
19943
|
function clearLock() {
|
|
19557
19944
|
try {
|
|
19558
|
-
(0,
|
|
19945
|
+
(0, import_node_fs5.unlinkSync)(lockPath());
|
|
19559
19946
|
} catch {
|
|
19560
19947
|
}
|
|
19561
19948
|
}
|
|
19562
19949
|
async function startDaemon(opts) {
|
|
19563
|
-
const state = loadRoverState(opts.
|
|
19950
|
+
const state = loadRoverState(opts.roverId);
|
|
19564
19951
|
if (!state) {
|
|
19565
19952
|
throw new Error(
|
|
19566
|
-
`No rover state for
|
|
19953
|
+
`No rover state for id "${opts.roverId}". Run \`launch-rover setup\` first.`
|
|
19567
19954
|
);
|
|
19568
19955
|
}
|
|
19569
19956
|
const port = opts.port ?? state.daemonPort;
|
|
@@ -19588,7 +19975,8 @@ async function startDaemon(opts) {
|
|
|
19588
19975
|
cwd: process.cwd(),
|
|
19589
19976
|
url: state.tunnelUrl,
|
|
19590
19977
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19591
|
-
|
|
19978
|
+
roverId: state.roverId,
|
|
19979
|
+
orgSlug: state.orgSlug
|
|
19592
19980
|
};
|
|
19593
19981
|
writeLock(lock);
|
|
19594
19982
|
console.log(`[rover] listening on 0.0.0.0:${port} (tunnel: ${state.tunnelUrl})`);
|
|
@@ -19633,7 +20021,33 @@ async function handleRequest(req, res, state) {
|
|
|
19633
20021
|
console.warn(`[rover] ${method} ${path} \u2192 401 (${failure})`);
|
|
19634
20022
|
return json(res, 401, { error: "invalid_signature" });
|
|
19635
20023
|
}
|
|
19636
|
-
|
|
20024
|
+
let parsed = {};
|
|
20025
|
+
if (rawBody.length > 0) {
|
|
20026
|
+
try {
|
|
20027
|
+
parsed = JSON.parse(rawBody);
|
|
20028
|
+
} catch {
|
|
20029
|
+
return json(res, 400, { error: "malformed_json" });
|
|
20030
|
+
}
|
|
20031
|
+
}
|
|
20032
|
+
try {
|
|
20033
|
+
if (path === "/pods/up") {
|
|
20034
|
+
const result2 = await podsUp(state, parsed);
|
|
20035
|
+
return json(res, 200, result2);
|
|
20036
|
+
}
|
|
20037
|
+
if (path === "/pods/down") {
|
|
20038
|
+
const result2 = await podsDown(state, parsed);
|
|
20039
|
+
return json(res, 200, result2);
|
|
20040
|
+
}
|
|
20041
|
+
const result = await podsList(state);
|
|
20042
|
+
return json(res, 200, result);
|
|
20043
|
+
} catch (err) {
|
|
20044
|
+
if (err instanceof PodError) {
|
|
20045
|
+
console.warn(`[rover] ${method} ${path} \u2192 ${err.httpStatus} (${err.message})`);
|
|
20046
|
+
return json(res, err.httpStatus, { error: err.message });
|
|
20047
|
+
}
|
|
20048
|
+
console.error(`[rover] ${method} ${path} crashed:`, err);
|
|
20049
|
+
return json(res, 500, { error: err instanceof Error ? err.message : "internal_error" });
|
|
20050
|
+
}
|
|
19637
20051
|
}
|
|
19638
20052
|
return json(res, 404, { error: "not_found", path });
|
|
19639
20053
|
}
|
|
@@ -19651,7 +20065,7 @@ function readBody(req) {
|
|
|
19651
20065
|
});
|
|
19652
20066
|
}
|
|
19653
20067
|
async function sendHeartbeat(state) {
|
|
19654
|
-
const payload = JSON.stringify({
|
|
20068
|
+
const payload = JSON.stringify({ roverId: state.roverId });
|
|
19655
20069
|
const { header } = buildSignatureHeader(state.secret, payload);
|
|
19656
20070
|
const url = new URL("/api/rover/heartbeat", state.serverUrl);
|
|
19657
20071
|
await postJson(url, payload, { "X-LS-Signature": header });
|
|
@@ -19690,18 +20104,19 @@ function postJson(url, body, extra) {
|
|
|
19690
20104
|
req.end();
|
|
19691
20105
|
});
|
|
19692
20106
|
}
|
|
19693
|
-
var
|
|
20107
|
+
var import_node_fs5, import_node_http2, import_node_http3, import_node_https2, import_node_os4, import_node_path4, ROVER_LOCK_FILENAME;
|
|
19694
20108
|
var init_daemon = __esm({
|
|
19695
20109
|
"src/server/rover/daemon.ts"() {
|
|
19696
20110
|
"use strict";
|
|
19697
|
-
|
|
20111
|
+
import_node_fs5 = require("node:fs");
|
|
19698
20112
|
import_node_http2 = require("node:http");
|
|
19699
20113
|
import_node_http3 = require("node:http");
|
|
19700
20114
|
import_node_https2 = require("node:https");
|
|
19701
|
-
|
|
19702
|
-
|
|
20115
|
+
import_node_os4 = require("node:os");
|
|
20116
|
+
import_node_path4 = require("node:path");
|
|
19703
20117
|
init_launch_kit_paths();
|
|
19704
20118
|
init_hmac();
|
|
20119
|
+
init_pods();
|
|
19705
20120
|
init_state();
|
|
19706
20121
|
ROVER_LOCK_FILENAME = "launch-rover.lock";
|
|
19707
20122
|
}
|
|
@@ -19748,7 +20163,7 @@ function printUsage() {
|
|
|
19748
20163
|
"Usage: launch-rover setup --pat=<PAT> --org=<orgSlug> [options]",
|
|
19749
20164
|
"",
|
|
19750
20165
|
"Required:",
|
|
19751
|
-
" --pat=<ls_pat_...> Setup PAT issued by LS (
|
|
20166
|
+
" --pat=<ls_pat_...> Setup PAT issued by LS (rover:setup scope)",
|
|
19752
20167
|
" --org=<slug> Organization slug the rover belongs to",
|
|
19753
20168
|
"",
|
|
19754
20169
|
"Optional:",
|
|
@@ -19773,26 +20188,33 @@ async function runSetup(argv) {
|
|
|
19773
20188
|
pat: args.pat,
|
|
19774
20189
|
orgSlug: args.orgSlug
|
|
19775
20190
|
});
|
|
19776
|
-
console.log("[rover] fetching
|
|
20191
|
+
console.log("[rover] fetching rover config from LS\u2026");
|
|
19777
20192
|
const lsConfig = await mcp.call("rover_get_config", {});
|
|
19778
|
-
if (!lsConfig.
|
|
20193
|
+
if (!lsConfig.id) {
|
|
19779
20194
|
throw new Error(
|
|
19780
|
-
"LS rover config
|
|
20195
|
+
"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
20196
|
);
|
|
19782
20197
|
}
|
|
19783
|
-
|
|
19784
|
-
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
|
|
19788
|
-
|
|
19789
|
-
const
|
|
19790
|
-
const
|
|
19791
|
-
console.log(`[rover]
|
|
20198
|
+
if (!lsConfig.cfApiToken || !lsConfig.cfDomainName) {
|
|
20199
|
+
throw new Error(
|
|
20200
|
+
"LS rover config is missing Cloudflare credentials. Connect a Cloudflare integration and set the rover's domain in LS Organization \u2192 Rovers."
|
|
20201
|
+
);
|
|
20202
|
+
}
|
|
20203
|
+
console.log(`[rover] LS rover "${lsConfig.name}" (id: ${lsConfig.id}, status: ${lsConfig.status})`);
|
|
20204
|
+
const roverId = lsConfig.id;
|
|
20205
|
+
const zone = await resolveZone(lsConfig.cfApiToken, lsConfig.cfDomainName);
|
|
20206
|
+
console.log(`[rover] resolved CF zone: ${zone.name} (account ${zone.accountId})`);
|
|
20207
|
+
const stateDir2 = (0, import_node_path5.join)((0, import_node_os5.homedir)(), LAUNCHSECURE_DIR, "rover");
|
|
20208
|
+
(0, import_node_fs6.mkdirSync)(stateDir2, { recursive: true });
|
|
20209
|
+
const tunnelStateFile = (0, import_node_path5.join)(stateDir2, `${roverId}.tunnel.json`);
|
|
20210
|
+
const idSuffix = roverId.replace(/[^a-z0-9]/gi, "").toLowerCase().slice(-8);
|
|
20211
|
+
const subdomain = `rover-${args.orgSlug.replace(/[^a-z0-9-]/gi, "-").toLowerCase().slice(0, 30)}-${idSuffix}`;
|
|
20212
|
+
const tunnelName = `launch-rover-${roverId}`;
|
|
20213
|
+
console.log(`[rover] provisioning CF tunnel ${tunnelName} \u2192 ${subdomain}.${zone.name}`);
|
|
19792
20214
|
const ingress = await provisionIngress({
|
|
19793
20215
|
apiToken: lsConfig.cfApiToken,
|
|
19794
|
-
accountId:
|
|
19795
|
-
zone: { id:
|
|
20216
|
+
accountId: zone.accountId,
|
|
20217
|
+
zone: { id: zone.id, name: zone.name },
|
|
19796
20218
|
tunnelName,
|
|
19797
20219
|
services: [{ name: subdomain, port: args.port }],
|
|
19798
20220
|
stateFile: tunnelStateFile
|
|
@@ -19803,18 +20225,19 @@ async function runSetup(argv) {
|
|
|
19803
20225
|
}
|
|
19804
20226
|
const tunnelUrl = `https://${hostname}`;
|
|
19805
20227
|
console.log(`[rover] tunnel public URL: ${tunnelUrl}`);
|
|
19806
|
-
const existing = loadRoverState(
|
|
20228
|
+
const existing = loadRoverState(roverId);
|
|
19807
20229
|
const outcome = await registerOrRefresh({
|
|
19808
20230
|
mcp,
|
|
19809
20231
|
orgSlug: args.orgSlug,
|
|
19810
20232
|
serverUrl: args.serverUrl,
|
|
19811
20233
|
tunnelUrl,
|
|
19812
20234
|
daemonPort: args.port,
|
|
20235
|
+
installPat: args.pat,
|
|
19813
20236
|
hostInfo: {
|
|
19814
|
-
platform: (0,
|
|
19815
|
-
hostname: args.name ?? (0,
|
|
20237
|
+
platform: (0, import_node_os5.platform)(),
|
|
20238
|
+
hostname: args.name ?? (0, import_node_os5.hostname)(),
|
|
19816
20239
|
dockerVersion: dockerInfo.version ?? void 0,
|
|
19817
|
-
kitVersion:
|
|
20240
|
+
kitVersion: KIT_VERSION
|
|
19818
20241
|
},
|
|
19819
20242
|
existing
|
|
19820
20243
|
});
|
|
@@ -19825,30 +20248,39 @@ async function runSetup(argv) {
|
|
|
19825
20248
|
console.log("");
|
|
19826
20249
|
if (outcome.approvalStatus === "pending_approval") {
|
|
19827
20250
|
console.log("\u2713 Rover is live and registered, awaiting admin approval in LS.");
|
|
19828
|
-
console.log(` ${args.serverUrl}/${args.orgSlug}/
|
|
20251
|
+
console.log(` ${args.serverUrl}/${args.orgSlug}/rovers`);
|
|
19829
20252
|
} else if (outcome.approvalStatus === "approved") {
|
|
19830
20253
|
console.log("\u2713 Rover is live and already approved. Heartbeats will start within 60s.");
|
|
19831
20254
|
} else {
|
|
19832
20255
|
console.log(`! Rover state: ${outcome.approvalStatus}.`);
|
|
19833
20256
|
}
|
|
19834
20257
|
}
|
|
19835
|
-
async function
|
|
19836
|
-
const res = await fetch(
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
|
|
19840
|
-
|
|
19841
|
-
|
|
19842
|
-
|
|
20258
|
+
async function resolveZone(apiToken, domainName) {
|
|
20259
|
+
const res = await fetch(
|
|
20260
|
+
`https://api.cloudflare.com/client/v4/zones?name=${encodeURIComponent(domainName)}`,
|
|
20261
|
+
{
|
|
20262
|
+
headers: {
|
|
20263
|
+
Authorization: `Bearer ${apiToken}`,
|
|
20264
|
+
Accept: "application/json"
|
|
20265
|
+
},
|
|
20266
|
+
signal: AbortSignal.timeout(1e4)
|
|
20267
|
+
}
|
|
20268
|
+
);
|
|
19843
20269
|
if (!res.ok) {
|
|
19844
20270
|
throw new Error(`CF zone lookup failed: HTTP ${res.status}`);
|
|
19845
20271
|
}
|
|
19846
20272
|
const body = await res.json();
|
|
19847
|
-
if (!body.success
|
|
20273
|
+
if (!body.success) {
|
|
19848
20274
|
const reason = body.errors?.[0]?.message ?? "unknown";
|
|
19849
20275
|
throw new Error(`CF zone lookup failed: ${reason}`);
|
|
19850
20276
|
}
|
|
19851
|
-
|
|
20277
|
+
const zone = body.result?.[0];
|
|
20278
|
+
if (!zone?.id || !zone.account?.id) {
|
|
20279
|
+
throw new Error(
|
|
20280
|
+
`No Cloudflare zone found for "${domainName}". The API token may lack Zone:Read, or the domain isn't on this Cloudflare account.`
|
|
20281
|
+
);
|
|
20282
|
+
}
|
|
20283
|
+
return { id: zone.id, name: zone.name, accountId: zone.account.id };
|
|
19852
20284
|
}
|
|
19853
20285
|
async function launchDaemon(args, ingress, state) {
|
|
19854
20286
|
if (!args.detach) {
|
|
@@ -19863,11 +20295,11 @@ async function launchDaemon(args, ingress, state) {
|
|
|
19863
20295
|
});
|
|
19864
20296
|
void tunnel.start();
|
|
19865
20297
|
const { startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
19866
|
-
await startDaemon2({
|
|
20298
|
+
await startDaemon2({ roverId: state.roverId, port: args.port });
|
|
19867
20299
|
return;
|
|
19868
20300
|
}
|
|
19869
20301
|
const argv0 = process.argv[1] ?? "launch-rover";
|
|
19870
|
-
const child = (0,
|
|
20302
|
+
const child = (0, import_node_child_process3.spawn)(process.execPath, [argv0, "serve", `--rover=${state.roverId}`], {
|
|
19871
20303
|
detached: true,
|
|
19872
20304
|
stdio: "ignore",
|
|
19873
20305
|
env: {
|
|
@@ -19879,23 +20311,24 @@ async function launchDaemon(args, ingress, state) {
|
|
|
19879
20311
|
child.unref();
|
|
19880
20312
|
console.log(`[rover] daemon spawned (pid ${child.pid}, detached)`);
|
|
19881
20313
|
}
|
|
19882
|
-
function runSetupReset(
|
|
19883
|
-
clearRoverState(
|
|
20314
|
+
function runSetupReset(roverId) {
|
|
20315
|
+
clearRoverState(roverId);
|
|
19884
20316
|
}
|
|
19885
|
-
var
|
|
20317
|
+
var import_node_os5, import_node_child_process3, import_node_path5, import_node_fs6, KIT_VERSION, DEFAULT_SERVER, DEFAULT_DAEMON_PORT;
|
|
19886
20318
|
var init_setup = __esm({
|
|
19887
20319
|
"src/server/rover/setup.ts"() {
|
|
19888
20320
|
"use strict";
|
|
19889
|
-
|
|
19890
|
-
|
|
19891
|
-
|
|
19892
|
-
|
|
20321
|
+
import_node_os5 = require("node:os");
|
|
20322
|
+
import_node_child_process3 = require("node:child_process");
|
|
20323
|
+
import_node_path5 = require("node:path");
|
|
20324
|
+
import_node_fs6 = require("node:fs");
|
|
19893
20325
|
init_cf_ingress();
|
|
19894
20326
|
init_launch_kit_paths();
|
|
19895
20327
|
init_docker_install();
|
|
19896
20328
|
init_mcp_client();
|
|
19897
20329
|
init_registration();
|
|
19898
20330
|
init_state();
|
|
20331
|
+
KIT_VERSION = require_package().version;
|
|
19899
20332
|
DEFAULT_SERVER = "https://launchsecure-v2.vercel.app";
|
|
19900
20333
|
DEFAULT_DAEMON_PORT = 52749;
|
|
19901
20334
|
}
|
|
@@ -19907,41 +20340,41 @@ __export(teardown_exports, {
|
|
|
19907
20340
|
runStatus: () => runStatus,
|
|
19908
20341
|
runTeardown: () => runTeardown
|
|
19909
20342
|
});
|
|
19910
|
-
function parseArgs2(argv,
|
|
19911
|
-
let
|
|
20343
|
+
function parseArgs2(argv, requireRover = true) {
|
|
20344
|
+
let roverId;
|
|
19912
20345
|
let force = false;
|
|
19913
20346
|
for (const arg of argv) {
|
|
19914
|
-
if (arg.startsWith("--
|
|
20347
|
+
if (arg.startsWith("--rover=")) roverId = arg.slice("--rover=".length);
|
|
19915
20348
|
else if (arg === "--force" || arg === "-f") force = true;
|
|
19916
20349
|
else if (arg === "--help" || arg === "-h") return null;
|
|
19917
20350
|
else throw new Error(`Unknown argument: ${arg}`);
|
|
19918
20351
|
}
|
|
19919
|
-
if (!
|
|
19920
|
-
const known =
|
|
20352
|
+
if (!roverId) {
|
|
20353
|
+
const known = listRoverIds();
|
|
19921
20354
|
if (known.length === 1) {
|
|
19922
|
-
|
|
19923
|
-
} else if (
|
|
20355
|
+
roverId = known[0];
|
|
20356
|
+
} else if (requireRover) {
|
|
19924
20357
|
const hint = known.length === 0 ? "no rovers found" : `multiple rovers found: ${known.join(", ")}`;
|
|
19925
|
-
throw new Error(`--
|
|
20358
|
+
throw new Error(`--rover=<id> is required (${hint})`);
|
|
19926
20359
|
} else {
|
|
19927
20360
|
return null;
|
|
19928
20361
|
}
|
|
19929
20362
|
}
|
|
19930
|
-
return {
|
|
20363
|
+
return { roverId, force };
|
|
19931
20364
|
}
|
|
19932
20365
|
async function runTeardown(argv) {
|
|
19933
20366
|
const parsed = parseArgs2(argv);
|
|
19934
20367
|
if (!parsed) {
|
|
19935
|
-
console.log("Usage: launch-rover teardown --
|
|
20368
|
+
console.log("Usage: launch-rover teardown --rover=<id> [--force]");
|
|
19936
20369
|
return;
|
|
19937
20370
|
}
|
|
19938
|
-
const state = loadRoverState(parsed.
|
|
20371
|
+
const state = loadRoverState(parsed.roverId);
|
|
19939
20372
|
if (!state) {
|
|
19940
|
-
console.log(`[rover] no local state for
|
|
20373
|
+
console.log(`[rover] no local state for rover "${parsed.roverId}" \u2014 nothing to tear down.`);
|
|
19941
20374
|
return;
|
|
19942
20375
|
}
|
|
19943
20376
|
const lock = readRoverLock();
|
|
19944
|
-
if (lock && lock.
|
|
20377
|
+
if (lock && lock.roverId === parsed.roverId) {
|
|
19945
20378
|
if (isPidAlive(lock.pid)) {
|
|
19946
20379
|
console.log(`[rover] stopping daemon (pid ${lock.pid})\u2026`);
|
|
19947
20380
|
try {
|
|
@@ -19961,44 +20394,43 @@ async function runTeardown(argv) {
|
|
|
19961
20394
|
}
|
|
19962
20395
|
}
|
|
19963
20396
|
}
|
|
19964
|
-
clearRoverState(parsed.
|
|
19965
|
-
const tunnelStateFile = (0,
|
|
19966
|
-
(0,
|
|
20397
|
+
clearRoverState(parsed.roverId);
|
|
20398
|
+
const tunnelStateFile = (0, import_node_path6.join)(
|
|
20399
|
+
(0, import_node_os6.homedir)(),
|
|
19967
20400
|
LAUNCHSECURE_DIR,
|
|
19968
20401
|
"rover",
|
|
19969
|
-
`${parsed.
|
|
20402
|
+
`${parsed.roverId}.tunnel.json`
|
|
19970
20403
|
);
|
|
19971
|
-
if ((0,
|
|
20404
|
+
if ((0, import_node_fs7.existsSync)(tunnelStateFile)) {
|
|
19972
20405
|
try {
|
|
19973
|
-
(0,
|
|
20406
|
+
(0, import_node_fs7.unlinkSync)(tunnelStateFile);
|
|
19974
20407
|
} catch {
|
|
19975
20408
|
}
|
|
19976
20409
|
}
|
|
19977
|
-
console.log(`[rover] teardown complete for
|
|
20410
|
+
console.log(`[rover] teardown complete for rover "${parsed.roverId}".`);
|
|
19978
20411
|
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}/${
|
|
20412
|
+
console.log(` "Reset" button at ${state.serverUrl}/${state.orgSlug}/rovers`);
|
|
19980
20413
|
}
|
|
19981
20414
|
function runStatus(argv) {
|
|
19982
20415
|
const parsed = parseArgs2(
|
|
19983
20416
|
argv,
|
|
19984
|
-
/*
|
|
20417
|
+
/* requireRover */
|
|
19985
20418
|
false
|
|
19986
20419
|
);
|
|
19987
|
-
const
|
|
19988
|
-
if (
|
|
20420
|
+
const rovers = parsed ? [parsed.roverId] : listRoverIds();
|
|
20421
|
+
if (rovers.length === 0) {
|
|
19989
20422
|
console.log("[rover] no rovers configured on this host.");
|
|
19990
20423
|
return;
|
|
19991
20424
|
}
|
|
19992
|
-
for (const
|
|
19993
|
-
const state = loadRoverState(
|
|
20425
|
+
for (const roverId of rovers) {
|
|
20426
|
+
const state = loadRoverState(roverId);
|
|
19994
20427
|
if (!state) {
|
|
19995
|
-
console.log(`[rover] ${
|
|
20428
|
+
console.log(`[rover] ${roverId}: no state file`);
|
|
19996
20429
|
continue;
|
|
19997
20430
|
}
|
|
19998
20431
|
const lock = readRoverLock();
|
|
19999
|
-
const live = lock && lock.
|
|
20000
|
-
console.log(`\u2500\u2500 ${orgSlug} \u2500\u2500`);
|
|
20001
|
-
console.log(` rover id: ${state.roverId}`);
|
|
20432
|
+
const live = lock && lock.roverId === roverId && isPidAlive(lock.pid);
|
|
20433
|
+
console.log(`\u2500\u2500 ${roverId} (${state.orgSlug}) \u2500\u2500`);
|
|
20002
20434
|
console.log(` org id: ${state.organizationId}`);
|
|
20003
20435
|
console.log(` server: ${state.serverUrl}`);
|
|
20004
20436
|
console.log(` tunnel url: ${state.tunnelUrl}`);
|
|
@@ -20022,13 +20454,13 @@ function isPidAlive(pid) {
|
|
|
20022
20454
|
function sleep(ms) {
|
|
20023
20455
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20024
20456
|
}
|
|
20025
|
-
var
|
|
20457
|
+
var import_node_fs7, import_node_os6, import_node_path6;
|
|
20026
20458
|
var init_teardown = __esm({
|
|
20027
20459
|
"src/server/rover/teardown.ts"() {
|
|
20028
20460
|
"use strict";
|
|
20029
|
-
|
|
20030
|
-
|
|
20031
|
-
|
|
20461
|
+
import_node_fs7 = require("node:fs");
|
|
20462
|
+
import_node_os6 = require("node:os");
|
|
20463
|
+
import_node_path6 = require("node:path");
|
|
20032
20464
|
init_launch_kit_paths();
|
|
20033
20465
|
init_daemon();
|
|
20034
20466
|
init_state();
|
|
@@ -20049,9 +20481,10 @@ function printUsage2() {
|
|
|
20049
20481
|
" setup --pat=<PAT> --org=<slug> [options]",
|
|
20050
20482
|
" install Docker, provision CF tunnel,",
|
|
20051
20483
|
" register with LaunchSecure, start daemon",
|
|
20052
|
-
" serve [--
|
|
20053
|
-
"
|
|
20054
|
-
"
|
|
20484
|
+
" serve [--rover=<id>] start the rover HTTP daemon (foreground).",
|
|
20485
|
+
" With one local rover, --rover is optional.",
|
|
20486
|
+
" status [--rover=<id>] print state + daemon liveness",
|
|
20487
|
+
" teardown --rover=<id> stop daemon and clear local state",
|
|
20055
20488
|
" (does NOT delete the LS RoverInstance \u2014 use",
|
|
20056
20489
|
" the Reset button in LS for that)",
|
|
20057
20490
|
"",
|
|
@@ -20074,21 +20507,21 @@ async function main() {
|
|
|
20074
20507
|
return;
|
|
20075
20508
|
}
|
|
20076
20509
|
case "serve": {
|
|
20077
|
-
const
|
|
20078
|
-
if (!
|
|
20079
|
-
const {
|
|
20080
|
-
const known =
|
|
20510
|
+
const roverId = argv.find((a) => a.startsWith("--rover="))?.slice("--rover=".length);
|
|
20511
|
+
if (!roverId) {
|
|
20512
|
+
const { listRoverIds: listRoverIds2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
20513
|
+
const known = listRoverIds2();
|
|
20081
20514
|
if (known.length !== 1) {
|
|
20082
20515
|
throw new Error(
|
|
20083
|
-
`serve needs --
|
|
20516
|
+
`serve needs --rover=<id>${known.length ? ` (known: ${known.join(", ")})` : " (no rovers configured)"}`
|
|
20084
20517
|
);
|
|
20085
20518
|
}
|
|
20086
20519
|
const { startDaemon: startDaemon3 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
20087
|
-
await startDaemon3({
|
|
20520
|
+
await startDaemon3({ roverId: known[0] });
|
|
20088
20521
|
return;
|
|
20089
20522
|
}
|
|
20090
20523
|
const { startDaemon: startDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
20091
|
-
await startDaemon2({
|
|
20524
|
+
await startDaemon2({ roverId });
|
|
20092
20525
|
return;
|
|
20093
20526
|
}
|
|
20094
20527
|
case "status": {
|