@launchsecure/launch-kit 0.0.44 → 0.0.46

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 (76) hide show
  1. package/dist/chart-client/assets/index-CXWFhknu.css +1 -0
  2. package/dist/chart-client/index.html +2 -2
  3. package/dist/client/assets/{index-BqiDfvZi.js → index-BQi0ccnm.js} +38 -38
  4. package/dist/client/assets/index-BxHD8tLY.css +32 -0
  5. package/dist/client/index.html +2 -2
  6. package/dist/council-client/assets/index-K5s7bSOk.css +1 -0
  7. package/dist/council-client/index.html +2 -2
  8. package/dist/deck-client/assets/{_baseUniq-C6w7kg8x.js → _baseUniq-Dc3nUqjn.js} +1 -1
  9. package/dist/deck-client/assets/{arc-Cx9pT3Nn.js → arc-Pk4Hg5AC.js} +1 -1
  10. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-BITSj3vA.js → architectureDiagram-Q4EWVU46-ClHfwBM_.js} +1 -1
  11. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BehOFuwh.js → blockDiagram-DXYQGD6D-byyj3c2H.js} +1 -1
  12. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-BZTYM4na.js → c4Diagram-AHTNJAMY-DAIz-EzX.js} +1 -1
  13. package/dist/deck-client/assets/channel-CrtYAG0j.js +1 -0
  14. package/dist/deck-client/assets/{chunk-4BX2VUAB-CCUx5CTd.js → chunk-4BX2VUAB-D9ILfgjn.js} +1 -1
  15. package/dist/deck-client/assets/{chunk-4TB4RGXK-UDZXXga6.js → chunk-4TB4RGXK-D5-01wU-.js} +1 -1
  16. package/dist/deck-client/assets/{chunk-55IACEB6-CfcU6PIW.js → chunk-55IACEB6-BQsygkyC.js} +1 -1
  17. package/dist/deck-client/assets/{chunk-EDXVE4YY-BK6F5Fof.js → chunk-EDXVE4YY-BEQCd4L_.js} +1 -1
  18. package/dist/deck-client/assets/{chunk-FMBD7UC4-C-2idlFB.js → chunk-FMBD7UC4-D347YT2V.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-OYMX7WX6-D6hBkYLP.js → chunk-OYMX7WX6-CN_JK0dw.js} +1 -1
  20. package/dist/deck-client/assets/{chunk-QZHKN3VN-DixNpysA.js → chunk-QZHKN3VN-Bt2J1da-.js} +1 -1
  21. package/dist/deck-client/assets/{chunk-YZCP3GAM-Cd3pNBtQ.js → chunk-YZCP3GAM-CyG1EIPO.js} +1 -1
  22. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-C6PSTf3r.js +1 -0
  23. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-C6PSTf3r.js +1 -0
  24. package/dist/deck-client/assets/clone-00e5l4vU.js +1 -0
  25. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-OF3JWdEt.js → cose-bilkent-S5V4N54A-pidjvoDW.js} +1 -1
  26. package/dist/deck-client/assets/{dagre-KV5264BT-Bqu-qcv4.js → dagre-KV5264BT-B7y7oCcY.js} +1 -1
  27. package/dist/deck-client/assets/{diagram-5BDNPKRD--0eHmUBS.js → diagram-5BDNPKRD-CCuv-nL4.js} +1 -1
  28. package/dist/deck-client/assets/{diagram-G4DWMVQ6-nss6oL20.js → diagram-G4DWMVQ6-UNm_Nh5Y.js} +1 -1
  29. package/dist/deck-client/assets/{diagram-MMDJMWI5-D_gSGnLR.js → diagram-MMDJMWI5-ozJ1XX_Q.js} +1 -1
  30. package/dist/deck-client/assets/{diagram-TYMM5635-BIt-P6Pk.js → diagram-TYMM5635-DbGkQand.js} +1 -1
  31. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-Bi-E4KQm.js → erDiagram-SMLLAGMA-CyyS0CgV.js} +1 -1
  32. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-DMJCvLMA.js → flowDiagram-DWJPFMVM-B5-cES0d.js} +1 -1
  33. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-C3xgEoPD.js → ganttDiagram-T4ZO3ILL-8uB-aHPV.js} +1 -1
  34. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-CD0BEGAW.js → gitGraphDiagram-UUTBAWPF-B3kn9oxf.js} +1 -1
  35. package/dist/deck-client/assets/{graph-Dtsd9Jwe.js → graph-48RwtCOC.js} +1 -1
  36. package/dist/deck-client/assets/{index-TFX8vtTG.js → index-B_IK1EJu.js} +1 -1
  37. package/dist/deck-client/assets/index-CvIV2mXs.css +1 -0
  38. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-7IcQYqe_.js → infoDiagram-42DDH7IO-DG1xrRXV.js} +1 -1
  39. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DsCEbx3u.js → ishikawaDiagram-UXIWVN3A-vHZT8oXQ.js} +1 -1
  40. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-1mP2JwCk.js → journeyDiagram-VCZTEJTY-BAAi_YhC.js} +1 -1
  41. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-vT0Xrqh9.js → kanban-definition-6JOO6SKY-CfNP3Cu6.js} +1 -1
  42. package/dist/deck-client/assets/{layout-Cw4rS2pn.js → layout-DIXGbv3Y.js} +1 -1
  43. package/dist/deck-client/assets/{linear-CzOjL-Ih.js → linear-C9PVcQFx.js} +1 -1
  44. package/dist/deck-client/assets/{mermaid.core-DYi3A-qK.js → mermaid.core-w8AQRITb.js} +4 -4
  45. package/dist/deck-client/assets/{min-DstloRoL.js → min-B0k-NaRG.js} +1 -1
  46. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-D-cCX2d2.js → mindmap-definition-QFDTVHPH-F3oNoTMv.js} +1 -1
  47. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-BqW2NTmy.js → pieDiagram-DEJITSTG-tsvvDV0T.js} +1 -1
  48. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-DbJoWA8f.js → quadrantDiagram-34T5L4WZ-UwD6L8ht.js} +1 -1
  49. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-DQrUiz_d.js → requirementDiagram-MS252O5E-De7hY7i9.js} +1 -1
  50. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-kB7PZc3g.js → sankeyDiagram-XADWPNL6-BAo56uzq.js} +1 -1
  51. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CpyVu1TN.js → sequenceDiagram-FGHM5R23-DBQe60Jv.js} +1 -1
  52. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-CjqQcnty.js → stateDiagram-FHFEXIEX-CcRfAns5.js} +1 -1
  53. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CpnNRLqf.js +1 -0
  54. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-B2PAO9bk.js → timeline-definition-GMOUNBTQ-BlMy5lUr.js} +1 -1
  55. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-C0G3ItCr.js → vennDiagram-DHZGUBPP-CiVqs92J.js} +1 -1
  56. package/dist/deck-client/assets/{wardley-RL74JXVD-B0TVaOmp.js → wardley-RL74JXVD-C19dhJoR.js} +1 -1
  57. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-B-qtbNZe.js → wardleyDiagram-NUSXRM2D-s_zVaEmu.js} +1 -1
  58. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-41kcBoBE.js → xychartDiagram-5P7HB3ND-DShsSL-Z.js} +1 -1
  59. package/dist/deck-client/index.html +2 -2
  60. package/dist/server/cli.js +472 -360
  61. package/dist/server/init-entry.js +40 -11
  62. package/dist/server/radar-docker-init-entry.js +34 -11
  63. package/dist/server/rover-entry.js +515 -130
  64. package/package.json +1 -1
  65. package/scaffolds/ls-marketplace/plugins/kit/skills/ship/SKILL.md +24 -18
  66. package/dist/chart-client/assets/index-DOKsFe5i.css +0 -1
  67. package/dist/client/assets/index-Mewz-s77.css +0 -32
  68. package/dist/council-client/assets/index-o_3y7Z0J.css +0 -1
  69. package/dist/deck-client/assets/channel-Cw2WDt9a.js +0 -1
  70. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-JLUXVCUr.js +0 -1
  71. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-JLUXVCUr.js +0 -1
  72. package/dist/deck-client/assets/clone-H0XCnSb6.js +0 -1
  73. package/dist/deck-client/assets/index-C6YxyZay.css +0 -1
  74. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-tfMSn8xx.js +0 -1
  75. /package/dist/chart-client/assets/{index-DJQYgFcp.js → index-B7lPHV9b.js} +0 -0
  76. /package/dist/council-client/assets/{index-Wn06apTg.js → index-B04u6r-y.js} +0 -0
@@ -33,12 +33,201 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
33
33
  mod
34
34
  ));
35
35
 
