@launchsecure/launch-kit 0.0.35 → 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 (83) 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 +261 -26
  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/init-entry.js +732 -237
  64. package/dist/server/orbit-entry.js +880 -144
  65. package/dist/server/radar-docker-init-entry.js +371 -37
  66. package/dist/server/rover-entry.js +108 -20
  67. package/package.json +1 -1
  68. package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check/SKILL.md +5 -0
  69. package/scaffolds/ls-marketplace/plugins/kit/skills/kickoff/SKILL.md +20 -4
  70. package/scaffolds/ls-marketplace/plugins/kit/skills/orbit/SKILL.md +27 -7
  71. package/dist/chart-client/assets/index-DpKO9p0s.css +0 -1
  72. package/dist/client/assets/index-Dv6dD2zY.css +0 -32
  73. package/dist/council-client/assets/index-AqQ9Sei6.css +0 -1
  74. package/dist/deck-client/assets/channel-DB6LxW_l.js +0 -1
  75. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-g944ZyG8.js +0 -1
  76. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-g944ZyG8.js +0 -1
  77. package/dist/deck-client/assets/clone-DiIRH1pI.js +0 -1
  78. package/dist/deck-client/assets/index-B-YQq5b5.css +0 -1
  79. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-DQYa2M1q.js +0 -1
  80. package/dist/deck-client/assets/wardley-RL74JXVD-CHZiUbBa.js +0 -162
  81. /package/dist/chart-client/assets/{index-DFu2xIrM.js → index-BgUxHxwE.js} +0 -0
  82. /package/dist/client/assets/{index-Cbw6bVdx.js → index-CUivaQnN.js} +0 -0
  83. /package/dist/council-client/assets/{index-CAsmGTzg.js → index-DN8HN_5K.js} +0 -0
@@ -39,8 +39,8 @@ __export(deck_serve_exports, {
39
39
  });
40
40
  module.exports = __toCommonJS(deck_serve_exports);
41
41
  var import_node_http = __toESM(require("node:http"));
42
- var import_node_fs3 = __toESM(require("node:fs"));
43
- var import_node_path3 = __toESM(require("node:path"));
42
+ var import_node_fs4 = __toESM(require("node:fs"));
43
+ var import_node_path4 = __toESM(require("node:path"));
44
44
  var import_ws = require("ws");
45
45
 
46
46
  // src/server/launch-kit-paths.ts
@@ -704,9 +704,9 @@ function generateBlastRadiusReport(manifest) {
704
704
  `;
705
705
  for (const n of nodes) {
706
706
  const ln = layerMap[n.layer] || n.layer;
707
- const path2 = (n.path || "-").replace(/\|/g, "/");
707
+ const path3 = (n.path || "-").replace(/\|/g, "/");
708
708
  const reason = (n.reason || "-").replace(/\|/g, "/");
709
- md += `| ${n.name} | ${ln} | ${n.type || "-"} | ${path2} | ${reason} |
709
+ md += `| ${n.name} | ${ln} | ${n.type || "-"} | ${path3} | ${reason} |
710
710
  `;
711
711
  }
