@launchsecure/launch-kit 0.0.39 → 0.0.41

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 (113) hide show
  1. package/dist/chart-client/assets/index-Dd6IotOZ.css +1 -0
  2. package/dist/chart-client/index.html +2 -2
  3. package/dist/client/assets/index-DE0uje6k.css +32 -0
  4. package/dist/client/index.html +2 -2
  5. package/dist/council-client/assets/index-CGYusOCK.css +1 -0
  6. package/dist/council-client/assets/{index-jjBWyhry.js → index-DkTFX53U.js} +1 -1
  7. package/dist/council-client/index.html +2 -2
  8. package/dist/deck-client/assets/_baseUniq-mvYvzeEJ.js +1 -0
  9. package/dist/deck-client/assets/arc-CX4ylnp2.js +1 -0
  10. package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-BkR-5IRK.js +36 -0
  11. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CwAGy9lU.js → blockDiagram-DXYQGD6D-DVNQht7c.js} +2 -2
  12. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-L_g_SS21.js → c4Diagram-AHTNJAMY-Cbq1rlG8.js} +2 -2
  13. package/dist/deck-client/assets/channel-B9GC-CLn.js +1 -0
  14. package/dist/deck-client/assets/chunk-4BX2VUAB-D58Co4lU.js +1 -0
  15. package/dist/deck-client/assets/{chunk-4TB4RGXK-Bk0FUbxU.js → chunk-4TB4RGXK-BYvhTm3d.js} +1 -1
  16. package/dist/deck-client/assets/chunk-55IACEB6-oWukUhYg.js +1 -0
  17. package/dist/deck-client/assets/chunk-EDXVE4YY-Cm58kVnZ.js +1 -0
  18. package/dist/deck-client/assets/{chunk-FMBD7UC4-DqOvWr1k.js → chunk-FMBD7UC4-Dg-i7kzi.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-OYMX7WX6-1Kd7yK5u.js → chunk-OYMX7WX6-C72wigPl.js} +1 -1
  20. package/dist/deck-client/assets/chunk-QZHKN3VN-CLgeuAKw.js +1 -0
  21. package/dist/deck-client/assets/chunk-YZCP3GAM-HDDlJ5oI.js +1 -0
  22. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CFBvYQ9j.js +1 -0
  23. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CFBvYQ9j.js +1 -0
  24. package/dist/deck-client/assets/clone-n-WQlAGe.js +1 -0
  25. package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CUXQKg2M.js +1 -0
  26. package/dist/deck-client/assets/dagre-KV5264BT-C5M-fVDc.js +4 -0
  27. package/dist/deck-client/assets/diagram-5BDNPKRD-CcVsQ0S8.js +10 -0
  28. package/dist/deck-client/assets/diagram-G4DWMVQ6-DJswXyep.js +24 -0
  29. package/dist/deck-client/assets/diagram-MMDJMWI5-CGT76fm1.js +43 -0
  30. package/dist/deck-client/assets/diagram-TYMM5635-BBsYUNN6.js +24 -0
  31. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-aiv9GZnL.js → erDiagram-SMLLAGMA-DKWYEHQS.js} +2 -2
  32. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-C6Fhvtsy.js → flowDiagram-DWJPFMVM-DLuDYIKT.js} +2 -2
  33. package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-B19b6Qtj.js +292 -0
  34. package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-BYLAfYVS.js +106 -0
  35. package/dist/deck-client/assets/graph-CfzQUfPh.js +1 -0
  36. package/dist/deck-client/assets/index-DlwdTgE_.js +892 -0
  37. package/dist/deck-client/assets/index-evAPhGvM.css +1 -0
  38. package/dist/deck-client/assets/infoDiagram-42DDH7IO-Dp3mUA9c.js +2 -0
  39. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BwCUmUVt.js → ishikawaDiagram-UXIWVN3A-BhrNX_jI.js} +5 -5
  40. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C6qoqJmJ.js → journeyDiagram-VCZTEJTY-B5lJI492.js} +2 -2
  41. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-Dz1Tt3sA.js → kanban-definition-6JOO6SKY-D9-lmhQf.js} +2 -2
  42. package/dist/deck-client/assets/layout-CfIe_Su8.js +1 -0
  43. package/dist/deck-client/assets/linear-09ZFRoh_.js +1 -0
  44. package/dist/deck-client/assets/mermaid.core-BaQyIOvj.js +309 -0
  45. package/dist/deck-client/assets/min-CYwCzYaL.js +1 -0
  46. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CfXcK1qH.js → mindmap-definition-QFDTVHPH-CouFxf6C.js} +2 -2
  47. package/dist/deck-client/assets/pieDiagram-DEJITSTG-DMB1ufC0.js +30 -0
  48. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-CXwvZ1i1.js → quadrantDiagram-34T5L4WZ-CBiOKudN.js} +2 -2
  49. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cl6xm0fR.js → requirementDiagram-MS252O5E-BMc3GJkx.js} +2 -2
  50. package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-CxACUncm.js +10 -0
  51. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-BC1MYBn6.js → sequenceDiagram-FGHM5R23-Ch-P3Mzc.js} +2 -2
  52. package/dist/deck-client/assets/stateDiagram-FHFEXIEX-Cy8n7Yzk.js +1 -0
  53. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-C14VKCzi.js +1 -0
  54. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DKnITsD4.js → timeline-definition-GMOUNBTQ-C2V4sSkm.js} +2 -2
  55. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-BdajXRrh.js → vennDiagram-DHZGUBPP-YOqt4VbE.js} +2 -2
  56. package/dist/deck-client/assets/wardley-RL74JXVD-Bxo5x40D.js +162 -0
  57. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-B2hDCDl2.js → wardleyDiagram-NUSXRM2D-DW9SOqbx.js} +2 -2
  58. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CvnYFs51.js → xychartDiagram-5P7HB3ND-D-rZvZOL.js} +2 -2
  59. package/dist/deck-client/index.html +2 -2
  60. package/dist/server/beacon-monitor-entry.js +106 -24
  61. package/dist/server/chart-serve.js +544 -247
  62. package/dist/server/cli.js +1016 -324
  63. package/dist/server/council-entry.js +23 -4
  64. package/dist/server/council-serve.js +22 -3
  65. package/dist/server/deck-mcp-entry.js +523 -125
  66. package/dist/server/deck-serve.js +326 -40
  67. package/dist/server/graph-mcp-entry.js +1160 -357
  68. package/dist/server/init-entry.js +17 -7
  69. package/dist/server/orbit-entry.js +91 -7
  70. package/dist/server/recall-entry.js +94 -12
  71. package/dist/server/rover-entry.js +1 -1
  72. package/package.json +1 -1
  73. package/scaffolds/ls-marketplace/plugins/kit/skills/comms/SKILL.md +34 -1
  74. package/scaffolds/ls-marketplace/plugins/kit/skills/gen-test/SKILL.md +126 -0
  75. package/scaffolds/statusline/statusline-mcp.sh +68 -19
  76. package/scaffolds/statusline/statusline-wrapper.sh +12 -9
  77. package/dist/chart-client/assets/index-ysGpLeOW.css +0 -1
  78. package/dist/client/assets/index-CMN3tlGP.css +0 -32
  79. package/dist/council-client/assets/index-ChmNX6bZ.css +0 -1
  80. package/dist/deck-client/assets/_baseUniq-DOrnEQMI.js +0 -1
  81. package/dist/deck-client/assets/arc-DOWK7V3m.js +0 -1
  82. package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DPhzvk7q.js +0 -36
  83. package/dist/deck-client/assets/channel-DqiACUUq.js +0 -1
  84. package/dist/deck-client/assets/chunk-4BX2VUAB-RKm0LXpu.js +0 -1
  85. package/dist/deck-client/assets/chunk-55IACEB6-Cl3hja-M.js +0 -1
  86. package/dist/deck-client/assets/chunk-EDXVE4YY-CNIMQCV2.js +0 -1
  87. package/dist/deck-client/assets/chunk-QZHKN3VN-6_kraYpP.js +0 -1
  88. package/dist/deck-client/assets/chunk-YZCP3GAM-FgAwIWlo.js +0 -1
  89. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-D23cq2C3.js +0 -1
  90. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-D23cq2C3.js +0 -1
  91. package/dist/deck-client/assets/clone-C7jSigGq.js +0 -1
  92. package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CigVnnPr.js +0 -1
  93. package/dist/deck-client/assets/dagre-KV5264BT-DHZXTktX.js +0 -4
  94. package/dist/deck-client/assets/diagram-5BDNPKRD-H5k0eauU.js +0 -10
  95. package/dist/deck-client/assets/diagram-G4DWMVQ6-Bg3dFhSY.js +0 -24
  96. package/dist/deck-client/assets/diagram-MMDJMWI5-CQLC410N.js +0 -43
  97. package/dist/deck-client/assets/diagram-TYMM5635-DFTCHVkP.js +0 -24
  98. package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DSaGMPM4.js +0 -292
  99. package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-DMjL2Vix.js +0 -106
  100. package/dist/deck-client/assets/graph-B7Vn5lkK.js +0 -1
  101. package/dist/deck-client/assets/index-BD36e-tD.js +0 -1196
  102. package/dist/deck-client/assets/index-CGbNOpk9.css +0 -1
  103. package/dist/deck-client/assets/infoDiagram-42DDH7IO-mNi4iygG.js +0 -2
  104. package/dist/deck-client/assets/layout-CZTyRhOG.js +0 -1
  105. package/dist/deck-client/assets/linear--7n7iEvd.js +0 -1
  106. package/dist/deck-client/assets/min-Bh130DN8.js +0 -1
  107. package/dist/deck-client/assets/pieDiagram-DEJITSTG-DjVHLAVw.js +0 -30
  108. package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-BOH9sLyh.js +0 -10
  109. package/dist/deck-client/assets/stateDiagram-FHFEXIEX-kNp9bv8K.js +0 -1
  110. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-hRsAFc2t.js +0 -1
  111. package/dist/deck-client/assets/wardley-RL74JXVD-BL802-su.js +0 -162
  112. /package/dist/chart-client/assets/{index-BlsuXuQ1.js → index-CrYM1-ac.js} +0 -0
  113. /package/dist/client/assets/{index-BA7BHBWT.js → index-BoIjawzY.js} +0 -0
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var deck_serve_exports = {};
32
32
  __export(deck_serve_exports, {
33
33
  broadcastToClients: () => broadcastToClients,
34
+ buildSharePayload: () => buildSharePayload,
34
35
  consumeRenderError: () => consumeRenderError,
35
36
  createFeedbackWaiter: () => createFeedbackWaiter,
36
37
  resolveFeedback: () => resolveFeedback,
@@ -1227,7 +1228,7 @@ function readCredFile(repoRoot) {
1227
1228
  var _config = null;
1228
1229
  var _requestId = 0;
1229
1230
  var _sessionId = null;
1230
- function mcpConfigFromCourse(projectRoot) {
1231
+ function mcpConfigForProfile(projectRoot, name) {
1231
1232
  let cred;
1232
1233
  try {
1233
1234
  cred = readCredFile(projectRoot);
@@ -1237,7 +1238,7 @@ function mcpConfigFromCourse(projectRoot) {
1237
1238
  if (!cred) return null;
1238
1239
  const nested = toNested(cred);
1239
1240
  if (!nested) return null;
1240
- const profile = nested.profiles[nested.active];
1241
+ const profile = nested.profiles[name];
1241
1242
  if (!profile?.pat || !profile.serverUrl || !profile.orgSlug || !profile.projectSlug) {
1242
1243
  return null;
1243
1244
  }
@@ -1250,6 +1251,37 @@ function mcpConfigFromCourse(projectRoot) {
1250
1251
  }
1251
1252
  };
1252
1253
  }
1254
+ function mcpConfigFromCourse(projectRoot) {
1255
+ let cred;
1256
+ try {
1257
+ cred = readCredFile(projectRoot);
1258
+ } catch {
1259
+ return null;
1260
+ }
1261
+ if (!cred) return null;
1262
+ const nested = toNested(cred);
1263
+ if (!nested) return null;
1264
+ return mcpConfigForProfile(projectRoot, nested.active);
1265
+ }
1266
+ function listCourses(projectRoot) {
1267
+ let cred;
1268
+ try {
1269
+ cred = readCredFile(projectRoot);
1270
+ } catch {
1271
+ return [];
1272
+ }
1273
+ if (!cred) return [];
1274
+ const nested = toNested(cred);
1275
+ if (!nested) return [];
1276
+ return Object.entries(nested.profiles).map(([name, p]) => ({
1277
+ name,
1278
+ org: p.orgSlug ?? "",
1279
+ project: p.projectSlug ?? "",
1280
+ serverUrl: p.serverUrl ?? "",
1281
+ active: name === nested.active,
1282
+ usable: Boolean(p.pat && p.serverUrl && p.orgSlug && p.projectSlug)
1283
+ }));
1284
+ }
1253
1285
  function loadMcpConfig(projectRoot) {
1254
1286
  if (_config) return _config;
1255
1287
  const fromCourse = mcpConfigFromCourse(projectRoot);
@@ -1276,6 +1308,24 @@ function loadMcpConfig(projectRoot) {
1276
1308
  };
1277
1309
  return _config;
1278
1310
  }
1311
+ var _overrideLock = Promise.resolve();
1312
+ function withMcpConfig(override, fn) {
1313
+ const run = async () => {
1314
+ const prevConfig = _config;
1315
+ const prevSession = _sessionId;
1316
+ _config = override;
1317
+ _sessionId = null;
1318
+ try {
1319
+ return await fn();
1320
+ } finally {
1321
+ _config = prevConfig;
1322
+ _sessionId = prevSession;
1323
+ }
1324
+ };
1325
+ const result = _overrideLock.then(run, run);
1326
+ _overrideLock = result.then(() => void 0, () => void 0);
1327
+ return result;
1328
+ }
1279
1329
  function parseSSE(text) {
1280
1330
  const lines = text.split("\n");
1281
1331
  for (const line of lines) {
@@ -1350,7 +1400,13 @@ async function callTool(toolName, args) {
1350
1400
  }
1351
1401
  const textContent = result.result?.content?.[0]?.text;
1352
1402
  if (!textContent) return null;
1353
- return JSON.parse(textContent);
1403
+ try {
1404
+ return JSON.parse(textContent);
1405
+ } catch {
1406
+ const match = textContent.match(/──+\s*Error\s*──+\s*\n([\s\S]*?)(?:\n\n──|$)/i);
1407
+ const message = (match?.[1] ?? textContent).trim();
1408
+ throw new Error(message || "MCP tool returned an unparseable response");
1409
+ }
1354
1410
  }
1355
1411
  async function writeDeck(input) {
1356
1412
  const result = await callTool("communication_write", {
@@ -1359,6 +1415,7 @@ async function writeDeck(input) {
1359
1415
  resource_type: "deck",
1360
1416
  fields: {
1361
1417
  deckHtml: input.html,
1418
+ deckBlocks: input.blocks,
1362
1419
  sessionId: input.sessionId,
1363
1420
  blockCount: input.blockCount,
1364
1421
  sharedFrom: "launch-deck"
@@ -1373,6 +1430,7 @@ async function updateDeck(commentId, input) {
1373
1430
  body: input.body,
1374
1431
  fields: {
1375
1432
  deckHtml: input.html,
1433
+ deckBlocks: input.blocks,
1376
1434
  sessionId: input.sessionId,
1377
1435
  blockCount: input.blockCount,
1378
1436
  sharedFrom: "launch-deck"
@@ -1383,6 +1441,31 @@ async function updateDeck(commentId, input) {
1383
1441
  async function deleteDeck(commentId) {
1384
1442
  await callTool("communication_delete", { comment_id: commentId });
1385
1443
  }
1444
+ function currentMcpOrigin() {
1445
+ const config = _config;
1446
+ if (!config) throw new Error("MCP config not loaded \u2014 call loadMcpConfig first");
1447
+ return new URL(config.url).origin;
1448
+ }
1449
+ async function createDeckShareLink(commentId) {
1450
+ const result = await callTool("deck_share_link", {
1451
+ comment_id: commentId,
1452
+ action: "create"
1453
+ });
1454
+ const token = result.link.token;
1455
+ return {
1456
+ linkId: result.link.id,
1457
+ token,
1458
+ shareUrl: `${currentMcpOrigin()}/s/deck/${token}`,
1459
+ reused: Boolean(result.reused)
1460
+ };
1461
+ }
1462
+ async function revokeDeckShareLink(commentId, linkId) {
1463
+ await callTool("deck_share_link", {
1464
+ comment_id: commentId,
1465
+ action: "revoke",
1466
+ link_id: linkId
1467
+ });
1468
+ }
1386
1469
 
1387
1470
  // src/server/deck-serve.ts
1388
1471
  var DEFAULT_PORT = 52829;
@@ -1447,7 +1530,9 @@ function listPersistedSessions(cwd) {
1447
1530
  createdAt: persisted.createdAt,
1448
1531
  updatedAt: persisted.updatedAt,
1449
1532
  shared: Boolean(sync),
1450
- stale: Boolean(sync) && persisted.version > (sync?.version ?? 0)
1533
+ stale: Boolean(sync) && persisted.version > (sync?.version ?? 0),
1534
+ shareUrl: sync?.publicShareUrl,
1535
+ course: sync?.course
1451
1536
  });
1452
1537
  }
1453
1538
  sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -1475,6 +1560,24 @@ function readSyncRecord(cwd, session) {
1475
1560
  function writeSyncRecord(cwd, session, rec) {
1476
1561
  import_node_fs4.default.writeFileSync(syncJsonPath(cwd, session), JSON.stringify(rec, null, 2));
1477
1562
  }
1563
+ function resolveShareConfig(cwd, course) {
1564
+ if (course) {
1565
+ const override = mcpConfigForProfile(cwd, course);
1566
+ if (!override) {
1567
+ return { error: `Course "${course}" is not a usable profile in .launch-secure.cred.config (missing pat/serverUrl/orgSlug/projectSlug?).` };
1568
+ }
1569
+ return { override };
1570
+ }
1571
+ try {
1572
+ loadMcpConfig(cwd);
1573
+ } catch (err) {
1574
+ return { error: `Cloud sharing needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}` };
1575
+ }
1576
+ return { override: null };
1577
+ }
1578
+ function runWithShareConfig(override, fn) {
1579
+ return override ? withMcpConfig(override, fn) : fn();
1580
+ }
1478
1581
  function escHtml2(s) {
1479
1582
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1480
1583
  }
@@ -1491,31 +1594,49 @@ function readIframeArtifact(cwd, src) {
1491
1594
  return null;
1492
1595
  }
1493
1596
  }
1494
- function renderShareSection(cwd, block) {
1495
- const label = block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
1597
+ function renderShareSection(cwd, block, showLabel = true) {
1598
+ const label = showLabel && block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
1496
1599
  switch (block?.type) {
1497
1600
  case "iframe": {
1498
1601
  const doc = readIframeArtifact(cwd, block.src) ?? "<p>(missing artifact)</p>";
1499
- return `<section class="db">${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe></section>`;
1602
+ return `${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe>`;
1500
1603
  }
1501
1604
  case "rich-html":
1502
1605
  case "html":
1503
- return `<section class="db">${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div></section>`;
1606
+ return `${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div>`;
1504
1607
  case "markdown":
1505
- return `<section class="db">${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div></section>`;
1608
+ return `${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div>`;
1506
1609
  case "mermaid":
1507
- return `<section class="db">${label}<pre class="mermaid">${escHtml2(typeof block.content === "string" ? block.content : "")}</pre></section>`;
1610
+ if (typeof block.renderedSvg === "string" && block.renderedSvg.trim()) {
1611
+ return `${label}<div class="mermaid-svg">${block.renderedSvg}</div>`;
1612
+ }
1613
+ return `${label}<pre class="mermaid">${escHtml2(typeof block.content === "string" ? block.content : "")}</pre>`;
1508
1614
  case "options": {
1509
1615
  const opts = Array.isArray(block.options) ? block.options : [];
1510
- const lis = opts.map(
1511
- (o) => `<li><strong>${escHtml2(String(o?.label ?? o?.id ?? ""))}</strong>${o?.description ? ` \u2014 ${escHtml2(String(o.description))}` : ""}</li>`
1616
+ const cards = opts.map(
1617
+ (o) => `<div class="db-option"><div class="db-option-label">${escHtml2(String(o?.label ?? o?.id ?? ""))}</div>${o?.description ? `<div class="db-option-desc">${escHtml2(String(o.description))}</div>` : ""}${o?.preview ? `<pre class="db-option-pre">${escHtml2(String(o.preview))}</pre>` : ""}</div>`
1512
1618
  ).join("");
1513
- return `<section class="db">${label}<ul>${lis}</ul></section>`;
1619
+ return `${label}<div class="db-options">${cards}</div>`;
1514
1620
  }
1515
1621
  default:
1516
- return `<section class="db">${label}</section>`;
1622
+ return label;
1517
1623
  }
1518
1624
  }
1625
+ function buildSharePayload(cwd, session, blocks, svgByIndex = {}) {
1626
+ const enriched = blocks.map((b, i) => {
1627
+ const svg = svgByIndex[String(i)];
1628
+ return b?.type === "mermaid" && typeof svg === "string" && svg.trim() ? { ...b, renderedSvg: svg } : b;
1629
+ });
1630
+ const html = buildShareHtml(cwd, session, enriched);
1631
+ const cloudBlocks = enriched.map((b) => {
1632
+ if (b?.type === "iframe" && typeof b.src === "string") {
1633
+ const content = readIframeArtifact(cwd, b.src);
1634
+ return content ? { ...b, content } : b;
1635
+ }
1636
+ return b;
1637
+ });
1638
+ return { html, cloudBlocks };
1639
+ }
1519
1640
  function buildShareHtml(cwd, session, blocks) {
1520
1641
  if (blocks.length === 1) {
1521
1642
  const only = blocks[0];
@@ -1525,30 +1646,61 @@ function buildShareHtml(cwd, session, blocks) {
1525
1646
  }
1526
1647
  }
1527
1648
  const list = blocks;
1528
- const sections = list.map((b) => renderShareSection(cwd, b)).join("\n");
1649
+ const multi = list.length > 1;
1650
+ const tabBar = multi ? `<nav class="deck-tabs">${list.map(
1651
+ (b, i) => `<button class="deck-tab${i === 0 ? " active" : ""}" data-tab-btn="${i}">${escHtml2(String(b?.label ?? `Tab ${i + 1}`))}</button>`
1652
+ ).join("")}</nav>` : "";
1653
+ const sections = list.map(
1654
+ (b, i) => `<section class="db" data-tab="${i}"${multi && i > 0 ? " hidden" : ""}>${renderShareSection(cwd, b, !multi)}</section>`
1655
+ ).join("\n");
1529
1656
  const hasMd = list.some((b) => b?.type === "markdown");
1530
- const hasMermaid = list.some((b) => b?.type === "mermaid");
1657
+ const hasUnrenderedMermaid = list.some(
1658
+ (b) => b?.type === "mermaid" && !(typeof b?.renderedSvg === "string" && b.renderedSvg.trim())
1659
+ );
1531
1660
  return `<!DOCTYPE html>
1532
1661
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
1533
1662
  <title>${escHtml2(session)}</title>
1663
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,100..1000&display=swap" rel="stylesheet">
1534
1664
  <style>
1535
- body { margin:0; font-family: ui-sans-serif, system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
1665
+ body { margin:0; font-family: 'DM Sans', system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
1536
1666
  .db { padding:20px 24px; border-bottom:1px solid #1e2433; }
1537
1667
  .db-title { font-size:13px; font-weight:600; margin:0 0 12px; color:#9aa4b2; text-transform:uppercase; letter-spacing:.05em; }
1538
1668
  .db-frame { width:100%; height:70vh; border:0; border-radius:8px; background:#fff; }
1539
1669
  .db-html { background:#fff; color:#111; border-radius:8px; padding:16px; overflow:auto; }
1540
1670
  ul { line-height:1.7; }
1541
- .mermaid { background:#fff; border-radius:8px; padding:16px; }
1671
+ /* The embedded SVG is dark-themed (captured from the deck client); keep its
1672
+ backdrop transparent so it sits on the dark page, matching the in-app
1673
+ DeckView \u2014 not a white card. The CDN-source fallback also renders dark. */
1674
+ .mermaid { background:transparent; border-radius:8px; padding:16px; }
1675
+ .mermaid-svg { background:transparent; border-radius:8px; padding:16px; overflow:auto; }
1676
+ .mermaid-svg svg { max-width:100%; height:auto; }
1677
+ .db-options { display:grid; gap:12px; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); }
1678
+ .db-option { border:2px solid #1e2433; border-radius:8px; padding:16px; }
1679
+ .db-option-label { font-size:14px; font-weight:600; color:#e6e9ef; }
1680
+ .db-option-desc { margin-top:4px; font-size:12px; color:#9aa4b2; }
1681
+ .db-option-pre { margin-top:8px; padding:8px; border-radius:6px; background:#11151f; font-size:12px; overflow:auto; }
1682
+ .deck-tabs { position:sticky; top:0; z-index:5; display:flex; flex-wrap:wrap; gap:2px; background:#0b0e14; border-bottom:1px solid #1e2433; padding:0 16px; }
1683
+ .deck-tab { appearance:none; border:0; background:transparent; color:#9aa4b2; font:inherit; font-size:13px; font-weight:500; padding:12px 16px; cursor:pointer; border-bottom:2px solid transparent; }
1684
+ .deck-tab:hover { color:#e6e9ef; }
1685
+ .deck-tab.active { color:#c084fc; border-bottom-color:#c084fc; }
1686
+ .db[hidden] { display:none; }
1542
1687
  </style></head>
1543
1688
  <body>
1689
+ ${tabBar}
1544
1690
  ${sections}
1691
+ ${multi ? `<script>(function(){var b=[].slice.call(document.querySelectorAll('[data-tab-btn]')),s=[].slice.call(document.querySelectorAll('[data-tab]'));b.forEach(function(x){x.addEventListener('click',function(){var i=x.getAttribute('data-tab-btn');s.forEach(function(y){y.hidden=y.getAttribute('data-tab')!==i;});b.forEach(function(z){z.classList.toggle('active',z===x);});});});})();</script>` : ""}
1545
1692
  ${hasMd ? `<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1546
1693
  <script>document.querySelectorAll('.db-md').forEach(function(el){try{el.innerHTML=marked.parse(el.dataset.md||'')}catch(e){el.textContent=el.dataset.md||''}});</script>` : ""}
1547
- ${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>` : ""}
1694
+ ${hasUnrenderedMermaid ? `<script type="module">import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';mermaid.initialize({startOnLoad:true,theme:document.documentElement.classList.contains('light')?'default':'dark',securityLevel:'loose',fontFamily:"'DM Sans', system-ui, sans-serif"});</script>` : ""}
1548
1695
  </body></html>`;
1549
1696
  }
1550
1697
  var pendingFeedback = /* @__PURE__ */ new Map();
1551
1698
  var lastRenderError = null;
1699
+ var activeSession = null;
1700
+ var discussSessions = [];
1701
+ function broadcastDiscuss() {
1702
+ broadcastToClients({ type: "discuss_session", sessions: discussSessions.map((d) => d.id) });
1703
+ }
1552
1704
  function consumeRenderError() {
1553
1705
  const err = lastRenderError;
1554
1706
  lastRenderError = null;
@@ -1685,6 +1837,10 @@ async function startDeckServer(opts = {}) {
1685
1837
  jsonResponse(res, 200, { config: cfg });
1686
1838
  return;
1687
1839
  }
1840
+ if (req.method === "GET" && url2.pathname === "/api/deck/courses") {
1841
+ jsonResponse(res, 200, { courses: listCourses(cwd) });
1842
+ return;
1843
+ }
1688
1844
  if (req.method === "POST" && url2.pathname === "/api/deck/config") {
1689
1845
  try {
1690
1846
  const body = JSON.parse(await readBody(req));
@@ -1783,40 +1939,128 @@ async function startDeckServer(opts = {}) {
1783
1939
  jsonResponse(res, 404, { ok: false, error: "session not found" });
1784
1940
  return;
1785
1941
  }
1942
+ let course;
1943
+ let svgByIndex = {};
1944
+ let publicLink = false;
1786
1945
  try {
1787
- loadMcpConfig(cwd);
1788
- } catch (err) {
1789
- jsonResponse(res, 400, {
1790
- ok: false,
1791
- error: `Cloud sharing needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}`
1792
- });
1946
+ const raw = await readBody(req);
1947
+ if (raw.trim()) {
1948
+ const parsed = JSON.parse(raw);
1949
+ course = parsed.course?.trim() || void 0;
1950
+ if (parsed.svgByIndex && typeof parsed.svgByIndex === "object") svgByIndex = parsed.svgByIndex;
1951
+ publicLink = parsed.publicLink === true;
1952
+ }
1953
+ } catch {
1954
+ }
1955
+ const cfg = resolveShareConfig(cwd, course);
1956
+ if ("error" in cfg) {
1957
+ jsonResponse(res, 400, { ok: false, error: cfg.error });
1793
1958
  return;
1794
1959
  }
1960
+ const { override } = cfg;
1795
1961
  try {
1796
1962
  const blocks = Array.isArray(persisted.blocks) ? persisted.blocks : [];
1797
- const html = buildShareHtml(cwd, id, blocks);
1963
+ const { html, cloudBlocks } = buildSharePayload(cwd, id, blocks, svgByIndex);
1798
1964
  const types = blocks.map((b) => b?.type).filter(Boolean);
1799
1965
  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.`;
1800
- const input = { title: id, body, html, sessionId: id, blockCount: blocks.length };
1801
- const existing2 = readSyncRecord(cwd, id);
1802
- let resourceId;
1803
- let updated = false;
1804
- if (existing2?.resourceId) {
1966
+ const input = { title: id, body, html, blocks: cloudBlocks, sessionId: id, blockCount: blocks.length };
1967
+ const existingSync = readSyncRecord(cwd, id);
1968
+ const doShare = async () => {
1969
+ if (existingSync?.resourceId) {
1970
+ try {
1971
+ const { id: rid2 } = await updateDeck(existingSync.resourceId, input);
1972
+ return { resourceId: rid2, updated: true };
1973
+ } catch {
1974
+ const { id: rid2 } = await writeDeck(input);
1975
+ return { resourceId: rid2, updated: false };
1976
+ }
1977
+ }
1978
+ const { id: rid } = await writeDeck(input);
1979
+ return { resourceId: rid, updated: false };
1980
+ };
1981
+ const result = await runWithShareConfig(override, async () => {
1982
+ const shared = await doShare();
1983
+ let link = null;
1984
+ let linkError;
1985
+ if (publicLink) {
1986
+ try {
1987
+ const minted = await createDeckShareLink(shared.resourceId);
1988
+ link = { shareUrl: minted.shareUrl, linkId: minted.linkId };
1989
+ } catch (err) {
1990
+ linkError = err instanceof Error ? err.message : String(err);
1991
+ }
1992
+ }
1993
+ return { shared, link, linkError };
1994
+ });
1995
+ if (publicLink && result.linkError && !result.shared.updated) {
1805
1996
  try {
1806
- ({ id: resourceId } = await updateDeck(existing2.resourceId, input));
1807
- updated = true;
1997
+ await runWithShareConfig(override, () => deleteDeck(result.shared.resourceId));
1808
1998
  } catch {
1809
- ({ id: resourceId } = await writeDeck(input));
1810
1999
  }
1811
- } else {
1812
- ({ id: resourceId } = await writeDeck(input));
2000
+ jsonResponse(res, 200, {
2001
+ ok: false,
2002
+ error: `Couldn't create the public link, so the deck wasn't shared: ${result.linkError}`
2003
+ });
2004
+ return;
1813
2005
  }
2006
+ const publicShareUrl = result.link?.shareUrl ?? existingSync?.publicShareUrl;
2007
+ const publicLinkId = result.link?.linkId ?? existingSync?.publicLinkId;
1814
2008
  writeSyncRecord(cwd, id, {
1815
- resourceId,
2009
+ resourceId: result.shared.resourceId,
1816
2010
  sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
1817
- version: persisted.version
2011
+ version: persisted.version,
2012
+ course,
2013
+ ...publicShareUrl ? { publicShareUrl } : {},
2014
+ ...publicLinkId ? { publicLinkId } : {}
2015
+ });
2016
+ jsonResponse(res, 200, {
2017
+ ok: true,
2018
+ resourceId: result.shared.resourceId,
2019
+ updated: result.shared.updated,
2020
+ course,
2021
+ shareUrl: publicShareUrl ?? null,
2022
+ ...result.linkError ? { linkWarning: result.linkError } : {}
1818
2023
  });
1819
- jsonResponse(res, 200, { ok: true, resourceId, updated });
2024
+ } catch (err) {
2025
+ jsonResponse(res, 500, { ok: false, error: String(err) });
2026
+ }
2027
+ return;
2028
+ }
2029
+ const publicLinkMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/public-link$/);
2030
+ if (publicLinkMatch && (req.method === "POST" || req.method === "DELETE")) {
2031
+ const id = decodeURIComponent(publicLinkMatch[1]);
2032
+ const sync = readSyncRecord(cwd, id);
2033
+ if (!sync?.resourceId) {
2034
+ jsonResponse(res, 409, { ok: false, error: "Share this deck to the cloud first, then create a public link." });
2035
+ return;
2036
+ }
2037
+ const cfg = resolveShareConfig(cwd, sync.course);
2038
+ if ("error" in cfg) {
2039
+ jsonResponse(res, 400, { ok: false, error: cfg.error });
2040
+ return;
2041
+ }
2042
+ const { override } = cfg;
2043
+ if (req.method === "DELETE") {
2044
+ if (!sync.publicLinkId) {
2045
+ if (sync.publicShareUrl) {
2046
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: void 0, publicLinkId: void 0 });
2047
+ }
2048
+ jsonResponse(res, 200, { ok: true, revoked: false });
2049
+ return;
2050
+ }
2051
+ try {
2052
+ await runWithShareConfig(override, () => revokeDeckShareLink(sync.resourceId, sync.publicLinkId));
2053
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: void 0, publicLinkId: void 0 });
2054
+ jsonResponse(res, 200, { ok: true, revoked: true });
2055
+ } catch (err) {
2056
+ jsonResponse(res, 500, { ok: false, error: String(err) });
2057
+ }
2058
+ return;
2059
+ }
2060
+ try {
2061
+ const minted = await runWithShareConfig(override, () => createDeckShareLink(sync.resourceId));
2062
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: minted.shareUrl, publicLinkId: minted.linkId });
2063
+ jsonResponse(res, 200, { ok: true, shareUrl: minted.shareUrl });
1820
2064
  } catch (err) {
1821
2065
  jsonResponse(res, 500, { ok: false, error: String(err) });
1822
2066
  }
@@ -1844,7 +2088,16 @@ async function startDeckServer(opts = {}) {
1844
2088
  }
1845
2089
  jsonResponse(res, 200, { ok: true, deleted: true });
1846
2090
  } catch (err) {
1847
- jsonResponse(res, 500, { ok: false, error: String(err) });
2091
+ const msg = err instanceof Error ? err.message : String(err);
2092
+ if (/not\s*found/i.test(msg)) {
2093
+ try {
2094
+ import_node_fs4.default.rmSync(syncJsonPath(cwd, id), { force: true });
2095
+ } catch {
2096
+ }
2097
+ jsonResponse(res, 200, { ok: true, deleted: false, reason: "already removed" });
2098
+ return;
2099
+ }
2100
+ jsonResponse(res, 500, { ok: false, error: msg });
1848
2101
  }
1849
2102
  return;
1850
2103
  }
@@ -1935,6 +2188,10 @@ async function startDeckServer(opts = {}) {
1935
2188
  deletePersistedSession(cwd, session);
1936
2189
  } catch {
1937
2190
  }
2191
+ const beforePins = discussSessions.length;
2192
+ discussSessions = session === "all" ? [] : discussSessions.filter((d) => d.id !== session);
2193
+ if (discussSessions.length !== beforePins) broadcastDiscuss();
2194
+ if (session === "all" || activeSession?.id === session) activeSession = null;
1938
2195
  jsonResponse(res, 200, { ok: true });
1939
2196
  return;
1940
2197
  }
@@ -1943,6 +2200,16 @@ async function startDeckServer(opts = {}) {
1943
2200
  jsonResponse(res, 200, err ?? null);
1944
2201
  return;
1945
2202
  }
2203
+ if (req.method === "GET" && url2.pathname === "/api/active-session") {
2204
+ jsonResponse(res, 200, {
2205
+ discuss: discussSessions.map((d) => d.id),
2206
+ // ordered = pin number
2207
+ active: activeSession?.id ?? null,
2208
+ focused: activeSession?.focused ?? false,
2209
+ activeAt: activeSession?.at ?? null
2210
+ });
2211
+ return;
2212
+ }
1946
2213
  if (req.method === "GET" && url2.pathname.startsWith("/deck-files/")) {
1947
2214
  const relative = decodeURIComponent(url2.pathname.slice("/deck-files/".length));
1948
2215
  if (relative.includes("..") || relative.startsWith("/")) {
@@ -2015,6 +2282,24 @@ async function startDeckServer(opts = {}) {
2015
2282
  source: msg.source ?? ""
2016
2283
  };
2017
2284
  }
2285
+ if (msg.type === "active_session" && msg.session) {
2286
+ activeSession = {
2287
+ id: msg.session,
2288
+ focused: msg.focused ?? false,
2289
+ at: Date.now()
2290
+ };
2291
+ }
2292
+ if (msg.type === "discuss_session" && msg.session) {
2293
+ const id = msg.session;
2294
+ const idx = discussSessions.findIndex((d) => d.id === id);
2295
+ if (idx >= 0) discussSessions.splice(idx, 1);
2296
+ else discussSessions.push({ id, at: Date.now() });
2297
+ broadcastDiscuss();
2298
+ }
2299
+ if (msg.type === "discuss_clear_all") {
2300
+ discussSessions = [];
2301
+ broadcastDiscuss();
2302
+ }
2018
2303
  } catch {
2019
2304
  }
2020
2305
  });
@@ -2083,6 +2368,7 @@ function runServeCli(argv) {
2083
2368
  // Annotate the CommonJS export names for ESM import in node:
2084
2369
  0 && (module.exports = {
2085
2370
  broadcastToClients,
2371
+ buildSharePayload,
2086
2372
  consumeRenderError,
2087
2373
  createFeedbackWaiter,
2088
2374
  resolveFeedback,