@launchsecure/launch-kit 0.0.40 → 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 (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-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-CUdblaWk.js → blockDiagram-DXYQGD6D-DVNQht7c.js} +2 -2
  12. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-MfAO5lak.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-BUJtZ7jO.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-PnZ9v6ey.js → chunk-FMBD7UC4-Dg-i7kzi.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-OYMX7WX6-DXrWNOsV.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-56pn_93p.js → erDiagram-SMLLAGMA-DKWYEHQS.js} +2 -2
  32. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BtV3M5xJ.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-DXYkdO3T.js → ishikawaDiagram-UXIWVN3A-BhrNX_jI.js} +5 -5
  40. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C2zBr-J5.js → journeyDiagram-VCZTEJTY-B5lJI492.js} +2 -2
  41. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CdoYLS4Z.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-oAybLedr.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-dtluDZXs.js → quadrantDiagram-34T5L4WZ-CBiOKudN.js} +2 -2
  49. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cq8l7bOl.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-CYkd7oQK.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-DZIxSyd1.js → timeline-definition-GMOUNBTQ-C2V4sSkm.js} +2 -2
  55. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-Ct4JVRDM.js → vennDiagram-DHZGUBPP-YOqt4VbE.js} +2 -2
  56. package/dist/deck-client/assets/{wardley-RL74JXVD-V29ycxOW.js → wardley-RL74JXVD-Bxo5x40D.js} +3 -3
  57. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-D-Ua6Cmi.js → wardleyDiagram-NUSXRM2D-DW9SOqbx.js} +2 -2
  58. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-BPCOuRVl.js → xychartDiagram-5P7HB3ND-D-rZvZOL.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 +287 -49
  64. package/dist/server/deck-serve.js +258 -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,31 @@ async function updateDeck(commentId, input) {
1432
1441
  async function deleteDeck(commentId) {
1433
1442
  await callTool("communication_delete", { comment_id: commentId });
1434
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
+ }
1435
1469
 
1436
1470
  // src/server/deck-serve.ts
1437
1471
  var DEFAULT_PORT = 52829;
@@ -1496,7 +1530,9 @@ function listPersistedSessions(cwd) {
1496
1530
  createdAt: persisted.createdAt,
1497
1531
  updatedAt: persisted.updatedAt,
1498
1532
  shared: Boolean(sync),
1499
- 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
1500
1536
  });
1501
1537
  }
1502
1538
  sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -1524,6 +1560,24 @@ function readSyncRecord(cwd, session) {
1524
1560
  function writeSyncRecord(cwd, session, rec) {
1525
1561
  import_node_fs4.default.writeFileSync(syncJsonPath(cwd, session), JSON.stringify(rec, null, 2));
1526
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
+ }
1527
1581
  function escHtml2(s) {
1528
1582
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1529
1583
  }
@@ -1540,31 +1594,49 @@ function readIframeArtifact(cwd, src) {
1540
1594
  return null;
1541
1595
  }
1542
1596
  }
1543
- function renderShareSection(cwd, block) {
1544
- 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>` : "";
1545
1599
  switch (block?.type) {
1546
1600
  case "iframe": {
1547
1601
  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>`;
1602
+ return `${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe>`;
1549
1603
  }
1550
1604
  case "rich-html":
1551
1605
  case "html":
1552
- 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>`;
1553
1607
  case "markdown":
1554
- 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>`;
1555
1609
  case "mermaid":
1556
- 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>`;
1557
1614
  case "options": {
1558
1615
  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>`
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>`
1561
1618
  ).join("");
1562
- return `<section class="db">${label}<ul>${lis}</ul></section>`;
1619
+ return `${label}<div class="db-options">${cards}</div>`;
1563
1620
  }
1564
1621
  default:
1565
- return `<section class="db">${label}</section>`;
1622
+ return label;
1566
1623
  }
1567
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
+ }
1568
1640
  function buildShareHtml(cwd, session, blocks) {
1569
1641
  if (blocks.length === 1) {
1570
1642
  const only = blocks[0];
@@ -1574,30 +1646,61 @@ function buildShareHtml(cwd, session, blocks) {
1574
1646
  }
1575
1647
  }
1576
1648
  const list = blocks;
1577
- 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");
1578
1656
  const hasMd = list.some((b) => b?.type === "markdown");
1579
- 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
+ );
1580
1660
  return `<!DOCTYPE html>
1581
1661
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
1582
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">
1583
1664
  <style>