36
+ // src/server/cf-access.ts
37
+ async function cf(opts) {
38
+ const res = await fetch(`${CF_API_BASE}${opts.path}`, {
39
+ method: opts.method,
40
+ headers: {
41
+ Authorization: `Bearer ${opts.apiToken}`,
42
+ "Content-Type": "application/json",
43
+ Accept: "application/json",
44
+ "User-Agent": "launch-kit/cf-access"
45
+ },
46
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
47
+ signal: AbortSignal.timeout(15e3)
48
+ });
49
+ const text = await res.text();
50
+ try {
51
+ return text ? JSON.parse(text) : { success: false };
52
+ } catch {
53
+ throw new Error(`[cf-access] ${opts.method} ${opts.path} \u2192 ${res.status}, non-JSON: ${text.slice(0, 200)}`);
54
+ }
55
+ }
56
+ function fail(env, what) {
57
+ throw new Error(`[cf-access] ${what} failed: ${JSON.stringify(env.errors)}`);
58
+ }
59
+ function loadState(path) {
60
+ if (!(0, import_node_fs.existsSync)(path)) return null;
61
+ try {
62
+ const parsed = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
63
+ return typeof parsed?.accountId === "string" ? parsed : null;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+ function saveState(path, state) {
69
+ const dir = (0, import_node_path.dirname)(path);
70
+ if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
71
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(state, null, 2));
72
+ }
73
+ async function getAccessAuthDomain(apiToken, accountId) {
74
+ const res = await cf({
75
+ apiToken,
76
+ method: "GET",
77
+ path: `/accounts/${accountId}/access/organizations`
78
+ });
79
+ if (!res.success || !res.result?.auth_domain) {
80
+ fail(res, "GET access/organizations (is Zero Trust enabled on this account?)");
81
+ }
82
+ return res.result.auth_domain;
83
+ }
84
+ async function ensureAccessIdp(input) {
85
+ const config = {
86
+ client_id: input.clientId,
87
+ client_secret: input.clientSecret,
88
+ auth_url: `${input.issuer}/api/oidc/authorize`,
89
+ token_url: `${input.issuer}/api/oidc/token`,
90
+ certs_url: `${input.issuer}/.well-known/jwks.json`,
91
+ scopes: ["openid", "email", "profile"],
92
+ claims: ["org", "project_access", "roles", "email"],
93
+ email_claim_name: "email",
94
+ pkce_enabled: true
95
+ };
96
+ const body = { name: IDP_NAME, type: "oidc", config };
97
+ let idpId = input.knownIdpId;
98
+ if (!idpId) {
99
+ const list3 = await cf({
100
+ apiToken: input.apiToken,
101
+ method: "GET",
102
+ path: `/accounts/${input.accountId}/access/identity_providers`
103
+ });
104
+ if (!list3.success) fail(list3, "list identity_providers");
105
+ idpId = (list3.result ?? []).find((p) => p.name === IDP_NAME)?.id ?? null;
106
+ }
107
+ if (idpId) {
108
+ const upd = await cf({
109
+ apiToken: input.apiToken,
110
+ method: "PUT",
111
+ path: `/accounts/${input.accountId}/access/identity_providers/${idpId}`,
112
+ body
113
+ });
114
+ if (!upd.success || !upd.result) fail(upd, "update identity_provider");
115
+ return upd.result.id;
116
+ }
117
+ const created = await cf({
118
+ apiToken: input.apiToken,
119
+ method: "POST",
120
+ path: `/accounts/${input.accountId}/access/identity_providers`,
121
+ body
122
+ });
123
+ if (!created.success || !created.result) fail(created, "create identity_provider");
124
+ return created.result.id;
125
+ }
126
+ async function ensureAccessApp(input) {
127
+ const { service } = input;
128
+ const appDomain = service.path ? `${service.hostname}${service.path}` : service.hostname;
129
+ const policy = service.bypass ? {
130
+ name: "launch-kit-public-bypass",
131
+ decision: "bypass",
132
+ include: [{ everyone: {} }]
133
+ } : {
134
+ name: "launch-kit-org-allow",
135
+ decision: "allow",
136
+ include: [
137
+ {
138
+ oidc: {
139
+ identity_provider_id: input.idpId,
140
+ claim_name: "org",
141
+ claim_value: input.organizationId
142
+ }
143
+ }
144
+ ]
145
+ };
146
+ const body = service.bypass ? {
147
+ name: `launch-kit ${appDomain} (public)`,
148
+ domain: appDomain,
149
+ type: "self_hosted",
150
+ policies: [policy]
151
+ } : {
152
+ name: `launch-kit ${appDomain}`,
153
+ domain: appDomain,
154
+ type: "self_hosted",
155
+ // Bot terminal = RCE surface → short session. Read portals = a workday.
156
+ session_duration: service.strict ? "30m" : "24h",
157
+ allowed_idps: [input.idpId],
158
+ auto_redirect_to_identity: true,
159
+ policies: [policy]
160
+ };
161
+ const list3 = await cf({
162
+ apiToken: input.apiToken,
163
+ method: "GET",
164
+ path: `/accounts/${input.accountId}/access/apps`
165
+ });
166
+ if (!list3.success) fail(list3, "list access apps");
167
+ const existing = (list3.result ?? []).find((a) => a.domain === appDomain);
168
+ if (existing) {
169
+ const upd = await cf({
170
+ apiToken: input.apiToken,
171
+ method: "PUT",
172
+ path: `/accounts/${input.accountId}/access/apps/${existing.id}`,
173
+ body
174
+ });
175
+ if (!upd.success || !upd.result) fail(upd, `update access app ${appDomain}`);
176
+ return upd.result.id;
177
+ }
178
+ const created = await cf({
179
+ apiToken: input.apiToken,
180
+ method: "POST",
181
+ path: `/accounts/${input.accountId}/access/apps`,
182
+ body
183
+ });
184
+ if (!created.success || !created.result) fail(created, `create access app ${appDomain}`);
185
+ return created.result.id;
186
+ }
187
+ async function provisionAccess(input) {
188
+ const authDomain = await getAccessAuthDomain(input.apiToken, input.accountId);
189
+ const callbackUrl = `https://${authDomain}/cdn-cgi/access/callback`;
190
+ const { clientId, clientSecret, organizationId } = await input.registerClient([callbackUrl]);
191
+ const prior = loadState(input.stateFile);
192
+ const idpId = await ensureAccessIdp({
193
+ apiToken: input.apiToken,
194
+ accountId: input.accountId,
195
+ issuer: input.issuer,
196
+ clientId,
197
+ clientSecret,
198
+ knownIdpId: prior?.idpId ?? null
199
+ });
200
+ saveState(input.stateFile, { idpId, accountId: input.accountId });
201
+ const appIds = {};
202
+ for (const service of input.services) {
203
+ const appDomain = service.path ? `${service.hostname}${service.path}` : service.hostname;
204
+ appIds[appDomain] = await ensureAccessApp({
205
+ apiToken: input.apiToken,
206
+ accountId: input.accountId,
207
+ idpId,
208
+ organizationId,
209
+ service
210
+ });
211
+ }
212
+ return { idpId, authDomain, appIds };
213
+ }
214
+ var import_node_fs, import_node_path, CF_API_BASE, IDP_NAME;
215
+ var init_cf_access = __esm({
216
+ "src/server/cf-access.ts"() {
217
+ "use strict";
218
+ import_node_fs = require("node:fs");
219
+ import_node_path = require("node:path");
220
+ CF_API_BASE = "https://api.cloudflare.com/client/v4";
221
+ IDP_NAME = "launch-kit-oidc";
222
+ }
223
+ });
224
+
36
225
  // src/server/cf-ingress.ts
37
226
  function serviceLabel(s) {
38
227
  return s.label ?? s.name;
39
228
  }
40
- async function cf(opts) {
41
- const res = await fetch(`${CF_API_BASE}${opts.path}`, {
229
+ async function cf2(opts) {
230
+ const res = await fetch(`${CF_API_BASE2}${opts.path}`, {
42
231
  method: opts.method,
43
232
  headers: {
44
233
  Authorization: `Bearer ${opts.apiToken}`,
@@ -63,7 +252,7 @@ function isNotFound(env) {
63
252
  }
64
253
  async function findTunnelByName(input) {
65
254
  const q = new URLSearchParams({ name: input.tunnelName, is_deleted: "false" }).toString();
66
- const res = await cf({
255
+ const res = await cf2({
67
256
  apiToken: input.apiToken,
68
257
  method: "GET",
69
258
  path: `/accounts/${input.accountId}/cfd_tunnel?${q}`
@@ -72,10 +261,10 @@ async function findTunnelByName(input) {
72
261
  const live = res.result.find((t) => t.name === input.tunnelName && !t.deleted_at);
73
262
  return live?.id ?? null;
74
263
  }
75
- function loadState(path) {
76
- if (!(0, import_node_fs.existsSync)(path)) return null;
264
+ function loadState2(path) {
265
+ if (!(0, import_node_fs2.existsSync)(path)) return null;
77
266
  try {
78
- const parsed = JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
267
+ const parsed = JSON.parse((0, import_node_fs2.readFileSync)(path, "utf8"));
79
268
  if (typeof parsed?.tunnelId === "string" && typeof parsed?.accountId === "string") {
80
269
  return parsed;
81
270
  }
@@ -84,14 +273,14 @@ function loadState(path) {
84
273
  return null;
85
274
  }
86
275
  }
87
- function saveState(path, state) {
88
- const dir = (0, import_node_path.dirname)(path);
89
- if (!(0, import_node_fs.existsSync)(dir)) (0, import_node_fs.mkdirSync)(dir, { recursive: true });
90
- (0, import_node_fs.writeFileSync)(path, JSON.stringify(state, null, 2));
276
+ function saveState2(path, state) {
277
+ const dir = (0, import_node_path2.dirname)(path);
278
+ if (!(0, import_node_fs2.existsSync)(dir)) (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
279
+ (0, import_node_fs2.writeFileSync)(path, JSON.stringify(state, null, 2));
91
280
  }
92
281
  async function ensureTunnel(input, knownTunnelId) {
93
282
  if (knownTunnelId) {
94
- const got = await cf({
283
+ const got = await cf2({
95
284
  apiToken: input.apiToken,
96
285
  method: "GET",
97
286
  path: `/accounts/${input.accountId}/cfd_tunnel/${knownTunnelId}`
@@ -108,7 +297,7 @@ async function ensureTunnel(input, knownTunnelId) {
108
297
  console.log(`[cf] adopted existing tunnel "${input.tunnelName}" (${existing}) \u2014 local state was missing`);
109
298
  return existing;
110
299
  }
111
- const created = await cf({
300
+ const created = await cf2({
112
301
  apiToken: input.apiToken,
113
302
  method: "POST",
114
303
  path: `/accounts/${input.accountId}/cfd_tunnel`,
@@ -125,7 +314,7 @@ async function ensureTunnel(input, knownTunnelId) {
125
314
  throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
126
315
  }
127
316
  async function fetchConnectorToken(input, tunnelId) {
128
- const res = await cf({
317
+ const res = await cf2({
129
318
  apiToken: input.apiToken,
130
319
  method: "GET",
131
320
  path: `/accounts/${input.accountId}/cfd_tunnel/${tunnelId}/token`
@@ -141,7 +330,7 @@ async function setIngressConfig(input, tunnelId) {
141
330
  service: `http://127.0.0.1:${s.port}`
142
331
  }));
143
332
  ingress.push({ service: "http_status:404" });
144
- const res = await cf({
333
+ const res = await cf2({
145
334
  apiToken: input.apiToken,
146
335
  method: "PUT",
147
336
  path: `/accounts/${input.accountId}/cfd_tunnel/${tunnelId}/configurations`,
@@ -154,7 +343,7 @@ async function setIngressConfig(input, tunnelId) {
154
343
  async function ensureDnsRecord(input, tunnelId, service) {
155
344
  const fqdn = `${serviceLabel(service)}.${input.zone.name}`;
156
345
  const target = `${tunnelId}.cfargotunnel.com`;
157
- const existing = await cf({
346
+ const existing = await cf2({
158
347
  apiToken: input.apiToken,
159
348
  method: "GET",
160
349
  path: `/zones/${input.zone.id}/dns_records?name=${encodeURIComponent(fqdn)}&type=CNAME`
@@ -162,7 +351,7 @@ async function ensureDnsRecord(input, tunnelId, service) {
162
351
  if (existing.success && Array.isArray(existing.result) && existing.result.length > 0) {
163
352
  const rec = existing.result[0];
164
353
  if (rec.content === target) return;
165
- const upd = await cf({
354
+ const upd = await cf2({
166
355
  apiToken: input.apiToken,
167
356
  method: "PUT",
168
357
  path: `/zones/${input.zone.id}/dns_records/${rec.id}`,
@@ -173,7 +362,7 @@ async function ensureDnsRecord(input, tunnelId, service) {
173
362
  }
174
363
  return;
175
364
  }
176
- const created = await cf({
365
+ const created = await cf2({
177
366
  apiToken: input.apiToken,
178
367
  method: "POST",
179
368
  path: `/zones/${input.zone.id}/dns_records`,
@@ -184,9 +373,9 @@ async function ensureDnsRecord(input, tunnelId, service) {
184
373
  throw new Error(`[cf] DNS record create for ${fqdn} failed: ${JSON.stringify(created.errors)}`);
185
374
  }
186
375
  async function provisionIngress(input) {
187
- const prior = loadState(input.stateFile);
376
+ const prior = loadState2(input.stateFile);
188
377
  const tunnelId = await ensureTunnel(input, prior?.tunnelId ?? null);
189
- saveState(input.stateFile, {
378
+ saveState2(input.stateFile, {
190
379
  tunnelId,
191
380
  accountId: input.accountId,
192
381
  tunnelName: input.tunnelName,
@@ -199,13 +388,13 @@ async function provisionIngress(input) {
199
388
  for (const s of input.services) hostnames[s.name] = `${serviceLabel(s)}.${input.zone.name}`;
200
389
  return { tunnelId, connectorToken, hostnames };
201
390
  }
202
- var import_node_fs, import_node_path, CF_API_BASE, CF_ERR_DNS_RECORD_EXISTS;
391
+ var import_node_fs2, import_node_path2, CF_API_BASE2, CF_ERR_DNS_RECORD_EXISTS;
203
392
  var init_cf_ingress = __esm({
204
393
  "src/server/cf-ingress.ts"() {
205
394
  "use strict";
206
- import_node_fs = require("node:fs");
207
- import_node_path = require("node:path");
208
- CF_API_BASE = "https://api.cloudflare.com/client/v4";
395
+ import_node_fs2 = require("node:fs");
396
+ import_node_path2 = require("node:path");
397
+ CF_API_BASE2 = "https://api.cloudflare.com/client/v4";
209
398
  CF_ERR_DNS_RECORD_EXISTS = 81053;
210
399
  }
211
400
  });
@@ -516,16 +705,16 @@ __export(state_exports, {
516
705
  saveRoverState: () => saveRoverState
517
706
  });
518
707
  function stateDir() {
519
- return (0, import_node_path2.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR, "rover");
708
+ return (0, import_node_path3.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR, "rover");
520
709
  }
521
710
  function statePath(roverId) {
522
- return (0, import_node_path2.join)(stateDir(), `${roverId}.json`);
711
+ return (0, import_node_path3.join)(stateDir(), `${roverId}.json`);
523
712
  }
524
713
  function loadRoverState(roverId) {
525
714
  const p = statePath(roverId);
526
- if (!(0, import_node_fs2.existsSync)(p)) return null;
715
+ if (!(0, import_node_fs3.existsSync)(p)) return null;
527
716
  try {
528
- const data = JSON.parse((0, import_node_fs2.readFileSync)(p, "utf-8"));
717
+ const data = JSON.parse((0, import_node_fs3.readFileSync)(p, "utf-8"));
529
718
  if (!data.roverId || !data.secret || !data.tunnelUrl) return null;
530
719
  return data;
531
720
  } catch {
@@ -533,21 +722,21 @@ function loadRoverState(roverId) {
533
722
  }
534
723
  }
535
724
  function saveRoverState(state) {
536
- (0, import_node_fs2.mkdirSync)(stateDir(), { recursive: true });
725
+ (0, import_node_fs3.mkdirSync)(stateDir(), { recursive: true });
537
726
  const final = statePath(state.roverId);
538
727
  const tmp = `${final}.tmp`;
539
- (0, import_node_fs2.writeFileSync)(tmp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
540
- (0, import_node_fs2.renameSync)(tmp, final);
728
+ (0, import_node_fs3.writeFileSync)(tmp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
729
+ (0, import_node_fs3.renameSync)(tmp, final);
541
730
  }
542
731
  function clearRoverState(roverId) {
543
732
  try {
544
- (0, import_node_fs2.unlinkSync)(statePath(roverId));
733
+ (0, import_node_fs3.unlinkSync)(statePath(roverId));
545
734
  } catch {
546
735
  }
547
736
  }
548
737
  function listRoverIds() {
549
738
  const dir = stateDir();
550
- if (!(0, import_node_fs2.existsSync)(dir)) return [];
739
+ if (!(0, import_node_fs3.existsSync)(dir)) return [];
551
740
  try {
552
741
  const fs = require("node:fs");
553
742
  return fs.readdirSync(dir).filter((f) => f.endsWith(".json") && !f.includes(".tunnel.")).map((f) => f.slice(0, -5));
@@ -555,13 +744,13 @@ function listRoverIds() {
555
744
  return [];
556
745
  }
557
746
  }
558
- var import_node_fs2, import_node_os, import_node_path2;
747
+ var import_node_fs3, import_node_os, import_node_path3;
559
748
  var init_state = __esm({
560
749
  "src/server/rover/state.ts"() {
561
750
  "use strict";
562
- import_node_fs2 = require("node:fs");
751
+ import_node_fs3 = require("node:fs");
563
752
  import_node_os = require("node:os");
564
- import_node_path2 = require("node:path");
753
+ import_node_path3 = require("node:path");
565
754
  init_launch_kit_paths();
566
755
  }
567
756
  });
@@ -610,7 +799,7 @@ var require_package = __commonJS({
610
799
  "package.json"(exports2, module2) {
611
800
  module2.exports = {
612
801
  name: "@launchsecure/launch-kit",
613
- version: "0.0.44",
802
+ version: "0.0.46",
614
803
  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.",
615
804
  license: "MIT",
616
805
  author: "LaunchSecure - AutomateWithUs",
@@ -881,7 +1070,7 @@ function renderDashboard(state, pods) {
881
1070
  </head><body>
882
1071
  <div class="topbar">
883
1072
  <div class="brand">
884
- <span class="logobox">${roverMark(20, "hsl(259 28% 12%)")}</span>
1073
+ <span class="logobox">${roverMark(28, "hsl(259 28% 12%)", "full")}</span>
885
1074
  <span class="wordmark">LaunchRover</span>
886
1075
  </div>
887
1076
  <div class="context">
@@ -1005,14 +1194,14 @@ function coerceEntry(raw, index) {
1005
1194
  if (r.args !== void 0 && (!Array.isArray(r.args) || r.args.some((a) => typeof a !== "string"))) {
1006
1195
  throw new Error(`[launch-kit-services] entry #${index}: args must be a string[]`);
1007
1196
  }
1008
- const skipSpawn = r.skipSpawn === true || r.bin === "";
1009
- return {
1197
+ const spec = {
1010
1198
  name: r.name,
1011
1199
  port: r.port,
1012
1200
  bin: r.bin,
1013
- args: r.args ?? [],
1014
- skipSpawn
1201
+ args: r.args ?? []
1015
1202
  };
1203
+ if (r.skipSpawn === true || r.bin === "") spec.skipSpawn = true;
1204
+ return spec;
1016
1205
  }
1017
1206
  function validate(services) {
1018
1207
  if (services.length === 0) {
@@ -1057,11 +1246,11 @@ function resolveServices(opts = {}) {
1057
1246
  }
1058
1247
  return validate(parsed.map(coerceEntry));
1059
1248
  }
1060
- const filePath = (0, import_node_path3.join)(cwd, ".launchpod", "services.json");
1061
- if ((0, import_node_fs3.existsSync)(filePath)) {
1249
+ const filePath = (0, import_node_path4.join)(cwd, ".launchpod", "services.json");
1250
+ if ((0, import_node_fs4.existsSync)(filePath)) {
1062
1251
  let parsed;
1063
1252
  try {
1064
- parsed = JSON.parse((0, import_node_fs3.readFileSync)(filePath, "utf8"));
1253
+ parsed = JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf8"));
1065
1254
  } catch (err) {
1066
1255
  throw new Error(`[launch-kit-services] ${filePath} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
1067
1256
  }
@@ -1072,12 +1261,12 @@ function resolveServices(opts = {}) {
1072
1261
  }
1073
1262
  return validate(defaultServices());
1074
1263
  }
1075
- var import_node_fs3, import_node_path3, SHORTHANDS, DNS_NAME_RE, SHORTHAND_NAMES;
1264
+ var import_node_fs4, import_node_path4, SHORTHANDS, DNS_NAME_RE, SHORTHAND_NAMES;
1076
1265
  var init_launch_kit_services = __esm({
1077
1266
  "src/server/launch-kit-services.ts"() {
1078
1267
  "use strict";
1079
- import_node_fs3 = require("node:fs");
1080
- import_node_path3 = require("node:path");
1268
+ import_node_fs4 = require("node:fs");
1269
+ import_node_path4 = require("node:path");
1081
1270
  SHORTHANDS = {
1082
1271
  radar: { port: 3517, bin: "launch-radar", args: [] },
1083
1272
  sequencer: { port: 3517, bin: "launch-sequencer", args: [] },
@@ -1097,12 +1286,23 @@ var init_launch_kit_services = __esm({
1097
1286
  function slugLabel(projectSlug) {
1098
1287
  return projectSlug.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1099
1288
  }
1100
- function serviceHostname(projectSlug, serviceName, zone) {
1101
- return `${slugLabel(projectSlug)}-${serviceName}.${zone}`;
1289
+ function roverToken(roverId) {
1290
+ let h = 2166136261;
1291
+ for (let i = 0; i < roverId.length; i++) {
1292
+ h ^= roverId.charCodeAt(i);
1293
+ h = Math.imul(h, 16777619);
1294
+ }
1295
+ return ((h >>> 0) % 2176782336).toString(36).padStart(6, "0");
1296
+ }
1297
+ function serviceLabel2(projectSlug, serviceName, roverId) {
1298
+ return `${serviceName}-${slugLabel(projectSlug)}-${roverToken(roverId)}`;
1102
1299
  }
1103
- function serviceUrl(projectSlug, serviceName, zone) {
1104
- if (!zone) return null;
1105
- return `https://${serviceHostname(projectSlug, serviceName, zone)}`;
1300
+ function serviceHostname(projectSlug, serviceName, roverId, zone) {
1301
+ return `${serviceLabel2(projectSlug, serviceName, roverId)}.${zone}`;
1302
+ }
1303
+ function serviceUrl(projectSlug, serviceName, roverId, zone) {
1304
+ if (!zone || !roverId) return null;
1305
+ return `https://${serviceHostname(projectSlug, serviceName, roverId, zone)}`;
1106
1306
  }
1107
1307
  var init_hostnames = __esm({
1108
1308
  "src/server/rover/runtime/hostnames.ts"() {
@@ -1124,8 +1324,12 @@ function validateUpRequest(state, body) {
1124
1324
  if (typeof body.projectSlug !== "string" || !DNS_SLUG_RE.test(body.projectSlug)) {
1125
1325
  throw new PodError("projectSlug must be a DNS-safe string (a-z, 0-9, -)");
1126
1326
  }
1127
- if (typeof body.claudeCredentialsB64 !== "string" || body.claudeCredentialsB64.length < 16) {
1128
- throw new PodError("claudeCredentialsB64 is required (base64-encoded Claude credentials JSON)");
1327
+ const hasBlob = typeof body.claudeCredentialsB64 === "string" && body.claudeCredentialsB64.length >= 16;
1328
+ const hasToken = typeof body.claudeOauthToken === "string" && body.claudeOauthToken.length >= 16;
1329
+ if (!hasBlob && !hasToken) {
1330
+ throw new PodError(
1331
+ "one of claudeCredentialsB64 (base64-encoded Claude credentials JSON) or claudeOauthToken (CLAUDE_CODE_OAUTH_TOKEN from `claude setup-token`) is required"
1332
+ );
1129
1333
  }
1130
1334
  }
1131
1335
  var DNS_SLUG_RE, PodError;
@@ -1183,12 +1387,17 @@ async function create(state, body) {
1183
1387
  if (body.podId) runArgs.push("--label", `launch-rover.podId=${body.podId}`);
1184
1388
  if (body.name) runArgs.push("--label", `launch-rover.name=${body.name}`);
1185
1389
  const envVars = {
1186
- CLAUDE_CREDENTIALS_B64: body.claudeCredentialsB64,
1187
1390
  LS_PAT: state.installPat,
1188
1391
  LS_ORG_SLUG: state.orgSlug,
1189
1392
  LS_PROJECT_SLUG: body.projectSlug,
1393
+ // Folded into each service's DNS label by the pod entry so two rovers
1394
+ // running the same project on the same zone don't collide — see
1395
+ // runtime/hostnames.ts. Also read back in servicesForContainer below.
1396
+ LS_ROVER_ID: state.roverId,
1190
1397
  LS_SERVER_URL: rewriteLocalhostForContainer(state.serverUrl)
1191
1398
  };
1399
+ if (body.claudeCredentialsB64) envVars.CLAUDE_CREDENTIALS_B64 = body.claudeCredentialsB64;
1400
+ if (body.claudeOauthToken) envVars.CLAUDE_CODE_OAUTH_TOKEN = body.claudeOauthToken;
1192
1401
  if (body.services && Array.isArray(body.services)) {
1193
1402
  envVars.LAUNCHKIT_SERVICES = JSON.stringify(body.services);
1194
1403
  }
@@ -1205,7 +1414,7 @@ async function create(state, body) {
1205
1414
  const containerId = (await dockerRun(runArgs)).stdout.trim();
1206
1415
  return { podId: body.podId ?? null, projectSlug: body.projectSlug, containerId, image, createdAt, runtime: "docker" };
1207
1416
  } finally {
1208
- if (dockerConfigDir) (0, import_node_fs4.rmSync)(dockerConfigDir, { recursive: true, force: true });
1417
+ if (dockerConfigDir) (0, import_node_fs5.rmSync)(dockerConfigDir, { recursive: true, force: true });
1209
1418
  }
1210
1419
  }
1211
1420
  async function stop(_state, body) {
@@ -1287,6 +1496,7 @@ async function servicesForContainer(ref, projectSlug) {
1287
1496
  if (eq > 0) env[kv.slice(0, eq)] = kv.slice(eq + 1);
1288
1497
  }
1289
1498
  const zone = env.LAUNCHKIT_CF_BASE_DOMAIN || null;
1499
+ const roverId = env.LS_ROVER_ID || null;
1290
1500
  let specs;
1291
1501
  try {
1292
1502
  specs = resolveServices({
@@ -1295,7 +1505,7 @@ async function servicesForContainer(ref, projectSlug) {
1295
1505
  } catch {
1296
1506
  return void 0;
1297
1507
  }
1298
- return specs.filter((s) => !s.skipSpawn).map((s) => ({ name: s.name, port: s.port, url: serviceUrl(projectSlug, s.name, zone) }));
1508
+ return specs.filter((s) => !s.skipSpawn).map((s) => ({ name: s.name, port: s.port, url: serviceUrl(projectSlug, s.name, roverId, zone) }));
1299
1509
  }
1300
1510
  async function purgeAll(state) {
1301
1511
  const { pods } = await list(state);
@@ -1385,10 +1595,10 @@ function setupDockerConfig() {
1385
1595
  );
1386
1596
  return null;
1387
1597
  }
1388
- const dir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os2.tmpdir)(), "launch-rover-docker-"));
1598
+ const dir = (0, import_node_fs5.mkdtempSync)((0, import_node_path5.join)((0, import_node_os2.tmpdir)(), "launch-rover-docker-"));
1389
1599
  const auth = Buffer.from(`${GHCR_USERNAME}:${token}`, "utf-8").toString("base64");
1390
1600
  const config = { auths: { [GHCR_REGISTRY]: { auth } } };
1391
- (0, import_node_fs4.writeFileSync)((0, import_node_path4.join)(dir, "config.json"), JSON.stringify(config), { mode: 384 });
1601
+ (0, import_node_fs5.writeFileSync)((0, import_node_path5.join)(dir, "config.json"), JSON.stringify(config), { mode: 384 });
1392
1602
  return dir;
1393
1603
  }
1394
1604
  function parsePsRow(row) {
@@ -1419,14 +1629,14 @@ function parseLabels(raw) {
1419
1629
  }
1420
1630
  return out;
1421
1631
  }
1422
- var import_node_child_process2, import_node_fs4, import_node_os2, import_node_path4, import_node_util, execFileAsync, DEFAULT_IMAGE, GHCR_REGISTRY, GHCR_USERNAME, MANAGED_LABEL, POD_NAME_PREFIX, VOLUME_PREFIX, dockerRuntime;
1632
+ var import_node_child_process2, import_node_fs5, import_node_os2, import_node_path5, import_node_util, execFileAsync, DEFAULT_IMAGE, GHCR_REGISTRY, GHCR_USERNAME, MANAGED_LABEL, POD_NAME_PREFIX, VOLUME_PREFIX, dockerRuntime;
1423
1633
  var init_docker = __esm({
1424
1634
  "src/server/rover/runtime/docker.ts"() {
1425
1635
  "use strict";
1426
1636
  import_node_child_process2 = require("node:child_process");
1427
- import_node_fs4 = require("node:fs");
1637
+ import_node_fs5 = require("node:fs");
1428
1638
  import_node_os2 = require("node:os");
1429
- import_node_path4 = require("node:path");
1639
+ import_node_path5 = require("node:path");
1430
1640
  import_node_util = require("node:util");
1431
1641
  init_launch_kit_services();
1432
1642
  init_hostnames();
@@ -1474,16 +1684,16 @@ var init_ports = __esm({
1474
1684
 
1475
1685
  // src/server/rover/runtime/registry.ts
1476
1686
  function podsRoot() {
1477
- return (0, import_node_path5.join)((0, import_node_os3.homedir)(), LAUNCHSECURE_DIR, "pods");
1687
+ return (0, import_node_path6.join)((0, import_node_os3.homedir)(), LAUNCHSECURE_DIR, "pods");
1478
1688
  }
1479
1689
  function registryPath() {
1480
- return (0, import_node_path5.join)(podsRoot(), "registry.json");
1690
+ return (0, import_node_path6.join)(podsRoot(), "registry.json");
1481
1691
  }
1482
1692
  function load() {
1483
1693
  const p = registryPath();
1484
- if (!(0, import_node_fs5.existsSync)(p)) return { pods: [] };
1694
+ if (!(0, import_node_fs6.existsSync)(p)) return { pods: [] };
1485
1695
  try {
1486
- const doc = JSON.parse((0, import_node_fs5.readFileSync)(p, "utf-8"));
1696
+ const doc = JSON.parse((0, import_node_fs6.readFileSync)(p, "utf-8"));
1487
1697
  if (!doc || !Array.isArray(doc.pods)) return { pods: [] };
1488
1698
  return doc;
1489
1699
  } catch {
@@ -1492,11 +1702,11 @@ function load() {
1492
1702
  }
1493
1703
  function save(doc) {
1494
1704
  const dir = podsRoot();
1495
- (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
1705
+ (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
1496
1706
  const final = registryPath();
1497
1707
  const tmp = `${final}.tmp`;
1498
- (0, import_node_fs5.writeFileSync)(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: 384 });
1499
- (0, import_node_fs5.renameSync)(tmp, final);
1708
+ (0, import_node_fs6.writeFileSync)(tmp, JSON.stringify(doc, null, 2) + "\n", { mode: 384 });
1709
+ (0, import_node_fs6.renameSync)(tmp, final);
1500
1710
  }
1501
1711
  function recordIsAlive(rec) {
1502
1712
  try {
@@ -1538,13 +1748,13 @@ function removeBySlug(projectSlug) {
1538
1748
  const next = doc.pods.filter((p) => p.projectSlug !== projectSlug);
1539
1749
  if (next.length !== doc.pods.length) save({ pods: next });
1540
1750
  }
1541
- var import_node_fs5, import_node_os3, import_node_path5;
1751
+ var import_node_fs6, import_node_os3, import_node_path6;
1542
1752
  var init_registry = __esm({
1543
1753
  "src/server/rover/runtime/registry.ts"() {
1544
1754
  "use strict";
1545
- import_node_fs5 = require("node:fs");
1755
+ import_node_fs6 = require("node:fs");
1546
1756
  import_node_os3 = require("node:os");
1547
- import_node_path5 = require("node:path");
1757
+ import_node_path6 = require("node:path");
1548
1758
  init_launch_kit_paths();
1549
1759
  init_ports();
1550
1760
  }
@@ -1555,27 +1765,29 @@ function isDeniedEnvKey(k) {
1555
1765
  return DENIED_ENV.has(k) || /^(LD_|DYLD_)/.test(k);
1556
1766
  }
1557
1767
  function buildPodEnv(state, spec, root, services) {
1558
- const home = (0, import_node_path6.join)(root, "home");
1559
- const workspace = (0, import_node_path6.join)(root, "workspace");
1768
+ const home = (0, import_node_path7.join)(root, "home");
1769
+ const workspace = (0, import_node_path7.join)(root, "workspace");
1560
1770
  const env = {};
1561
1771
  for (const k of BASE_ENV_PASSTHROUGH) {
1562
1772
  const v = process.env[k];
1563
1773
  if (typeof v === "string") env[k] = v;
1564
1774
  }
1565
1775
  if (spec.claudeCredentialsB64) env.CLAUDE_CREDENTIALS_B64 = spec.claudeCredentialsB64;
1776
+ if (spec.claudeOauthToken) env.CLAUDE_CODE_OAUTH_TOKEN = spec.claudeOauthToken;
1566
1777
  env.LS_PAT = state.installPat;
1567
1778
  env.LS_ORG_SLUG = state.orgSlug;
1568
1779
  env.LS_PROJECT_SLUG = spec.projectSlug;
1780
+ env.LS_ROVER_ID = state.roverId;
1569
1781
  env.LS_SERVER_URL = state.serverUrl;
1570
1782
  env.LAUNCHKIT_SERVICES = JSON.stringify(servicesToEntries(services));
1571
1783
  if (spec.cfBaseDomain) env.LAUNCHKIT_CF_BASE_DOMAIN = spec.cfBaseDomain;
1572
1784
  env.HOME = home;
1573
1785
  env.LAUNCHPOD_WORKSPACE = workspace;
1574
- env.XDG_CONFIG_HOME = (0, import_node_path6.join)(home, ".config");
1575
- env.XDG_DATA_HOME = (0, import_node_path6.join)(home, ".local", "share");
1576
- env.XDG_CACHE_HOME = (0, import_node_path6.join)(home, ".cache");
1577
- env.GIT_CONFIG_GLOBAL = (0, import_node_path6.join)(home, ".gitconfig");
1578
- env.GH_CONFIG_DIR = (0, import_node_path6.join)(home, ".config", "gh");
1786
+ env.XDG_CONFIG_HOME = (0, import_node_path7.join)(home, ".config");
1787
+ env.XDG_DATA_HOME = (0, import_node_path7.join)(home, ".local", "share");
1788
+ env.XDG_CACHE_HOME = (0, import_node_path7.join)(home, ".cache");
1789
+ env.GIT_CONFIG_GLOBAL = (0, import_node_path7.join)(home, ".gitconfig");
1790
+ env.GH_CONFIG_DIR = (0, import_node_path7.join)(home, ".config", "gh");
1579
1791
  env.LAUNCHPOD_RUNTIME = "process";
1580
1792
  env.LAUNCHPOD_NO_PARK = "1";
1581
1793
  if (spec.env && typeof spec.env === "object") {
@@ -1594,18 +1806,18 @@ function servicesToEntries(services) {
1594
1806
  return services.map((s) => ({ name: s.name, port: s.port, bin: s.bin, args: s.args, skipSpawn: s.skipSpawn }));
1595
1807
  }
1596
1808
  function ensurePodDirs(root) {
1597
- const workspace = (0, import_node_path6.join)(root, "workspace");
1598
- const home = (0, import_node_path6.join)(root, "home");
1599
- (0, import_node_fs6.mkdirSync)(root, { recursive: true, mode: 448 });
1600
- (0, import_node_fs6.mkdirSync)(workspace, { recursive: true, mode: 448 });
1601
- (0, import_node_fs6.mkdirSync)((0, import_node_path6.join)(home, ".claude"), { recursive: true, mode: 448 });
1602
- (0, import_node_fs6.mkdirSync)((0, import_node_path6.join)(home, ".config", "gh"), { recursive: true, mode: 448 });
1603
- (0, import_node_fs6.mkdirSync)((0, import_node_path6.join)(home, ".local", "share"), { recursive: true, mode: 448 });
1604
- (0, import_node_fs6.mkdirSync)((0, import_node_path6.join)(home, ".cache"), { recursive: true, mode: 448 });
1809
+ const workspace = (0, import_node_path7.join)(root, "workspace");
1810
+ const home = (0, import_node_path7.join)(root, "home");
1811
+ (0, import_node_fs7.mkdirSync)(root, { recursive: true, mode: 448 });
1812
+ (0, import_node_fs7.mkdirSync)(workspace, { recursive: true, mode: 448 });
1813
+ (0, import_node_fs7.mkdirSync)((0, import_node_path7.join)(home, ".claude"), { recursive: true, mode: 448 });
1814
+ (0, import_node_fs7.mkdirSync)((0, import_node_path7.join)(home, ".config", "gh"), { recursive: true, mode: 448 });
1815
+ (0, import_node_fs7.mkdirSync)((0, import_node_path7.join)(home, ".local", "share"), { recursive: true, mode: 448 });
1816
+ (0, import_node_fs7.mkdirSync)((0, import_node_path7.join)(home, ".cache"), { recursive: true, mode: 448 });
1605
1817
  return { workspace, home };
1606
1818
  }
1607
1819
  function spawnPod(workspace, env, logFile, projectSlug) {
1608
- const logFd = (0, import_node_fs6.openSync)(logFile, "a", 384);
1820
+ const logFd = (0, import_node_fs7.openSync)(logFile, "a", 384);
1609
1821
  let child;
1610
1822
  try {
1611
1823
  child = (0, import_node_child_process3.spawn)(POD_ENTRY_BIN, [POD_ENTRY_ARG], {
@@ -1616,7 +1828,7 @@ function spawnPod(workspace, env, logFile, projectSlug) {
1616
1828
  stdio: ["ignore", logFd, logFd]
1617
1829
  });
1618
1830
  } finally {
1619
- (0, import_node_fs6.closeSync)(logFd);
1831
+ (0, import_node_fs7.closeSync)(logFd);
1620
1832
  }
1621
1833
  if (typeof child.pid !== "number") {
1622
1834
  throw new PodError(`failed to spawn pod process for "${projectSlug}"`, 500);
@@ -1624,12 +1836,12 @@ function spawnPod(workspace, env, logFile, projectSlug) {
1624
1836
  child.unref();
1625
1837
  return child.pid;
1626
1838
  }
1627
- function reconstructUrls(projectSlug, services, cfBaseDomain, localOnly) {
1839
+ function reconstructUrls(projectSlug, services, roverId, cfBaseDomain, localOnly) {
1628
1840
  const urls = {};
1629
1841
  if (localOnly || !cfBaseDomain) return urls;
1630
1842
  for (const s of services) {
1631
1843
  if (s.skipSpawn) continue;
1632
- const u = serviceUrl(projectSlug, s.name, cfBaseDomain);
1844
+ const u = serviceUrl(projectSlug, s.name, roverId, cfBaseDomain);
1633
1845
  if (u) urls[s.name] = u;
1634
1846
  }
1635
1847
  return urls;
@@ -1641,7 +1853,7 @@ async function create2(state, body) {
1641
1853
  await signalGroupDown(prior, "SIGTERM");
1642
1854
  }
1643
1855
  const slot = assignSlot(body.projectSlug);
1644
- const root = (0, import_node_path6.join)(podsRoot(), body.projectSlug);
1856
+ const root = (0, import_node_path7.join)(podsRoot(), body.projectSlug);
1645
1857
  const { workspace } = ensurePodDirs(root);
1646
1858
  let baseServices;
1647
1859
  try {
@@ -1654,12 +1866,12 @@ async function create2(state, body) {
1654
1866
  const services = assignPortsForSlot(baseServices, slot);
1655
1867
  const env = buildPodEnv(state, body, root, services);
1656
1868
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1657
- const logFile = (0, import_node_path6.join)(root, "pod.log");
1869
+ const logFile = (0, import_node_path7.join)(root, "pod.log");
1658
1870
  const pid = spawnPod(workspace, env, logFile, body.projectSlug);
1659
1871
  const ports = {};
1660
1872
  for (const s of services) if (!s.skipSpawn) ports[s.name] = s.port;
1661
1873
  const localOnly = body.env?.LAUNCHKIT_LOCAL_ONLY === "1";
1662
- const urls = reconstructUrls(body.projectSlug, services, body.cfBaseDomain, localOnly);
1874
+ const urls = reconstructUrls(body.projectSlug, services, state.roverId, body.cfBaseDomain, localOnly);
1663
1875
  const record = {
1664
1876
  projectSlug: body.projectSlug,
1665
1877
  podId: body.podId ?? null,
@@ -1670,9 +1882,13 @@ async function create2(state, body) {
1670
1882
  ports,
1671
1883
  urls,
1672
1884
  // Kept so `start` can respawn the same service set + ingress without the
1673
- // original up request (creds come from the pod's own on-disk store).
1885
+ // original up request (blob creds come from the pod's own on-disk store).
1674
1886
  services: baseServices.map((s) => s.name),
1675
1887
  cfBaseDomain: body.cfBaseDomain,
1888
+ // Token mode writes nothing to disk, so the long-lived token is kept here to
1889
+ // re-inject on respawn. Undefined (omitted) in blob mode. registry.json is
1890
+ // 0o600 — same protection blob mode gives the on-disk credentials file.
1891
+ claudeOauthToken: body.claudeOauthToken,
1676
1892
  stopped: false,
1677
1893
  root,
1678
1894
  logFile,
@@ -1709,7 +1925,7 @@ async function tear2(_state, body) {
1709
1925
  let volumesRemoved = false;
1710
1926
  if (body.purge) {
1711
1927
  try {
1712
- (0, import_node_fs6.rmSync)(rec.root, { recursive: true, force: true });
1928
+ (0, import_node_fs7.rmSync)(rec.root, { recursive: true, force: true });
1713
1929
  volumesRemoved = true;
1714
1930
  } catch (err) {
1715
1931
  console.warn(`[rover] purge of process pod folder "${rec.root}" failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -1735,7 +1951,12 @@ async function start2(state, body) {
1735
1951
  }
1736
1952
  const services = assignPortsForSlot(baseServices, rec.slot);
1737
1953
  const { workspace } = ensurePodDirs(rec.root);
1738
- const env = buildPodEnv(state, { projectSlug: rec.projectSlug, cfBaseDomain: rec.cfBaseDomain }, rec.root, services);
1954
+ const env = buildPodEnv(
1955
+ state,
1956
+ { projectSlug: rec.projectSlug, cfBaseDomain: rec.cfBaseDomain, claudeOauthToken: rec.claudeOauthToken },
1957
+ rec.root,
1958
+ services
1959
+ );
1739
1960
  const pid = spawnPod(workspace, env, rec.logFile, rec.projectSlug);
1740
1961
  upsert({ ...rec, pid, pgid: pid, stopped: false });
1741
1962
  console.log(`[rover] process pod "${body.projectSlug}" resumed \u2014 pid ${pid}, slot ${rec.slot}`);
@@ -1801,13 +2022,13 @@ async function signalGroupDown(rec, signal) {
1801
2022
  function sleep2(ms) {
1802
2023
  return new Promise((resolve) => setTimeout(resolve, ms));
1803
2024
  }
1804
- var import_node_child_process3, import_node_fs6, import_node_path6, POD_ENTRY_BIN, POD_ENTRY_ARG, DENIED_ENV, BASE_ENV_PASSTHROUGH, processRuntime;
2025
+ var import_node_child_process3, import_node_fs7, import_node_path7, POD_ENTRY_BIN, POD_ENTRY_ARG, DENIED_ENV, BASE_ENV_PASSTHROUGH, processRuntime;
1805
2026
  var init_process = __esm({
1806
2027
  "src/server/rover/runtime/process.ts"() {
1807
2028
  "use strict";
1808
2029
  import_node_child_process3 = require("node:child_process");
1809
- import_node_fs6 = require("node:fs");
1810
- import_node_path6 = require("node:path");
2030
+ import_node_fs7 = require("node:fs");
2031
+ import_node_path7 = require("node:path");
1811
2032
  init_launch_kit_services();
1812
2033
  init_hostnames();
1813
2034
  init_ports();
@@ -1902,25 +2123,25 @@ __export(daemon_exports, {
1902
2123
  startDaemon: () => startDaemon
1903
2124
  });
1904
2125
  function lockPath() {
1905
- return (0, import_node_path7.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR, ROVER_LOCK_FILENAME);
2126
+ return (0, import_node_path8.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR, ROVER_LOCK_FILENAME);
1906
2127
  }
1907
2128
  function readRoverLock() {
1908
2129
  const p = lockPath();
1909
- if (!(0, import_node_fs7.existsSync)(p)) return null;
2130
+ if (!(0, import_node_fs8.existsSync)(p)) return null;
1910
2131
  try {
1911
- return JSON.parse((0, import_node_fs7.readFileSync)(p, "utf-8"));
2132
+ return JSON.parse((0, import_node_fs8.readFileSync)(p, "utf-8"));
1912
2133
  } catch {
1913
2134
  return null;
1914
2135
  }
1915
2136
  }
1916
2137
  function writeLock(lock) {
1917
- const dir = (0, import_node_path7.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR);
1918
- (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
1919
- (0, import_node_fs7.writeFileSync)(lockPath(), JSON.stringify(lock, null, 2) + "\n", "utf-8");
2138
+ const dir = (0, import_node_path8.join)((0, import_node_os4.homedir)(), LAUNCHSECURE_DIR);
2139
+ (0, import_node_fs8.mkdirSync)(dir, { recursive: true });
2140
+ (0, import_node_fs8.writeFileSync)(lockPath(), JSON.stringify(lock, null, 2) + "\n", "utf-8");
1920
2141
  }
1921
2142
  function clearLock() {
1922
2143
  try {
1923
- (0, import_node_fs7.unlinkSync)(lockPath());
2144
+ (0, import_node_fs8.unlinkSync)(lockPath());
1924
2145
  } catch {
1925
2146
  }
1926
2147
  }
@@ -1990,6 +2211,9 @@ async function handleRequest(req, res, state) {
1990
2211
  if (method === "GET" && path === "/health") {
1991
2212
  return json(res, 200, { ok: true, roverId: state.roverId, orgSlug: state.orgSlug });
1992
2213
  }
2214
+ if (method === "GET" && path === "/self/status") {
2215
+ return json(res, 200, { roverId: state.roverId, kitVersion: KIT_VERSION, podImage: POD_IMAGE });
2216
+ }
1993
2217
  if (method === "GET" && (path === "/" || path === "/pods" || path === "/ui")) {
1994
2218
  try {
1995
2219
  const { pods } = await podsList(state);
@@ -2040,6 +2264,47 @@ async function handleRequest(req, res, state) {
2040
2264
  return json(res, 500, { error: err instanceof Error ? err.message : "internal_error" });
2041
2265
  }
2042
2266
  }
2267
+ if (method === "POST" && path === "/self/update") {
2268
+ const rawBody = await readBody(req);
2269
+ const sigHeader = req.headers["x-ls-signature"];
2270
+ const sigStr = Array.isArray(sigHeader) ? sigHeader[0] : sigHeader;
2271
+ const failure = verifySignature(state.secret, rawBody, sigStr);
2272
+ if (failure) {
2273
+ console.warn(`[rover] POST /self/update \u2192 401 (${failure})`);
2274
+ return json(res, 401, { error: "invalid_signature" });
2275
+ }
2276
+ let targetVersion;
2277
+ if (rawBody.length > 0) {
2278
+ try {
2279
+ targetVersion = JSON.parse(rawBody).targetVersion;
2280
+ } catch {
2281
+ return json(res, 400, { error: "malformed_json" });
2282
+ }
2283
+ }
2284
+ if (!targetVersion || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(targetVersion)) {
2285
+ return json(res, 400, { error: "invalid_target_version", detail: "expected a concrete semver, e.g. 0.0.45" });
2286
+ }
2287
+ if (targetVersion === KIT_VERSION) {
2288
+ return json(res, 200, { ok: true, updated: false, reason: "already_on_target", kitVersion: KIT_VERSION });
2289
+ }
2290
+ json(res, 200, { ok: true, updating: true, from: KIT_VERSION, to: targetVersion });
2291
+ const argv0 = process.argv[1] ?? "launch-rover";
2292
+ const child = (0, import_node_child_process4.spawn)(
2293
+ process.execPath,
2294
+ [argv0, "rover-update", `--rover=${state.roverId}`, `--version=${targetVersion}`, `--pod-image=${POD_IMAGE}`],
2295
+ { detached: true, stdio: "ignore", env: process.env }
2296
+ );
2297
+ child.unref();
2298
+ console.log(`[rover] /self/update \u2014 spawned updater (pid ${child.pid}) ${KIT_VERSION} \u2192 ${targetVersion}; exiting for re-exec`);
2299
+ setTimeout(() => {
2300
+ try {
2301
+ process.kill(process.pid, "SIGTERM");
2302
+ } catch {
2303
+ process.exit(0);
2304
+ }
2305
+ }, 250).unref();
2306
+ return;
2307
+ }
2043
2308
  if (method === "POST" && path === "/shutdown") {
2044
2309
  const rawBody = await readBody(req);
2045
2310
  const sigHeader = req.headers["x-ls-signature"];
@@ -2087,7 +2352,7 @@ function readBody(req) {
2087
2352
  });
2088
2353
  }
2089
2354
  async function sendHeartbeat(state) {
2090
- const payload = JSON.stringify({ roverId: state.roverId });
2355
+ const payload = JSON.stringify({ roverId: state.roverId, kitVersion: KIT_VERSION, podImage: POD_IMAGE });
2091
2356
  const { header } = buildSignatureHeader(state.secret, payload);
2092
2357
  const url = new URL("/api/rover/heartbeat", state.serverUrl);
2093
2358
  await postJson(url, payload, { "X-LS-Signature": header });
@@ -2126,21 +2391,24 @@ function postJson(url, body, extra) {
2126
2391
  req.end();
2127
2392
  });
2128
2393
  }
2129
- var import_node_fs7, import_node_http2, import_node_http3, import_node_https2, import_node_os4, import_node_path7, ROVER_LOCK_FILENAME;
2394
+ var import_node_child_process4, import_node_fs8, import_node_http2, import_node_http3, import_node_https2, import_node_os4, import_node_path8, KIT_VERSION, POD_IMAGE, ROVER_LOCK_FILENAME;
2130
2395
  var init_daemon = __esm({
2131
2396
  "src/server/rover/daemon.ts"() {
2132
2397
  "use strict";
2133
- import_node_fs7 = require("node:fs");
2398
+ import_node_child_process4 = require("node:child_process");
2399
+ import_node_fs8 = require("node:fs");
2134
2400
  import_node_http2 = require("node:http");
2135
2401
  import_node_http3 = require("node:http");
2136
2402
  import_node_https2 = require("node:https");
2137
2403
  import_node_os4 = require("node:os");
2138
- import_node_path7 = require("node:path");
2404
+ import_node_path8 = require("node:path");
2139
2405
  init_launch_kit_paths();
2140
2406
  init_dashboard();
2141
2407
  init_hmac();
2142
2408
  init_pods();
2143
2409
  init_state();
2410
+ KIT_VERSION = require_package().version;
2411
+ POD_IMAGE = process.env.LAUNCHKIT_POD_IMAGE ?? "ghcr.io/launchsecure/launch-pod:latest";
2144
2412
  ROVER_LOCK_FILENAME = "launch-rover.lock";
2145
2413
  }
2146
2414
  });
@@ -20903,11 +21171,11 @@ function createTunnel(opts) {
20903
21171
  const exhaustive = opts.provider;
20904
21172
  throw new Error(`[tunnel] unknown provider: ${String(exhaustive)}`);
20905
21173
  }
20906
- var import_node_fs8, 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;
21174
+ var import_node_fs9, 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;
20907
21175
  var init_tunnel = __esm({
20908
21176
  "src/server/tunnel/index.ts"() {
20909
21177
  "use strict";
20910
- import_node_fs8 = require("node:fs");
21178
+ import_node_fs9 = require("node:fs");
20911
21179
  import_node_events = require("node:events");
20912
21180
  import_promises3 = require("node:dns/promises");
20913
21181
  init_source();
@@ -20955,7 +21223,7 @@ var init_tunnel = __esm({
20955
21223
  }
20956
21224
  async start() {
20957
21225
  if (this.tunnel) return;
20958
- if (!(0, import_node_fs8.existsSync)(import_cloudflared.bin)) {
21226
+ if (!(0, import_node_fs9.existsSync)(import_cloudflared.bin)) {
20959
21227
  console.log("[tunnel] downloading cloudflared binary (one-time setup)\u2026");
20960
21228
  try {
20961
21229
  await (0, import_cloudflared.install)(import_cloudflared.bin);
@@ -21222,9 +21490,9 @@ async function runSetup(argv) {
21222
21490
  }
21223
21491
  const zone = await resolveZone(lsConfig.cfApiToken, lsConfig.cfDomainName);
21224
21492
  console.log(`[rover] resolved CF zone: ${zone.name} (account ${zone.accountId})`);
21225
- const stateDir2 = (0, import_node_path8.join)((0, import_node_os6.homedir)(), LAUNCHSECURE_DIR, "rover");
21226
- (0, import_node_fs9.mkdirSync)(stateDir2, { recursive: true });
21227
- const tunnelStateFile = (0, import_node_path8.join)(stateDir2, `${roverId}.tunnel.json`);
21493
+ const stateDir2 = (0, import_node_path9.join)((0, import_node_os6.homedir)(), LAUNCHSECURE_DIR, "rover");
21494
+ (0, import_node_fs10.mkdirSync)(stateDir2, { recursive: true });
21495
+ const tunnelStateFile = (0, import_node_path9.join)(stateDir2, `${roverId}.tunnel.json`);
21228
21496
  const idSuffix = roverId.replace(/[^a-z0-9]/gi, "").toLowerCase().slice(-8);
21229
21497
  const subdomain = `rover-${args.orgSlug.replace(/[^a-z0-9-]/gi, "-").toLowerCase().slice(0, 30)}-${idSuffix}`;
21230
21498
  const tunnelName = `launch-rover-${roverId}`;
@@ -21257,7 +21525,7 @@ async function runSetup(argv) {
21257
21525
  platform: (0, import_node_os6.platform)(),
21258
21526
  hostname: args.name ?? (0, import_node_os6.hostname)(),
21259
21527
  dockerVersion,
21260
- kitVersion: KIT_VERSION,
21528
+ kitVersion: KIT_VERSION2,
21261
21529
  // The actual pod backend this host runs — lets LS show the reported
21262
21530
  // runtime and flag drift from the configured `defaultRuntime`.
21263
21531
  runtime: args.runtime
@@ -21267,6 +21535,14 @@ async function runSetup(argv) {
21267
21535
  console.log(
21268
21536
  `[rover] LS state: ${outcome.approvalStatus}${outcome.reused ? " (reused existing secret)" : " (new secret minted)"}`
21269
21537
  );
21538
+ await gateDashboard({
21539
+ apiToken: lsConfig.cfApiToken,
21540
+ accountId: zone.accountId,
21541
+ issuer: args.serverUrl,
21542
+ hostname,
21543
+ pat: args.pat,
21544
+ stateFile: (0, import_node_path9.join)(stateDir2, `${roverId}.access.json`)
21545
+ });
21270
21546
  await launchDaemon(args, ingress, outcome.state);
21271
21547
  console.log("");
21272
21548
  if (outcome.approvalStatus === "pending_approval") {
@@ -21322,7 +21598,7 @@ async function launchDaemon(args, ingress, state) {
21322
21598
  return;
21323
21599
  }
21324
21600
  const argv0 = process.argv[1] ?? "launch-rover";
21325
- const child = (0, import_node_child_process4.spawn)(process.execPath, [argv0, "serve", `--rover=${state.roverId}`], {
21601
+ const child = (0, import_node_child_process5.spawn)(process.execPath, [argv0, "serve", `--rover=${state.roverId}`], {
21326
21602
  detached: true,
21327
21603
  stdio: "ignore",
21328
21604
  env: {
@@ -21334,6 +21610,52 @@ async function launchDaemon(args, ingress, state) {
21334
21610
  child.unref();
21335
21611
  console.log(`[rover] daemon spawned (pid ${child.pid}, detached)`);
21336
21612
  }
21613
+ async function gateDashboard(input) {
21614
+ const services = [
21615
+ // Host-level gate — the read-only dashboard. 24h session (not RCE-bearing,
21616
+ // unlike the bot terminal which the pod entrypoint marks strict).
21617
+ { hostname: input.hostname, strict: false },
21618
+ // Public carve-outs for the machine routes.
21619
+ ...ACCESS_BYPASS_PATHS.map((path) => ({ hostname: input.hostname, path, bypass: true }))
21620
+ ];
21621
+ console.log(`[rover] gating ${input.hostname} behind CF Access (IdP: ${input.issuer})`);
21622
+ try {
21623
+ const result = await provisionAccess({
21624
+ apiToken: input.apiToken,
21625
+ accountId: input.accountId,
21626
+ issuer: input.issuer,
21627
+ services,
21628
+ stateFile: input.stateFile,
21629
+ registerClient: (redirectUris) => registerOidcClient(input.issuer, input.pat, redirectUris)
21630
+ });
21631
+ console.log(`[rover] CF Access gate live \u2014 IdP ${result.idpId}, auth domain ${result.authDomain}`);
21632
+ } catch (err) {
21633
+ const msg = err instanceof Error ? err.message : String(err);
21634
+ throw new Error(
21635
+ `CF Access gating failed: ${msg}
21636
+ The dashboard would be publicly reachable without it, so setup is aborting before the tunnel goes live.
21637
+ Most common cause: the rover's Cloudflare token lacks Access scopes (Access: Apps and Policies + Account: Access Organizations, both Edit).
21638
+ Re-mint the token from LS \u2192 Organization \u2192 Integrations \u2192 Cloudflare (the "Create token" link pre-selects them), update the rover, and re-run setup.`
21639
+ );
21640
+ }
21641
+ }
21642
+ async function registerOidcClient(serverUrl, pat, redirectUris) {
21643
+ const res = await fetch(new URL("/api/rover/oidc-client", serverUrl), {
21644
+ method: "POST",
21645
+ headers: {
21646
+ Authorization: `Bearer ${pat}`,
21647
+ "Content-Type": "application/json",
21648
+ Accept: "application/json"
21649
+ },
21650
+ body: JSON.stringify({ redirectUris }),
21651
+ signal: AbortSignal.timeout(15e3)
21652
+ });
21653
+ const body = await res.json().catch(() => null);
21654
+ if (!res.ok || !body?.success || !body.data) {
21655
+ throw new Error(`OIDC client provisioning failed (HTTP ${res.status}): ${body?.error ?? "unexpected response"}`);
21656
+ }
21657
+ return body.data;
21658
+ }
21337
21659
  function runSetupReset(roverId) {
21338
21660
  clearRoverState(roverId);
21339
21661
  }
@@ -21345,24 +21667,26 @@ function isPidAlive(pid) {
21345
21667
  return false;
21346
21668
  }
21347
21669
  }
21348
- var import_node_os6, import_node_child_process4, import_node_path8, import_node_fs9, KIT_VERSION, DEFAULT_SERVER, DEFAULT_DAEMON_PORT, MIN_NODE_MAJOR;
21670
+ var import_node_os6, import_node_child_process5, import_node_path9, import_node_fs10, KIT_VERSION2, DEFAULT_SERVER, DEFAULT_DAEMON_PORT, MIN_NODE_MAJOR, ACCESS_BYPASS_PATHS;
21349
21671
  var init_setup = __esm({
21350
21672
  "src/server/rover/setup.ts"() {
21351
21673
  "use strict";
21352
21674
  import_node_os6 = require("node:os");
21353
- import_node_child_process4 = require("node:child_process");
21354
- import_node_path8 = require("node:path");
21355
- import_node_fs9 = require("node:fs");
21675
+ import_node_child_process5 = require("node:child_process");
21676
+ import_node_path9 = require("node:path");
21677
+ import_node_fs10 = require("node:fs");
21678
+ init_cf_access();
21356
21679
  init_cf_ingress();
21357
21680
  init_launch_kit_paths();
21358
21681
  init_docker_install();
21359
21682
  init_mcp_client();
21360
21683
  init_registration();
21361
21684
  init_state();
21362
- KIT_VERSION = require_package().version;
21685
+ KIT_VERSION2 = require_package().version;
21363
21686
  DEFAULT_SERVER = "https://launchsecure-v2.vercel.app";
21364
21687
  DEFAULT_DAEMON_PORT = 52749;
21365
21688
  MIN_NODE_MAJOR = 18;
21689
+ ACCESS_BYPASS_PATHS = ["/health", "/pods/up", "/pods/down", "/pods/start", "/pods/list", "/shutdown"];
21366
21690
  }
21367
21691
  });
21368
21692
 
@@ -21427,15 +21751,15 @@ async function runTeardown(argv) {
21427
21751
  }
21428
21752
  }
21429
21753
  clearRoverState(parsed.roverId);
21430
- const tunnelStateFile = (0, import_node_path9.join)(
21754
+ const tunnelStateFile = (0, import_node_path10.join)(
21431
21755
  (0, import_node_os7.homedir)(),
21432
21756
  LAUNCHSECURE_DIR,
21433
21757
  "rover",
21434
21758
  `${parsed.roverId}.tunnel.json`
21435
21759
  );
21436
- if ((0, import_node_fs10.existsSync)(tunnelStateFile)) {
21760
+ if ((0, import_node_fs11.existsSync)(tunnelStateFile)) {
21437
21761
  try {
21438
- (0, import_node_fs10.unlinkSync)(tunnelStateFile);
21762
+ (0, import_node_fs11.unlinkSync)(tunnelStateFile);
21439
21763
  } catch {
21440
21764
  }
21441
21765
  }
@@ -21486,19 +21810,75 @@ function isPidAlive2(pid) {
21486
21810
  function sleep3(ms) {
21487
21811
  return new Promise((resolve) => setTimeout(resolve, ms));
21488
21812
  }
21489
- var import_node_fs10, import_node_os7, import_node_path9;
21813
+ var import_node_fs11, import_node_os7, import_node_path10;
21490
21814
  var init_teardown = __esm({
21491
21815
  "src/server/rover/teardown.ts"() {
21492
21816
  "use strict";
21493
- import_node_fs10 = require("node:fs");
21817
+ import_node_fs11 = require("node:fs");
21494
21818
  import_node_os7 = require("node:os");
21495
- import_node_path9 = require("node:path");
21819
+ import_node_path10 = require("node:path");
21496
21820
  init_launch_kit_paths();
21497
21821
  init_daemon();
21498
21822
  init_state();
21499
21823
  }
21500
21824
  });
21501
21825
 
21826
+ // src/server/rover/update.ts
21827
+ var update_exports = {};
21828
+ __export(update_exports, {
21829
+ runUpdate: () => runUpdate
21830
+ });
21831
+ function parseArgs3(argv) {
21832
+ const get = (k) => argv.find((a) => a.startsWith(`--${k}=`))?.slice(`--${k}=`.length);
21833
+ const roverId = get("rover");
21834
+ const version = get("version");
21835
+ if (!roverId) throw new Error("rover-update requires --rover=<id>");
21836
+ if (!version || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
21837
+ throw new Error(`rover-update requires --version=<concrete semver> (got "${version ?? ""}")`);
21838
+ }
21839
+ return { roverId, version, podImage: get("pod-image") };
21840
+ }
21841
+ function log(msg) {
21842
+ process.stdout.write(`[rover-update] ${msg}
21843
+ `);
21844
+ }
21845
+ async function runUpdate(argv) {
21846
+ const args = parseArgs3(argv);
21847
+ log(`updating ${KIT_PACKAGE} \u2192 ${args.version} for rover ${args.roverId}`);
21848
+ const install2 = (0, import_node_child_process6.spawnSync)("npm", ["install", "-g", `${KIT_PACKAGE}@${args.version}`], {
21849
+ stdio: "inherit",
21850
+ env: process.env
21851
+ });
21852
+ if (install2.status !== 0) {
21853
+ log(`\u2717 npm install failed (status ${install2.status ?? "unknown"}); re-launching the existing version`);
21854
+ } else {
21855
+ log(`\u2713 installed ${KIT_PACKAGE}@${args.version}`);
21856
+ }
21857
+ if (args.podImage) {
21858
+ const pull = (0, import_node_child_process6.spawnSync)("docker", ["pull", args.podImage], { stdio: "inherit", env: process.env });
21859
+ if (pull.status === 0) {
21860
+ log(`\u2713 pulled ${args.podImage}`);
21861
+ } else {
21862
+ log(`\u26A0 docker pull skipped/failed (status ${pull.status ?? "unknown"}) \u2014 pod will pull on next up`);
21863
+ }
21864
+ }
21865
+ const child = (0, import_node_child_process6.spawn)("launch-rover", ["serve", `--rover=${args.roverId}`], {
21866
+ detached: true,
21867
+ stdio: "ignore",
21868
+ env: process.env
21869
+ });
21870
+ child.unref();
21871
+ log(`re-launched daemon (pid ${child.pid}); updater exiting`);
21872
+ }
21873
+ var import_node_child_process6, KIT_PACKAGE;
21874
+ var init_update = __esm({
21875
+ "src/server/rover/update.ts"() {
21876
+ "use strict";
21877
+ import_node_child_process6 = require("node:child_process");
21878
+ KIT_PACKAGE = "@launchsecure/launch-kit";
21879
+ }
21880
+ });
21881
+
21502
21882
  // src/server/rover-entry.ts
21503
21883
  function logStderr(msg) {
21504
21884
  process.stderr.write(`[launch-rover] ${msg}
@@ -21561,6 +21941,11 @@ async function main() {
21561
21941
  runStatus2(argv.slice(1));
21562
21942
  return;
21563
21943
  }
21944
+ case "rover-update": {
21945
+ const { runUpdate: runUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
21946
+ await runUpdate2(argv.slice(1));
21947
+ return;
21948
+ }
21564
21949
  case "teardown": {
21565
21950
  const { runTeardown: runTeardown2 } = await Promise.resolve().then(() => (init_teardown(), teardown_exports));
21566
21951
  await runTeardown2(argv.slice(1));