@launchsecure/launch-kit 0.0.41 → 0.0.43
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-DOKsFe5i.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-BqiDfvZi.js +294 -0
- package/dist/client/assets/index-Mewz-s77.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/council-client/assets/index-o_3y7Z0J.css +1 -0
- package/dist/council-client/index.html +2 -2
- package/dist/deck-client/assets/{_baseUniq-mvYvzeEJ.js → _baseUniq-C6w7kg8x.js} +1 -1
- package/dist/deck-client/assets/{arc-CX4ylnp2.js → arc-Cx9pT3Nn.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-BkR-5IRK.js → architectureDiagram-Q4EWVU46-BITSj3vA.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-DVNQht7c.js → blockDiagram-DXYQGD6D-BehOFuwh.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-Cbq1rlG8.js → c4Diagram-AHTNJAMY-BZTYM4na.js} +1 -1
- package/dist/deck-client/assets/channel-Cw2WDt9a.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-D58Co4lU.js → chunk-4BX2VUAB-CCUx5CTd.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-BYvhTm3d.js → chunk-4TB4RGXK-UDZXXga6.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-oWukUhYg.js → chunk-55IACEB6-CfcU6PIW.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-Cm58kVnZ.js → chunk-EDXVE4YY-BK6F5Fof.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-Dg-i7kzi.js → chunk-FMBD7UC4-C-2idlFB.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-C72wigPl.js → chunk-OYMX7WX6-D6hBkYLP.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-CLgeuAKw.js → chunk-QZHKN3VN-DixNpysA.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-HDDlJ5oI.js → chunk-YZCP3GAM-Cd3pNBtQ.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-JLUXVCUr.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-JLUXVCUr.js +1 -0
- package/dist/deck-client/assets/clone-H0XCnSb6.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-CUXQKg2M.js → cose-bilkent-S5V4N54A-OF3JWdEt.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-C5M-fVDc.js → dagre-KV5264BT-Bqu-qcv4.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-CcVsQ0S8.js → diagram-5BDNPKRD--0eHmUBS.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-DJswXyep.js → diagram-G4DWMVQ6-nss6oL20.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-CGT76fm1.js → diagram-MMDJMWI5-D_gSGnLR.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-BBsYUNN6.js → diagram-TYMM5635-BIt-P6Pk.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-DKWYEHQS.js → erDiagram-SMLLAGMA-Bi-E4KQm.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-DLuDYIKT.js → flowDiagram-DWJPFMVM-DMJCvLMA.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-B19b6Qtj.js → ganttDiagram-T4ZO3ILL-C3xgEoPD.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-BYLAfYVS.js → gitGraphDiagram-UUTBAWPF-CD0BEGAW.js} +1 -1
- package/dist/deck-client/assets/{graph-CfzQUfPh.js → graph-Dtsd9Jwe.js} +1 -1
- package/dist/deck-client/assets/index-C6YxyZay.css +1 -0
- package/dist/deck-client/assets/{index-DlwdTgE_.js → index-TFX8vtTG.js} +2 -2
- package/dist/deck-client/assets/{infoDiagram-42DDH7IO-Dp3mUA9c.js → infoDiagram-42DDH7IO-7IcQYqe_.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BhrNX_jI.js → ishikawaDiagram-UXIWVN3A-DsCEbx3u.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-B5lJI492.js → journeyDiagram-VCZTEJTY-1mP2JwCk.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-D9-lmhQf.js → kanban-definition-6JOO6SKY-vT0Xrqh9.js} +1 -1
- package/dist/deck-client/assets/{layout-CfIe_Su8.js → layout-Cw4rS2pn.js} +1 -1
- package/dist/deck-client/assets/{linear-09ZFRoh_.js → linear-CzOjL-Ih.js} +1 -1
- package/dist/deck-client/assets/{mermaid.core-BaQyIOvj.js → mermaid.core-DYi3A-qK.js} +4 -4
- package/dist/deck-client/assets/{min-CYwCzYaL.js → min-DstloRoL.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CouFxf6C.js → mindmap-definition-QFDTVHPH-D-cCX2d2.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DMB1ufC0.js → pieDiagram-DEJITSTG-BqW2NTmy.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-CBiOKudN.js → quadrantDiagram-34T5L4WZ-DbJoWA8f.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-BMc3GJkx.js → requirementDiagram-MS252O5E-DQrUiz_d.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-CxACUncm.js → sankeyDiagram-XADWPNL6-kB7PZc3g.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-Ch-P3Mzc.js → sequenceDiagram-FGHM5R23-CpyVu1TN.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-Cy8n7Yzk.js → stateDiagram-FHFEXIEX-CjqQcnty.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-tfMSn8xx.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-C2V4sSkm.js → timeline-definition-GMOUNBTQ-B2PAO9bk.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-YOqt4VbE.js → vennDiagram-DHZGUBPP-C0G3ItCr.js} +1 -1
- package/dist/deck-client/assets/{wardley-RL74JXVD-Bxo5x40D.js → wardley-RL74JXVD-B0TVaOmp.js} +1 -1
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-DW9SOqbx.js → wardleyDiagram-NUSXRM2D-B-qtbNZe.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-D-rZvZOL.js → xychartDiagram-5P7HB3ND-41kcBoBE.js} +1 -1
- package/dist/deck-client/index.html +2 -2
- package/dist/server/cli.js +484 -77
- package/dist/server/deck-mcp-entry.js +137 -2
- package/dist/server/deck-serve.js +93 -1
- package/dist/server/init-entry.js +47 -21
- package/dist/server/launch-bot-entry.js +38 -4
- package/dist/server/radar-docker-init-entry.js +46 -20
- package/dist/server/rover-entry.js +6047 -5155
- package/package.json +1 -1
- package/dist/chart-client/assets/index-Dd6IotOZ.css +0 -1
- package/dist/client/assets/index-BoIjawzY.js +0 -294
- package/dist/client/assets/index-DE0uje6k.css +0 -32
- package/dist/council-client/assets/index-CGYusOCK.css +0 -1
- package/dist/deck-client/assets/channel-B9GC-CLn.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CFBvYQ9j.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CFBvYQ9j.js +0 -1
- package/dist/deck-client/assets/clone-n-WQlAGe.js +0 -1
- package/dist/deck-client/assets/index-evAPhGvM.css +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-C14VKCzi.js +0 -1
- /package/dist/chart-client/assets/{index-CrYM1-ac.js → index-DJQYgFcp.js} +0 -0
- /package/dist/council-client/assets/{index-DkTFX53U.js → index-Wn06apTg.js} +0 -0
|
@@ -1462,6 +1462,24 @@ async function updateDeck(commentId, input) {
|
|
|
1462
1462
|
async function deleteDeck(commentId) {
|
|
1463
1463
|
await callTool("communication_delete", { comment_id: commentId });
|
|
1464
1464
|
}
|
|
1465
|
+
function authorLabel(c) {
|
|
1466
|
+
return c.author?.name || c.author?.email || "unknown";
|
|
1467
|
+
}
|
|
1468
|
+
async function readCloudDeck(commentId) {
|
|
1469
|
+
const result = await callTool("communication_read", {
|
|
1470
|
+
resource_type: "deck",
|
|
1471
|
+
limit: 50
|
|
1472
|
+
});
|
|
1473
|
+
const deck = result?.comments?.find((c) => c.id === commentId);
|
|
1474
|
+
if (!deck) return null;
|
|
1475
|
+
return {
|
|
1476
|
+
id: deck.id,
|
|
1477
|
+
title: deck.title ?? "(untitled deck)",
|
|
1478
|
+
author: authorLabel(deck),
|
|
1479
|
+
html: typeof deck.fields?.deckHtml === "string" ? deck.fields.deckHtml : null,
|
|
1480
|
+
blocks: Array.isArray(deck.fields?.deckBlocks) && deck.fields.deckBlocks.length > 0 ? deck.fields.deckBlocks : null
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1465
1483
|
function currentMcpOrigin() {
|
|
1466
1484
|
const config = _config;
|
|
1467
1485
|
if (!config) throw new Error("MCP config not loaded \u2014 call loadMcpConfig first");
|
|
@@ -1518,6 +1536,10 @@ function sessionDir(cwd, session) {
|
|
|
1518
1536
|
function sessionJsonPath(cwd, session) {
|
|
1519
1537
|
return import_node_path5.default.join(sessionDir(cwd, session), "session.json");
|
|
1520
1538
|
}
|
|
1539
|
+
function sessionIdFromCloud(title, fallbackId) {
|
|
1540
|
+
const slug = title.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
|
|
1541
|
+
return slug || `deck-${fallbackId.slice(0, 8)}`;
|
|
1542
|
+
}
|
|
1521
1543
|
function readPersistedSession(cwd, session) {
|
|
1522
1544
|
try {
|
|
1523
1545
|
const raw = import_node_fs5.default.readFileSync(sessionJsonPath(cwd, session), "utf-8");
|
|
@@ -1563,7 +1585,9 @@ function listPersistedSessions(cwd) {
|
|
|
1563
1585
|
shared: Boolean(sync),
|
|
1564
1586
|
stale: Boolean(sync) && persisted.version > (sync?.version ?? 0),
|
|
1565
1587
|
shareUrl: sync?.publicShareUrl,
|
|
1566
|
-
course: sync?.course
|
|
1588
|
+
course: sync?.course,
|
|
1589
|
+
watching: sync?.watching,
|
|
1590
|
+
watchedFrom: sync?.watchedFrom
|
|
1567
1591
|
});
|
|
1568
1592
|
}
|
|
1569
1593
|
sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
@@ -1957,6 +1981,74 @@ async function startDeckServer(opts = {}) {
|
|
|
1957
1981
|
jsonResponse(res, 200, { sessions: listPersistedSessions(cwd) });
|
|
1958
1982
|
return;
|
|
1959
1983
|
}
|
|
1984
|
+
const pullMatch = url2.pathname.match(/^\/api\/cloud-decks\/([^/]+)\/pull$/);
|
|
1985
|
+
if (pullMatch && req.method === "POST") {
|
|
1986
|
+
const deckId = decodeURIComponent(pullMatch[1]);
|
|
1987
|
+
let course;
|
|
1988
|
+
try {
|
|
1989
|
+
const raw = await readBody(req);
|
|
1990
|
+
if (raw.trim()) {
|
|
1991
|
+
const parsed = JSON.parse(raw);
|
|
1992
|
+
course = parsed.course?.trim() || void 0;
|
|
1993
|
+
}
|
|
1994
|
+
} catch {
|
|
1995
|
+
}
|
|
1996
|
+
const cfg = resolveShareConfig(cwd, course);
|
|
1997
|
+
if ("error" in cfg) {
|
|
1998
|
+
jsonResponse(res, 400, { ok: false, error: cfg.error });
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
try {
|
|
2002
|
+
const deck = await runWithShareConfig(cfg.override, () => readCloudDeck(deckId));
|
|
2003
|
+
if (!deck) {
|
|
2004
|
+
jsonResponse(res, 404, { ok: false, error: `No shared deck "${deckId}" in this course.` });
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (!deck.html && !deck.blocks) {
|
|
2008
|
+
jsonResponse(res, 422, { ok: false, error: "That deck carries no viewable content (no deckHtml or deckBlocks)." });
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
const session = sessionIdFromCloud(deck.title, deck.id);
|
|
2012
|
+
const sessionEncoded = encodeURIComponent(session);
|
|
2013
|
+
let blocks;
|
|
2014
|
+
if (deck.html) {
|
|
2015
|
+
const dir = sessionDir(cwd, session);
|
|
2016
|
+
import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
2017
|
+
import_node_fs5.default.writeFileSync(import_node_path5.default.join(dir, "imported.html"), deck.html);
|
|
2018
|
+
blocks = [{ type: "iframe", label: deck.title, src: `/deck-files/${sessionEncoded}/imported.html` }];
|
|
2019
|
+
} else {
|
|
2020
|
+
blocks = deck.blocks;
|
|
2021
|
+
}
|
|
2022
|
+
const persisted = writePersistedSession(cwd, session, { mode: "show", blocks });
|
|
2023
|
+
writeSyncRecord(cwd, session, {
|
|
2024
|
+
resourceId: deck.id,
|
|
2025
|
+
sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2026
|
+
version: persisted.version,
|
|
2027
|
+
course,
|
|
2028
|
+
watching: true,
|
|
2029
|
+
watchedFrom: deck.author
|
|
2030
|
+
});
|
|
2031
|
+
broadcastToClients({
|
|
2032
|
+
type: "session",
|
|
2033
|
+
session,
|
|
2034
|
+
mode: "show",
|
|
2035
|
+
blocks,
|
|
2036
|
+
version: persisted.version
|
|
2037
|
+
});
|
|
2038
|
+
jsonResponse(res, 200, {
|
|
2039
|
+
ok: true,
|
|
2040
|
+
session,
|
|
2041
|
+
deckId: deck.id,
|
|
2042
|
+
title: deck.title,
|
|
2043
|
+
author: deck.author,
|
|
2044
|
+
blockCount: blocks.length,
|
|
2045
|
+
course
|
|
2046
|
+
});
|
|
2047
|
+
} catch (err) {
|
|
2048
|
+
jsonResponse(res, 500, { ok: false, error: String(err) });
|
|
2049
|
+
}
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
1960
2052
|
const shareMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/share$/);
|
|
1961
2053
|
if (shareMatch && req.method === "POST") {
|
|
1962
2054
|
const id = decodeURIComponent(shareMatch[1]);
|
|
@@ -2436,7 +2528,7 @@ function httpGet(url) {
|
|
|
2436
2528
|
return new Promise((resolve, reject) => {
|
|
2437
2529
|
const parsed = new URL(url);
|
|
2438
2530
|
const req = import_node_http2.default.request(
|
|
2439
|
-
{ hostname: parsed.hostname, port: parsed.port, path: parsed.pathname, method: "GET" },
|
|
2531
|
+
{ hostname: parsed.hostname, port: parsed.port, path: parsed.pathname + parsed.search, method: "GET" },
|
|
2440
2532
|
(res) => {
|
|
2441
2533
|
let buf = "";
|
|
2442
2534
|
res.on("data", (chunk) => {
|
|
@@ -2728,6 +2820,31 @@ async function handleTool(name, args) {
|
|
|
2728
2820
|
return text(JSON.stringify({ error: `Failed to generate contract: ${err}` }));
|
|
2729
2821
|
}
|
|
2730
2822
|
}
|
|
2823
|
+
case "deck_pull": {
|
|
2824
|
+
const lock = getLiveLock(projectRoot);
|
|
2825
|
+
if (!lock) {
|
|
2826
|
+
return text(JSON.stringify({ error: "LaunchDeck server is not running. Call start_server first." }));
|
|
2827
|
+
}
|
|
2828
|
+
const deckId = args.deck?.trim();
|
|
2829
|
+
if (!deckId) {
|
|
2830
|
+
return text(JSON.stringify({ error: "Provide the deck's comment id (from launch-secure deck_list / watched_decks)." }));
|
|
2831
|
+
}
|
|
2832
|
+
const course = args.course?.trim();
|
|
2833
|
+
try {
|
|
2834
|
+
const resp = await httpPost(`${lock.url}/api/cloud-decks/${encodeURIComponent(deckId)}/pull`, course ? { course } : {});
|
|
2835
|
+
const parsed = JSON.parse(resp);
|
|
2836
|
+
if (parsed.ok === false) {
|
|
2837
|
+
return text(JSON.stringify({ error: parsed.error ?? "Failed to pull the deck." }));
|
|
2838
|
+
}
|
|
2839
|
+
return text(JSON.stringify({
|
|
2840
|
+
...parsed,
|
|
2841
|
+
url: lock.url,
|
|
2842
|
+
action: "Deck pulled into a local tab. Open the LaunchDeck browser to view it; it can now be \u{1F441}-watched like any local deck."
|
|
2843
|
+
}));
|
|
2844
|
+
} catch (err) {
|
|
2845
|
+
return text(JSON.stringify({ error: `Failed to pull deck: ${err}` }));
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2731
2848
|
default:
|
|
2732
2849
|
return text(`Unknown tool: ${name}`);
|
|
2733
2850
|
}
|
|
@@ -2964,6 +3081,24 @@ var init_deck_mcp = __esm({
|
|
|
2964
3081
|
},
|
|
2965
3082
|
required: ["session"]
|
|
2966
3083
|
}
|
|
3084
|
+
},
|
|
3085
|
+
{
|
|
3086
|
+
name: "deck_pull",
|
|
3087
|
+
description: "Pull a cloud-shared deck DOWN into a local LaunchDeck session tab \u2014 the inverse of publishing a deck up. After this, the deck is an ordinary local tab the user can view, \u{1F441}-watch, download, or re-pull. Takes the deck's comment id \u2014 get it from launch-secure's deck_list or watched_decks. The deck is reconstructed from its shared HTML snapshot as one self-contained block (view-faithful; not re-editable block-by-block). Requires the LaunchDeck server to be running (start_server first). Note: listing shared decks and reading watch state live on the launch-secure MCP (deck_list / watched_decks), not here.",
|
|
3088
|
+
inputSchema: {
|
|
3089
|
+
type: "object",
|
|
3090
|
+
properties: {
|
|
3091
|
+
deck: {
|
|
3092
|
+
type: "string",
|
|
3093
|
+
description: "Deck comment id (from launch-secure deck_list / watched_decks)."
|
|
3094
|
+
},
|
|
3095
|
+
course: {
|
|
3096
|
+
type: "string",
|
|
3097
|
+
description: "Optional course (cred profile) to read from. Omit for the active course."
|
|
3098
|
+
}
|
|
3099
|
+
},
|
|
3100
|
+
required: ["deck"]
|
|
3101
|
+
}
|
|
2967
3102
|
}
|
|
2968
3103
|
];
|
|
2969
3104
|
}
|
|
@@ -1441,6 +1441,24 @@ async function updateDeck(commentId, input) {
|
|
|
1441
1441
|
async function deleteDeck(commentId) {
|
|
1442
1442
|
await callTool("communication_delete", { comment_id: commentId });
|
|
1443
1443
|
}
|
|
1444
|
+
function authorLabel(c) {
|
|
1445
|
+
return c.author?.name || c.author?.email || "unknown";
|
|
1446
|
+
}
|
|
1447
|
+
async function readCloudDeck(commentId) {
|
|
1448
|
+
const result = await callTool("communication_read", {
|
|
1449
|
+
resource_type: "deck",
|
|
1450
|
+
limit: 50
|
|
1451
|
+
});
|
|
1452
|
+
const deck = result?.comments?.find((c) => c.id === commentId);
|
|
1453
|
+
if (!deck) return null;
|
|
1454
|
+
return {
|
|
1455
|
+
id: deck.id,
|
|
1456
|
+
title: deck.title ?? "(untitled deck)",
|
|
1457
|
+
author: authorLabel(deck),
|
|
1458
|
+
html: typeof deck.fields?.deckHtml === "string" ? deck.fields.deckHtml : null,
|
|
1459
|
+
blocks: Array.isArray(deck.fields?.deckBlocks) && deck.fields.deckBlocks.length > 0 ? deck.fields.deckBlocks : null
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1444
1462
|
function currentMcpOrigin() {
|
|
1445
1463
|
const config = _config;
|
|
1446
1464
|
if (!config) throw new Error("MCP config not loaded \u2014 call loadMcpConfig first");
|
|
@@ -1487,6 +1505,10 @@ function sessionDir(cwd, session) {
|
|
|
1487
1505
|
function sessionJsonPath(cwd, session) {
|
|
1488
1506
|
return import_node_path4.default.join(sessionDir(cwd, session), "session.json");
|
|
1489
1507
|
}
|
|
1508
|
+
function sessionIdFromCloud(title, fallbackId) {
|
|
1509
|
+
const slug = title.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
|
|
1510
|
+
return slug || `deck-${fallbackId.slice(0, 8)}`;
|
|
1511
|
+
}
|
|
1490
1512
|
function readPersistedSession(cwd, session) {
|
|
1491
1513
|
try {
|
|
1492
1514
|
const raw = import_node_fs4.default.readFileSync(sessionJsonPath(cwd, session), "utf-8");
|
|
@@ -1532,7 +1554,9 @@ function listPersistedSessions(cwd) {
|
|
|
1532
1554
|
shared: Boolean(sync),
|
|
1533
1555
|
stale: Boolean(sync) && persisted.version > (sync?.version ?? 0),
|
|
1534
1556
|
shareUrl: sync?.publicShareUrl,
|
|
1535
|
-
course: sync?.course
|
|
1557
|
+
course: sync?.course,
|
|
1558
|
+
watching: sync?.watching,
|
|
1559
|
+
watchedFrom: sync?.watchedFrom
|
|
1536
1560
|
});
|
|
1537
1561
|
}
|
|
1538
1562
|
sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
@@ -1931,6 +1955,74 @@ async function startDeckServer(opts = {}) {
|
|
|
1931
1955
|
jsonResponse(res, 200, { sessions: listPersistedSessions(cwd) });
|
|
1932
1956
|
return;
|
|
1933
1957
|
}
|
|
1958
|
+
const pullMatch = url2.pathname.match(/^\/api\/cloud-decks\/([^/]+)\/pull$/);
|
|
1959
|
+
if (pullMatch && req.method === "POST") {
|
|
1960
|
+
const deckId = decodeURIComponent(pullMatch[1]);
|
|
1961
|
+
let course;
|
|
1962
|
+
try {
|
|
1963
|
+
const raw = await readBody(req);
|
|
1964
|
+
if (raw.trim()) {
|
|
1965
|
+
const parsed = JSON.parse(raw);
|
|
1966
|
+
course = parsed.course?.trim() || void 0;
|
|
1967
|
+
}
|
|
1968
|
+
} catch {
|
|
1969
|
+
}
|
|
1970
|
+
const cfg = resolveShareConfig(cwd, course);
|
|
1971
|
+
if ("error" in cfg) {
|
|
1972
|
+
jsonResponse(res, 400, { ok: false, error: cfg.error });
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
try {
|
|
1976
|
+
const deck = await runWithShareConfig(cfg.override, () => readCloudDeck(deckId));
|
|
1977
|
+
if (!deck) {
|
|
1978
|
+
jsonResponse(res, 404, { ok: false, error: `No shared deck "${deckId}" in this course.` });
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
if (!deck.html && !deck.blocks) {
|
|
1982
|
+
jsonResponse(res, 422, { ok: false, error: "That deck carries no viewable content (no deckHtml or deckBlocks)." });
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
const session = sessionIdFromCloud(deck.title, deck.id);
|
|
1986
|
+
const sessionEncoded = encodeURIComponent(session);
|
|
1987
|
+
let blocks;
|
|
1988
|
+
if (deck.html) {
|
|
1989
|
+
const dir = sessionDir(cwd, session);
|
|
1990
|
+
import_node_fs4.default.mkdirSync(dir, { recursive: true });
|
|
1991
|
+
import_node_fs4.default.writeFileSync(import_node_path4.default.join(dir, "imported.html"), deck.html);
|
|
1992
|
+
blocks = [{ type: "iframe", label: deck.title, src: `/deck-files/${sessionEncoded}/imported.html` }];
|
|
1993
|
+
} else {
|
|
1994
|
+
blocks = deck.blocks;
|
|
1995
|
+
}
|
|
1996
|
+
const persisted = writePersistedSession(cwd, session, { mode: "show", blocks });
|
|
1997
|
+
writeSyncRecord(cwd, session, {
|
|
1998
|
+
resourceId: deck.id,
|
|
1999
|
+
sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2000
|
+
version: persisted.version,
|
|
2001
|
+
course,
|
|
2002
|
+
watching: true,
|
|
2003
|
+
watchedFrom: deck.author
|
|
2004
|
+
});
|
|
2005
|
+
broadcastToClients({
|
|
2006
|
+
type: "session",
|
|
2007
|
+
session,
|
|
2008
|
+
mode: "show",
|
|
2009
|
+
blocks,
|
|
2010
|
+
version: persisted.version
|
|
2011
|
+
});
|
|
2012
|
+
jsonResponse(res, 200, {
|
|
2013
|
+
ok: true,
|
|
2014
|
+
session,
|
|
2015
|
+
deckId: deck.id,
|
|
2016
|
+
title: deck.title,
|
|
2017
|
+
author: deck.author,
|
|
2018
|
+
blockCount: blocks.length,
|
|
2019
|
+
course
|
|
2020
|
+
});
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
jsonResponse(res, 500, { ok: false, error: String(err) });
|
|
2023
|
+
}
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
1934
2026
|
const shareMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/share$/);
|
|
1935
2027
|
if (shareMatch && req.method === "POST") {
|
|
1936
2028
|
const id = decodeURIComponent(shareMatch[1]);
|
|
@@ -1076,14 +1076,30 @@ async function setupFromCloud() {
|
|
|
1076
1076
|
console.log(`[entrypoint] bundle from cloud: org=${orgSlug} project=${projectSlug} git=${process.env.GIT_USER_NAME} <${process.env.GIT_USER_EMAIL}> github=${bundle.githubTokenStatus.toLowerCase()} ${cfNote}`);
|
|
1077
1077
|
return bundle;
|
|
1078
1078
|
}
|
|
1079
|
+
function hasUsableCredentials(credsPath) {
|
|
1080
|
+
if (!(0, import_node_fs4.existsSync)(credsPath)) return false;
|
|
1081
|
+
try {
|
|
1082
|
+
const raw = (0, import_node_fs4.readFileSync)(credsPath, "utf8").trim();
|
|
1083
|
+
if (raw.length === 0) return false;
|
|
1084
|
+
const parsed = JSON.parse(raw);
|
|
1085
|
+
return parsed != null && typeof parsed === "object" && "claudeAiOauth" in parsed;
|
|
1086
|
+
} catch {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1079
1090
|
function setupClaudeCredentials() {
|
|
1080
1091
|
const home = process.env.HOME ?? "/home/launchpod";
|
|
1081
1092
|
const claudeDir = (0, import_node_path4.join)(home, ".claude");
|
|
1082
1093
|
(0, import_node_fs4.mkdirSync)(claudeDir, { recursive: true });
|
|
1083
|
-
const decoded = Buffer.from(requireEnv("CLAUDE_CREDENTIALS_B64"), "base64").toString("utf8");
|
|
1084
1094
|
const credsPath = (0, import_node_path4.join)(claudeDir, ".credentials.json");
|
|
1085
|
-
|
|
1086
|
-
|
|
1095
|
+
if (hasUsableCredentials(credsPath)) {
|
|
1096
|
+
console.log("[entrypoint] ~/.claude/.credentials.json already present \u2014 leaving Claude Code's own (refreshed) credentials intact");
|
|
1097
|
+
} else {
|
|
1098
|
+
const decoded = Buffer.from(requireEnv("CLAUDE_CREDENTIALS_B64"), "base64").toString("utf8");
|
|
1099
|
+
(0, import_node_fs4.writeFileSync)(credsPath, decoded);
|
|
1100
|
+
(0, import_node_fs4.chmodSync)(credsPath, 384);
|
|
1101
|
+
console.log("[entrypoint] seeded ~/.claude/.credentials.json from CLAUDE_CREDENTIALS_B64");
|
|
1102
|
+
}
|
|
1087
1103
|
const configPath = (0, import_node_path4.join)(home, ".claude.json");
|
|
1088
1104
|
let cfg = {};
|
|
1089
1105
|
if ((0, import_node_fs4.existsSync)(configPath)) {
|
|
@@ -1107,7 +1123,7 @@ function setupClaudeCredentials() {
|
|
|
1107
1123
|
"launch-sequencer"
|
|
1108
1124
|
];
|
|
1109
1125
|
const projects = cfg.projects ?? {};
|
|
1110
|
-
const wsKey =
|
|
1126
|
+
const wsKey = POD_WS;
|
|
1111
1127
|
const wsProject = projects[wsKey] ?? {};
|
|
1112
1128
|
const existingEnabled = Array.isArray(wsProject.enabledMcpjsonServers) ? wsProject.enabledMcpjsonServers : [];
|
|
1113
1129
|
const mergedEnabled = Array.from(/* @__PURE__ */ new Set([...existingEnabled, ...PREAPPROVED_MCPS]));
|
|
@@ -1126,7 +1142,7 @@ function setupGitAndGh() {
|
|
|
1126
1142
|
function detectAndSetPreviewPort() {
|
|
1127
1143
|
if (process.env.PREVIEW_PORT) return;
|
|
1128
1144
|
try {
|
|
1129
|
-
const pkgPath = "
|
|
1145
|
+
const pkgPath = (0, import_node_path4.join)(POD_WS, "package.json");
|
|
1130
1146
|
if (!(0, import_node_fs4.existsSync)(pkgPath)) return;
|
|
1131
1147
|
const pkg = JSON.parse((0, import_node_fs4.readFileSync)(pkgPath, "utf-8"));
|
|
1132
1148
|
const scripts = pkg.scripts ?? {};
|
|
@@ -1145,23 +1161,27 @@ function detectAndSetPreviewPort() {
|
|
|
1145
1161
|
}
|
|
1146
1162
|
}
|
|
1147
1163
|
function initWorkspaceIfEmpty() {
|
|
1148
|
-
process.chdir(
|
|
1164
|
+
process.chdir(POD_WS);
|
|
1149
1165
|
if ((0, import_node_fs4.existsSync)(".git")) {
|
|
1150
|
-
console.log(
|
|
1166
|
+
console.log(`[entrypoint] ${POD_WS} already initialized \u2014 skipping init`);
|
|
1151
1167
|
return;
|
|
1152
1168
|
}
|
|
1153
|
-
console.log(
|
|
1169
|
+
console.log(`[entrypoint] ${POD_WS} is empty \u2014 running launch-kit init`);
|
|
1154
1170
|
const status = run2("launch-kit", [
|
|
1155
1171
|
"init",
|
|
1156
1172
|
`--token=${requireEnv("LS_PAT")}`,
|
|
1157
1173
|
`--org=${requireEnv("LS_ORG_SLUG")}`,
|
|
1158
1174
|
`--project=${requireEnv("LS_PROJECT_SLUG")}`,
|
|
1159
1175
|
`--url=${process.env.LS_SERVER_URL ?? "https://launchsecure-v2.vercel.app"}`,
|
|
1160
|
-
`--dir
|
|
1176
|
+
`--dir=${POD_WS}`
|
|
1161
1177
|
]);
|
|
1162
1178
|
if (status !== 0) fail2(`[entrypoint] launch-kit init failed (status ${status})`);
|
|
1163
1179
|
}
|
|
1164
1180
|
async function maybeProvisionIngress(bundle, services, projectSlug) {
|
|
1181
|
+
if (LOCAL_ONLY) {
|
|
1182
|
+
console.log("[entrypoint] LAUNCHKIT_LOCAL_ONLY=1 \u2014 skipping CF ingress; services run on localhost only");
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1165
1185
|
const token = bundle.cloudflareToken ?? null;
|
|
1166
1186
|
const accountId = bundle.cloudflareAccountId ?? null;
|
|
1167
1187
|
const zones = bundle.cloudflareZones ?? [];
|
|
@@ -1181,7 +1201,7 @@ async function maybeProvisionIngress(bundle, services, projectSlug) {
|
|
|
1181
1201
|
} else {
|
|
1182
1202
|
fail2(`[entrypoint] cloudflare token covers ${zones.length} zones (${zones.map((z) => z.name).join(", ")}) \u2014 set LAUNCHKIT_CF_BASE_DOMAIN to pick one.`);
|
|
1183
1203
|
}
|
|
1184
|
-
const stateFile = "
|
|
1204
|
+
const stateFile = (0, import_node_path4.join)(POD_WS, ".launchpod", "launch-kit-tunnel.json");
|
|
1185
1205
|
const slugLabel = projectSlug.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1186
1206
|
const DNS_LABEL_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
1187
1207
|
for (const s of services) {
|
|
@@ -1247,7 +1267,7 @@ async function maybeProvisionAccess(bundle, ingress) {
|
|
|
1247
1267
|
}
|
|
1248
1268
|
const serverUrl = process.env.LS_SERVER_URL ?? "https://launchsecure-v2.vercel.app";
|
|
1249
1269
|
const pat = requireEnv("LS_PAT");
|
|
1250
|
-
const stateFile = "
|
|
1270
|
+
const stateFile = (0, import_node_path4.join)(POD_WS, ".launchpod", "launch-kit-access.json");
|
|
1251
1271
|
const gatedHosts = services.filter((s) => !s.bypass).map((s) => s.hostname);
|
|
1252
1272
|
const bypassed = services.filter((s) => s.bypass).map((s) => `${s.hostname}${s.path ?? ""}`);
|
|
1253
1273
|
console.log(`[entrypoint] gating ${gatedHosts.join(", ")} behind CF Access (IdP: ${serverUrl})`);
|
|
@@ -1346,11 +1366,13 @@ function spawnServiceGroup(services) {
|
|
|
1346
1366
|
}).finally(removeSignals);
|
|
1347
1367
|
}
|
|
1348
1368
|
async function main() {
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1369
|
+
if (!NO_PARK) {
|
|
1370
|
+
const priorCrashes = readCrashState()?.count ?? 0;
|
|
1371
|
+
if (priorCrashes >= MAX_BOOT_CRASHES) await parkAfterCrashLoop(priorCrashes);
|
|
1372
|
+
const bootAttempt = bumpCrashCount();
|
|
1373
|
+
if (bootAttempt > 1) {
|
|
1374
|
+
console.warn(`[entrypoint] boot attempt ${bootAttempt}/${MAX_BOOT_CRASHES} \u2014 prior boot(s) crashed before becoming stable`);
|
|
1375
|
+
}
|
|
1354
1376
|
}
|
|
1355
1377
|
for (const k of REQUIRED_ENV) requireEnv(k);
|
|
1356
1378
|
const bundle = await setupFromCloud();
|
|
@@ -1384,8 +1406,10 @@ async function main() {
|
|
|
1384
1406
|
}
|
|
1385
1407
|
}
|
|
1386
1408
|
const stableTimer = setTimeout(() => {
|
|
1387
|
-
|
|
1388
|
-
|
|
1409
|
+
if (!NO_PARK) {
|
|
1410
|
+
clearCrashCount();
|
|
1411
|
+
console.log(`[entrypoint] services stable for ${Math.round(STABLE_AFTER_MS / 1e3)}s \u2014 boot-crash counter cleared`);
|
|
1412
|
+
}
|
|
1389
1413
|
}, STABLE_AFTER_MS);
|
|
1390
1414
|
stableTimer.unref?.();
|
|
1391
1415
|
try {
|
|
@@ -1398,7 +1422,7 @@ async function main() {
|
|
|
1398
1422
|
process.exit(1);
|
|
1399
1423
|
}
|
|
1400
1424
|
}
|
|
1401
|
-
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;
|
|
1425
|
+
var import_node_child_process2, import_node_fs4, import_node_path4, REQUIRED_ENV, POD_WS, NO_PARK, LOCAL_ONLY, LAUNCHPOD_DIR, CRASH_STATE_FILE, MAX_BOOT_CRASHES, STABLE_AFTER_MS, GATED_SERVICES;
|
|
1402
1426
|
var init_radar_docker_init_entry = __esm({
|
|
1403
1427
|
"src/server/radar-docker-init-entry.ts"() {
|
|
1404
1428
|
"use strict";
|
|
@@ -1411,12 +1435,14 @@ var init_radar_docker_init_entry = __esm({
|
|
|
1411
1435
|
init_cf_access();
|
|
1412
1436
|
init_registration();
|
|
1413
1437
|
REQUIRED_ENV = [
|
|
1414
|
-
"CLAUDE_CREDENTIALS_B64",
|
|
1415
1438
|
"LS_PAT",
|
|
1416
1439
|
"LS_ORG_SLUG",
|
|
1417
1440
|
"LS_PROJECT_SLUG"
|
|
1418
1441
|
];
|
|
1419
|
-
|
|
1442
|
+
POD_WS = process.env.LAUNCHPOD_WORKSPACE ?? "/workspace";
|
|
1443
|
+
NO_PARK = process.env.LAUNCHPOD_NO_PARK === "1";
|
|
1444
|
+
LOCAL_ONLY = process.env.LAUNCHKIT_LOCAL_ONLY === "1";
|
|
1445
|
+
LAUNCHPOD_DIR = (0, import_node_path4.join)(POD_WS, ".launchpod");
|
|
1420
1446
|
CRASH_STATE_FILE = (0, import_node_path4.join)(LAUNCHPOD_DIR, ".boot-crash.json");
|
|
1421
1447
|
MAX_BOOT_CRASHES = 5;
|
|
1422
1448
|
STABLE_AFTER_MS = 3e4;
|
|
@@ -611,6 +611,14 @@ var require_claude_bridge = __commonJS({
|
|
|
611
611
|
},
|
|
612
612
|
onError = () => {
|
|
613
613
|
},
|
|
614
|
+
// Fired (at most once per session) when Claude Code prints its
|
|
615
|
+
// authentication-failure banner — i.e. the credential it resolved was
|
|
616
|
+
// rejected by the Anthropic API. In a headless pod nobody is watching the
|
|
617
|
+
// pty, so this is the only signal the credential died; callers surface it
|
|
618
|
+
// (radar card + status banner) instead of failing silently. Detail is a
|
|
619
|
+
// short human string.
|
|
620
|
+
onAuthFailure = () => {
|
|
621
|
+
},
|
|
614
622
|
cols = 80,
|
|
615
623
|
rows = 24,
|
|
616
624
|
// When true, spawn `claude --resume <sessionId>` instead of starting a
|
|
@@ -671,6 +679,7 @@ var require_claude_bridge = __commonJS({
|
|
|
671
679
|
this.sessions.set(sessionId, session);
|
|
672
680
|
let trustPromptHandled = false;
|
|
673
681
|
let autoStartSent = !!initialPrompt;
|
|
682
|
+
let authFailureReported = false;
|
|
674
683
|
let dataBuffer = "";
|
|
675
684
|
claudeProcess.onData((data) => {
|
|
676
685
|
if (process.env.DEBUG) {
|
|
@@ -685,6 +694,15 @@ var require_claude_bridge = __commonJS({
|
|
|
685
694
|
console.log(`Sent Enter to accept trust prompt for session ${sessionId}`);
|
|
686
695
|
}, 500);
|
|
687
696
|
}
|
|
697
|
+
if (!authFailureReported && /Please run \/login|Invalid authentication credentials/i.test(dataBuffer)) {
|
|
698
|
+
authFailureReported = true;
|
|
699
|
+
console.error(`Claude session ${sessionId}: authentication failed (credential rejected by Anthropic API)`);
|
|
700
|
+
try {
|
|
701
|
+
onAuthFailure("Claude credential rejected (401) \u2014 pod could not authenticate to the Anthropic API.");
|
|
702
|
+
} catch (e) {
|
|
703
|
+
console.error(`onAuthFailure handler threw for session ${sessionId}:`, e);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
688
706
|
if (!autoStartSent && appendSystemPrompt && dataBuffer.includes("\u276F")) {
|
|
689
707
|
autoStartSent = true;
|
|
690
708
|
console.log(`Auto-starting agent in session ${sessionId}`);
|
|
@@ -3246,6 +3264,13 @@ var require_src = __commonJS({
|
|
|
3246
3264
|
message: error.message
|
|
3247
3265
|
});
|
|
3248
3266
|
},
|
|
3267
|
+
onAuthFailure: (detail) => {
|
|
3268
|
+
this.broadcastToSession(sessionId, {
|
|
3269
|
+
type: "auth_failed",
|
|
3270
|
+
sessionId,
|
|
3271
|
+
message: detail
|
|
3272
|
+
});
|
|
3273
|
+
},
|
|
3249
3274
|
...options
|
|
3250
3275
|
});
|
|
3251
3276
|
session.active = true;
|
|
@@ -3717,13 +3742,22 @@ var require_src = __commonJS({
|
|
|
3717
3742
|
return new TerminalHandler(options);
|
|
3718
3743
|
}
|
|
3719
3744
|
function createWebSocketHandler2(httpServer, handler2, wsPath = "/terminal/ws") {
|
|
3720
|
-
const wss2 = new WebSocket.Server({
|
|
3721
|
-
server: httpServer,
|
|
3722
|
-
path: wsPath
|
|
3723
|
-
});
|
|
3745
|
+
const wss2 = new WebSocket.Server({ noServer: true });
|
|
3724
3746
|
wss2.on("connection", (ws, req) => {
|
|
3725
3747
|
handler2.handleWebSocketConnection(ws, req);
|
|
3726
3748
|
});
|
|
3749
|
+
httpServer.on("upgrade", (req, socket, head) => {
|
|
3750
|
+
let pathname;
|
|
3751
|
+
try {
|
|
3752
|
+
pathname = new URL(req.url || "/", "http://localhost").pathname;
|
|
3753
|
+
} catch {
|
|
3754
|
+
return;
|
|
3755
|
+
}
|
|
3756
|
+
if (pathname !== wsPath) return;
|
|
3757
|
+
wss2.handleUpgrade(req, socket, head, (ws) => {
|
|
3758
|
+
wss2.emit("connection", ws, req);
|
|
3759
|
+
});
|
|
3760
|
+
});
|
|
3727
3761
|
return wss2;
|
|
3728
3762
|
}
|
|
3729
3763
|
function detectClaudeCli2() {
|