1584
- 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; }
1585
1666
  .db { padding:20px 24px; border-bottom:1px solid #1e2433; }
1586
1667
  .db-title { font-size:13px; font-weight:600; margin:0 0 12px; color:#9aa4b2; text-transform:uppercase; letter-spacing:.05em; }
1587
1668
  .db-frame { width:100%; height:70vh; border:0; border-radius:8px; background:#fff; }
1588
1669
  .db-html { background:#fff; color:#111; border-radius:8px; padding:16px; overflow:auto; }
1589
1670
  ul { line-height:1.7; }
1590
- .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; }
1591
1687
  </style></head>
1592
1688
  <body>
1689
+ ${tabBar}
1593
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>` : ""}
1594
1692
  ${hasMd ? `<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1595
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>` : ""}
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>` : ""}
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>` : ""}
1597
1695
  </body></html>`;
1598
1696
  }
1599
1697
  var pendingFeedback = /* @__PURE__ */ new Map();
1600
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
+ }
1601
1704
  function consumeRenderError() {
1602
1705
  const err = lastRenderError;
1603
1706
  lastRenderError = null;
@@ -1837,45 +1940,35 @@ async function startDeckServer(opts = {}) {
1837
1940
  return;
1838
1941
  }
1839
1942
  let course;
1943
+ let svgByIndex = {};
1944
+ let publicLink = false;
1840
1945
  try {
1841
1946
  const raw = await readBody(req);
1842
1947
  if (raw.trim()) {
1843
- course = JSON.parse(raw).course?.trim() || void 0;
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;
1844
1952
  }
1845
1953
  } catch {
1846
1954
  }
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
- }
1955
+ const cfg = resolveShareConfig(cwd, course);
1956
+ if ("error" in cfg) {
1957
+ jsonResponse(res, 400, { ok: false, error: cfg.error });
1958
+ return;
1867
1959
  }
1960
+ const { override } = cfg;
1868
1961
  try {
1869
1962
  const blocks = Array.isArray(persisted.blocks) ? persisted.blocks : [];
1870
- const html = buildShareHtml(cwd, id, blocks);
1963
+ const { html, cloudBlocks } = buildSharePayload(cwd, id, blocks, svgByIndex);
1871
1964
  const types = blocks.map((b) => b?.type).filter(Boolean);
1872
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.`;
1873
- const input = { title: id, body, html, sessionId: id, blockCount: blocks.length };
1874
- const existing2 = readSyncRecord(cwd, id);
1966
+ const input = { title: id, body, html, blocks: cloudBlocks, sessionId: id, blockCount: blocks.length };
1967
+ const existingSync = readSyncRecord(cwd, id);
1875
1968
  const doShare = async () => {
1876
- if (existing2?.resourceId) {
1969
+ if (existingSync?.resourceId) {
1877
1970
  try {
1878
- const { id: rid2 } = await updateDeck(existing2.resourceId, input);
1971
+ const { id: rid2 } = await updateDeck(existingSync.resourceId, input);
1879
1972
  return { resourceId: rid2, updated: true };
1880
1973
  } catch {
1881
1974
  const { id: rid2 } = await writeDeck(input);
@@ -1885,14 +1978,89 @@ async function startDeckServer(opts = {}) {
1885
1978
  const { id: rid } = await writeDeck(input);
1886
1979
  return { resourceId: rid, updated: false };
1887
1980
  };
1888
- const { resourceId, updated } = override ? await withMcpConfig(override, doShare) : await doShare();
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) {
1996
+ try {
1997
+ await runWithShareConfig(override, () => deleteDeck(result.shared.resourceId));
1998
+ } catch {
1999
+ }
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;
2005
+ }
2006
+ const publicShareUrl = result.link?.shareUrl ?? existingSync?.publicShareUrl;
2007
+ const publicLinkId = result.link?.linkId ?? existingSync?.publicLinkId;
1889
2008
  writeSyncRecord(cwd, id, {
1890
- resourceId,
2009
+ resourceId: result.shared.resourceId,
1891
2010
  sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
1892
2011
  version: persisted.version,
1893
- course
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 } : {}
1894
2023
  });
1895
- jsonResponse(res, 200, { ok: true, resourceId, updated, course });
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 });
1896
2064
  } catch (err) {
1897
2065
  jsonResponse(res, 500, { ok: false, error: String(err) });
1898
2066
  }
@@ -1920,7 +2088,16 @@ async function startDeckServer(opts = {}) {
1920
2088
  }
1921
2089
  jsonResponse(res, 200, { ok: true, deleted: true });
1922
2090
  } catch (err) {
1923
- 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 });
1924
2101
  }
1925
2102
  return;
1926
2103
  }
@@ -2011,6 +2188,10 @@ async function startDeckServer(opts = {}) {
2011
2188
  deletePersistedSession(cwd, session);
2012
2189
  } catch {
2013
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;
2014
2195
  jsonResponse(res, 200, { ok: true });
2015
2196
  return;
2016
2197
  }
@@ -2019,6 +2200,16 @@ async function startDeckServer(opts = {}) {
2019
2200
  jsonResponse(res, 200, err ?? null);
2020
2201
  return;
2021
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
+ }
2022
2213
  if (req.method === "GET" && url2.pathname.startsWith("/deck-files/")) {
2023
2214
  const relative = decodeURIComponent(url2.pathname.slice("/deck-files/".length));
2024
2215
  if (relative.includes("..") || relative.startsWith("/")) {
@@ -2091,6 +2282,24 @@ async function startDeckServer(opts = {}) {
2091
2282
  source: msg.source ?? ""
2092
2283
  };
2093
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
+ }
2094
2303
  } catch {
2095
2304
  }
2096
2305
  });
@@ -2159,6 +2368,7 @@ function runServeCli(argv) {
2159
2368
  // Annotate the CommonJS export names for ESM import in node:
2160
2369
  0 && (module.exports = {
2161
2370
  broadcastToClients,
2371
+ buildSharePayload,
2162
2372
  consumeRenderError,
2163
2373
  createFeedbackWaiter,
2164
2374
  resolveFeedback,