@launchsecure/launch-kit 0.0.36 → 0.0.37

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 (85) hide show
  1. package/dist/chart-client/assets/index-DJrjyXbN.css +1 -0
  2. package/dist/chart-client/index.html +2 -2
  3. package/dist/client/assets/index-8eSXr3Ez.css +32 -0
  4. package/dist/client/index.html +2 -2
  5. package/dist/council-client/assets/index-4K0t2WrZ.css +1 -0
  6. package/dist/council-client/index.html +2 -2
  7. package/dist/deck-client/assets/{_baseUniq-BiVx0WO_.js → _baseUniq-Cn5TyL9s.js} +1 -1
  8. package/dist/deck-client/assets/{arc-DGMkiEzS.js → arc-D61amKYu.js} +1 -1
  9. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-Y2WRmHtk.js → architectureDiagram-Q4EWVU46-CpKrvC2W.js} +1 -1
  10. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-_Lbfu5BQ.js → blockDiagram-DXYQGD6D-Yj5OjxvG.js} +1 -1
  11. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CTqpYTBX.js → c4Diagram-AHTNJAMY-BIR810Tv.js} +1 -1
  12. package/dist/deck-client/assets/channel-DrJz2x-n.js +1 -0
  13. package/dist/deck-client/assets/{chunk-4BX2VUAB-liEIbPHs.js → chunk-4BX2VUAB-BeSHwGvx.js} +1 -1
  14. package/dist/deck-client/assets/{chunk-4TB4RGXK-CCc6lYvL.js → chunk-4TB4RGXK-CCqzsLpg.js} +1 -1
  15. package/dist/deck-client/assets/{chunk-55IACEB6-D02jJUR2.js → chunk-55IACEB6-CuW_aq4-.js} +1 -1
  16. package/dist/deck-client/assets/{chunk-EDXVE4YY-BFmGMbLD.js → chunk-EDXVE4YY-Dl35ixYh.js} +1 -1
  17. package/dist/deck-client/assets/{chunk-FMBD7UC4-6wFLOVcJ.js → chunk-FMBD7UC4-TwreZQTv.js} +1 -1
  18. package/dist/deck-client/assets/{chunk-OYMX7WX6-Bnr8RiBf.js → chunk-OYMX7WX6-Ahfw8EUo.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-QZHKN3VN-Ct82MksJ.js → chunk-QZHKN3VN-DlE_zlU-.js} +1 -1
  20. package/dist/deck-client/assets/{chunk-YZCP3GAM-BXmN1diQ.js → chunk-YZCP3GAM-Dj6QWzSg.js} +1 -1
  21. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-a3tg9w7z.js +1 -0
  22. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-a3tg9w7z.js +1 -0
  23. package/dist/deck-client/assets/clone-Dd7JBCL5.js +1 -0
  24. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-CmQCT-mH.js → cose-bilkent-S5V4N54A-BO1z5aOM.js} +1 -1
  25. package/dist/deck-client/assets/{dagre-KV5264BT-DDdSa9EX.js → dagre-KV5264BT-DVsw17fE.js} +1 -1
  26. package/dist/deck-client/assets/{diagram-5BDNPKRD-Bccks2xJ.js → diagram-5BDNPKRD-6jYs7oZk.js} +1 -1
  27. package/dist/deck-client/assets/{diagram-G4DWMVQ6-CPPNgxmQ.js → diagram-G4DWMVQ6-6DbggeGE.js} +1 -1
  28. package/dist/deck-client/assets/{diagram-MMDJMWI5-KrD300pS.js → diagram-MMDJMWI5-CQtk1cSU.js} +1 -1
  29. package/dist/deck-client/assets/{diagram-TYMM5635-DefnLuQf.js → diagram-TYMM5635-BR-gt75b.js} +1 -1
  30. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-DI9FfnFP.js → erDiagram-SMLLAGMA-C9qMtjdY.js} +1 -1
  31. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-twKyd3Fx.js → flowDiagram-DWJPFMVM-CdaPhPYb.js} +1 -1
  32. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-Wau3jhBr.js → ganttDiagram-T4ZO3ILL-BRsZWUy4.js} +1 -1
  33. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-D9GgYXwb.js → gitGraphDiagram-UUTBAWPF-B8Z90jCj.js} +1 -1
  34. package/dist/deck-client/assets/{graph-BhNLzyXS.js → graph-my2Zphm4.js} +1 -1
  35. package/dist/deck-client/assets/index-ByqxPEgU.css +1 -0
  36. package/dist/deck-client/assets/{index-BtQBaQ7s.js → index-DqAoYZwV.js} +43 -42
  37. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-TylGlSG-.js → infoDiagram-42DDH7IO-Csr9loin.js} +1 -1
  38. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DAT8icpg.js → ishikawaDiagram-UXIWVN3A-HWdvUNFi.js} +1 -1
  39. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-D3v_XL72.js → journeyDiagram-VCZTEJTY-CjYHG6EM.js} +1 -1
  40. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-DNUOBiNr.js → kanban-definition-6JOO6SKY-CX3JdUu7.js} +1 -1
  41. package/dist/deck-client/assets/{layout-COfodgwF.js → layout-Bcucv5Gi.js} +1 -1
  42. package/dist/deck-client/assets/{linear-DmTsuIvK.js → linear-CUGM5FJZ.js} +1 -1
  43. package/dist/deck-client/assets/{min-BW1F7i1D.js → min-Dw4g5w9z.js} +1 -1
  44. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CErFzKWl.js → mindmap-definition-QFDTVHPH-C8oo61fg.js} +1 -1
  45. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DW5F757o.js → pieDiagram-DEJITSTG-D2WYGkq8.js} +1 -1
  46. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-B1S2-TfI.js → quadrantDiagram-34T5L4WZ-Vh00GISt.js} +1 -1
  47. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BY5BAR-5.js → requirementDiagram-MS252O5E-DxI-DFrN.js} +1 -1
  48. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-CE1Cp9HS.js → sankeyDiagram-XADWPNL6-QgwyjasI.js} +1 -1
  49. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-IaHnbKye.js → sequenceDiagram-FGHM5R23-DmOmD5Ni.js} +1 -1
  50. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-CwPJm9hU.js → stateDiagram-FHFEXIEX-CRwglGg_.js} +1 -1
  51. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BvZLEWAA.js +1 -0
  52. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DVFGGSgN.js → timeline-definition-GMOUNBTQ-Dj9YGKOh.js} +1 -1
  53. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-C1194MJi.js → vennDiagram-DHZGUBPP-xzIaOzEU.js} +1 -1
  54. package/dist/deck-client/assets/wardley-RL74JXVD-CEAay09T.js +162 -0
  55. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-hpwdFfGj.js → wardleyDiagram-NUSXRM2D-BIYYh-JZ.js} +1 -1
  56. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-DYkotwy8.js → xychartDiagram-5P7HB3ND-Cy9EoJCh.js} +1 -1
  57. package/dist/deck-client/index.html +2 -2
  58. package/dist/server/cli.js +21 -2
  59. package/dist/server/council-entry.js +86 -2
  60. package/dist/server/council-serve.js +81 -2
  61. package/dist/server/deck-mcp-entry.js +449 -68
  62. package/dist/server/deck-serve.js +411 -42
  63. package/dist/server/fb-wizard.js +0 -0
  64. package/dist/server/init-entry.js +96 -4
  65. package/dist/server/radar-docker-init-entry.js +95 -3
  66. package/dist/server/radar-entrypoint-entry.js +0 -0
  67. package/dist/server/radar-teardown-entry.js +0 -0
  68. package/dist/server/rover-entry.js +25 -4
  69. package/package.json +22 -23
  70. package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check/SKILL.md +5 -0
  71. package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +0 -0
  72. package/scaffolds/recall-hook/scripts/ensure-recall.sh +0 -0
  73. package/dist/chart-client/assets/index-DpKO9p0s.css +0 -1
  74. package/dist/client/assets/index-Dv6dD2zY.css +0 -32
  75. package/dist/council-client/assets/index-AqQ9Sei6.css +0 -1
  76. package/dist/deck-client/assets/channel-DB6LxW_l.js +0 -1
  77. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-g944ZyG8.js +0 -1
  78. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-g944ZyG8.js +0 -1
  79. package/dist/deck-client/assets/clone-DiIRH1pI.js +0 -1
  80. package/dist/deck-client/assets/index-B-YQq5b5.css +0 -1
  81. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-DQYa2M1q.js +0 -1
  82. package/dist/deck-client/assets/wardley-RL74JXVD-CHZiUbBa.js +0 -162
  83. /package/dist/chart-client/assets/{index-DFu2xIrM.js → index-BgUxHxwE.js} +0 -0
  84. /package/dist/client/assets/{index-Cbw6bVdx.js → index-CUivaQnN.js} +0 -0
  85. /package/dist/council-client/assets/{index-CAsmGTzg.js → index-DN8HN_5K.js} +0 -0