712
712
  md += `
@@ -1174,6 +1174,216 @@ function slugify(label) {
1174
1174
  return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "block";
1175
1175
  }
1176
1176
 
1177
+ // src/server/mcp-client.ts
1178
+ var import_node_fs3 = require("node:fs");
1179
+ var import_node_path3 = require("node:path");
1180
+
1181
+ // src/server/cred-shape.ts
1182
+ var fs = __toESM(require("node:fs"));
1183
+ var path = __toESM(require("node:path"));
1184
+ var CONFIG_FILENAME2 = ".launch-secure.cred.config";
1185
+ function inferCourseName(serverUrl) {
1186
+ try {
1187
+ const host = new URL(serverUrl).hostname.toLowerCase();
1188
+ if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) return "local";
1189
+ if (host.includes("staging")) return "staging";
1190
+ if (host.endsWith(".vercel.app")) return "prod";
1191
+ return host.split(".")[0] || "default";
1192
+ } catch {
1193
+ return "default";
1194
+ }
1195
+ }
1196
+ function toNested(cred) {
1197
+ if (cred.profiles && cred.active && cred.profiles[cred.active]) {
1198
+ return { active: cred.active, profiles: cred.profiles };
1199
+ }
1200
+ if (!cred.pat || !cred.orgSlug || !cred.projectSlug || !cred.serverUrl) {
1201
+ return null;
1202
+ }
1203
+ const name = inferCourseName(cred.serverUrl);
1204
+ return {
1205
+ active: name,
1206
+ profiles: {
1207
+ [name]: {
1208
+ pat: cred.pat,
1209
+ orgSlug: cred.orgSlug,
1210
+ projectSlug: cred.projectSlug,
1211
+ serverUrl: cred.serverUrl
1212
+ }
1213
+ }
1214
+ };
1215
+ }
1216
+ function readCredFile(repoRoot) {
1217
+ const p = path.join(repoRoot, CONFIG_FILENAME2);
1218
+ if (!fs.existsSync(p)) return null;
1219
+ try {
1220
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
1221
+ } catch (err) {
1222
+ throw new Error(`could not parse ${CONFIG_FILENAME2}: ${err instanceof Error ? err.message : String(err)}`);
1223
+ }
1224
+ }
1225
+
1226
+ // src/server/mcp-client.ts
1227
+ var _config = null;
1228
+ var _requestId = 0;
1229
+ var _sessionId = null;
1230
+ function mcpConfigFromCourse(projectRoot) {
1231
+ let cred;
1232
+ try {
1233
+ cred = readCredFile(projectRoot);
1234
+ } catch {
1235
+ return null;
1236
+ }
1237
+ if (!cred) return null;
1238
+ const nested = toNested(cred);
1239
+ if (!nested) return null;
1240
+ const profile = nested.profiles[nested.active];
1241
+ if (!profile?.pat || !profile.serverUrl || !profile.orgSlug || !profile.projectSlug) {
1242
+ return null;
1243
+ }
1244
+ return {
1245
+ url: new URL("/api/mcp/project", profile.serverUrl).toString(),
1246
+ headers: {
1247
+ Authorization: `Bearer ${profile.pat}`,
1248
+ "X-Org-Slug": profile.orgSlug,
1249
+ "X-Project-Slug": profile.projectSlug
1250
+ }
1251
+ };
1252
+ }
1253
+ function loadMcpConfig(projectRoot) {
1254
+ if (_config) return _config;
1255
+ const fromCourse = mcpConfigFromCourse(projectRoot);
1256
+ if (fromCourse) {
1257
+ _config = fromCourse;
1258
+ return _config;
1259
+ }
1260
+ const mcpPath = (0, import_node_path3.join)(projectRoot, ".mcp.json");
1261
+ if (!(0, import_node_fs3.existsSync)(mcpPath)) {
1262
+ throw new Error(
1263
+ `No active course in ${CONFIG_FILENAME2} and no .mcp.json at ${mcpPath}. Run \`launch-course\` or bootstrap with \`launch-kit init\`.`
1264
+ );
1265
+ }
1266
+ const raw = JSON.parse((0, import_node_fs3.readFileSync)(mcpPath, "utf-8"));
1267
+ const entry = raw.mcpServers["launch-secure"];
1268
+ if (!entry?.url) {
1269
+ throw new Error(
1270
+ `No active course in ${CONFIG_FILENAME2} and no "launch-secure" entry with url in .mcp.json`
1271
+ );
1272
+ }
1273
+ _config = {
1274
+ url: entry.url,
1275
+ headers: entry.headers ?? {}
1276
+ };
1277
+ return _config;
1278
+ }
1279
+ function parseSSE(text) {
1280
+ const lines = text.split("\n");
1281
+ for (const line of lines) {
1282
+ if (line.startsWith("data: ")) {
1283
+ const data = line.slice(6);
1284
+ try {
1285
+ return JSON.parse(data);
1286
+ } catch {
1287
+ }
1288
+ }
1289
+ }
1290
+ return JSON.parse(text);
1291
+ }
1292
+ async function mcpRequest(method, params) {
1293
+ const config = _config;
1294
+ if (!config) throw new Error("MCP config not loaded \u2014 call loadMcpConfig first");
1295
+ const id = ++_requestId;
1296
+ const headers = {
1297
+ ...config.headers,
1298
+ "Content-Type": "application/json",
1299
+ "Accept": "application/json, text/event-stream"
1300
+ };
1301
+ if (_sessionId) {
1302
+ headers["Mcp-Session-Id"] = _sessionId;
1303
+ }
1304
+ const response = await fetch(config.url, {
1305
+ method: "POST",
1306
+ headers,
1307
+ body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
1308
+ });
1309
+ const sid = response.headers.get("mcp-session-id");
1310
+ if (sid) _sessionId = sid;
1311
+ if (!response.ok) {
1312
+ const text = await response.text();
1313
+ throw new Error(`MCP ${method} failed (${response.status}): ${text}`);
1314
+ }
1315
+ const contentType = response.headers.get("content-type") ?? "";
1316
+ const body = await response.text();
1317
+ if (contentType.includes("text/event-stream")) {
1318
+ return parseSSE(body);
1319
+ }
1320
+ return JSON.parse(body);
1321
+ }
1322
+ async function ensureInitialized() {
1323
+ if (_sessionId) return;
1324
+ await mcpRequest("initialize", {
1325
+ protocolVersion: "2024-11-05",
1326
+ capabilities: {},
1327
+ clientInfo: { name: "launch-council", version: "0.0.1" }
1328
+ });
1329
+ const config = _config;
1330
+ const headers = {
1331
+ ...config.headers,
1332
+ "Content-Type": "application/json",
1333
+ "Accept": "application/json, text/event-stream"
1334
+ };
1335
+ if (_sessionId) headers["Mcp-Session-Id"] = _sessionId;
1336
+ await fetch(config.url, {
1337
+ method: "POST",
1338
+ headers,
1339
+ body: JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
1340
+ });
1341
+ }
1342
+ async function callTool(toolName, args) {
1343
+ await ensureInitialized();
1344
+ const result = await mcpRequest("tools/call", {
1345
+ name: toolName,
1346
+ arguments: args
1347
+ });
1348
+ if (result.error) {
1349
+ throw new Error(`MCP tool error: ${result.error.message}`);
1350
+ }
1351
+ const textContent = result.result?.content?.[0]?.text;
1352
+ if (!textContent) return null;
1353
+ return JSON.parse(textContent);
1354
+ }
1355
+ async function writeDeck(input) {
1356
+ const result = await callTool("communication_write", {
1357
+ title: input.title,
1358
+ body: input.body,
1359
+ resource_type: "deck",
1360
+ fields: {
1361
+ deckHtml: input.html,
1362
+ sessionId: input.sessionId,
1363
+ blockCount: input.blockCount,
1364
+ sharedFrom: "launch-deck"
1365
+ }
1366
+ });
1367
+ return { id: result.created.id };
1368
+ }
1369
+ async function updateDeck(commentId, input) {
1370
+ await callTool("communication_update", {
1371
+ comment_id: commentId,
1372
+ title: input.title,
1373
+ body: input.body,
1374
+ fields: {
1375
+ deckHtml: input.html,
1376
+ sessionId: input.sessionId,
1377
+ blockCount: input.blockCount,
1378
+ sharedFrom: "launch-deck"
1379
+ }
1380
+ });
1381
+ return { id: commentId };
1382
+ }
1383
+ async function deleteDeck(commentId) {
1384
+ await callTool("communication_delete", { comment_id: commentId });
1385
+ }
1386
+
1177
1387
  // src/server/deck-serve.ts
