@launchsecure/launch-kit 0.0.40 → 0.0.42

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 (106) 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-B1v46vTE.js → index-DkTFX53U.js} +1 -1
  7. package/dist/council-client/index.html +2 -2
  8. package/dist/deck-client/assets/_baseUniq-BrhDuG3C.js +1 -0
  9. package/dist/deck-client/assets/arc-DXtPHMhw.js +1 -0
  10. package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-Cs9IdLQQ.js +36 -0
  11. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CUdblaWk.js → blockDiagram-DXYQGD6D--wpvoBAt.js} +2 -2
  12. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-MfAO5lak.js → c4Diagram-AHTNJAMY-BNIKE8-Z.js} +2 -2
  13. package/dist/deck-client/assets/channel-Bb5wIjTD.js +1 -0
  14. package/dist/deck-client/assets/chunk-4BX2VUAB-Bz3EWmUo.js +1 -0
  15. package/dist/deck-client/assets/{chunk-4TB4RGXK-BUJtZ7jO.js → chunk-4TB4RGXK-D55BBvVZ.js} +1 -1
  16. package/dist/deck-client/assets/chunk-55IACEB6-BzjEcoTi.js +1 -0
  17. package/dist/deck-client/assets/chunk-EDXVE4YY-CH1vs4Lu.js +1 -0
  18. package/dist/deck-client/assets/{chunk-FMBD7UC4-PnZ9v6ey.js → chunk-FMBD7UC4-CVRnaGM0.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-OYMX7WX6-DXrWNOsV.js → chunk-OYMX7WX6-BCTV_aEZ.js} +1 -1
  20. package/dist/deck-client/assets/chunk-QZHKN3VN-BqM8r4ee.js +1 -0
  21. package/dist/deck-client/assets/chunk-YZCP3GAM-Rhbb691A.js +1 -0
  22. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CN-ZYMbT.js +1 -0
  23. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CN-ZYMbT.js +1 -0
  24. package/dist/deck-client/assets/clone-D6e7poKG.js +1 -0
  25. package/dist/deck-client/assets/cose-bilkent-S5V4N54A-fWyBR9Nn.js +1 -0
  26. package/dist/deck-client/assets/dagre-KV5264BT-Ds0Sqext.js +4 -0
  27. package/dist/deck-client/assets/diagram-5BDNPKRD-BGz_X007.js +10 -0
  28. package/dist/deck-client/assets/diagram-G4DWMVQ6-C78jeb-r.js +24 -0
  29. package/dist/deck-client/assets/diagram-MMDJMWI5-BdoHW8mF.js +43 -0
  30. package/dist/deck-client/assets/diagram-TYMM5635-C8i9zc-U.js +24 -0
  31. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-56pn_93p.js → erDiagram-SMLLAGMA-DplAZ-yG.js} +2 -2
  32. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BtV3M5xJ.js → flowDiagram-DWJPFMVM-F3N9yTAB.js} +2 -2
  33. package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DebdM4S5.js +292 -0
  34. package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-htCk0oPz.js +106 -0
  35. package/dist/deck-client/assets/graph-Dtxc2PT4.js +1 -0
  36. package/dist/deck-client/assets/index-CwAiam97.js +892 -0
  37. package/dist/deck-client/assets/index-evAPhGvM.css +1 -0
  38. package/dist/deck-client/assets/infoDiagram-42DDH7IO-gTxegviJ.js +2 -0
  39. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DXYkdO3T.js → ishikawaDiagram-UXIWVN3A-DYSEsfUK.js} +5 -5
  40. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C2zBr-J5.js → journeyDiagram-VCZTEJTY-DgVc1q4D.js} +2 -2
  41. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CdoYLS4Z.js → kanban-definition-6JOO6SKY-DcOf3i9N.js} +2 -2
  42. package/dist/deck-client/assets/layout-CHP9HIw4.js +1 -0
  43. package/dist/deck-client/assets/linear-XjVb7x4Q.js +1 -0
  44. package/dist/deck-client/assets/mermaid.core-bouKOnsR.js +309 -0
  45. package/dist/deck-client/assets/min-Jl4GV9DB.js +1 -0
  46. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-oAybLedr.js → mindmap-definition-QFDTVHPH-FXXf4cJx.js} +2 -2
  47. package/dist/deck-client/assets/pieDiagram-DEJITSTG-Dl4plCTi.js +30 -0
  48. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-dtluDZXs.js → quadrantDiagram-34T5L4WZ-BeOEeelg.js} +2 -2
  49. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cq8l7bOl.js → requirementDiagram-MS252O5E-CBh1jchM.js} +2 -2
  50. package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-BK6gG-ub.js +10 -0
  51. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CYkd7oQK.js → sequenceDiagram-FGHM5R23-BtQwzsF5.js} +2 -2
  52. package/dist/deck-client/assets/stateDiagram-FHFEXIEX--kLQOi8R.js +1 -0
  53. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CHEPjgB0.js +1 -0
  54. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DZIxSyd1.js → timeline-definition-GMOUNBTQ-CQ_-CLh7.js} +2 -2
  55. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-Ct4JVRDM.js → vennDiagram-DHZGUBPP-DBhKYAFT.js} +2 -2
  56. package/dist/deck-client/assets/{wardley-RL74JXVD-V29ycxOW.js → wardley-RL74JXVD-DLKe29q9.js} +3 -3
  57. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-D-Ua6Cmi.js → wardleyDiagram-NUSXRM2D-Brb8ezsV.js} +2 -2
  58. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-BPCOuRVl.js → xychartDiagram-5P7HB3ND-LogZ0Az9.js} +2 -2
  59. package/dist/deck-client/index.html +2 -2
  60. package/dist/server/cli.js +488 -215
  61. package/dist/server/council-entry.js +7 -1
  62. package/dist/server/council-serve.js +7 -1
  63. package/dist/server/deck-mcp-entry.js +423 -50
  64. package/dist/server/deck-serve.js +350 -48
  65. package/dist/server/graph-mcp-entry.js +331 -37
  66. package/dist/server/init-entry.js +17 -7
  67. package/dist/server/rover-entry.js +1 -1
  68. package/package.json +1 -1
  69. package/scaffolds/ls-marketplace/plugins/kit/skills/comms/SKILL.md +34 -1
  70. package/scaffolds/ls-marketplace/plugins/kit/skills/gen-test/SKILL.md +126 -0
  71. package/dist/chart-client/assets/index-CWJFFDPu.css +0 -1
  72. package/dist/client/assets/index-CTzFcfGV.css +0 -32
  73. package/dist/council-client/assets/index-ArgRc5mN.css +0 -1
  74. package/dist/deck-client/assets/_baseUniq-BZP7n41F.js +0 -1
  75. package/dist/deck-client/assets/arc-31biU3Az.js +0 -1
  76. package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DHg6Ss--.js +0 -36
  77. package/dist/deck-client/assets/channel-BBkRLdnC.js +0 -1
  78. package/dist/deck-client/assets/chunk-4BX2VUAB-DQ1MrGgN.js +0 -1
  79. package/dist/deck-client/assets/chunk-55IACEB6-BdSnXB6g.js +0 -1
  80. package/dist/deck-client/assets/chunk-EDXVE4YY-94yZIUI8.js +0 -1
  81. package/dist/deck-client/assets/chunk-QZHKN3VN-CsIGIDKX.js +0 -1
  82. package/dist/deck-client/assets/chunk-YZCP3GAM-DVkBO9tn.js +0 -1
  83. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-DFCaeF-7.js +0 -1
  84. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-DFCaeF-7.js +0 -1
  85. package/dist/deck-client/assets/clone-GCEVRScB.js +0 -1
  86. package/dist/deck-client/assets/cose-bilkent-S5V4N54A-m126Oh3b.js +0 -1
  87. package/dist/deck-client/assets/dagre-KV5264BT-C2aig8U5.js +0 -4
  88. package/dist/deck-client/assets/diagram-5BDNPKRD-CKpoRfGn.js +0 -10
  89. package/dist/deck-client/assets/diagram-G4DWMVQ6-Cjh115Ep.js +0 -24
  90. package/dist/deck-client/assets/diagram-MMDJMWI5-DKlBv_2L.js +0 -43
  91. package/dist/deck-client/assets/diagram-TYMM5635-CdBh4cEn.js +0 -24
  92. package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DTIsC6Zg.js +0 -292
  93. package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-CJYeyCLe.js +0 -106
  94. package/dist/deck-client/assets/graph-BDvMu1Ss.js +0 -1
  95. package/dist/deck-client/assets/index-D4eSxcBn.css +0 -1
  96. package/dist/deck-client/assets/index-QnGVE9PZ.js +0 -1196
  97. package/dist/deck-client/assets/infoDiagram-42DDH7IO-BWyKJnpW.js +0 -2
  98. package/dist/deck-client/assets/layout-vOnxnCQU.js +0 -1
  99. package/dist/deck-client/assets/linear-B0J0WCGz.js +0 -1
  100. package/dist/deck-client/assets/min-B0AXlT9L.js +0 -1
  101. package/dist/deck-client/assets/pieDiagram-DEJITSTG-BjHyHxGk.js +0 -30
  102. package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-C1Vih91z.js +0 -10
  103. package/dist/deck-client/assets/stateDiagram-FHFEXIEX-CtyG8wBK.js +0 -1
  104. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BLyKWfcN.js +0 -1
  105. /package/dist/chart-client/assets/{index-Dzlj-oCj.js → index-CrYM1-ac.js} +0 -0
  106. /package/dist/client/assets/{index-tTg_ezUF.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,
@@ -1399,7 +1400,13 @@ async function callTool(toolName, args) {
1399
1400
  }
1400
1401
  const textContent = result.result?.content?.[0]?.text;
1401
1402
  if (!textContent) return null;
1402
- 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
+ }
1403
1410
  }