@@ -617,6 +617,17 @@ async function cf(opts) {
617
617
  function isNotFound(env) {
618
618
  return !env.success && (env.errors ?? []).some((e) => e.code === 7003 || e.code === 1001 || e.code === 81044);
619
619
  }
620
+ async function findTunnelByName(input) {
621
+ const q = new URLSearchParams({ name: input.tunnelName, is_deleted: "false" }).toString();
622
+ const res = await cf({
623
+ apiToken: input.apiToken,
624
+ method: "GET",
625
+ path: `/accounts/${input.accountId}/cfd_tunnel?${q}`
626
+ });
627
+ if (!res.success || !Array.isArray(res.result)) return null;
628
+ const live = res.result.find((t) => t.name === input.tunnelName && !t.deleted_at);
629
+ return live?.id ?? null;
630
+ }
620
631
  function loadState(path6) {
621
632
  if (!(0, import_node_fs2.existsSync)(path6)) return null;
622
633
  try {
@@ -648,16 +659,26 @@ async function ensureTunnel(input, knownTunnelId) {
648
659
  throw new Error(`[cf] tunnel GET failed: ${JSON.stringify(got.errors)}`);
649
660
  }
650
661
  }
662
+ const existing = await findTunnelByName(input);
663
+ if (existing) {
664
+ console.log(`[cf] adopted existing tunnel "${input.tunnelName}" (${existing}) \u2014 local state was missing`);
665
+ return existing;
666
+ }
651
667
  const created = await cf({
652
668
  apiToken: input.apiToken,
653
669
  method: "POST",
654
670
  path: `/accounts/${input.accountId}/cfd_tunnel`,
655
671
  body: { name: input.tunnelName, config_src: "cloudflare" }
656
672
  });
657
- if (!created.success || !created.result) {
658
- throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
673
+ if (created.success && created.result) return created.result.id;
674
+ if ((created.errors ?? []).some((e) => e.code === 1013)) {
675
+ const adopted = await findTunnelByName(input);
676
+ if (adopted) {
677
+ console.log(`[cf] tunnel "${input.tunnelName}" already existed (1013) \u2014 adopted ${adopted}`);
678
+ return adopted;
679
+ }
659
680
  }
660
- return created.result.id;
681
+ throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
661
682
  }
662
683
  async function fetchConnectorToken(input, tunnelId) {
663
684
  const res = await cf({
@@ -942,6 +963,60 @@ function run2(cmd, args, stdio = "inherit") {
942
963
  const r = (0, import_node_child_process2.spawnSync)(cmd, args, { stdio });
943
964
  return r.status ?? 1;
944
965
  }
966
+ function readCrashState() {
967
+ try {
968
+ const s = JSON.parse((0, import_node_fs4.readFileSync)(CRASH_STATE_FILE, "utf8"));
969
+ return typeof s?.count === "number" && s.count >= 0 ? s : null;
970
+ } catch {
971
+ return null;
972
+ }
973
+ }
974
+ function bumpCrashCount() {
975
+ const prev = readCrashState();
976
+ const now = (/* @__PURE__ */ new Date()).toISOString();
977
+ const next = {
978
+ count: (prev?.count ?? 0) + 1,
979
+ firstAt: prev?.firstAt ?? now,
980
+ lastAt: now
981
+ };
982
+ try {
983
+ (0, import_node_fs4.mkdirSync)(LAUNCHPOD_DIR, { recursive: true });
984
+ (0, import_node_fs4.writeFileSync)(CRASH_STATE_FILE, JSON.stringify(next, null, 2));
985
+ } catch (err) {
986
+ console.warn(`[entrypoint] could not persist boot-crash counter (continuing unprotected): ${err instanceof Error ? err.message : String(err)}`);
987
+ }
988
+ return next.count;
989
+ }
990
+ function clearCrashCount() {
991
+ try {
992
+ if ((0, import_node_fs4.existsSync)(CRASH_STATE_FILE)) (0, import_node_fs4.writeFileSync)(CRASH_STATE_FILE, JSON.stringify({ count: 0, firstAt: "", lastAt: "" }));
993
+ } catch {
994
+ }
995
+ }
996
+ async function parkAfterCrashLoop(count) {
997
+ const lines = [
998
+ "==================================================================",
999
+ `[entrypoint] CRASH-LOOP HALT \u2014 ${count} consecutive failed boots (cap ${MAX_BOOT_CRASHES}).`,
1000
+ "[entrypoint] Refusing to restart again. Container is now PARKED (idle, not",
1001
+ "[entrypoint] exiting) so it stops thrashing CF APIs and logs. Fix the root",
1002
+ "[entrypoint] cause, clear the counter, then restart the container:",
1003
+ `[entrypoint] rm ${CRASH_STATE_FILE} && docker restart <container>`,
1004
+ "=================================================================="
1005
+ ];
1006
+ for (const l of lines) console.error(l);
1007
+ for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
1008
+ process.on(sig, () => {
1009
+ console.log(`[entrypoint] received ${sig} while parked \u2014 exiting`);
1010
+ process.exit(0);
1011
+ });
1012
+ }
1013
+ setInterval(() => {
1014
+ console.error(`[entrypoint] still parked after crash-loop halt \u2014 clear ${CRASH_STATE_FILE} and restart to retry`);
1015
+ }, 15 * 6e4);
1016
+ await new Promise(() => {
1017
+ });
1018
+ throw new Error("unreachable");
1019
+ }
945
1020
  async function setupFromCloud() {
946
1021
  const pat = requireEnv("LS_PAT");
947
1022
  const orgSlug = requireEnv("LS_ORG_SLUG");
@@ -1226,6 +1301,12 @@ function spawnServiceGroup(services) {
1226
1301
  }).finally(removeSignals);
1227
1302
  }
1228
1303
  async function main() {
1304
+ const priorCrashes = readCrashState()?.count ?? 0;
1305
+ if (priorCrashes >= MAX_BOOT_CRASHES) await parkAfterCrashLoop(priorCrashes);
1306
+ const bootAttempt = bumpCrashCount();
1307
+ if (bootAttempt > 1) {
1308
+ console.warn(`[entrypoint] boot attempt ${bootAttempt}/${MAX_BOOT_CRASHES} \u2014 prior boot(s) crashed before becoming stable`);
1309
+ }
1229
1310
  for (const k of REQUIRED_ENV) requireEnv(k);
1230
1311
  const bundle = await setupFromCloud();
1231
1312
  setupClaudeCredentials();
@@ -1257,15 +1338,22 @@ async function main() {
1257
1338
  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.`);
1258
1339
  }
1259
1340
  }
1341
+ const stableTimer = setTimeout(() => {
1342
+ clearCrashCount();
1343
+ console.log(`[entrypoint] services stable for ${Math.round(STABLE_AFTER_MS / 1e3)}s \u2014 boot-crash counter cleared`);
1344
+ }, STABLE_AFTER_MS);
1345
+ stableTimer.unref?.();
1260
1346
  try {
1261
1347
  await spawnServiceGroup(services);
1348
+ clearTimeout(stableTimer);
1262
1349
  process.exit(0);
1263
1350
  } catch (err) {
1351
+ clearTimeout(stableTimer);
1264
1352
  console.error(`[entrypoint] ${err instanceof Error ? err.message : String(err)}`);
1265
1353
  process.exit(1);
1266
1354
  }
1267
1355
  }
1268
- var import_node_child_process2, import_node_fs4, import_node_path4, REQUIRED_ENV, GATED_SERVICES;
1356
+ var import_node_child_process2, import_node_fs4, import_node_path4, REQUIRED_ENV, LAUNCHPOD_DIR, CRASH_STATE_FILE, MAX_BOOT_CRASHES, STABLE_AFTER_MS, GATED_SERVICES;
1269
1357
  var init_radar_docker_init_entry = __esm({
1270
1358
  "src/server/radar-docker-init-entry.ts"() {
1271
1359
  "use strict";
@@ -1282,6 +1370,10 @@ var init_radar_docker_init_entry = __esm({
1282
1370
  "LS_ORG_SLUG",
1283
1371
  "LS_PROJECT_SLUG"
1284
1372
  ];
1373
+ LAUNCHPOD_DIR = "/workspace/.launchpod";
1374
+ CRASH_STATE_FILE = (0, import_node_path4.join)(LAUNCHPOD_DIR, ".boot-crash.json");
1375
+ MAX_BOOT_CRASHES = 5;
1376
+ STABLE_AFTER_MS = 3e4;
1285
1377
  GATED_SERVICES = {
1286
1378
  // Claude web terminal — live drivable shell ⇒ RCE surface ⇒ short session.
1287
1379
  bot: { strict: true },
@@ -286,6 +286,17 @@ async function cf(opts) {
286
286
  function isNotFound(env) {
287
287
  return !env.success && (env.errors ?? []).some((e) => e.code === 7003 || e.code === 1001 || e.code === 81044);
288
288
  }
289
+ async function findTunnelByName(input) {
290
+ const q = new URLSearchParams({ name: input.tunnelName, is_deleted: "false" }).toString();
291
+ const res = await cf({
292
+ apiToken: input.apiToken,
293
+ method: "GET",
294
+ path: `/accounts/${input.accountId}/cfd_tunnel?${q}`
295
+ });
296
+ if (!res.success || !Array.isArray(res.result)) return null;
297
+ const live = res.result.find((t) => t.name === input.tunnelName && !t.deleted_at);
298
+ return live?.id ?? null;
299
+ }
289
300
  function loadState(path) {
290
301
  if (!(0, import_node_fs2.existsSync)(path)) return null;
291
302
  try {
@@ -317,16 +328,26 @@ async function ensureTunnel(input, knownTunnelId) {
317
328
  throw new Error(`[cf] tunnel GET failed: ${JSON.stringify(got.errors)}`);
318
329
  }
319
330
  }
331
+ const existing = await findTunnelByName(input);
332
+ if (existing) {
333
+ console.log(`[cf] adopted existing tunnel "${input.tunnelName}" (${existing}) \u2014 local state was missing`);
334
+ return existing;
335
+ }
320
336
  const created = await cf({
321
337
  apiToken: input.apiToken,
322
338
  method: "POST",
323
339
  path: `/accounts/${input.accountId}/cfd_tunnel`,
324
340
  body: { name: input.tunnelName, config_src: "cloudflare" }
325
341
  });
326
- if (!created.success || !created.result) {
327
- throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
342
+ if (created.success && created.result) return created.result.id;
343
+ if ((created.errors ?? []).some((e) => e.code === 1013)) {
344
+ const adopted = await findTunnelByName(input);
345
+ if (adopted) {
346
+ console.log(`[cf] tunnel "${input.tunnelName}" already existed (1013) \u2014 adopted ${adopted}`);
347
+ return adopted;
348
+ }
328
349
  }
329
- return created.result.id;
350
+ throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
330
351
  }
331
352
  async function fetchConnectorToken(input, tunnelId) {
332
353
  const res = await cf({
@@ -595,6 +616,64 @@ function run(cmd, args, stdio = "inherit") {
595
616
  const r = (0, import_node_child_process.spawnSync)(cmd, args, { stdio });
596
617
  return r.status ?? 1;
597
618
  }
619
+ var LAUNCHPOD_DIR = "/workspace/.launchpod";
620
+ var CRASH_STATE_FILE = (0, import_node_path4.join)(LAUNCHPOD_DIR, ".boot-crash.json");
621
+ var MAX_BOOT_CRASHES = 5;
622
+ var STABLE_AFTER_MS = 3e4;
623
+ function readCrashState() {
624
+ try {
625
+ const s = JSON.parse((0, import_node_fs4.readFileSync)(CRASH_STATE_FILE, "utf8"));
626
+ return typeof s?.count === "number" && s.count >= 0 ? s : null;
627
+ } catch {
628
+ return null;
629
+ }
630
+ }
631
+ function bumpCrashCount() {
632
+ const prev = readCrashState();
633
+ const now = (/* @__PURE__ */ new Date()).toISOString();
634
+ const next = {
635
+ count: (prev?.count ?? 0) + 1,
636
+ firstAt: prev?.firstAt ?? now,
637
+ lastAt: now
638
+ };
639
+ try {
640
+ (0, import_node_fs4.mkdirSync)(LAUNCHPOD_DIR, { recursive: true });
641
+ (0, import_node_fs4.writeFileSync)(CRASH_STATE_FILE, JSON.stringify(next, null, 2));
642
+ } catch (err) {
643
+ console.warn(`[entrypoint] could not persist boot-crash counter (continuing unprotected): ${err instanceof Error ? err.message : String(err)}`);
644
+ }
645
+ return next.count;
646
+ }
647
+ function clearCrashCount() {
648
+ try {
649
+ if ((0, import_node_fs4.existsSync)(CRASH_STATE_FILE)) (0, import_node_fs4.writeFileSync)(CRASH_STATE_FILE, JSON.stringify({ count: 0, firstAt: "", lastAt: "" }));
650
+ } catch {
651
+ }
652
+ }
653
+ async function parkAfterCrashLoop(count) {
654
+ const lines = [
655
+ "==================================================================",
656
+ `[entrypoint] CRASH-LOOP HALT \u2014 ${count} consecutive failed boots (cap ${MAX_BOOT_CRASHES}).`,
657
+ "[entrypoint] Refusing to restart again. Container is now PARKED (idle, not",
658
+ "[entrypoint] exiting) so it stops thrashing CF APIs and logs. Fix the root",
659
+ "[entrypoint] cause, clear the counter, then restart the container:",
660
+ `[entrypoint] rm ${CRASH_STATE_FILE} && docker restart <container>`,
661
+ "=================================================================="
662
+ ];
663
+ for (const l of lines) console.error(l);
664
+ for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
665
+ process.on(sig, () => {
666
+ console.log(`[entrypoint] received ${sig} while parked \u2014 exiting`);
667
+ process.exit(0);
668
+ });
669
+ }
670
+ setInterval(() => {
671
+ console.error(`[entrypoint] still parked after crash-loop halt \u2014 clear ${CRASH_STATE_FILE} and restart to retry`);
672
+ }, 15 * 6e4);
673
+ await new Promise(() => {
674
+ });
675
+ throw new Error("unreachable");
676
+ }
598
677
  async function setupFromCloud() {
599
678
  const pat = requireEnv("LS_PAT");
600
679
  const orgSlug = requireEnv("LS_ORG_SLUG");
@@ -885,6 +964,12 @@ function spawnServiceGroup(services) {
885
964
  }).finally(removeSignals);
886
965
  }
887
966
  async function main() {
967
+ const priorCrashes = readCrashState()?.count ?? 0;
968
+ if (priorCrashes >= MAX_BOOT_CRASHES) await parkAfterCrashLoop(priorCrashes);
969
+ const bootAttempt = bumpCrashCount();
970
+ if (bootAttempt > 1) {
971
+ console.warn(`[entrypoint] boot attempt ${bootAttempt}/${MAX_BOOT_CRASHES} \u2014 prior boot(s) crashed before becoming stable`);
972
+ }
888
973
  for (const k of REQUIRED_ENV) requireEnv(k);
889
974
  const bundle = await setupFromCloud();
890
975
  setupClaudeCredentials();
@@ -916,10 +1001,17 @@ async function main() {
916
1001
  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.`);
917
1002
  }
918
1003
  }
1004
+ const stableTimer = setTimeout(() => {
1005
+ clearCrashCount();
1006
+ console.log(`[entrypoint] services stable for ${Math.round(STABLE_AFTER_MS / 1e3)}s \u2014 boot-crash counter cleared`);
1007
+ }, STABLE_AFTER_MS);
1008
+ stableTimer.unref?.();
919
1009
  try {
920
1010
  await spawnServiceGroup(services);
1011
+ clearTimeout(stableTimer);
921
1012
  process.exit(0);
922
1013
  } catch (err) {
1014
+ clearTimeout(stableTimer);
923
1015
  console.error(`[entrypoint] ${err instanceof Error ? err.message : String(err)}`);
924
1016
  process.exit(1);
925
1017
  }
File without changes
File without changes
@@ -61,6 +61,17 @@ async function cf(opts) {
61
61
  function isNotFound(env) {
62
62
  return !env.success && (env.errors ?? []).some((e) => e.code === 7003 || e.code === 1001 || e.code === 81044);
63
63
  }
64
+ async function findTunnelByName(input) {
65
+ const q = new URLSearchParams({ name: input.tunnelName, is_deleted: "false" }).toString();
66
+ const res = await cf({
67
+ apiToken: input.apiToken,
68
+ method: "GET",
69
+ path: `/accounts/${input.accountId}/cfd_tunnel?${q}`
70
+ });
71
+ if (!res.success || !Array.isArray(res.result)) return null;
72
+ const live = res.result.find((t) => t.name === input.tunnelName && !t.deleted_at);
73
+ return live?.id ?? null;
74
+ }
64
75
  function loadState(path) {
65
76
  if (!(0, import_node_fs.existsSync)(path)) return null;
66
77
  try {
@@ -92,16 +103,26 @@ async function ensureTunnel(input, knownTunnelId) {
92
103
  throw new Error(`[cf] tunnel GET failed: ${JSON.stringify(got.errors)}`);
93
104
  }
94
105
  }
106
+ const existing = await findTunnelByName(input);
107
+ if (existing) {
108
+ console.log(`[cf] adopted existing tunnel "${input.tunnelName}" (${existing}) \u2014 local state was missing`);
109
+ return existing;
110
+ }
95
111
  const created = await cf({
96
112
  apiToken: input.apiToken,
97
113
  method: "POST",
98
114
  path: `/accounts/${input.accountId}/cfd_tunnel`,
99
115
  body: { name: input.tunnelName, config_src: "cloudflare" }
100
116
  });
101
- if (!created.success || !created.result) {
102
- throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
117
+ if (created.success && created.result) return created.result.id;
118
+ if ((created.errors ?? []).some((e) => e.code === 1013)) {
119
+ const adopted = await findTunnelByName(input);
120
+ if (adopted) {
121
+ console.log(`[cf] tunnel "${input.tunnelName}" already existed (1013) \u2014 adopted ${adopted}`);
122
+ return adopted;
123
+ }
103
124
  }
104
- return created.result.id;
125
+ throw new Error(`[cf] tunnel create failed: ${JSON.stringify(created.errors)}`);
105
126
  }
106
127
  async function fetchConnectorToken(input, tunnelId) {
107
128
  const res = await cf({
@@ -587,7 +608,7 @@ var require_package = __commonJS({
587
608
  "package.json"(exports2, module2) {
588
609
  module2.exports = {
589
610
  name: "@launchsecure/launch-kit",
590
- version: "0.0.36",
611
+ version: "0.0.37",
591
612
  description: "LaunchSecure toolkit \u2014 launch-sequencer (pipeline runner + terminal bridge), launch-radar (feedback webhook receiver), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup). launch-pod is the container image these run inside.",
592
613
  license: "MIT",
593
614
  author: "LaunchSecure - AutomateWithUs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchsecure/launch-kit",
3
- "version": "0.0.36",
3
+ "version": "0.0.37",
4
4
  "description": "LaunchSecure toolkit — 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.",
5
5
  "license": "MIT",
6
6
  "author": "LaunchSecure - AutomateWithUs",
@@ -59,24 +59,6 @@
59
59
  "launch-rover": "./dist/server/rover-entry.js",
60
60
  "launch-bot": "./dist/server/launch-bot-entry.js"
61
61
  },
62
- "scripts": {
63
- "build": "pnpm build:client && pnpm build:chart-client && pnpm build:deck-client && pnpm build:council-client && pnpm build:beacon && pnpm build:server",
64
- "build:beacon": "vite build --config vite.beacon.config.ts && tsc -p tsconfig.beacon.json --emitDeclarationOnly --outDir dist/beacon/types",
65
- "test:beacon": "vitest run --config vite.beacon.config.ts",
66
- "test:radar": "vitest run --config vite.radar.config.ts",
67
- "test:chart": "vitest run --config vite.chart.test.config.ts",
68
- "build:deck-client": "vite build --config vite.deck.config.ts",
69
- "build:council-client": "vite build --config vite.council.config.ts",
70
- "build:client": "vite build",
71
- "build:chart-client": "vite build --config vite.chart.config.ts",
72
- "build:server": "esbuild src/server/cli.ts src/server/fb-wizard.ts src/server/graph-mcp-entry.ts src/server/chart-serve.ts src/server/deck-mcp-entry.ts src/server/deck-serve.ts src/server/council-entry.ts src/server/council-serve.ts src/server/recall-entry.ts src/server/init-entry.ts src/server/orbit-entry.ts src/server/course-entry.ts src/server/beacon-monitor-entry.ts src/server/parse-worker-entry.ts src/server/radar-teardown-entry.ts src/server/radar-docker-init-entry.ts src/server/launch-radar-entry.ts src/server/rover-entry.ts src/server/launch-bot-entry.ts --bundle --platform=node --target=node18 --outdir=dist/server --external:node-pty --external:ws --external:typescript --external:web-tree-sitter --external:tree-sitter-typescript --external:cloudflared --external:pg --external:pg-native --external:pgsql-parser --external:libpg-query && rm -rf dist/server/public && cp -r ../claude-code-web/src/public dist/server/public && rm -rf dist/server/graph/queries && mkdir -p dist/server/graph && cp -r src/server/graph/queries dist/server/graph/queries",
73
- "dev:client": "vite",
74
- "dev:deck-serve": "cd ../.. && tsx watch packages/cli/src/server/deck-mcp-entry.ts serve",
75
- "dev:chart": "pnpm build:server && pnpm build:chart-client && node dist/server/graph-mcp-entry.js serve",
76
- "dev:server": "pnpm build:server && node dist/server/cli.js",
77
- "dev": "pnpm build:server && concurrently -k -n client,server -c cyan,magenta \"vite\" \"node dist/server/cli.js\"",
78
- "prepublishOnly": "pnpm build"
79
- },
80
62
  "files": [
81
63
  "dist",
82
64
  "prompts",
@@ -97,8 +79,6 @@
97
79
  "ws": "^8.18.0"
98
80
  },
99
81
  "devDependencies": {
100
- "@launchsecure/claude-code-web": "workspace:*",
101
- "@launchsecure/ui": "workspace:*",
102
82
  "@types/node": "^20.0.0",
103
83
  "@types/pg": "^8.11.10",
104
84
  "@types/react": "^18.3.12",
@@ -122,6 +102,25 @@
122
102
  "react-router-dom": "^6.28.0",
123
103
  "tailwindcss": "^3.4.19",
124
104
  "vite": "^5.4.11",
125
- "vitest": "^1.6.0"
105
+ "vitest": "^1.6.0",
106
+ "@launchsecure/claude-code-web": "0.0.1",
107
+ "@launchsecure/ui": "0.0.1"
108
+ },
109
+ "scripts": {
110
+ "build": "pnpm build:client && pnpm build:chart-client && pnpm build:deck-client && pnpm build:council-client && pnpm build:beacon && pnpm build:server",
111
+ "build:beacon": "vite build --config vite.beacon.config.ts && tsc -p tsconfig.beacon.json --emitDeclarationOnly --outDir dist/beacon/types",
112
+ "test:beacon": "vitest run --config vite.beacon.config.ts",
113
+ "test:radar": "vitest run --config vite.radar.config.ts",
114
+ "test:chart": "vitest run --config vite.chart.test.config.ts",
115
+ "build:deck-client": "vite build --config vite.deck.config.ts",
116
+ "build:council-client": "vite build --config vite.council.config.ts",
117
+ "build:client": "vite build",
118
+ "build:chart-client": "vite build --config vite.chart.config.ts",
119
+ "build:server": "esbuild src/server/cli.ts src/server/fb-wizard.ts src/server/graph-mcp-entry.ts src/server/chart-serve.ts src/server/deck-mcp-entry.ts src/server/deck-serve.ts src/server/council-entry.ts src/server/council-serve.ts src/server/recall-entry.ts src/server/init-entry.ts src/server/orbit-entry.ts src/server/course-entry.ts src/server/beacon-monitor-entry.ts src/server/parse-worker-entry.ts src/server/radar-teardown-entry.ts src/server/radar-docker-init-entry.ts src/server/launch-radar-entry.ts src/server/rover-entry.ts src/server/launch-bot-entry.ts --bundle --platform=node --target=node18 --outdir=dist/server --external:node-pty --external:ws --external:typescript --external:web-tree-sitter --external:tree-sitter-typescript --external:cloudflared --external:pg --external:pg-native --external:pgsql-parser --external:libpg-query && rm -rf dist/server/public && cp -r ../claude-code-web/src/public dist/server/public && rm -rf dist/server/graph/queries && mkdir -p dist/server/graph && cp -r src/server/graph/queries dist/server/graph/queries",
120
+ "dev:client": "vite",
121
+ "dev:deck-serve": "cd ../.. && tsx watch packages/cli/src/server/deck-mcp-entry.ts serve",
122
+ "dev:chart": "pnpm build:server && pnpm build:chart-client && node dist/server/graph-mcp-entry.js serve",
123
+ "dev:server": "pnpm build:server && node dist/server/cli.js",
124
+ "dev": "pnpm build:server && concurrently -k -n client,server -c cyan,magenta \"vite\" \"node dist/server/cli.js\""
126
125
  }
127
- }
126
+ }
@@ -80,6 +80,11 @@ Run `BUILD_CMD` (the project's real build, so the check matches CI). Skip if `--
80
80
 
81
81
  **DB-safety guard (only when the build touches a DB).** If the detected `BUILD_CMD` runs migrations as part of the build (e.g. a Node build script that chains `prisma migrate deploy`, or any build that applies `MIGRATION_TOOL` migrations — detect by scanning the build script / target), then a prod DB connection string must NOT be used. If the relevant connection env (`DATABASE_URL`, `DB_URL`, etc.) looks production-y (matches `neon.tech`, `rds.amazonaws.com`, `supabase.co`, a `vercel.app` proxy host, etc.), refuse the build step: `⚠ build skipped — build applies migrations and DATABASE_URL looks production-y (<host>); re-run with a dev DB or pass --skip=build`. If the build does NOT touch a DB (most stacks), skip this guard.
82
82
 
83
+ **Build-cache hygiene (avoid stale-cache false positives).** Incremental-cache frameworks reuse a build cache across runs, and a cache left over from a prior build/dev run on a different commit can make a *clean-tree* build fail with a stale-manifest error even though CI (which always builds clean) succeeds. The classic signature is Next.js's `PageNotFoundError: Cannot find module for page: <route>` / `Failed to collect page data for <route>` (often `ENOENT` pointing into `.next/`) — observed on `.well-known/*` and other dynamic routes that build fine in CI. To keep the build check faithful to CI:
84
+
85
+ 1. **Clear the framework cache before building** when the project uses a cache-based framework. Remove only the framework's own *regenerable, gitignored* cache dir if it exists — `.next/` (Next.js) · `.nuxt/` + `.output/` (Nuxt) · `.svelte-kit/` (SvelteKit) · `.astro/` (Astro) · `.turbo/` (Turborepo/Turbopack). **Never** remove `dist/`, `build/`, `out/`, `node_modules/`, or anything git-tracked — confirm the dir is gitignored before deleting. Non-cache stacks: skip this, there is nothing to clear.
86
+ 2. **Retry-once backstop.** If a build still fails AND the stderr matches a stale-cache signature (`Cannot find module for page`, `Failed to collect page data`, or an `ENOENT` referencing the cache dir), clear the cache (step 1) and re-run `BUILD_CMD` exactly once. Report ✗ only if the clean rebuild also fails; if the rebuild passes, report ✓ and note `(first attempt failed on a stale-cache artifact — passed clean)`. This prevents a regenerable-cache quirk from producing a NO-GO.
87
+
83
88
  ### 5. schema_drift (only when MIGRATION_TOOL supports it)
84
89
 
85
90
  Schema-vs-migrations consistency, using the tool's own drift command:
File without changes