@launchsecure/launch-kit 0.0.32 → 0.0.34

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.
Files changed (93) hide show
  1. package/dist/chart-client/assets/{index-B__ARB8k.js → index-DFu2xIrM.js} +2 -2
  2. package/dist/chart-client/assets/index-DpKO9p0s.css +1 -0
  3. package/dist/chart-client/index.html +2 -2
  4. package/dist/client/assets/{index-h8kMzVtG.js → index-Cbw6bVdx.js} +2 -2
  5. package/dist/client/assets/index-Dv6dD2zY.css +32 -0
  6. package/dist/client/index.html +2 -2
  7. package/dist/council-client/assets/index-AqQ9Sei6.css +1 -0
  8. package/dist/council-client/assets/{index-CWaDcsFR.js → index-CAsmGTzg.js} +2 -2
  9. package/dist/council-client/index.html +2 -2
  10. package/dist/deck-client/assets/{_baseUniq-C7GsHvgg.js → _baseUniq-BiVx0WO_.js} +1 -1
  11. package/dist/deck-client/assets/{arc-CSrZRINY.js → arc-DGMkiEzS.js} +1 -1
  12. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-zoB-G17J.js → architectureDiagram-Q4EWVU46-Y2WRmHtk.js} +1 -1
  13. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BRjjtYH6.js → blockDiagram-DXYQGD6D-_Lbfu5BQ.js} +1 -1
  14. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-C3D3sd2U.js → c4Diagram-AHTNJAMY-CTqpYTBX.js} +1 -1
  15. package/dist/deck-client/assets/channel-DB6LxW_l.js +1 -0
  16. package/dist/deck-client/assets/{chunk-4BX2VUAB-DhpDMOPO.js → chunk-4BX2VUAB-liEIbPHs.js} +1 -1
  17. package/dist/deck-client/assets/{chunk-4TB4RGXK-BIRgPXRl.js → chunk-4TB4RGXK-CCc6lYvL.js} +1 -1
  18. package/dist/deck-client/assets/{chunk-55IACEB6-BF24dwDZ.js → chunk-55IACEB6-D02jJUR2.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-EDXVE4YY-CW75Y61B.js → chunk-EDXVE4YY-BFmGMbLD.js} +1 -1
  20. package/dist/deck-client/assets/{chunk-FMBD7UC4-B5-oyL79.js → chunk-FMBD7UC4-6wFLOVcJ.js} +1 -1
  21. package/dist/deck-client/assets/{chunk-OYMX7WX6-BB2bHe_Q.js → chunk-OYMX7WX6-Bnr8RiBf.js} +1 -1
  22. package/dist/deck-client/assets/{chunk-QZHKN3VN-D80eZO4B.js → chunk-QZHKN3VN-Ct82MksJ.js} +1 -1
  23. package/dist/deck-client/assets/{chunk-YZCP3GAM-Dz9787p_.js → chunk-YZCP3GAM-BXmN1diQ.js} +1 -1
  24. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-g944ZyG8.js +1 -0
  25. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-g944ZyG8.js +1 -0
  26. package/dist/deck-client/assets/clone-DiIRH1pI.js +1 -0
  27. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-MQjiZLcL.js → cose-bilkent-S5V4N54A-CmQCT-mH.js} +1 -1
  28. package/dist/deck-client/assets/{dagre-KV5264BT-DG4EcLpJ.js → dagre-KV5264BT-DDdSa9EX.js} +1 -1
  29. package/dist/deck-client/assets/{diagram-5BDNPKRD-1n7hM3Gc.js → diagram-5BDNPKRD-Bccks2xJ.js} +1 -1
  30. package/dist/deck-client/assets/{diagram-G4DWMVQ6-CYMarncV.js → diagram-G4DWMVQ6-CPPNgxmQ.js} +1 -1
  31. package/dist/deck-client/assets/{diagram-MMDJMWI5-DSisoipe.js → diagram-MMDJMWI5-KrD300pS.js} +1 -1
  32. package/dist/deck-client/assets/{diagram-TYMM5635-Btnq49OJ.js → diagram-TYMM5635-DefnLuQf.js} +1 -1
  33. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-Cu2Hb_Tz.js → erDiagram-SMLLAGMA-DI9FfnFP.js} +1 -1
  34. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-CGJzUzsO.js → flowDiagram-DWJPFMVM-twKyd3Fx.js} +1 -1
  35. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-D9sqGUBT.js → ganttDiagram-T4ZO3ILL-Wau3jhBr.js} +1 -1
  36. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-C0QwX2od.js → gitGraphDiagram-UUTBAWPF-D9GgYXwb.js} +1 -1
  37. package/dist/deck-client/assets/{graph-CcBjOQCl.js → graph-BhNLzyXS.js} +1 -1
  38. package/dist/deck-client/assets/index-B-YQq5b5.css +1 -0
  39. package/dist/deck-client/assets/{index-0arwoc0z.js → index-BtQBaQ7s.js} +3 -3
  40. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-DTimhhhS.js → infoDiagram-42DDH7IO-TylGlSG-.js} +1 -1
  41. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DxOxg_B4.js → ishikawaDiagram-UXIWVN3A-DAT8icpg.js} +1 -1
  42. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-Bpq0qa4j.js → journeyDiagram-VCZTEJTY-D3v_XL72.js} +1 -1
  43. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-aTIrpcVO.js → kanban-definition-6JOO6SKY-DNUOBiNr.js} +1 -1
  44. package/dist/deck-client/assets/{layout-DqglLR2E.js → layout-COfodgwF.js} +1 -1
  45. package/dist/deck-client/assets/{linear-D5GxehPc.js → linear-DmTsuIvK.js} +1 -1
  46. package/dist/deck-client/assets/{min-DXLfSREq.js → min-BW1F7i1D.js} +1 -1
  47. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-mO5Vys7I.js → mindmap-definition-QFDTVHPH-CErFzKWl.js} +1 -1
  48. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-Dm0gzdAr.js → pieDiagram-DEJITSTG-DW5F757o.js} +1 -1
  49. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-Daq7j3qD.js → quadrantDiagram-34T5L4WZ-B1S2-TfI.js} +1 -1
  50. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-CmwV95um.js → requirementDiagram-MS252O5E-BY5BAR-5.js} +1 -1
  51. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BOYl3Nkf.js → sankeyDiagram-XADWPNL6-CE1Cp9HS.js} +1 -1
  52. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-BuUjhIcW.js → sequenceDiagram-FGHM5R23-IaHnbKye.js} +1 -1
  53. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-LUZ_uwio.js → stateDiagram-FHFEXIEX-CwPJm9hU.js} +1 -1
  54. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-DQYa2M1q.js +1 -0
  55. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-CDUxCCAW.js → timeline-definition-GMOUNBTQ-DVFGGSgN.js} +1 -1
  56. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-BRb24Tf7.js → vennDiagram-DHZGUBPP-C1194MJi.js} +1 -1
  57. package/dist/deck-client/assets/wardley-RL74JXVD-CHZiUbBa.js +162 -0
  58. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BLGlYrQz.js → wardleyDiagram-NUSXRM2D-hpwdFfGj.js} +1 -1
  59. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-De31MSnk.js → xychartDiagram-5P7HB3ND-DYkotwy8.js} +1 -1
  60. package/dist/deck-client/index.html +2 -2
  61. package/dist/server/chart-serve.js +167 -2
  62. package/dist/server/cli.js +328 -42
  63. package/dist/server/course-entry.js +1 -1
  64. package/dist/server/graph-mcp-entry.js +180 -4
  65. package/dist/server/init-entry.js +1133 -219
  66. package/dist/server/launch-radar-entry.js +45 -0
  67. package/dist/server/parse-worker-entry.js +167 -2
  68. package/dist/server/radar-docker-init-entry.js +644 -0
  69. package/dist/server/radar-entrypoint-entry.js +99 -0
  70. package/dist/server/radar-teardown-entry.js +478 -0
  71. package/dist/server/recall-entry.js +4 -1
  72. package/dist/server/rover-entry.js +20122 -0
  73. package/package.json +7 -5
  74. package/scaffolds/ls-marketplace/plugins/kit/commands/activate-statusline.md +5 -5
  75. package/scaffolds/ls-marketplace/plugins/kit/commands/standup.md +6 -6
  76. package/scaffolds/ls-marketplace/plugins/kit/skills/analyse/SKILL.md +6 -0
  77. package/scaffolds/ls-marketplace/plugins/kit/skills/brief/SKILL.md +40 -48
  78. package/scaffolds/ls-marketplace/plugins/kit/skills/debug/SKILL.md +45 -20
  79. package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check/SKILL.md +76 -67
  80. package/scaffolds/ls-marketplace/plugins/kit/skills/handoff/SKILL.md +132 -0
  81. package/scaffolds/ls-marketplace/plugins/kit/skills/ship/SKILL.md +290 -0
  82. package/scaffolds/statusline/statusline-mcp.sh +82 -2
  83. package/scaffolds/statusline/statusline-wrapper.sh +8 -1
  84. package/dist/chart-client/assets/index-CDIhdgWg.css +0 -1
  85. package/dist/client/assets/index-CfW4n40I.css +0 -32
  86. package/dist/council-client/assets/index-CZim6x1u.css +0 -1
  87. package/dist/deck-client/assets/channel-8ReQnQfH.js +0 -1
  88. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-cRxTeGkK.js +0 -1
  89. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-cRxTeGkK.js +0 -1
  90. package/dist/deck-client/assets/clone-LSHZ3K6R.js +0 -1
  91. package/dist/deck-client/assets/index-BlTlhxFW.css +0 -1
  92. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CnnRwE5D.js +0 -1
  93. package/dist/deck-client/assets/wardley-RL74JXVD-B0BYyVBY.js +0 -162