1178
1388
  var DEFAULT_PORT = 52829;
1179
1389
  var MAX_PORT_SCAN = 3;
@@ -1189,14 +1399,14 @@ var MIME_TYPES = {
1189
1399
  ".woff2": "font/woff2"
1190
1400
  };
1191
1401
  function sessionDir(cwd, session) {
1192
- return import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", session);
1402
+ return import_node_path4.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", session);
1193
1403
  }
1194
1404
  function sessionJsonPath(cwd, session) {
1195
- return import_node_path3.default.join(sessionDir(cwd, session), "session.json");
1405
+ return import_node_path4.default.join(sessionDir(cwd, session), "session.json");
1196
1406
  }
1197
1407
  function readPersistedSession(cwd, session) {
1198
1408
  try {
1199
- const raw = import_node_fs3.default.readFileSync(sessionJsonPath(cwd, session), "utf-8");
1409
+ const raw = import_node_fs4.default.readFileSync(sessionJsonPath(cwd, session), "utf-8");
1200
1410
  return JSON.parse(raw);
1201
1411
  } catch {
1202
1412
  return null;
@@ -1204,7 +1414,7 @@ function readPersistedSession(cwd, session) {
1204
1414
  }
1205
1415
  function writePersistedSession(cwd, session, payload) {
1206
1416
  const dir = sessionDir(cwd, session);
1207
- import_node_fs3.default.mkdirSync(dir, { recursive: true });
1417
+ import_node_fs4.default.mkdirSync(dir, { recursive: true });
1208
1418
  const existing = readPersistedSession(cwd, session);
1209
1419
  const now = (/* @__PURE__ */ new Date()).toISOString();
1210
1420
  const next = {
@@ -1216,13 +1426,13 @@ function writePersistedSession(cwd, session, payload) {
1216
1426
  createdAt: existing?.createdAt ?? now,
1217
1427
  updatedAt: now
1218
1428
  };
1219
- import_node_fs3.default.writeFileSync(sessionJsonPath(cwd, session), JSON.stringify(next, null, 2));
1429
+ import_node_fs4.default.writeFileSync(sessionJsonPath(cwd, session), JSON.stringify(next, null, 2));
1220
1430
  return next;
1221
1431
  }
1222
1432
  function listPersistedSessions(cwd) {
1223
- const base = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files");
1224
- if (!import_node_fs3.default.existsSync(base)) return [];
1225
- const entries = import_node_fs3.default.readdirSync(base, { withFileTypes: true });
1433
+ const base = import_node_path4.default.join(cwd, LAUNCHSECURE_DIR, "deck-files");
1434
+ if (!import_node_fs4.default.existsSync(base)) return [];
1435
+ const entries = import_node_fs4.default.readdirSync(base, { withFileTypes: true });
1226
1436
  const sessions = [];
1227
1437
  for (const entry of entries) {
1228
1438
  if (!entry.isDirectory()) continue;
@@ -1234,20 +1444,106 @@ function listPersistedSessions(cwd) {
1234
1444
  blockCount: Array.isArray(persisted.blocks) ? persisted.blocks.length : 0,
1235
1445
  version: persisted.version,
1236
1446
  createdAt: persisted.createdAt,
1237
- updatedAt: persisted.updatedAt
1447
+ updatedAt: persisted.updatedAt,
1448
+ shared: Boolean(readSyncRecord(cwd, persisted.id))
1238
1449
  });
1239
1450
  }
1240
1451
  sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1241
1452
  return sessions;
1242
1453
  }
1243
1454
  function deletePersistedSession(cwd, session) {
1244
- const base = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files");
1455
+ const base = import_node_path4.default.join(cwd, LAUNCHSECURE_DIR, "deck-files");
1245
1456
  if (session === "all") {
1246
- if (import_node_fs3.default.existsSync(base)) import_node_fs3.default.rmSync(base, { recursive: true, force: true });
1457
+ if (import_node_fs4.default.existsSync(base)) import_node_fs4.default.rmSync(base, { recursive: true, force: true });
1247
1458
  return;
1248
1459
  }
1249
1460
  const dir = sessionDir(cwd, session);
1250
- if (import_node_fs3.default.existsSync(dir)) import_node_fs3.default.rmSync(dir, { recursive: true, force: true });
1461
+ if (import_node_fs4.default.existsSync(dir)) import_node_fs4.default.rmSync(dir, { recursive: true, force: true });
1462
+ }
1463
+ function syncJsonPath(cwd, session) {
1464
+ return import_node_path4.default.join(sessionDir(cwd, session), ".sync.json");
1465
+ }
1466
+ function readSyncRecord(cwd, session) {
1467
+ try {
1468
+ return JSON.parse(import_node_fs4.default.readFileSync(syncJsonPath(cwd, session), "utf-8"));
1469
+ } catch {
1470
+ return null;
1471
+ }
1472
+ }
1473
+ function writeSyncRecord(cwd, session, rec) {
1474
+ import_node_fs4.default.writeFileSync(syncJsonPath(cwd, session), JSON.stringify(rec, null, 2));
1475
+ }
1476
+ function escHtml2(s) {
1477
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1478
+ }
1479
+ function escAttr(s) {
1480
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
1481
+ }
1482
+ function readIframeArtifact(cwd, src) {
1483
+ if (typeof src !== "string" || !src.startsWith("/deck-files/")) return null;
1484
+ const rel = decodeURIComponent(src.slice("/deck-files/".length));
1485
+ if (rel.includes("..")) return null;
1486
+ try {
1487
+ return import_node_fs4.default.readFileSync(import_node_path4.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", rel), "utf-8");
1488
+ } catch {
1489
+ return null;
1490
+ }
1491
+ }
1492
+ function renderShareSection(cwd, block) {
1493
+ const label = block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
1494
+ switch (block?.type) {
1495
+ case "iframe": {
1496
+ const doc = readIframeArtifact(cwd, block.src) ?? "<p>(missing artifact)</p>";
1497
+ return `<section class="db">${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe></section>`;
1498
+ }
1499
+ case "rich-html":
1500
+ case "html":
1501
+ return `<section class="db">${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div></section>`;
1502
+ case "markdown":
1503
+ return `<section class="db">${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div></section>`;
1504
+ case "mermaid":
1505
+ return `<section class="db">${label}<pre class="mermaid">${escHtml2(typeof block.content === "string" ? block.content : "")}</pre></section>`;
1506
+ case "options": {
1507
+ const opts = Array.isArray(block.options) ? block.options : [];
1508
+ const lis = opts.map(
1509
+ (o) => `<li><strong>${escHtml2(String(o?.label ?? o?.id ?? ""))}</strong>${o?.description ? ` \u2014 ${escHtml2(String(o.description))}` : ""}</li>`
1510
+ ).join("");
1511
+ return `<section class="db">${label}<ul>${lis}</ul></section>`;
1512
+ }
1513
+ default:
1514
+ return `<section class="db">${label}</section>`;
1515
+ }
1516
+ }
1517
+ function buildShareHtml(cwd, session, blocks) {
1518
+ if (blocks.length === 1) {
1519
+ const only = blocks[0];
1520
+ if (only?.type === "iframe") {
1521
+ const doc = readIframeArtifact(cwd, only.src);
1522
+ if (doc) return doc;
1523
+ }
1524
+ }
1525
+ const list = blocks;
1526
+ const sections = list.map((b) => renderShareSection(cwd, b)).join("\n");
1527
+ const hasMd = list.some((b) => b?.type === "markdown");
1528
+ const hasMermaid = list.some((b) => b?.type === "mermaid");
1529
+ return `<!DOCTYPE html>
1530
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
1531
+ <title>${escHtml2(session)}</title>
1532
+ <style>
1533
+ body { margin:0; font-family: ui-sans-serif, system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
1534
+ .db { padding:20px 24px; border-bottom:1px solid #1e2433; }
1535
+ .db-title { font-size:13px; font-weight:600; margin:0 0 12px; color:#9aa4b2; text-transform:uppercase; letter-spacing:.05em; }
1536
+ .db-frame { width:100%; height:70vh; border:0; border-radius:8px; background:#fff; }
1537
+ .db-html { background:#fff; color:#111; border-radius:8px; padding:16px; overflow:auto; }
1538
+ ul { line-height:1.7; }
1539
+ .mermaid { background:#fff; border-radius:8px; padding:16px; }
1540
+ </style></head>
1541
+ <body>
1542
+ ${sections}
1543
+ ${hasMd ? `<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1544
+ <script>document.querySelectorAll('.db-md').forEach(function(el){try{el.innerHTML=marked.parse(el.dataset.md||'')}catch(e){el.textContent=el.dataset.md||''}});</script>` : ""}
1545
+ ${hasMermaid ? `<script type="module">import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';mermaid.initialize({startOnLoad:true,theme:'dark'});</script>` : ""}
1546
+ </body></html>`;
1251
1547
  }
1252
1548
  var pendingFeedback = /* @__PURE__ */ new Map();
1253
1549
  var lastRenderError = null;
@@ -1288,23 +1584,23 @@ function broadcastToClients(message) {
1288
1584
  }
1289
1585
  }
1290
1586
  function serveStatic(res, filePath) {
1291
- if (!import_node_fs3.default.existsSync(filePath) || !import_node_fs3.default.statSync(filePath).isFile()) return false;
1292
- const ext = import_node_path3.default.extname(filePath).toLowerCase();
1587
+ if (!import_node_fs4.default.existsSync(filePath) || !import_node_fs4.default.statSync(filePath).isFile()) return false;
1588
+ const ext = import_node_path4.default.extname(filePath).toLowerCase();
1293
1589
  const mime = MIME_TYPES[ext] ?? "application/octet-stream";
1294
1590
  res.writeHead(200, { "Content-Type": mime, "Cache-Control": "no-cache" });
1295
- import_node_fs3.default.createReadStream(filePath).pipe(res);
1591
+ import_node_fs4.default.createReadStream(filePath).pipe(res);
1296
1592
  return true;
1297
1593
  }
1298
1594
  function resolveClientDir() {
1299
- const sibling = import_node_path3.default.join(__dirname, "..", "deck-client");
1300
- if (import_node_fs3.default.existsSync(import_node_path3.default.join(sibling, "assets"))) return sibling;
1301
- const devDist = import_node_path3.default.join(__dirname, "..", "..", "dist", "deck-client");
1302
- if (import_node_fs3.default.existsSync(import_node_path3.default.join(devDist, "assets"))) return devDist;
1595
+ const sibling = import_node_path4.default.join(__dirname, "..", "deck-client");
1596
+ if (import_node_fs4.default.existsSync(import_node_path4.default.join(sibling, "assets"))) return sibling;
1597
+ const devDist = import_node_path4.default.join(__dirname, "..", "..", "dist", "deck-client");
1598
+ if (import_node_fs4.default.existsSync(import_node_path4.default.join(devDist, "assets"))) return devDist;
1303
1599
  return sibling;
1304
1600
  }
1305
1601
  function serveIndex(res, clientDir) {
1306
- const indexPath = import_node_path3.default.join(clientDir, "index.html");
1307
- if (!import_node_fs3.default.existsSync(indexPath)) {
1602
+ const indexPath = import_node_path4.default.join(clientDir, "index.html");
1603
+ if (!import_node_fs4.default.existsSync(indexPath)) {
1308
1604
  res.writeHead(500, { "Content-Type": "text/plain" });
1309
1605
  res.end(`LaunchDeck client bundle not found at ${clientDir}. Run 'npm run build:client'.`);
1310
1606
  return;
@@ -1408,15 +1704,15 @@ async function startDeckServer(opts = {}) {
1408
1704
  const baseUrl = `/deck-files/${sessionEncoded}`;
1409
1705
  const html = generateBlastRadiusHtml(block.manifest, baseUrl);
1410
1706
  const report = generateBlastRadiusReport(block.manifest);
1411
- const dir = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", body.session);
1412
- import_node_fs3.default.mkdirSync(dir, { recursive: true });
1413
- import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, "blast-radius.html"), html);
1414
- import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, "blast-radius-report.md"), report);
1415
- import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, "blast-radius-manifest.json"), JSON.stringify(block.manifest, null, 2));
1707
+ const dir = import_node_path4.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", body.session);
1708
+ import_node_fs4.default.mkdirSync(dir, { recursive: true });
1709
+ import_node_fs4.default.writeFileSync(import_node_path4.default.join(dir, "blast-radius.html"), html);
1710
+ import_node_fs4.default.writeFileSync(import_node_path4.default.join(dir, "blast-radius-report.md"), report);
1711
+ import_node_fs4.default.writeFileSync(import_node_path4.default.join(dir, "blast-radius-manifest.json"), JSON.stringify(block.manifest, null, 2));
1416
1712
  const contractData = generateContract(block.manifest);
1417
1713
  const contractMd = contractToMarkdown(contractData);
1418
- import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, "contract.json"), JSON.stringify(contractData, null, 2));
1419
- import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, "contract.md"), contractMd);
1714
+ import_node_fs4.default.writeFileSync(import_node_path4.default.join(dir, "contract.json"), JSON.stringify(contractData, null, 2));
1715
+ import_node_fs4.default.writeFileSync(import_node_path4.default.join(dir, "contract.md"), contractMd);
1420
1716
  block.type = "iframe";
1421
1717
  block.src = `${baseUrl}/blast-radius.html`;
1422
1718
  delete block.manifest;
@@ -1443,9 +1739,9 @@ async function startDeckServer(opts = {}) {
1443
1739
  const sessionEncoded = encodeURIComponent(body.session);
1444
1740
  const baseUrl = `/deck-files/${sessionEncoded}`;
1445
1741
  const filename = `${slugify(block.label)}.html`;
1446
- const dir = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", body.session);
1447
- import_node_fs3.default.mkdirSync(dir, { recursive: true });
1448
- import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, filename), html);
1742
+ const dir = import_node_path4.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", body.session);
1743
+ import_node_fs4.default.mkdirSync(dir, { recursive: true });
1744
+ import_node_fs4.default.writeFileSync(import_node_path4.default.join(dir, filename), html);
1449
1745
  block.type = "iframe";
1450
1746
  block.src = `${baseUrl}/${filename}`;
1451
1747
  delete block.content;
@@ -1477,6 +1773,79 @@ async function startDeckServer(opts = {}) {
1477
1773
  jsonResponse(res, 200, { sessions: listPersistedSessions(cwd) });
1478
1774
  return;
1479
1775
  }
1776
+ const shareMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/share$/);
1777
+ if (shareMatch && req.method === "POST") {
1778
+ const id = decodeURIComponent(shareMatch[1]);
1779
+ const persisted = readPersistedSession(cwd, id);
1780
+ if (!persisted) {
1781
+ jsonResponse(res, 404, { ok: false, error: "session not found" });
1782
+ return;
1783
+ }
1784
+ try {
1785
+ loadMcpConfig(cwd);
1786
+ } catch (err) {
1787
+ jsonResponse(res, 400, {
1788
+ ok: false,
1789
+ error: `Cloud sharing needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}`
1790
+ });
1791
+ return;
1792
+ }
1793
+ try {
1794
+ const blocks = Array.isArray(persisted.blocks) ? persisted.blocks : [];
1795
+ const html = buildShareHtml(cwd, id, blocks);
1796
+ const types = blocks.map((b) => b?.type).filter(Boolean);
1797
+ const body = `Shared deck "${id}" \u2014 ${blocks.length} block${blocks.length === 1 ? "" : "s"}${types.length ? ` (${types.join(", ")})` : ""}. Open the Decks tab in the Communication Center to view.`;
1798
+ const input = { title: id, body, html, sessionId: id, blockCount: blocks.length };
1799
+ const existing2 = readSyncRecord(cwd, id);
1800
+ let resourceId;
1801
+ let updated = false;
1802
+ if (existing2?.resourceId) {
1803
+ try {
1804
+ ({ id: resourceId } = await updateDeck(existing2.resourceId, input));
1805
+ updated = true;
1806
+ } catch {
1807
+ ({ id: resourceId } = await writeDeck(input));
1808
+ }
1809
+ } else {
1810
+ ({ id: resourceId } = await writeDeck(input));
1811
+ }
1812
+ writeSyncRecord(cwd, id, {
1813
+ resourceId,
1814
+ sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
1815
+ version: persisted.version
1816
+ });
1817
+ jsonResponse(res, 200, { ok: true, resourceId, updated });
1818
+ } catch (err) {
1819
+ jsonResponse(res, 500, { ok: false, error: String(err) });
1820
+ }
1821
+ return;
1822
+ }
1823
+ const remoteDelMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/remote$/);
1824
+ if (remoteDelMatch && req.method === "DELETE") {
1825
+ const id = decodeURIComponent(remoteDelMatch[1]);
1826
+ const sync = readSyncRecord(cwd, id);
1827
+ if (!sync?.resourceId) {
1828
+ jsonResponse(res, 200, { ok: true, deleted: false, reason: "not shared" });
1829
+ return;
1830
+ }
1831
+ try {
1832
+ loadMcpConfig(cwd);
1833
+ } catch (err) {
1834
+ jsonResponse(res, 400, { ok: false, error: `Cloud delete needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}` });
1835
+ return;
1836
+ }
1837
+ try {
1838
+ await deleteDeck(sync.resourceId);
1839
+ try {
1840
+ import_node_fs4.default.rmSync(syncJsonPath(cwd, id), { force: true });
1841
+ } catch {
1842
+ }
1843
+ jsonResponse(res, 200, { ok: true, deleted: true });
1844
+ } catch (err) {
1845
+ jsonResponse(res, 500, { ok: false, error: String(err) });
1846
+ }
1847
+ return;
1848
+ }
1480
1849
  const sessionMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)$/);
1481
1850
  if (sessionMatch) {
1482
1851
  const id = decodeURIComponent(sessionMatch[1]);
@@ -1579,7 +1948,7 @@ async function startDeckServer(opts = {}) {
1579
1948
  res.end("Forbidden");
1580
1949
  return;
1581
1950
  }
1582
- const filePath = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", relative);
1951
+ const filePath = import_node_path4.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", relative);
1583
1952
  if (serveStatic(res, filePath)) return;
1584
1953
  res.writeHead(404);
1585
1954
  res.end("Not found");
@@ -1592,25 +1961,25 @@ async function startDeckServer(opts = {}) {
1592
1961
  res.end("Forbidden");
1593
1962
  return;
1594
1963
  }
1595
- const filePath = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", relative);
1596
- if (!import_node_fs3.default.existsSync(filePath) || !import_node_fs3.default.statSync(filePath).isFile()) {
1964
+ const filePath = import_node_path4.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", relative);
1965
+ if (!import_node_fs4.default.existsSync(filePath) || !import_node_fs4.default.statSync(filePath).isFile()) {
1597
1966
  res.writeHead(404);
1598
1967
  res.end("Not found");
1599
1968
  return;
1600
1969
  }
1601
- const ext = import_node_path3.default.extname(filePath).toLowerCase();
1970
+ const ext = import_node_path4.default.extname(filePath).toLowerCase();
1602
1971
  const mime = MIME_TYPES[ext] ?? "application/octet-stream";
1603
- const filename = import_node_path3.default.basename(filePath);
1972
+ const filename = import_node_path4.default.basename(filePath);
1604
1973
  res.writeHead(200, {
1605
1974
  "Content-Type": mime,
1606
1975
  "Content-Disposition": `attachment; filename="${filename}"`,
1607
1976
  "Cache-Control": "no-cache"
1608
1977
  });
1609
- import_node_fs3.default.createReadStream(filePath).pipe(res);
1978
+ import_node_fs4.default.createReadStream(filePath).pipe(res);
1610
1979
  return;
1611
1980
  }
1612
1981
  if (url2.pathname !== "/") {
1613
- const staticPath = import_node_path3.default.join(clientDir, url2.pathname);
1982
+ const staticPath = import_node_path4.default.join(clientDir, url2.pathname);
1614
1983
  if (serveStatic(res, staticPath)) return;
1615
1984
  }
1616
1985
  serveIndex(res, clientDir);