@launchsecure/launch-kit 0.0.36 → 0.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chart-client/assets/index-B6rR0CWx.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-D6uX1lQe.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/council-client/assets/index-CleYLarJ.css +1 -0
- package/dist/council-client/index.html +2 -2
- package/dist/deck-client/assets/{_baseUniq-BiVx0WO_.js → _baseUniq-CgW32Gdk.js} +1 -1
- package/dist/deck-client/assets/{arc-DGMkiEzS.js → arc-D-Mg9gvM.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-Y2WRmHtk.js → architectureDiagram-Q4EWVU46-CdTsXsgl.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-_Lbfu5BQ.js → blockDiagram-DXYQGD6D-mwTneYyB.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CTqpYTBX.js → c4Diagram-AHTNJAMY-C4R8IbjO.js} +1 -1
- package/dist/deck-client/assets/channel-CuSee7GO.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-liEIbPHs.js → chunk-4BX2VUAB-ZWuRIUwb.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-CCc6lYvL.js → chunk-4TB4RGXK-PNHX10sF.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-D02jJUR2.js → chunk-55IACEB6-CD9MUgPr.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-BFmGMbLD.js → chunk-EDXVE4YY-C_CpORb3.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-6wFLOVcJ.js → chunk-FMBD7UC4-Bg5RoVC-.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-Bnr8RiBf.js → chunk-OYMX7WX6-DhTwgQwd.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-Ct82MksJ.js → chunk-QZHKN3VN-C5VLMaFa.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-BXmN1diQ.js → chunk-YZCP3GAM-NAGHy4Sr.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-_kTisqzs.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-_kTisqzs.js +1 -0
- package/dist/deck-client/assets/clone-kb3zkY60.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-CmQCT-mH.js → cose-bilkent-S5V4N54A-CpUczjZk.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-DDdSa9EX.js → dagre-KV5264BT-BOvb07MG.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-Bccks2xJ.js → diagram-5BDNPKRD-BPxwTiC-.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-CPPNgxmQ.js → diagram-G4DWMVQ6-Dz_gsHgx.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-KrD300pS.js → diagram-MMDJMWI5-B7z-oVTW.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-DefnLuQf.js → diagram-TYMM5635-CAIAglLQ.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-DI9FfnFP.js → erDiagram-SMLLAGMA-BiViTWF3.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-twKyd3Fx.js → flowDiagram-DWJPFMVM-DYVemp0H.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-Wau3jhBr.js → ganttDiagram-T4ZO3ILL-Chc1Iyu1.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-D9GgYXwb.js → gitGraphDiagram-UUTBAWPF-B7eFgaj5.js} +1 -1
- package/dist/deck-client/assets/{graph-BhNLzyXS.js → graph-CKaIoNwb.js} +1 -1
- package/dist/deck-client/assets/index-55P73aS_.css +1 -0
- package/dist/deck-client/assets/{index-BtQBaQ7s.js → index-BRawc7RA.js} +49 -48
- package/dist/deck-client/assets/{infoDiagram-42DDH7IO-TylGlSG-.js → infoDiagram-42DDH7IO-BxsVq7vO.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DAT8icpg.js → ishikawaDiagram-UXIWVN3A-DAM7vPwa.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-D3v_XL72.js → journeyDiagram-VCZTEJTY-Xe20Nf7R.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-DNUOBiNr.js → kanban-definition-6JOO6SKY-DS8YguzB.js} +1 -1
- package/dist/deck-client/assets/{layout-COfodgwF.js → layout-DKMBpzR-.js} +1 -1
- package/dist/deck-client/assets/{linear-DmTsuIvK.js → linear-DTNtBg5h.js} +1 -1
- package/dist/deck-client/assets/{min-BW1F7i1D.js → min-C4DrxCcA.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CErFzKWl.js → mindmap-definition-QFDTVHPH-B4nEtsw5.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DW5F757o.js → pieDiagram-DEJITSTG-BzHdGNu5.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-B1S2-TfI.js → quadrantDiagram-34T5L4WZ-CaX0SD4-.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BY5BAR-5.js → requirementDiagram-MS252O5E-QeG4p2ni.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-CE1Cp9HS.js → sankeyDiagram-XADWPNL6-BoAwgAj-.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-IaHnbKye.js → sequenceDiagram-FGHM5R23-Dn4pYYgu.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-CwPJm9hU.js → stateDiagram-FHFEXIEX-Is6KRmQV.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-Cy45Ttqq.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DVFGGSgN.js → timeline-definition-GMOUNBTQ-v64IZGuY.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-C1194MJi.js → vennDiagram-DHZGUBPP-noh9eouF.js} +1 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-cJ_1is2S.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-hpwdFfGj.js → wardleyDiagram-NUSXRM2D-DxR4j737.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-DYkotwy8.js → xychartDiagram-5P7HB3ND-B26vodaL.js} +1 -1
- package/dist/deck-client/index.html +2 -2
- package/dist/server/cli.js +25 -2
- package/dist/server/council-entry.js +86 -2
- package/dist/server/council-serve.js +81 -2
- package/dist/server/deck-mcp-entry.js +449 -68
- package/dist/server/deck-serve.js +411 -42
- package/dist/server/fb-wizard.js +0 -0
- package/dist/server/init-entry.js +147 -18
- package/dist/server/radar-docker-init-entry.js +139 -17
- package/dist/server/radar-entrypoint-entry.js +0 -0
- package/dist/server/radar-teardown-entry.js +0 -0
- package/dist/server/rover-entry.js +25 -4
- package/package.json +22 -23
- package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check/SKILL.md +5 -0
- package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +0 -0
- package/scaffolds/recall-hook/scripts/ensure-recall.sh +0 -0
- package/dist/chart-client/assets/index-DpKO9p0s.css +0 -1
- package/dist/client/assets/index-Dv6dD2zY.css +0 -32
- package/dist/council-client/assets/index-AqQ9Sei6.css +0 -1
- package/dist/deck-client/assets/channel-DB6LxW_l.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-g944ZyG8.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-g944ZyG8.js +0 -1
- package/dist/deck-client/assets/clone-DiIRH1pI.js +0 -1
- package/dist/deck-client/assets/index-B-YQq5b5.css +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-DQYa2M1q.js +0 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-CHZiUbBa.js +0 -162
- /package/dist/chart-client/assets/{index-DFu2xIrM.js → index-C_xCi3gW.js} +0 -0
- /package/dist/client/assets/{index-Cbw6bVdx.js → index-CRecYFUA.js} +0 -0
- /package/dist/council-client/assets/{index-CAsmGTzg.js → index-DO-Vn15O.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 (
|
|
658
|
-
|
|
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
|
-
|
|
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({
|
|
@@ -836,7 +857,13 @@ async function ensureAccessIdp(input) {
|
|
|
836
857
|
return created.result.id;
|
|
837
858
|
}
|
|
838
859
|
async function ensureAccessApp(input) {
|
|
839
|
-
const
|
|
860
|
+
const { service } = input;
|
|
861
|
+
const appDomain = service.path ? `${service.hostname}${service.path}` : service.hostname;
|
|
862
|
+
const policy = service.bypass ? {
|
|
863
|
+
name: "launch-kit-public-bypass",
|
|
864
|
+
decision: "bypass",
|
|
865
|
+
include: [{ everyone: {} }]
|
|
866
|
+
} : {
|
|
840
867
|
name: "launch-kit-org-allow",
|
|
841
868
|
decision: "allow",
|
|
842
869
|
include: [
|
|
@@ -849,12 +876,17 @@ async function ensureAccessApp(input) {
|
|
|
849
876
|
}
|
|
850
877
|
]
|
|
851
878
|
};
|
|
852
|
-
const body = {
|
|
853
|
-
name: `launch-kit ${
|
|
854
|
-
domain:
|
|
879
|
+
const body = service.bypass ? {
|
|
880
|
+
name: `launch-kit ${appDomain} (public)`,
|
|
881
|
+
domain: appDomain,
|
|
882
|
+
type: "self_hosted",
|
|
883
|
+
policies: [policy]
|
|
884
|
+
} : {
|
|
885
|
+
name: `launch-kit ${appDomain}`,
|
|
886
|
+
domain: appDomain,
|
|
855
887
|
type: "self_hosted",
|
|
856
888
|
// Bot terminal = RCE surface → short session. Read portals = a workday.
|
|
857
|
-
session_duration:
|
|
889
|
+
session_duration: service.strict ? "30m" : "24h",
|
|
858
890
|
allowed_idps: [input.idpId],
|
|
859
891
|
auto_redirect_to_identity: true,
|
|
860
892
|
policies: [policy]
|
|
@@ -865,7 +897,7 @@ async function ensureAccessApp(input) {
|
|
|
865
897
|
path: `/accounts/${input.accountId}/access/apps`
|
|
866
898
|
});
|
|
867
899
|
if (!list.success) fail(list, "list access apps");
|
|
868
|
-
const existing = (list.result ?? []).find((a) => a.domain ===
|
|
900
|
+
const existing = (list.result ?? []).find((a) => a.domain === appDomain);
|
|
869
901
|
if (existing) {
|
|
870
902
|
const upd = await cf2({
|
|
871
903
|
apiToken: input.apiToken,
|
|
@@ -873,7 +905,7 @@ async function ensureAccessApp(input) {
|
|
|
873
905
|
path: `/accounts/${input.accountId}/access/apps/${existing.id}`,
|
|
874
906
|
body
|
|
875
907
|
});
|
|
876
|
-
if (!upd.success || !upd.result) fail(upd, `update access app ${
|
|
908
|
+
if (!upd.success || !upd.result) fail(upd, `update access app ${appDomain}`);
|
|
877
909
|
return upd.result.id;
|
|
878
910
|
}
|
|
879
911
|
const created = await cf2({
|
|
@@ -882,7 +914,7 @@ async function ensureAccessApp(input) {
|
|
|
882
914
|
path: `/accounts/${input.accountId}/access/apps`,
|
|
883
915
|
body
|
|
884
916
|
});
|
|
885
|
-
if (!created.success || !created.result) fail(created, `create access app ${
|
|
917
|
+
if (!created.success || !created.result) fail(created, `create access app ${appDomain}`);
|
|
886
918
|
return created.result.id;
|
|
887
919
|
}
|
|
888
920
|
async function provisionAccess(input) {
|
|
@@ -901,7 +933,8 @@ async function provisionAccess(input) {
|
|
|
901
933
|
saveState2(input.stateFile, { idpId, accountId: input.accountId });
|
|
902
934
|
const appIds = {};
|
|
903
935
|
for (const service of input.services) {
|
|
904
|
-
|
|
936
|
+
const appDomain = service.path ? `${service.hostname}${service.path}` : service.hostname;
|
|
937
|
+
appIds[appDomain] = await ensureAccessApp({
|
|
905
938
|
apiToken: input.apiToken,
|
|
906
939
|
accountId: input.accountId,
|
|
907
940
|
idpId,
|
|
@@ -922,6 +955,15 @@ var init_cf_access = __esm({
|
|
|
922
955
|
}
|
|
923
956
|
});
|
|
924
957
|
|
|
958
|
+
// src/server/radar/registration.ts
|
|
959
|
+
var RECEIVER_PATH;
|
|
960
|
+
var init_registration = __esm({
|
|
961
|
+
"src/server/radar/registration.ts"() {
|
|
962
|
+
"use strict";
|
|
963
|
+
RECEIVER_PATH = "/api/radar/ingest";
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
|
|
925
967
|
// src/server/radar-docker-init-entry.ts
|
|
926
968
|
var radar_docker_init_entry_exports = {};
|
|
927
969
|
__export(radar_docker_init_entry_exports, {
|
|
@@ -942,6 +984,60 @@ function run2(cmd, args, stdio = "inherit") {
|
|
|
942
984
|
const r = (0, import_node_child_process2.spawnSync)(cmd, args, { stdio });
|
|
943
985
|
return r.status ?? 1;
|
|
944
986
|
}
|
|
987
|
+
function readCrashState() {
|
|
988
|
+
try {
|
|
989
|
+
const s = JSON.parse((0, import_node_fs4.readFileSync)(CRASH_STATE_FILE, "utf8"));
|
|
990
|
+
return typeof s?.count === "number" && s.count >= 0 ? s : null;
|
|
991
|
+
} catch {
|
|
992
|
+
return null;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function bumpCrashCount() {
|
|
996
|
+
const prev = readCrashState();
|
|
997
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
998
|
+
const next = {
|
|
999
|
+
count: (prev?.count ?? 0) + 1,
|
|
1000
|
+
firstAt: prev?.firstAt ?? now,
|
|
1001
|
+
lastAt: now
|
|
1002
|
+
};
|
|
1003
|
+
try {
|
|
1004
|
+
(0, import_node_fs4.mkdirSync)(LAUNCHPOD_DIR, { recursive: true });
|
|
1005
|
+
(0, import_node_fs4.writeFileSync)(CRASH_STATE_FILE, JSON.stringify(next, null, 2));
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
console.warn(`[entrypoint] could not persist boot-crash counter (continuing unprotected): ${err instanceof Error ? err.message : String(err)}`);
|
|
1008
|
+
}
|
|
1009
|
+
return next.count;
|
|
1010
|
+
}
|
|
1011
|
+
function clearCrashCount() {
|
|
1012
|
+
try {
|
|
1013
|
+
if ((0, import_node_fs4.existsSync)(CRASH_STATE_FILE)) (0, import_node_fs4.writeFileSync)(CRASH_STATE_FILE, JSON.stringify({ count: 0, firstAt: "", lastAt: "" }));
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
async function parkAfterCrashLoop(count) {
|
|
1018
|
+
const lines = [
|
|
1019
|
+
"==================================================================",
|
|
1020
|
+
`[entrypoint] CRASH-LOOP HALT \u2014 ${count} consecutive failed boots (cap ${MAX_BOOT_CRASHES}).`,
|
|
1021
|
+
"[entrypoint] Refusing to restart again. Container is now PARKED (idle, not",
|
|
1022
|
+
"[entrypoint] exiting) so it stops thrashing CF APIs and logs. Fix the root",
|
|
1023
|
+
"[entrypoint] cause, clear the counter, then restart the container:",
|
|
1024
|
+
`[entrypoint] rm ${CRASH_STATE_FILE} && docker restart <container>`,
|
|
1025
|
+
"=================================================================="
|
|
1026
|
+
];
|
|
1027
|
+
for (const l of lines) console.error(l);
|
|
1028
|
+
for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
1029
|
+
process.on(sig, () => {
|
|
1030
|
+
console.log(`[entrypoint] received ${sig} while parked \u2014 exiting`);
|
|
1031
|
+
process.exit(0);
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
setInterval(() => {
|
|
1035
|
+
console.error(`[entrypoint] still parked after crash-loop halt \u2014 clear ${CRASH_STATE_FILE} and restart to retry`);
|
|
1036
|
+
}, 15 * 6e4);
|
|
1037
|
+
await new Promise(() => {
|
|
1038
|
+
});
|
|
1039
|
+
throw new Error("unreachable");
|
|
1040
|
+
}
|
|
945
1041
|
async function setupFromCloud() {
|
|
946
1042
|
const pat = requireEnv("LS_PAT");
|
|
947
1043
|
const orgSlug = requireEnv("LS_ORG_SLUG");
|
|
@@ -1120,20 +1216,31 @@ async function maybeProvisionAccess(bundle, ingress) {
|
|
|
1120
1216
|
const skipped = [];
|
|
1121
1217
|
for (const [name, hostname] of Object.entries(ingress.hostnames)) {
|
|
1122
1218
|
const cfg = GATED_SERVICES[name];
|
|
1123
|
-
if (cfg)
|
|
1124
|
-
|
|
1219
|
+
if (!cfg) {
|
|
1220
|
+
skipped.push(name);
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
services.push({ hostname, strict: cfg.strict });
|
|
1224
|
+
for (const path6 of cfg.publicPaths ?? []) {
|
|
1225
|
+
services.push({ hostname, path: path6, bypass: true });
|
|
1226
|
+
}
|
|
1125
1227
|
}
|
|
1126
1228
|
if (skipped.length > 0) {
|
|
1127
1229
|
console.log(`[entrypoint] CF Access: leaving machine surface(s) ungated: ${skipped.join(", ")}`);
|
|
1128
1230
|
}
|
|
1129
1231
|
if (services.length === 0) {
|
|
1130
|
-
console.log("[entrypoint] CF Access: no human-facing service to gate (bot/preview not provisioned)");
|
|
1232
|
+
console.log("[entrypoint] CF Access: no human-facing service to gate (bot/preview/radar/deck not provisioned)");
|
|
1131
1233
|
return;
|
|
1132
1234
|
}
|
|
1133
1235
|
const serverUrl = process.env.LS_SERVER_URL ?? "https://launchsecure-v2.vercel.app";
|
|
1134
1236
|
const pat = requireEnv("LS_PAT");
|
|
1135
1237
|
const stateFile = "/workspace/.launchpod/launch-kit-access.json";
|
|
1136
|
-
|
|
1238
|
+
const gatedHosts = services.filter((s) => !s.bypass).map((s) => s.hostname);
|
|
1239
|
+
const bypassed = services.filter((s) => s.bypass).map((s) => `${s.hostname}${s.path ?? ""}`);
|
|
1240
|
+
console.log(`[entrypoint] gating ${gatedHosts.join(", ")} behind CF Access (IdP: ${serverUrl})`);
|
|
1241
|
+
if (bypassed.length > 0) {
|
|
1242
|
+
console.log(`[entrypoint] CF Access: public bypass for ${bypassed.join(", ")}`);
|
|
1243
|
+
}
|
|
1137
1244
|
const result = await provisionAccess({
|
|
1138
1245
|
apiToken: token,
|
|
1139
1246
|
accountId,
|
|
@@ -1226,6 +1333,12 @@ function spawnServiceGroup(services) {
|
|
|
1226
1333
|
}).finally(removeSignals);
|
|
1227
1334
|
}
|
|
1228
1335
|
async function main() {
|
|
1336
|
+
const priorCrashes = readCrashState()?.count ?? 0;
|
|
1337
|
+
if (priorCrashes >= MAX_BOOT_CRASHES) await parkAfterCrashLoop(priorCrashes);
|
|
1338
|
+
const bootAttempt = bumpCrashCount();
|
|
1339
|
+
if (bootAttempt > 1) {
|
|
1340
|
+
console.warn(`[entrypoint] boot attempt ${bootAttempt}/${MAX_BOOT_CRASHES} \u2014 prior boot(s) crashed before becoming stable`);
|
|
1341
|
+
}
|
|
1229
1342
|
for (const k of REQUIRED_ENV) requireEnv(k);
|
|
1230
1343
|
const bundle = await setupFromCloud();
|
|
1231
1344
|
setupClaudeCredentials();
|
|
@@ -1257,15 +1370,22 @@ async function main() {
|
|
|
1257
1370
|
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
1371
|
}
|
|
1259
1372
|
}
|
|
1373
|
+
const stableTimer = setTimeout(() => {
|
|
1374
|
+
clearCrashCount();
|
|
1375
|
+
console.log(`[entrypoint] services stable for ${Math.round(STABLE_AFTER_MS / 1e3)}s \u2014 boot-crash counter cleared`);
|
|
1376
|
+
}, STABLE_AFTER_MS);
|
|
1377
|
+
stableTimer.unref?.();
|
|
1260
1378
|
try {
|
|
1261
1379
|
await spawnServiceGroup(services);
|
|
1380
|
+
clearTimeout(stableTimer);
|
|
1262
1381
|
process.exit(0);
|
|
1263
1382
|
} catch (err) {
|
|
1383
|
+
clearTimeout(stableTimer);
|
|
1264
1384
|
console.error(`[entrypoint] ${err instanceof Error ? err.message : String(err)}`);
|
|
1265
1385
|
process.exit(1);
|
|
1266
1386
|
}
|
|
1267
1387
|
}
|
|
1268
|
-
var import_node_child_process2, import_node_fs4, import_node_path4, REQUIRED_ENV, GATED_SERVICES;
|
|
1388
|
+
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
1389
|
var init_radar_docker_init_entry = __esm({
|
|
1270
1390
|
"src/server/radar-docker-init-entry.ts"() {
|
|
1271
1391
|
"use strict";
|
|
@@ -1276,17 +1396,26 @@ var init_radar_docker_init_entry = __esm({
|
|
|
1276
1396
|
init_launch_kit_services();
|
|
1277
1397
|
init_cf_ingress();
|
|
1278
1398
|
init_cf_access();
|
|
1399
|
+
init_registration();
|
|
1279
1400
|
REQUIRED_ENV = [
|
|
1280
1401
|
"CLAUDE_CREDENTIALS_B64",
|
|
1281
1402
|
"LS_PAT",
|
|
1282
1403
|
"LS_ORG_SLUG",
|
|
1283
1404
|
"LS_PROJECT_SLUG"
|
|
1284
1405
|
];
|
|
1406
|
+
LAUNCHPOD_DIR = "/workspace/.launchpod";
|
|
1407
|
+
CRASH_STATE_FILE = (0, import_node_path4.join)(LAUNCHPOD_DIR, ".boot-crash.json");
|
|
1408
|
+
MAX_BOOT_CRASHES = 5;
|
|
1409
|
+
STABLE_AFTER_MS = 3e4;
|
|
1285
1410
|
GATED_SERVICES = {
|
|
1286
1411
|
// Claude web terminal — live drivable shell ⇒ RCE surface ⇒ short session.
|
|
1287
1412
|
bot: { strict: true },
|
|
1288
1413
|
// The user's own dev/preview server — a workday-length session is fine.
|
|
1289
|
-
preview: { strict: false }
|
|
1414
|
+
preview: { strict: false },
|
|
1415
|
+
// Radar: gate the UI, bypass the (HMAC-verified) webhook receiver path.
|
|
1416
|
+
radar: { strict: false, publicPaths: [RECEIVER_PATH] },
|
|
1417
|
+
// Deck: gate the whole host — its push is localhost-only, never hits the edge.
|
|
1418
|
+
deck: { strict: false }
|
|
1290
1419
|
};
|
|
1291
1420
|
if (!process.env.VITEST) {
|
|
1292
1421
|
main().catch((err) => {
|
|
@@ -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 (
|
|
327
|
-
|
|
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
|
-
|
|
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({
|
|
@@ -499,7 +520,13 @@ async function ensureAccessIdp(input) {
|
|
|
499
520
|
return created.result.id;
|
|
500
521
|
}
|
|
501
522
|
async function ensureAccessApp(input) {
|
|
502
|
-
const
|
|
523
|
+
const { service } = input;
|
|
524
|
+
const appDomain = service.path ? `${service.hostname}${service.path}` : service.hostname;
|
|
525
|
+
const policy = service.bypass ? {
|
|
526
|
+
name: "launch-kit-public-bypass",
|
|
527
|
+
decision: "bypass",
|
|
528
|
+
include: [{ everyone: {} }]
|
|
529
|
+
} : {
|
|
503
530
|
name: "launch-kit-org-allow",
|
|
504
531
|
decision: "allow",
|
|
505
532
|
include: [
|
|
@@ -512,12 +539,17 @@ async function ensureAccessApp(input) {
|
|
|
512
539
|
}
|
|
513
540
|
]
|
|
514
541
|
};
|
|
515
|
-
const body = {
|
|
516
|
-
name: `launch-kit ${
|
|
517
|
-
domain:
|
|
542
|
+
const body = service.bypass ? {
|
|
543
|
+
name: `launch-kit ${appDomain} (public)`,
|
|
544
|
+
domain: appDomain,
|
|
545
|
+
type: "self_hosted",
|
|
546
|
+
policies: [policy]
|
|
547
|
+
} : {
|
|
548
|
+
name: `launch-kit ${appDomain}`,
|
|
549
|
+
domain: appDomain,
|
|
518
550
|
type: "self_hosted",
|
|
519
551
|
// Bot terminal = RCE surface → short session. Read portals = a workday.
|
|
520
|
-
session_duration:
|
|
552
|
+
session_duration: service.strict ? "30m" : "24h",
|
|
521
553
|
allowed_idps: [input.idpId],
|
|
522
554
|
auto_redirect_to_identity: true,
|
|
523
555
|
policies: [policy]
|
|
@@ -528,7 +560,7 @@ async function ensureAccessApp(input) {
|
|
|
528
560
|
path: `/accounts/${input.accountId}/access/apps`
|
|
529
561
|
});
|
|
530
562
|
if (!list.success) fail(list, "list access apps");
|
|
531
|
-
const existing = (list.result ?? []).find((a) => a.domain ===
|
|
563
|
+
const existing = (list.result ?? []).find((a) => a.domain === appDomain);
|
|
532
564
|
if (existing) {
|
|
533
565
|
const upd = await cf2({
|
|
534
566
|
apiToken: input.apiToken,
|
|
@@ -536,7 +568,7 @@ async function ensureAccessApp(input) {
|
|
|
536
568
|
path: `/accounts/${input.accountId}/access/apps/${existing.id}`,
|
|
537
569
|
body
|
|
538
570
|
});
|
|
539
|
-
if (!upd.success || !upd.result) fail(upd, `update access app ${
|
|
571
|
+
if (!upd.success || !upd.result) fail(upd, `update access app ${appDomain}`);
|
|
540
572
|
return upd.result.id;
|
|
541
573
|
}
|
|
542
574
|
const created = await cf2({
|
|
@@ -545,7 +577,7 @@ async function ensureAccessApp(input) {
|
|
|
545
577
|
path: `/accounts/${input.accountId}/access/apps`,
|
|
546
578
|
body
|
|
547
579
|
});
|
|
548
|
-
if (!created.success || !created.result) fail(created, `create access app ${
|
|
580
|
+
if (!created.success || !created.result) fail(created, `create access app ${appDomain}`);
|
|
549
581
|
return created.result.id;
|
|
550
582
|
}
|
|
551
583
|
async function provisionAccess(input) {
|
|
@@ -564,7 +596,8 @@ async function provisionAccess(input) {
|
|
|
564
596
|
saveState2(input.stateFile, { idpId, accountId: input.accountId });
|
|
565
597
|
const appIds = {};
|
|
566
598
|
for (const service of input.services) {
|
|
567
|
-
|
|
599
|
+
const appDomain = service.path ? `${service.hostname}${service.path}` : service.hostname;
|
|
600
|
+
appIds[appDomain] = await ensureAccessApp({
|
|
568
601
|
apiToken: input.apiToken,
|
|
569
602
|
accountId: input.accountId,
|
|
570
603
|
idpId,
|
|
@@ -575,6 +608,9 @@ async function provisionAccess(input) {
|
|
|
575
608
|
return { idpId, authDomain, appIds };
|
|
576
609
|
}
|
|
577
610
|
|
|
611
|
+
// src/server/radar/registration.ts
|
|
612
|
+
var RECEIVER_PATH = "/api/radar/ingest";
|
|
613
|
+
|
|
578
614
|
// src/server/radar-docker-init-entry.ts
|
|
579
615
|
var REQUIRED_ENV = [
|
|
580
616
|
"CLAUDE_CREDENTIALS_B64",
|
|
@@ -595,6 +631,64 @@ function run(cmd, args, stdio = "inherit") {
|
|
|
595
631
|
const r = (0, import_node_child_process.spawnSync)(cmd, args, { stdio });
|
|
596
632
|
return r.status ?? 1;
|
|
597
633
|
}
|
|
634
|
+
var LAUNCHPOD_DIR = "/workspace/.launchpod";
|
|
635
|
+
var CRASH_STATE_FILE = (0, import_node_path4.join)(LAUNCHPOD_DIR, ".boot-crash.json");
|
|
636
|
+
var MAX_BOOT_CRASHES = 5;
|
|
637
|
+
var STABLE_AFTER_MS = 3e4;
|
|
638
|
+
function readCrashState() {
|
|
639
|
+
try {
|
|
640
|
+
const s = JSON.parse((0, import_node_fs4.readFileSync)(CRASH_STATE_FILE, "utf8"));
|
|
641
|
+
return typeof s?.count === "number" && s.count >= 0 ? s : null;
|
|
642
|
+
} catch {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function bumpCrashCount() {
|
|
647
|
+
const prev = readCrashState();
|
|
648
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
649
|
+
const next = {
|
|
650
|
+
count: (prev?.count ?? 0) + 1,
|
|
651
|
+
firstAt: prev?.firstAt ?? now,
|
|
652
|
+
lastAt: now
|
|
653
|
+
};
|
|
654
|
+
try {
|
|
655
|
+
(0, import_node_fs4.mkdirSync)(LAUNCHPOD_DIR, { recursive: true });
|
|
656
|
+
(0, import_node_fs4.writeFileSync)(CRASH_STATE_FILE, JSON.stringify(next, null, 2));
|
|
657
|
+
} catch (err) {
|
|
658
|
+
console.warn(`[entrypoint] could not persist boot-crash counter (continuing unprotected): ${err instanceof Error ? err.message : String(err)}`);
|
|
659
|
+
}
|
|
660
|
+
return next.count;
|
|
661
|
+
}
|
|
662
|
+
function clearCrashCount() {
|
|
663
|
+
try {
|
|
664
|
+
if ((0, import_node_fs4.existsSync)(CRASH_STATE_FILE)) (0, import_node_fs4.writeFileSync)(CRASH_STATE_FILE, JSON.stringify({ count: 0, firstAt: "", lastAt: "" }));
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function parkAfterCrashLoop(count) {
|
|
669
|
+
const lines = [
|
|
670
|
+
"==================================================================",
|
|
671
|
+
`[entrypoint] CRASH-LOOP HALT \u2014 ${count} consecutive failed boots (cap ${MAX_BOOT_CRASHES}).`,
|
|
672
|
+
"[entrypoint] Refusing to restart again. Container is now PARKED (idle, not",
|
|
673
|
+
"[entrypoint] exiting) so it stops thrashing CF APIs and logs. Fix the root",
|
|
674
|
+
"[entrypoint] cause, clear the counter, then restart the container:",
|
|
675
|
+
`[entrypoint] rm ${CRASH_STATE_FILE} && docker restart <container>`,
|
|
676
|
+
"=================================================================="
|
|
677
|
+
];
|
|
678
|
+
for (const l of lines) console.error(l);
|
|
679
|
+
for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
680
|
+
process.on(sig, () => {
|
|
681
|
+
console.log(`[entrypoint] received ${sig} while parked \u2014 exiting`);
|
|
682
|
+
process.exit(0);
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
setInterval(() => {
|
|
686
|
+
console.error(`[entrypoint] still parked after crash-loop halt \u2014 clear ${CRASH_STATE_FILE} and restart to retry`);
|
|
687
|
+
}, 15 * 6e4);
|
|
688
|
+
await new Promise(() => {
|
|
689
|
+
});
|
|
690
|
+
throw new Error("unreachable");
|
|
691
|
+
}
|
|
598
692
|
async function setupFromCloud() {
|
|
599
693
|
const pat = requireEnv("LS_PAT");
|
|
600
694
|
const orgSlug = requireEnv("LS_ORG_SLUG");
|
|
@@ -752,7 +846,11 @@ var GATED_SERVICES = {
|
|
|
752
846
|
// Claude web terminal — live drivable shell ⇒ RCE surface ⇒ short session.
|
|
753
847
|
bot: { strict: true },
|
|
754
848
|
// The user's own dev/preview server — a workday-length session is fine.
|
|
755
|
-
preview: { strict: false }
|
|
849
|
+
preview: { strict: false },
|
|
850
|
+
// Radar: gate the UI, bypass the (HMAC-verified) webhook receiver path.
|
|
851
|
+
radar: { strict: false, publicPaths: [RECEIVER_PATH] },
|
|
852
|
+
// Deck: gate the whole host — its push is localhost-only, never hits the edge.
|
|
853
|
+
deck: { strict: false }
|
|
756
854
|
};
|
|
757
855
|
async function registerOidcClient(serverUrl, pat, redirectUris) {
|
|
758
856
|
const res = await fetch(new URL("/api/rover/oidc-client", serverUrl), {
|
|
@@ -779,20 +877,31 @@ async function maybeProvisionAccess(bundle, ingress) {
|
|
|
779
877
|
const skipped = [];
|
|
780
878
|
for (const [name, hostname] of Object.entries(ingress.hostnames)) {
|
|
781
879
|
const cfg = GATED_SERVICES[name];
|
|
782
|
-
if (cfg)
|
|
783
|
-
|
|
880
|
+
if (!cfg) {
|
|
881
|
+
skipped.push(name);
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
services.push({ hostname, strict: cfg.strict });
|
|
885
|
+
for (const path of cfg.publicPaths ?? []) {
|
|
886
|
+
services.push({ hostname, path, bypass: true });
|
|
887
|
+
}
|
|
784
888
|
}
|
|
785
889
|
if (skipped.length > 0) {
|
|
786
890
|
console.log(`[entrypoint] CF Access: leaving machine surface(s) ungated: ${skipped.join(", ")}`);
|
|
787
891
|
}
|
|
788
892
|
if (services.length === 0) {
|
|
789
|
-
console.log("[entrypoint] CF Access: no human-facing service to gate (bot/preview not provisioned)");
|
|
893
|
+
console.log("[entrypoint] CF Access: no human-facing service to gate (bot/preview/radar/deck not provisioned)");
|
|
790
894
|
return;
|
|
791
895
|
}
|
|
792
896
|
const serverUrl = process.env.LS_SERVER_URL ?? "https://launchsecure-v2.vercel.app";
|
|
793
897
|
const pat = requireEnv("LS_PAT");
|
|
794
898
|
const stateFile = "/workspace/.launchpod/launch-kit-access.json";
|
|
795
|
-
|
|
899
|
+
const gatedHosts = services.filter((s) => !s.bypass).map((s) => s.hostname);
|
|
900
|
+
const bypassed = services.filter((s) => s.bypass).map((s) => `${s.hostname}${s.path ?? ""}`);
|
|
901
|
+
console.log(`[entrypoint] gating ${gatedHosts.join(", ")} behind CF Access (IdP: ${serverUrl})`);
|
|
902
|
+
if (bypassed.length > 0) {
|
|
903
|
+
console.log(`[entrypoint] CF Access: public bypass for ${bypassed.join(", ")}`);
|
|
904
|
+
}
|
|
796
905
|
const result = await provisionAccess({
|
|
797
906
|
apiToken: token,
|
|
798
907
|
accountId,
|
|
@@ -885,6 +994,12 @@ function spawnServiceGroup(services) {
|
|
|
885
994
|
}).finally(removeSignals);
|
|
886
995
|
}
|
|
887
996
|
async function main() {
|
|
997
|
+
const priorCrashes = readCrashState()?.count ?? 0;
|
|
998
|
+
if (priorCrashes >= MAX_BOOT_CRASHES) await parkAfterCrashLoop(priorCrashes);
|
|
999
|
+
const bootAttempt = bumpCrashCount();
|
|
1000
|
+
if (bootAttempt > 1) {
|
|
1001
|
+
console.warn(`[entrypoint] boot attempt ${bootAttempt}/${MAX_BOOT_CRASHES} \u2014 prior boot(s) crashed before becoming stable`);
|
|
1002
|
+
}
|
|
888
1003
|
for (const k of REQUIRED_ENV) requireEnv(k);
|
|
889
1004
|
const bundle = await setupFromCloud();
|
|
890
1005
|
setupClaudeCredentials();
|
|
@@ -916,10 +1031,17 @@ async function main() {
|
|
|
916
1031
|
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
1032
|
}
|
|
918
1033
|
}
|
|
1034
|
+
const stableTimer = setTimeout(() => {
|
|
1035
|
+
clearCrashCount();
|
|
1036
|
+
console.log(`[entrypoint] services stable for ${Math.round(STABLE_AFTER_MS / 1e3)}s \u2014 boot-crash counter cleared`);
|
|
1037
|
+
}, STABLE_AFTER_MS);
|
|
1038
|
+
stableTimer.unref?.();
|
|
919
1039
|
try {
|
|
920
1040
|
await spawnServiceGroup(services);
|
|
1041
|
+
clearTimeout(stableTimer);
|
|
921
1042
|
process.exit(0);
|
|
922
1043
|
} catch (err) {
|
|
1044
|
+
clearTimeout(stableTimer);
|
|
923
1045
|
console.error(`[entrypoint] ${err instanceof Error ? err.message : String(err)}`);
|
|
924
1046
|
process.exit(1);
|
|
925
1047
|
}
|
|
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 (
|
|
102
|
-
|
|
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
|
-
|
|
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.
|
|
611
|
+
version: "0.0.38",
|
|
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",
|