@@ -0,0 +1,644 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/server/radar-docker-init-entry.ts
22
+ var radar_docker_init_entry_exports = {};
23
+ __export(radar_docker_init_entry_exports, {
24
+ maybeProvisionIngress: () => maybeProvisionIngress,
25
+ spawnServiceGroup: () => spawnServiceGroup
26
+ });
27
+ module.exports = __toCommonJS(radar_docker_init_entry_exports);
28
+ var import_node_child_process = require("node:child_process");
29
+ var import_node_fs3 = require("node:fs");
30
+ var import_node_path3 = require("node:path");
31
+
32
+ // src/server/radar/mcp.ts
33
+ var import_node_https = require("node:https");
34
+ var import_node_http = require("node:http");
35
+ var ProjectMcpClient = class {
36
+ constructor(opts) {
37
+ this.initialized = false;
38
+ this.callId = 1;
39
+ this.mcpUrl = new URL("/api/mcp/project", opts.serverUrl);
40
+ this.headers = {
41
+ "Content-Type": "application/json",
42
+ "Accept": "application/json, text/event-stream",
43
+ "Authorization": `Bearer ${opts.pat}`
44
+ };
45
+ if (opts.orgSlug) this.headers["X-Org-Slug"] = opts.orgSlug;
46
+ if (opts.projectSlug) this.headers["X-Project-Slug"] = opts.projectSlug;
47
+ }
48
+ async call(toolName, args) {
49
+ await this.ensureInitialized();
50
+ const body = JSON.stringify({
51
+ jsonrpc: "2.0",
52
+ id: this.callId++,
53
+ method: "tools/call",
54
+ params: { name: toolName, arguments: args }
55
+ });
56
+ const resp = await this.send(body);
57
+ const parsed = parseBody(resp.body);
58
+ if (parsed.error) {
59
+ throw new Error(`MCP ${toolName} failed: ${parsed.error.message ?? JSON.stringify(parsed.error)}`);
60
+ }
61
+ const text = parsed.result?.content?.[0]?.text;
62
+ if (!text) return parsed.result;
63
+ if (text.startsWith("\u2500\u2500 Error")) {
64
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
65
+ const reason = lines.find((l) => !l.startsWith("\u2500\u2500")) ?? text;
66
+ throw new Error(`MCP ${toolName} failed: ${reason}`);
67
+ }
68
+ try {
69
+ return JSON.parse(text);
70
+ } catch {
71
+ return text;
72
+ }
73
+ }
74
+ async ensureInitialized() {
75
+ if (this.initialized) return;
76
+ const initBody = JSON.stringify({
77
+ jsonrpc: "2.0",
78
+ id: this.callId++,
79
+ method: "initialize",
80
+ params: {
81
+ protocolVersion: "2025-03-26",
82
+ capabilities: {},
83
+ clientInfo: { name: "launchpod-feedback-agent", version: "0.0.1" }
84
+ }
85
+ });
86
+ const initResp = await this.send(initBody);
87
+ if (initResp.sessionId) this.sessionId = initResp.sessionId;
88
+ const notifBody = JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" });
89
+ await this.send(notifBody);
90
+ this.initialized = true;
91
+ }
92
+ send(body) {
93
+ return new Promise((resolve, reject) => {
94
+ const headers = {
95
+ ...this.headers,
96
+ "Content-Length": String(Buffer.byteLength(body))
97
+ };
98
+ if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
99
+ const requester = this.mcpUrl.protocol === "https:" ? import_node_https.request : import_node_http.request;
100
+ const req = requester(
101
+ {
102
+ host: this.mcpUrl.hostname,
103
+ port: this.mcpUrl.port || (this.mcpUrl.protocol === "https:" ? 443 : 80),
104
+ path: this.mcpUrl.pathname + this.mcpUrl.search,
105
+ method: "POST",
106
+ headers
107
+ },
108
+ (res) => {
109
+ const chunks = [];
110
+ res.on("data", (c) => chunks.push(c));
111
+ res.on("end", () => {
112
+ const text = Buffer.concat(chunks).toString("utf-8");
113
+ if (res.statusCode && res.statusCode >= 400) {
114
+ reject(new Error(`MCP HTTP ${res.statusCode}: ${text}`));
115
+ return;
116
+ }
117
+ const sid = res.headers["mcp-session-id"];
118
+ resolve({ body: text, sessionId: typeof sid === "string" ? sid : void 0 });
119
+ });
120
+ }
121
+ );
122
+ req.on("error", reject);
123
+ req.write(body);
124
+ req.end();
125
+ });
126
+ }
127
+ };
128
+ function parseBody(text) {
129
+ if (!text) return {};
130
+ if (text.startsWith("event:") || text.includes("\ndata: ")) {
131
+ for (const line of text.split("\n")) {
132
+ if (line.startsWith("data: ")) {
133
+ try {
134
+ return JSON.parse(line.slice(6));
135
+ } catch {
136
+ }
137
+ }
138
+ }
139
+ }
140
+ try {
141
+ return JSON.parse(text);
142
+ } catch {
143
+ return {};
144
+ }
145
+ }
146
+
147
+ // src/server/launch-kit-services.ts
148
+ var import_node_fs = require("node:fs");
149
+ var import_node_path = require("node:path");
150
+ var SHORTHANDS = {
151
+ radar: { port: 3517, bin: "launch-radar", args: [] },
152
+ sequencer: { port: 3517, bin: "launch-sequencer", args: [] },
153
+ chart: { port: 52819, bin: "launch-chart", args: ["serve"] },
154
+ deck: { port: 52829, bin: "launch-deck", args: ["serve"] },
155
+ council: { port: 52839, bin: "launch-council", args: ["serve"] }
156
+ };
157
+ var DNS_NAME_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
158
+ function defaultServices() {
159
+ return [expandShorthand("radar")];
160
+ }
161
+ function expandShorthand(name) {
162
+ const def = SHORTHANDS[name];
163
+ if (!def) {
164
+ throw new Error(
165
+ `[launch-kit-services] unknown shorthand "${name}" \u2014 known: ${Object.keys(SHORTHANDS).join(", ")}. Use an object entry { name, port, bin, args } for custom services.`
166
+ );
167
+ }
168
+ return { name, port: def.port, bin: def.bin, args: [...def.args] };
169
+ }
170
+ function coerceEntry(raw, index) {
171
+ if (typeof raw === "string") {
172
+ return expandShorthand(raw);
173
+ }
174
+ if (typeof raw !== "object" || raw === null) {
175
+ throw new Error(`[launch-kit-services] entry #${index} must be a string shorthand or an object`);
176
+ }
177
+ const r = raw;
178
+ if (typeof r.name !== "string" || typeof r.port !== "number" || typeof r.bin !== "string") {
179
+ throw new Error(`[launch-kit-services] entry #${index}: { name:string, port:number, bin:string } required`);
180
+ }
181
+ if (r.args !== void 0 && (!Array.isArray(r.args) || r.args.some((a) => typeof a !== "string"))) {
182
+ throw new Error(`[launch-kit-services] entry #${index}: args must be a string[]`);
183
+ }
184
+ return {
185
+ name: r.name,
186
+ port: r.port,
187
+ bin: r.bin,
188
+ args: r.args ?? []
189
+ };
190
+ }
191
+ function validate(services) {
192
+ if (services.length === 0) {
193
+ throw new Error(`[launch-kit-services] resolved an empty service list`);
194
+ }
195
+ const seenNames = /* @__PURE__ */ new Set();
196
+ const seenPorts = /* @__PURE__ */ new Set();
197
+ for (const s of services) {
198
+ if (!DNS_NAME_RE.test(s.name)) {
199
+ throw new Error(`[launch-kit-services] service name "${s.name}" is not DNS-safe (lowercase letters/digits/hyphens, \u226463 chars, no leading/trailing hyphen)`);
200
+ }
201
+ if (seenNames.has(s.name)) {
202
+ throw new Error(`[launch-kit-services] duplicate service name "${s.name}"`);
203
+ }
204
+ seenNames.add(s.name);
205
+ if (!Number.isInteger(s.port) || s.port < 1 || s.port > 65535) {
206
+ throw new Error(`[launch-kit-services] service "${s.name}" has invalid port ${s.port}`);
207
+ }
208
+ if (seenPorts.has(s.port)) {
209
+ throw new Error(`[launch-kit-services] duplicate port ${s.port} (services must each listen on a unique port)`);
210
+ }
211
+ seenPorts.add(s.port);
212
+ }
213
+ return services;
214
+ }
215
+ function resolveServices(opts = {}) {
216
+ const env = opts.env ?? process.env;
217
+ const cwd = opts.cwd ?? process.cwd();
218
+ const rawEnv = env.LAUNCHKIT_SERVICES?.trim();
219
+ if (rawEnv) {
220
+ let parsed;
221
+ try {
222
+ parsed = JSON.parse(rawEnv);
223
+ } catch (err) {
224
+ throw new Error(`[launch-kit-services] LAUNCHKIT_SERVICES is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
225
+ }
226
+ if (!Array.isArray(parsed)) {
227
+ throw new Error(`[launch-kit-services] LAUNCHKIT_SERVICES must be a JSON array`);
228
+ }
229
+ return validate(parsed.map(coerceEntry));
230
+ }
231
+ const filePath = (0, import_node_path.join)(cwd, ".launchpod", "services.json");
232
+ if ((0, import_node_fs.existsSync)(filePath)) {
233
+ let parsed;
234
+ try {
235
+ parsed = JSON.parse((0, import_node_fs.readFileSync)(filePath, "utf8"));
236
+ } catch (err) {
237
+ throw new Error(`[launch-kit-services] ${filePath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
238
+ }
239
+ if (!Array.isArray(parsed)) {
240
+ throw new Error(`[launch-kit-services] ${filePath} must be a JSON array`);
241
+ }
242
+ return validate(parsed.map(coerceEntry));
243
+ }
244
+ return validate(defaultServices());
245
+ }
246
+ var SHORTHAND_NAMES = Object.keys(SHORTHANDS);
247
+
248
+ // src/server/cf-ingress.ts
249
+ var import_node_fs2 = require("node:fs");
250
+ var import_node_path2 = require("node:path");
251
+ var CF_API_BASE = "https://api.cloudflare.com/client/v4";
252
+ var CF_ERR_DNS_RECORD_EXISTS = 81053;
253
+ async function cf(opts) {
254
+ const res = await fetch(`${CF_API_BASE}${opts.path}`, {
255
+ method: opts.method,
256
+ headers: {
257
+ Authorization: `Bearer ${opts.apiToken}`,
258
+ "Content-Type": "application/json",
259
+ Accept: "application/json",
260
+ "User-Agent": "launch-kit/cf-ingress"
261
+ },
262
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
263
+ signal: AbortSignal.timeout(15e3)
264
+ });
265
+ const text = await res.text();
266
+ let parsed;
267
+ try {
268
+ parsed = text ? JSON.parse(text) : { success: false };
269
+ } catch {
270
+ throw new Error(`[cf] ${opts.method} ${opts.path} \u2192 ${res.status}, non-JSON body: ${text.slice(0, 200)}`);
271
+ }
272
+ return parsed;
273
+ }
274
+ function isNotFound(env) {
275
+ return !env.success && (env.errors ?? []).some((e) => e.code === 7003 || e.code === 1001 || e.code === 81044);
276
+ }
277
+ function loadState(path) {
278
+ if (!(0, import_node_fs2.existsSync)(path)) return null;
279
+ try {
280
+ const parsed = JSON.parse((0, import_node_fs2.readFileSync)(path, "utf8"));
281
+ if (typeof parsed?.tunnelId === "string" && typeof parsed?.accountId === "string") {
282
+ return parsed;
283
+ }
284
+ return null;
285
+ } catch {
286
+ return null;
287
+ }
288
+ }
289
+ function saveState(path, state) {
290
+ const dir = (0, import_node_path2.dirname)(path);
291
+ if (!(0, import_node_fs2.existsSync)(dir)) (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
292
+ (0, import_node_fs2.writeFileSync)(path, JSON.stringify(state, null, 2));
293
+ }
294
+ async function ensureTunnel(input, knownTunnelId) {
295
+ if (knownTunnelId) {
296
+ const got = await cf({
297
+ apiToken: input.apiToken,
298
+ method: "GET",
299
+ path: `/accounts/${input.accountId}/cfd_tunnel/${knownTunnelId}`
300
+ });
301
+ if (got.success && got.result && !got.result.deleted_at) {
302
+ return knownTunnelId;
303
+ }
304
+ if (!isNotFound(got) && !got.success) {
305
+ throw new Error(`[cf] tunnel GET failed: ${JSON.stringify(got.errors)}`);
306
+ }
307
+ }
308
+ const created = await cf({
309
+ apiToken: input.apiToken,
310
+ method: "POST",
311
+ path: `/accounts/${input.accountId}/cfd_tunnel`,
312
+ body: { name: input.tunnelName, config_src: "cloudflare" }
313
+ });
314
+ if (!created.success || !created.result) {
315
+ throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
316
+ }
317
+ return created.result.id;
318
+ }
319
+ async function fetchConnectorToken(input, tunnelId) {
320
+ const res = await cf({
321
+ apiToken: input.apiToken,
322
+ method: "GET",
323
+ path: `/accounts/${input.accountId}/cfd_tunnel/${tunnelId}/token`
324
+ });
325
+ if (!res.success || typeof res.result !== "string") {
326
+ throw new Error(`[cf] connector-token fetch failed: ${JSON.stringify(res.errors)}`);
327
+ }
328
+ return res.result;
329
+ }
330
+ async function setIngressConfig(input, tunnelId) {
331
+ const ingress = input.services.map((s) => ({
332
+ hostname: `${s.name}.${input.zone.name}`,
333
+ service: `http://localhost:${s.port}`
334
+ }));
335
+ ingress.push({ service: "http_status:404" });
336
+ const res = await cf({
337
+ apiToken: input.apiToken,
338
+ method: "PUT",
339
+ path: `/accounts/${input.accountId}/cfd_tunnel/${tunnelId}/configurations`,
340
+ body: { config: { ingress } }
341
+ });
342
+ if (!res.success) {
343
+ throw new Error(`[cf] ingress config PUT failed: ${JSON.stringify(res.errors)}`);
344
+ }
345
+ }
346
+ async function ensureDnsRecord(input, tunnelId, service) {
347
+ const fqdn = `${service.name}.${input.zone.name}`;
348
+ const target = `${tunnelId}.cfargotunnel.com`;
349
+ const existing = await cf({
350
+ apiToken: input.apiToken,
351
+ method: "GET",
352
+ path: `/zones/${input.zone.id}/dns_records?name=${encodeURIComponent(fqdn)}&type=CNAME`
353
+ });
354
+ if (existing.success && Array.isArray(existing.result) && existing.result.length > 0) {
355
+ const rec = existing.result[0];
356
+ if (rec.content === target) return;
357
+ const upd = await cf({
358
+ apiToken: input.apiToken,
359
+ method: "PUT",
360
+ path: `/zones/${input.zone.id}/dns_records/${rec.id}`,
361
+ body: { type: "CNAME", name: fqdn, content: target, proxied: true, ttl: 1 }
362
+ });
363
+ if (!upd.success) {
364
+ throw new Error(`[cf] DNS record update for ${fqdn} failed: ${JSON.stringify(upd.errors)}`);
365
+ }
366
+ return;
367
+ }
368
+ const created = await cf({
369
+ apiToken: input.apiToken,
370
+ method: "POST",
371
+ path: `/zones/${input.zone.id}/dns_records`,
372
+ body: { type: "CNAME", name: fqdn, content: target, proxied: true, ttl: 1 }
373
+ });
374
+ if (created.success) return;
375
+ if ((created.errors ?? []).some((e) => e.code === CF_ERR_DNS_RECORD_EXISTS)) return;
376
+ throw new Error(`[cf] DNS record create for ${fqdn} failed: ${JSON.stringify(created.errors)}`);
377
+ }
378
+ async function provisionIngress(input) {
379
+ const prior = loadState(input.stateFile);
380
+ const tunnelId = await ensureTunnel(input, prior?.tunnelId ?? null);
381
+ saveState(input.stateFile, {
382
+ tunnelId,
383
+ accountId: input.accountId,
384
+ tunnelName: input.tunnelName,
385
+ zoneId: input.zone.id
386
+ });
387
+ const connectorToken = await fetchConnectorToken(input, tunnelId);
388
+ await setIngressConfig(input, tunnelId);
389
+ await Promise.all(input.services.map((s) => ensureDnsRecord(input, tunnelId, s)));
390
+ const hostnames = {};
391
+ for (const s of input.services) hostnames[s.name] = `${s.name}.${input.zone.name}`;
392
+ return { tunnelId, connectorToken, hostnames };
393
+ }
394
+
395
+ // src/server/radar-docker-init-entry.ts
396
+ var REQUIRED_ENV = [
397
+ "CLAUDE_CREDENTIALS_B64",
398
+ "LS_PAT",
399
+ "LS_ORG_SLUG",
400
+ "LS_PROJECT_SLUG"
401
+ ];
402
+ function fail(message) {
403
+ console.error(message);
404
+ process.exit(1);
405
+ }
406
+ function requireEnv(name) {
407
+ const v = process.env[name];
408
+ if (!v) fail(`ERROR: ${name} is required but not set`);
409
+ return v;
410
+ }
411
+ function run(cmd, args, stdio = "inherit") {
412
+ const r = (0, import_node_child_process.spawnSync)(cmd, args, { stdio });
413
+ return r.status ?? 1;
414
+ }
415
+ async function setupFromCloud() {
416
+ const pat = requireEnv("LS_PAT");
417
+ const orgSlug = requireEnv("LS_ORG_SLUG");
418
+ const projectSlug = requireEnv("LS_PROJECT_SLUG");
419
+ const serverUrl = process.env.LS_SERVER_URL ?? "https://launchsecure-v2.vercel.app";
420
+ const mcp = new ProjectMcpClient({ serverUrl, pat, orgSlug, projectSlug });
421
+ let bundle;
422
+ try {
423
+ bundle = await mcp.call("radar_bootstrap_get", {});
424
+ } catch (err) {
425
+ fail(`[entrypoint] radar_bootstrap_get failed (${err instanceof Error ? err.message : String(err)}) \u2014 check LS_PAT has mcp:radar:bootstrap scope and LS_ORG_SLUG/LS_PROJECT_SLUG point at a project the user has access to.`);
426
+ }
427
+ if (!process.env.GIT_USER_NAME) process.env.GIT_USER_NAME = bundle.gitName;
428
+ if (!process.env.GIT_USER_EMAIL) process.env.GIT_USER_EMAIL = bundle.gitEmail;
429
+ if (!process.env.GH_TOKEN && bundle.githubToken) process.env.GH_TOKEN = bundle.githubToken;
430
+ if (!process.env.GH_TOKEN) {
431
+ fail(`[entrypoint] no GH_TOKEN available \u2014 user has not connected GitHub (githubTokenStatus=${bundle.githubTokenStatus}). Connect GitHub in LS or pre-set GH_TOKEN in the container env.`);
432
+ }
433
+ const cfNote = bundle.cloudflareToken ? "cloudflare=connected" : "cloudflare=none";
434
+ console.log(`[entrypoint] bundle from cloud: org=${orgSlug} project=${projectSlug} git=${process.env.GIT_USER_NAME} <${process.env.GIT_USER_EMAIL}> github=${bundle.githubTokenStatus.toLowerCase()} ${cfNote}`);
435
+ return bundle;
436
+ }
437
+ function setupClaudeCredentials() {
438
+ const home = process.env.HOME ?? "/home/launchpod";
439
+ const claudeDir = (0, import_node_path3.join)(home, ".claude");
440
+ (0, import_node_fs3.mkdirSync)(claudeDir, { recursive: true });
441
+ const decoded = Buffer.from(requireEnv("CLAUDE_CREDENTIALS_B64"), "base64").toString("utf8");
442
+ const credsPath = (0, import_node_path3.join)(claudeDir, ".credentials.json");
443
+ (0, import_node_fs3.writeFileSync)(credsPath, decoded);
444
+ (0, import_node_fs3.chmodSync)(credsPath, 384);
445
+ const configPath = (0, import_node_path3.join)(home, ".claude.json");
446
+ let cfg = {};
447
+ if ((0, import_node_fs3.existsSync)(configPath)) {
448
+ try {
449
+ cfg = JSON.parse((0, import_node_fs3.readFileSync)(configPath, "utf8"));
450
+ } catch {
451
+ cfg = {};
452
+ }
453
+ }
454
+ cfg.hasCompletedOnboarding = true;
455
+ cfg.lastOnboardingVersion = cfg.lastOnboardingVersion ?? "2.1.159";
456
+ cfg.numStartups = (cfg.numStartups ?? 0) + 1;
457
+ cfg.installMethod = cfg.installMethod ?? "global";
458
+ (0, import_node_fs3.writeFileSync)(configPath, JSON.stringify(cfg, null, 2));
459
+ (0, import_node_fs3.chmodSync)(configPath, 384);
460
+ }
461
+ function setupGitAndGh() {
462
+ const name = process.env.GIT_USER_NAME ?? "Radar Bot";
463
+ const email = process.env.GIT_USER_EMAIL ?? "radar@launchpod.local";
464
+ const status = run("launch-kit", ["setup-git", `--identity=${name} <${email}>`]);
465
+ if (status !== 0) fail(`[entrypoint] launch-kit setup-git failed (status ${status})`);
466
+ }
467
+ function initWorkspaceIfEmpty() {
468
+ process.chdir("/workspace");
469
+ if ((0, import_node_fs3.existsSync)(".git")) {
470
+ console.log("[entrypoint] /workspace already initialized \u2014 skipping init");
471
+ return;
472
+ }
473
+ console.log("[entrypoint] /workspace is empty \u2014 running launch-kit init");
474
+ const status = run("launch-kit", [
475
+ "init",
476
+ `--token=${requireEnv("LS_PAT")}`,
477
+ `--org=${requireEnv("LS_ORG_SLUG")}`,
478
+ `--project=${requireEnv("LS_PROJECT_SLUG")}`,
479
+ `--url=${process.env.LS_SERVER_URL ?? "https://launchsecure-v2.vercel.app"}`,
480
+ `--dir=/workspace`
481
+ ]);
482
+ if (status !== 0) fail(`[entrypoint] launch-kit init failed (status ${status})`);
483
+ }
484
+ async function maybeProvisionIngress(bundle, services, projectSlug) {
485
+ const token = bundle.cloudflareToken ?? null;
486
+ const accountId = bundle.cloudflareAccountId ?? null;
487
+ const zones = bundle.cloudflareZones ?? [];
488
+ if (!token && !accountId && zones.length === 0) return null;
489
+ if (!token || !accountId) {
490
+ fail(`[entrypoint] cloudflare integration is partial \u2014 token=${token ? "set" : "missing"} accountId=${accountId ? "set" : "missing"}. Re-connect the Cloudflare provider in LS.`);
491
+ }
492
+ const baseDomain = process.env.LAUNCHKIT_CF_BASE_DOMAIN?.trim();
493
+ let chosen = null;
494
+ if (baseDomain) {
495
+ chosen = zones.find((z) => z.name === baseDomain) ?? null;
496
+ if (!chosen) {
497
+ fail(`[entrypoint] LAUNCHKIT_CF_BASE_DOMAIN="${baseDomain}" is not among the connected CF token's zones (${zones.map((z) => z.name).join(", ") || "none"}). Either change the env or grant Zone:Read on that zone in the CF token.`);
498
+ }
499
+ } else if (zones.length === 1) {
500
+ chosen = { id: zones[0].id, name: zones[0].name };
501
+ } else {
502
+ fail(`[entrypoint] cloudflare token covers ${zones.length} zones (${zones.map((z) => z.name).join(", ")}) \u2014 set LAUNCHKIT_CF_BASE_DOMAIN to pick one.`);
503
+ }
504
+ const stateFile = "/workspace/.launchpod/launch-kit-tunnel.json";
505
+ console.log(`[entrypoint] provisioning CF named tunnel \u2014 name=launch-kit-${projectSlug} zone=${chosen.name} services=${services.map((s) => s.name).join(",")}`);
506
+ const result = await provisionIngress({
507
+ apiToken: token,
508
+ accountId,
509
+ zone: chosen,
510
+ tunnelName: `launch-kit-${projectSlug}`,
511
+ services: services.map((s) => ({ name: s.name, port: s.port })),
512
+ stateFile
513
+ });
514
+ for (const [name, fqdn] of Object.entries(result.hostnames)) {
515
+ console.log(`[entrypoint] ${name} \u2192 https://${fqdn}`);
516
+ }
517
+ return result;
518
+ }
519
+ function spawnServiceGroup(services) {
520
+ const children = [];
521
+ let shuttingDown = false;
522
+ const killAll = (signal = "SIGTERM") => {
523
+ if (shuttingDown) return;
524
+ shuttingDown = true;
525
+ for (const c of children) {
526
+ try {
527
+ c.proc.kill(signal);
528
+ } catch {
529
+ }
530
+ }
531
+ };
532
+ const prefixStream = (name, stream, sink) => {
533
+ let buf = "";
534
+ stream.setEncoding("utf8");
535
+ stream.on("data", (chunk) => {
536
+ buf += chunk;
537
+ const lines = buf.split("\n");
538
+ buf = lines.pop() ?? "";
539
+ for (const line of lines) sink.write(`[${name}] ${line}
540
+ `);
541
+ });
542
+ stream.on("end", () => {
543
+ if (buf) sink.write(`[${name}] ${buf}
544
+ `);
545
+ });
546
+ };
547
+ const signalHandlers = [];
548
+ const installSignals = () => {
549
+ for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
550
+ const fn = () => {
551
+ console.log(`[entrypoint] received ${sig} \u2014 forwarding to ${children.length} child process(es)`);
552
+ killAll(sig);
553
+ };
554
+ process.on(sig, fn);
555
+ signalHandlers.push({ sig, fn });
556
+ }
557
+ };
558
+ const removeSignals = () => {
559
+ for (const h of signalHandlers) process.off(h.sig, h.fn);
560
+ signalHandlers.length = 0;
561
+ };
562
+ return new Promise((resolve, reject) => {
563
+ let exitedCount = 0;
564
+ let firstFailure = null;
565
+ for (const spec of services) {
566
+ const args = [...spec.args, "--port", String(spec.port)];
567
+ console.log(`[entrypoint] starting ${spec.name}: ${spec.bin} ${args.join(" ")}`);
568
+ const proc = (0, import_node_child_process.spawn)(spec.bin, args, { stdio: ["ignore", "pipe", "pipe"] });
569
+ children.push({ spec, proc });
570
+ if (proc.stdout) prefixStream(spec.name, proc.stdout, process.stdout);
571
+ if (proc.stderr) prefixStream(spec.name, proc.stderr, process.stderr);
572
+ proc.on("exit", (code, signal) => {
573
+ exitedCount += 1;
574
+ const label = `[${spec.name}] exited code=${code ?? "?"} signal=${signal ?? "-"}`;
575
+ if (!shuttingDown && code !== 0) {
576
+ console.error(`[entrypoint] ${label} \u2014 bringing the group down`);
577
+ if (!firstFailure) firstFailure = { name: spec.name, code, signal };
578
+ killAll();
579
+ } else {
580
+ console.log(`[entrypoint] ${label}`);
581
+ }
582
+ if (exitedCount === children.length) {
583
+ if (firstFailure) reject(new Error(`service "${firstFailure.name}" exited code=${firstFailure.code ?? "?"}`));
584
+ else resolve();
585
+ }
586
+ });
587
+ proc.on("error", (err) => {
588
+ console.error(`[entrypoint] [${spec.name}] spawn error: ${err.message}`);
589
+ if (!firstFailure) firstFailure = { name: spec.name, code: null, signal: null };
590
+ killAll();
591
+ });
592
+ }
593
+ installSignals();
594
+ }).finally(removeSignals);
595
+ }
596
+ async function main() {
597
+ for (const k of REQUIRED_ENV) requireEnv(k);
598
+ const bundle = await setupFromCloud();
599
+ setupClaudeCredentials();
600
+ setupGitAndGh();
601
+ initWorkspaceIfEmpty();
602
+ let services;
603
+ try {
604
+ services = resolveServices();
605
+ } catch (err) {
606
+ fail(`[entrypoint] ${err instanceof Error ? err.message : String(err)}`);
607
+ }
608
+ console.log(`[entrypoint] services: ${services.map((s) => `${s.name}@${s.port}`).join(", ")}`);
609
+ const ingress = await maybeProvisionIngress(bundle, services, requireEnv("LS_PROJECT_SLUG"));
610
+ if (ingress) {
611
+ process.env.RADAR_CF_TUNNEL_TOKEN = ingress.connectorToken;
612
+ const radarFqdn = ingress.hostnames.radar;
613
+ if (radarFqdn) process.env.RADAR_CF_TUNNEL_HOSTNAME = radarFqdn;
614
+ else if (services.some((s) => s.name === "radar")) {
615
+ fail(`[entrypoint] internal: ingress provisioned but no hostname for radar`);
616
+ }
617
+ } else if (services.length > 1) {
618
+ const first = services[0];
619
+ console.warn(
620
+ `[entrypoint] \u26A0 quick mode \u2014 only the first service "${first.name}" (port ${first.port}) will be exposed via the ephemeral *.trycloudflare.com URL. Other service(s) [${services.slice(1).map((s) => s.name).join(", ")}] will run on localhost inside the container only. Connect a Cloudflare provider in LS and set LAUNCHKIT_CF_BASE_DOMAIN to expose all services with stable subdomains.`
621
+ );
622
+ if (first.name !== "radar") {
623
+ console.warn(`[entrypoint] \u26A0 first service is "${first.name}", not "radar" \u2014 quick tunneling is owned by the radar agent today, so NO external URL will be available.`);
624
+ }
625
+ }
626
+ try {
627
+ await spawnServiceGroup(services);
628
+ process.exit(0);
629
+ } catch (err) {
630
+ console.error(`[entrypoint] ${err instanceof Error ? err.message : String(err)}`);
631
+ process.exit(1);
632
+ }
633
+ }
634
+ if (!process.env.VITEST) {
635
+ main().catch((err) => {
636
+ console.error(`[entrypoint] fatal: ${err instanceof Error ? err.message : String(err)}`);
637
+ process.exit(1);
638
+ });
639
+ }
640
+ // Annotate the CommonJS export names for ESM import in node:
641
+ 0 && (module.exports = {
642
+ maybeProvisionIngress,
643
+ spawnServiceGroup
644
+ });