1404
1411
  async function writeDeck(input) {
1405
1412
  const result = await callTool("communication_write", {
@@ -1408,6 +1415,7 @@ async function writeDeck(input) {
1408
1415
  resource_type: "deck",
1409
1416
  fields: {
1410
1417
  deckHtml: input.html,
1418
+ deckBlocks: input.blocks,
1411
1419
  sessionId: input.sessionId,
1412
1420
  blockCount: input.blockCount,
1413
1421
  sharedFrom: "launch-deck"
@@ -1422,6 +1430,7 @@ async function updateDeck(commentId, input) {
1422
1430
  body: input.body,
1423
1431
  fields: {
1424
1432
  deckHtml: input.html,
1433
+ deckBlocks: input.blocks,
1425
1434
  sessionId: input.sessionId,
1426
1435
  blockCount: input.blockCount,
1427
1436
  sharedFrom: "launch-deck"
@@ -1432,6 +1441,49 @@ async function updateDeck(commentId, input) {
1432
1441
  async function deleteDeck(commentId) {
1433
1442
  await callTool("communication_delete", { comment_id: commentId });
1434
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
+ }
1462
+ function currentMcpOrigin() {
1463
+ const config = _config;
1464
+ if (!config) throw new Error("MCP config not loaded \u2014 call loadMcpConfig first");
1465
+ return new URL(config.url).origin;
1466
+ }
1467
+ async function createDeckShareLink(commentId) {
1468
+ const result = await callTool("deck_share_link", {
1469
+ comment_id: commentId,
1470
+ action: "create"
1471
+ });
1472
+ const token = result.link.token;
1473
+ return {
1474
+ linkId: result.link.id,
1475
+ token,
1476
+ shareUrl: `${currentMcpOrigin()}/s/deck/${token}`,
1477
+ reused: Boolean(result.reused)
1478
+ };
1479
+ }
1480
+ async function revokeDeckShareLink(commentId, linkId) {
1481
+ await callTool("deck_share_link", {
1482
+ comment_id: commentId,
1483
+ action: "revoke",
1484
+ link_id: linkId
1485
+ });
1486
+ }
1435
1487
 
1436
1488
  // src/server/deck-serve.ts
1437
1489
  var DEFAULT_PORT = 52829;
@@ -1453,6 +1505,10 @@ function sessionDir(cwd, session) {
1453
1505
  function sessionJsonPath(cwd, session) {
1454
1506
  return import_node_path4.default.join(sessionDir(cwd, session), "session.json");
1455
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
+ }
1456
1512
  function readPersistedSession(cwd, session) {
1457
1513
  try {
1458
1514
  const raw = import_node_fs4.default.readFileSync(sessionJsonPath(cwd, session), "utf-8");
@@ -1496,7 +1552,11 @@ function listPersistedSessions(cwd) {
1496
1552
  createdAt: persisted.createdAt,
1497
1553
  updatedAt: persisted.updatedAt,
1498
1554
  shared: Boolean(sync),
1499
- stale: Boolean(sync) && persisted.version > (sync?.version ?? 0)
1555
+ stale: Boolean(sync) && persisted.version > (sync?.version ?? 0),
1556
+ shareUrl: sync?.publicShareUrl,
1557
+ course: sync?.course,
1558
+ watching: sync?.watching,
1559
+ watchedFrom: sync?.watchedFrom
1500
1560
  });
1501
1561
  }
1502
1562
  sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -1524,6 +1584,24 @@ function readSyncRecord(cwd, session) {
1524
1584
  function writeSyncRecord(cwd, session, rec) {
1525
1585
  import_node_fs4.default.writeFileSync(syncJsonPath(cwd, session), JSON.stringify(rec, null, 2));
1526
1586
  }
1587
+ function resolveShareConfig(cwd, course) {
1588
+ if (course) {
1589
+ const override = mcpConfigForProfile(cwd, course);
1590
+ if (!override) {
1591
+ return { error: `Course "${course}" is not a usable profile in .launch-secure.cred.config (missing pat/serverUrl/orgSlug/projectSlug?).` };
1592
+ }
1593
+ return { override };
1594
+ }
1595
+ try {
1596
+ loadMcpConfig(cwd);
1597
+ } catch (err) {
1598
+ return { error: `Cloud sharing needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}` };
1599
+ }
1600
+ return { override: null };
1601
+ }
1602
+ function runWithShareConfig(override, fn) {
1603
+ return override ? withMcpConfig(override, fn) : fn();
1604
+ }
1527
1605
  function escHtml2(s) {
1528
1606
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1529
1607
  }
@@ -1540,31 +1618,49 @@ function readIframeArtifact(cwd, src) {
1540
1618
  return null;
1541
1619
  }
1542
1620
  }
1543
- function renderShareSection(cwd, block) {
1544
- const label = block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
1621
+ function renderShareSection(cwd, block, showLabel = true) {
1622
+ const label = showLabel && block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
1545
1623
  switch (block?.type) {
1546
1624
  case "iframe": {
1547
1625
  const doc = readIframeArtifact(cwd, block.src) ?? "<p>(missing artifact)</p>";
1548
- return `<section class="db">${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe></section>`;
1626
+ return `${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe>`;
1549
1627
  }
1550
1628
  case "rich-html":
1551
1629
  case "html":
1552
- return `<section class="db">${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div></section>`;
1630
+ return `${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div>`;
1553
1631
  case "markdown":
1554
- return `<section class="db">${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div></section>`;
1632
+ return `${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div>`;
1555
1633
  case "mermaid":
1556
- return `<section class="db">${label}<pre class="mermaid">${escHtml2(typeof block.content === "string" ? block.content : "")}</pre></section>`;
1634
+ if (typeof block.renderedSvg === "string" && block.renderedSvg.trim()) {
1635
+ return `${label}<div class="mermaid-svg">${block.renderedSvg}</div>`;
1636
+ }
1637
+ return `${label}<pre class="mermaid">${escHtml2(typeof block.content === "string" ? block.content : "")}</pre>`;
1557
1638
  case "options": {
1558
1639
  const opts = Array.isArray(block.options) ? block.options : [];
1559
- const lis = opts.map(
1560
- (o) => `<li><strong>${escHtml2(String(o?.label ?? o?.id ?? ""))}</strong>${o?.description ? ` \u2014 ${escHtml2(String(o.description))}` : ""}</li>`
1640
+ const cards = opts.map(
1641
+ (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>`
1561
1642
  ).join("");
1562
- return `<section class="db">${label}<ul>${lis}</ul></section>`;
1643
+ return `${label}<div class="db-options">${cards}</div>`;
1563
1644
  }
1564
1645
  default:
1565
- return `<section class="db">${label}</section>`;
1646
+ return label;
1566
1647
  }
1567
1648
  }
1649
+ function buildSharePayload(cwd, session, blocks, svgByIndex = {}) {
1650
+ const enriched = blocks.map((b, i) => {
1651
+ const svg = svgByIndex[String(i)];
1652
+ return b?.type === "mermaid" && typeof svg === "string" && svg.trim() ? { ...b, renderedSvg: svg } : b;
1653
+ });
1654
+ const html = buildShareHtml(cwd, session, enriched);
1655
+ const cloudBlocks = enriched.map((b) => {
1656
+ if (b?.type === "iframe" && typeof b.src === "string") {
1657
+ const content = readIframeArtifact(cwd, b.src);
1658
+ return content ? { ...b, content } : b;
1659
+ }
1660
+ return b;
1661
+ });
1662
+ return { html, cloudBlocks };
1663
+ }
1568
1664
  function buildShareHtml(cwd, session, blocks) {
1569
1665
  if (blocks.length === 1) {
1570
1666
  const only = blocks[0];
@@ -1574,30 +1670,61 @@ function buildShareHtml(cwd, session, blocks) {
1574
1670
  }
1575
1671
  }
1576
1672
  const list = blocks;
1577
- const sections = list.map((b) => renderShareSection(cwd, b)).join("\n");
1673
+ const multi = list.length > 1;
1674
+ const tabBar = multi ? `<nav class="deck-tabs">${list.map(
1675
+ (b, i) => `<button class="deck-tab${i === 0 ? " active" : ""}" data-tab-btn="${i}">${escHtml2(String(b?.label ?? `Tab ${i + 1}`))}</button>`
1676
+ ).join("")}</nav>` : "";
1677
+ const sections = list.map(
1678
+ (b, i) => `<section class="db" data-tab="${i}"${multi && i > 0 ? " hidden" : ""}>${renderShareSection(cwd, b, !multi)}</section>`
1679
+ ).join("\n");
1578
1680
  const hasMd = list.some((b) => b?.type === "markdown");
1579
- const hasMermaid = list.some((b) => b?.type === "mermaid");
1681
+ const hasUnrenderedMermaid = list.some(
1682
+ (b) => b?.type === "mermaid" && !(typeof b?.renderedSvg === "string" && b.renderedSvg.trim())
1683
+ );
1580
1684
  return `<!DOCTYPE html>
1581
1685
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
1582
1686
  <title>${escHtml2(session)}</title>
1687
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,100..1000&display=swap" rel="stylesheet">
1583
1688
  <style>
1584
- body { margin:0; font-family: ui-sans-serif, system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
1689
+ body { margin:0; font-family: 'DM Sans', system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
1585
1690
  .db { padding:20px 24px; border-bottom:1px solid #1e2433; }
1586
1691
  .db-title { font-size:13px; font-weight:600; margin:0 0 12px; color:#9aa4b2; text-transform:uppercase; letter-spacing:.05em; }
1587
1692
  .db-frame { width:100%; height:70vh; border:0; border-radius:8px; background:#fff; }
1588
1693
  .db-html { background:#fff; color:#111; border-radius:8px; padding:16px; overflow:auto; }
1589
1694
  ul { line-height:1.7; }
1590
- .mermaid { background:#fff; border-radius:8px; padding:16px; }
1695
+ /* The embedded SVG is dark-themed (captured from the deck client); keep its
1696
+ backdrop transparent so it sits on the dark page, matching the in-app
1697
+ DeckView \u2014 not a white card. The CDN-source fallback also renders dark. */
1698
+ .mermaid { background:transparent; border-radius:8px; padding:16px; }
1699
+ .mermaid-svg { background:transparent; border-radius:8px; padding:16px; overflow:auto; }
1700
+ .mermaid-svg svg { max-width:100%; height:auto; }
1701
+ .db-options { display:grid; gap:12px; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); }
1702
+ .db-option { border:2px solid #1e2433; border-radius:8px; padding:16px; }
1703
+ .db-option-label { font-size:14px; font-weight:600; color:#e6e9ef; }
1704
+ .db-option-desc { margin-top:4px; font-size:12px; color:#9aa4b2; }
1705
+ .db-option-pre { margin-top:8px; padding:8px; border-radius:6px; background:#11151f; font-size:12px; overflow:auto; }
1706
+ .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; }
1707
+ .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; }
1708
+ .deck-tab:hover { color:#e6e9ef; }
1709
+ .deck-tab.active { color:#c084fc; border-bottom-color:#c084fc; }
1710
+ .db[hidden] { display:none; }
1591
1711
  </style></head>
1592
1712
  <body>
1713
+ ${tabBar}
1593
1714
  ${sections}
1715
+ ${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>` : ""}
1594
1716
  ${hasMd ? `<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1595
1717
  <script>document.querySelectorAll('.db-md').forEach(function(el){try{el.innerHTML=marked.parse(el.dataset.md||'')}catch(e){el.textContent=el.dataset.md||''}});</script>` : ""}
1596
- ${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>` : ""}
1718
+ ${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>` : ""}
1597
1719
  </body></html>`;
1598
1720
  }
1599
1721
  var pendingFeedback = /* @__PURE__ */ new Map();
1600
1722
  var lastRenderError = null;
1723
+ var activeSession = null;
1724
+ var discussSessions = [];
1725
+ function broadcastDiscuss() {
1726
+ broadcastToClients({ type: "discuss_session", sessions: discussSessions.map((d) => d.id) });
1727
+ }
1601
1728
  function consumeRenderError() {
1602
1729
  const err = lastRenderError;
1603
1730
  lastRenderError = null;
@@ -1828,6 +1955,74 @@ async function startDeckServer(opts = {}) {
1828
1955
  jsonResponse(res, 200, { sessions: listPersistedSessions(cwd) });
1829
1956
  return;
1830
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
+ }
1831
2026
  const shareMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/share$/);
1832
2027
  if (shareMatch && req.method === "POST") {
1833
2028
  const id = decodeURIComponent(shareMatch[1]);
@@ -1837,45 +2032,35 @@ async function startDeckServer(opts = {}) {
1837
2032
  return;
1838
2033
  }
1839
2034
  let course;
2035
+ let svgByIndex = {};
2036
+ let publicLink = false;
1840
2037
  try {
1841
2038
  const raw = await readBody(req);
1842
2039
  if (raw.trim()) {
1843
- course = JSON.parse(raw).course?.trim() || void 0;
2040
+ const parsed = JSON.parse(raw);
2041
+ course = parsed.course?.trim() || void 0;
2042
+ if (parsed.svgByIndex && typeof parsed.svgByIndex === "object") svgByIndex = parsed.svgByIndex;
2043
+ publicLink = parsed.publicLink === true;
1844
2044
  }
1845
2045
  } catch {
1846
2046
  }
1847
- let override = null;
1848
- if (course) {
1849
- override = mcpConfigForProfile(cwd, course);
1850
- if (!override) {
1851
- jsonResponse(res, 400, {
1852
- ok: false,
1853
- error: `Course "${course}" is not a usable profile in .launch-secure.cred.config (missing pat/serverUrl/orgSlug/projectSlug?).`
1854
- });
1855
- return;
1856
- }
1857
- } else {
1858
- try {
1859
- loadMcpConfig(cwd);
1860
- } catch (err) {
1861
- jsonResponse(res, 400, {
1862
- ok: false,
1863
- error: `Cloud sharing needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}`
1864
- });
1865
- return;
1866
- }
2047
+ const cfg = resolveShareConfig(cwd, course);
2048
+ if ("error" in cfg) {
2049
+ jsonResponse(res, 400, { ok: false, error: cfg.error });
2050
+ return;
1867
2051
  }
2052
+ const { override } = cfg;
1868
2053
  try {
1869
2054
  const blocks = Array.isArray(persisted.blocks) ? persisted.blocks : [];
1870
- const html = buildShareHtml(cwd, id, blocks);
2055
+ const { html, cloudBlocks } = buildSharePayload(cwd, id, blocks, svgByIndex);
1871
2056
  const types = blocks.map((b) => b?.type).filter(Boolean);
1872
2057
  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.`;
1873
- const input = { title: id, body, html, sessionId: id, blockCount: blocks.length };
1874
- const existing2 = readSyncRecord(cwd, id);
2058
+ const input = { title: id, body, html, blocks: cloudBlocks, sessionId: id, blockCount: blocks.length };
2059
+ const existingSync = readSyncRecord(cwd, id);
1875
2060
  const doShare = async () => {
1876
- if (existing2?.resourceId) {
2061
+ if (existingSync?.resourceId) {
1877
2062
  try {
1878
- const { id: rid2 } = await updateDeck(existing2.resourceId, input);
2063
+ const { id: rid2 } = await updateDeck(existingSync.resourceId, input);
1879
2064
  return { resourceId: rid2, updated: true };
1880
2065
  } catch {
1881
2066
  const { id: rid2 } = await writeDeck(input);
@@ -1885,14 +2070,89 @@ async function startDeckServer(opts = {}) {
1885
2070
  const { id: rid } = await writeDeck(input);
1886
2071
  return { resourceId: rid, updated: false };
1887
2072
  };
1888
- const { resourceId, updated } = override ? await withMcpConfig(override, doShare) : await doShare();
2073
+ const result = await runWithShareConfig(override, async () => {
2074
+ const shared = await doShare();
2075
+ let link = null;
2076
+ let linkError;
2077
+ if (publicLink) {
2078
+ try {
2079
+ const minted = await createDeckShareLink(shared.resourceId);
2080
+ link = { shareUrl: minted.shareUrl, linkId: minted.linkId };
2081
+ } catch (err) {
2082
+ linkError = err instanceof Error ? err.message : String(err);
2083
+ }
2084
+ }
2085
+ return { shared, link, linkError };
2086
+ });
2087
+ if (publicLink && result.linkError && !result.shared.updated) {
2088
+ try {
2089
+ await runWithShareConfig(override, () => deleteDeck(result.shared.resourceId));
2090
+ } catch {
2091
+ }
2092
+ jsonResponse(res, 200, {
2093
+ ok: false,
2094
+ error: `Couldn't create the public link, so the deck wasn't shared: ${result.linkError}`
2095
+ });
2096
+ return;
2097
+ }
2098
+ const publicShareUrl = result.link?.shareUrl ?? existingSync?.publicShareUrl;
2099
+ const publicLinkId = result.link?.linkId ?? existingSync?.publicLinkId;
1889
2100
  writeSyncRecord(cwd, id, {
1890
- resourceId,
2101
+ resourceId: result.shared.resourceId,
1891
2102
  sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
1892
2103
  version: persisted.version,
1893
- course
2104
+ course,
2105
+ ...publicShareUrl ? { publicShareUrl } : {},
2106
+ ...publicLinkId ? { publicLinkId } : {}
2107
+ });
2108
+ jsonResponse(res, 200, {
2109
+ ok: true,
2110
+ resourceId: result.shared.resourceId,
2111
+ updated: result.shared.updated,
2112
+ course,
2113
+ shareUrl: publicShareUrl ?? null,
2114
+ ...result.linkError ? { linkWarning: result.linkError } : {}
1894
2115
  });
1895
- jsonResponse(res, 200, { ok: true, resourceId, updated, course });
2116
+ } catch (err) {
2117
+ jsonResponse(res, 500, { ok: false, error: String(err) });
2118
+ }
2119
+ return;
2120
+ }
2121
+ const publicLinkMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/public-link$/);
2122
+ if (publicLinkMatch && (req.method === "POST" || req.method === "DELETE")) {
2123
+ const id = decodeURIComponent(publicLinkMatch[1]);
2124
+ const sync = readSyncRecord(cwd, id);
2125
+ if (!sync?.resourceId) {
2126
+ jsonResponse(res, 409, { ok: false, error: "Share this deck to the cloud first, then create a public link." });
2127
+ return;
2128
+ }
2129
+ const cfg = resolveShareConfig(cwd, sync.course);
2130
+ if ("error" in cfg) {
2131
+ jsonResponse(res, 400, { ok: false, error: cfg.error });
2132
+ return;
2133
+ }
2134
+ const { override } = cfg;
2135
+ if (req.method === "DELETE") {
2136
+ if (!sync.publicLinkId) {
2137
+ if (sync.publicShareUrl) {
2138
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: void 0, publicLinkId: void 0 });
2139
+ }
2140
+ jsonResponse(res, 200, { ok: true, revoked: false });
2141
+ return;
2142
+ }
2143
+ try {
2144
+ await runWithShareConfig(override, () => revokeDeckShareLink(sync.resourceId, sync.publicLinkId));
2145
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: void 0, publicLinkId: void 0 });
2146
+ jsonResponse(res, 200, { ok: true, revoked: true });
2147
+ } catch (err) {
2148
+ jsonResponse(res, 500, { ok: false, error: String(err) });
2149
+ }
2150
+ return;
2151
+ }
2152
+ try {
2153
+ const minted = await runWithShareConfig(override, () => createDeckShareLink(sync.resourceId));
2154
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: minted.shareUrl, publicLinkId: minted.linkId });
2155
+ jsonResponse(res, 200, { ok: true, shareUrl: minted.shareUrl });
1896
2156
  } catch (err) {
1897
2157
  jsonResponse(res, 500, { ok: false, error: String(err) });
1898
2158
  }
@@ -1920,7 +2180,16 @@ async function startDeckServer(opts = {}) {
1920
2180
  }
1921
2181
  jsonResponse(res, 200, { ok: true, deleted: true });
1922
2182
  } catch (err) {
1923
- jsonResponse(res, 500, { ok: false, error: String(err) });
2183
+ const msg = err instanceof Error ? err.message : String(err);
2184
+ if (/not\s*found/i.test(msg)) {
2185
+ try {
2186
+ import_node_fs4.default.rmSync(syncJsonPath(cwd, id), { force: true });
2187
+ } catch {
2188
+ }
2189
+ jsonResponse(res, 200, { ok: true, deleted: false, reason: "already removed" });
2190
+ return;
2191
+ }
2192
+ jsonResponse(res, 500, { ok: false, error: msg });
1924
2193
  }
1925
2194
  return;
1926
2195
  }
@@ -2011,6 +2280,10 @@ async function startDeckServer(opts = {}) {
2011
2280
  deletePersistedSession(cwd, session);
2012
2281
  } catch {
2013
2282
  }
2283
+ const beforePins = discussSessions.length;
2284
+ discussSessions = session === "all" ? [] : discussSessions.filter((d) => d.id !== session);
2285
+ if (discussSessions.length !== beforePins) broadcastDiscuss();
2286
+ if (session === "all" || activeSession?.id === session) activeSession = null;
2014
2287
  jsonResponse(res, 200, { ok: true });
2015
2288
  return;
2016
2289
  }
@@ -2019,6 +2292,16 @@ async function startDeckServer(opts = {}) {
2019
2292
  jsonResponse(res, 200, err ?? null);
2020
2293
  return;
2021
2294
  }
2295
+ if (req.method === "GET" && url2.pathname === "/api/active-session") {
2296
+ jsonResponse(res, 200, {
2297
+ discuss: discussSessions.map((d) => d.id),
2298
+ // ordered = pin number
2299
+ active: activeSession?.id ?? null,
2300
+ focused: activeSession?.focused ?? false,
2301
+ activeAt: activeSession?.at ?? null
2302
+ });
2303
+ return;
2304
+ }
2022
2305
  if (req.method === "GET" && url2.pathname.startsWith("/deck-files/")) {
2023
2306
  const relative = decodeURIComponent(url2.pathname.slice("/deck-files/".length));
2024
2307
  if (relative.includes("..") || relative.startsWith("/")) {
@@ -2091,6 +2374,24 @@ async function startDeckServer(opts = {}) {
2091
2374
  source: msg.source ?? ""
2092
2375
  };
2093
2376
  }
2377
+ if (msg.type === "active_session" && msg.session) {
2378
+ activeSession = {
2379
+ id: msg.session,
2380
+ focused: msg.focused ?? false,
2381
+ at: Date.now()
2382
+ };
2383
+ }
2384
+ if (msg.type === "discuss_session" && msg.session) {
2385
+ const id = msg.session;
2386
+ const idx = discussSessions.findIndex((d) => d.id === id);
2387
+ if (idx >= 0) discussSessions.splice(idx, 1);
2388
+ else discussSessions.push({ id, at: Date.now() });
2389
+ broadcastDiscuss();
2390
+ }
2391
+ if (msg.type === "discuss_clear_all") {
2392
+ discussSessions = [];
2393
+ broadcastDiscuss();
2394
+ }
2094
2395
  } catch {
2095
2396
  }
2096
2397
  });
@@ -2159,6 +2460,7 @@ function runServeCli(argv) {
2159
2460
  // Annotate the CommonJS export names for ESM import in node:
2160
2461
  0 && (module.exports = {
2161
2462
  broadcastToClients,
2463
+ buildSharePayload,
2162
2464
  consumeRenderError,
2163
2465
  createFeedbackWaiter,
2164
2466
  resolveFeedback,