@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
@@ -335,7 +335,13 @@ async function callTool(toolName, args) {
335
335
  }
336
336
  const textContent = result.result?.content?.[0]?.text;
337
337
  if (!textContent) return null;
338
- return JSON.parse(textContent);
338
+ try {
339
+ return JSON.parse(textContent);
340
+ } catch {
341
+ const match = textContent.match(/──+\s*Error\s*──+\s*\n([\s\S]*?)(?:\n\n──|$)/i);
342
+ const message = (match?.[1] ?? textContent).trim();
343
+ throw new Error(message || "MCP tool returned an unparseable response");
344
+ }
339
345
  }
340
346
  async function readDiscussion(discussionId) {
341
347
  const result = await callTool("communication_read", {
@@ -434,7 +434,13 @@ async function callTool(toolName, args) {
434
434
  }
435
435
  const textContent = result.result?.content?.[0]?.text;
436
436
  if (!textContent) return null;
437
- return JSON.parse(textContent);
437
+ try {
438
+ return JSON.parse(textContent);
439
+ } catch {
440
+ const match = textContent.match(/──+\s*Error\s*──+\s*\n([\s\S]*?)(?:\n\n──|$)/i);
441
+ const message = (match?.[1] ?? textContent).trim();
442
+ throw new Error(message || "MCP tool returned an unparseable response");
443
+ }
438
444
  }
439
445
  async function readDiscussion(discussionId) {
440
446
  const result = await callTool("communication_read", {
@@ -1421,7 +1421,13 @@ async function callTool(toolName, args) {
1421
1421
  }
1422
1422
  const textContent = result.result?.content?.[0]?.text;
1423
1423
  if (!textContent) return null;
1424
- return JSON.parse(textContent);
1424
+ try {
1425
+ return JSON.parse(textContent);
1426
+ } catch {
1427
+ const match = textContent.match(/──+\s*Error\s*──+\s*\n([\s\S]*?)(?:\n\n──|$)/i);
1428
+ const message = (match?.[1] ?? textContent).trim();
1429
+ throw new Error(message || "MCP tool returned an unparseable response");
1430
+ }
1425
1431
  }
1426
1432
  async function writeDeck(input) {
1427
1433
  const result = await callTool("communication_write", {
@@ -1430,6 +1436,7 @@ async function writeDeck(input) {
1430
1436
  resource_type: "deck",
1431
1437
  fields: {
1432
1438
  deckHtml: input.html,
1439
+ deckBlocks: input.blocks,
1433
1440
  sessionId: input.sessionId,
1434
1441
  blockCount: input.blockCount,
1435
1442
  sharedFrom: "launch-deck"
@@ -1444,6 +1451,7 @@ async function updateDeck(commentId, input) {
1444
1451
  body: input.body,
1445
1452
  fields: {
1446
1453
  deckHtml: input.html,
1454
+ deckBlocks: input.blocks,
1447
1455
  sessionId: input.sessionId,
1448
1456
  blockCount: input.blockCount,
1449
1457
  sharedFrom: "launch-deck"
@@ -1454,6 +1462,31 @@ async function updateDeck(commentId, input) {
1454
1462
  async function deleteDeck(commentId) {
1455
1463
  await callTool("communication_delete", { comment_id: commentId });
1456
1464
  }
1465
+ function currentMcpOrigin() {
1466
+ const config = _config;
1467
+ if (!config) throw new Error("MCP config not loaded \u2014 call loadMcpConfig first");
1468
+ return new URL(config.url).origin;
1469
+ }
1470
+ async function createDeckShareLink(commentId) {
1471
+ const result = await callTool("deck_share_link", {
1472
+ comment_id: commentId,
1473
+ action: "create"
1474
+ });
1475
+ const token = result.link.token;
1476
+ return {
1477
+ linkId: result.link.id,
1478
+ token,
1479
+ shareUrl: `${currentMcpOrigin()}/s/deck/${token}`,
1480
+ reused: Boolean(result.reused)
1481
+ };
1482
+ }
1483
+ async function revokeDeckShareLink(commentId, linkId) {
1484
+ await callTool("deck_share_link", {
1485
+ comment_id: commentId,
1486
+ action: "revoke",
1487
+ link_id: linkId
1488
+ });
1489
+ }
1457
1490
  var import_node_fs4, import_node_path4, _config, _requestId, _sessionId, _overrideLock;
1458
1491
  var init_mcp_client = __esm({
1459
1492
  "src/server/mcp-client.ts"() {
@@ -1472,6 +1505,7 @@ var init_mcp_client = __esm({
1472
1505
  var deck_serve_exports = {};
1473
1506
  __export(deck_serve_exports, {
1474
1507
  broadcastToClients: () => broadcastToClients,
1508
+ buildSharePayload: () => buildSharePayload,
1475
1509
  consumeRenderError: () => consumeRenderError,
1476
1510
  createFeedbackWaiter: () => createFeedbackWaiter,
1477
1511
  resolveFeedback: () => resolveFeedback,
@@ -1527,7 +1561,9 @@ function listPersistedSessions(cwd) {
1527
1561
  createdAt: persisted.createdAt,
1528
1562
  updatedAt: persisted.updatedAt,
1529
1563
  shared: Boolean(sync),
1530
- stale: Boolean(sync) && persisted.version > (sync?.version ?? 0)
1564
+ stale: Boolean(sync) && persisted.version > (sync?.version ?? 0),
1565
+ shareUrl: sync?.publicShareUrl,
1566
+ course: sync?.course
1531
1567
  });
1532
1568
  }
1533
1569
  sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -1555,6 +1591,24 @@ function readSyncRecord(cwd, session) {
1555
1591
  function writeSyncRecord(cwd, session, rec) {
1556
1592
  import_node_fs5.default.writeFileSync(syncJsonPath(cwd, session), JSON.stringify(rec, null, 2));
1557
1593
  }
1594
+ function resolveShareConfig(cwd, course) {
1595
+ if (course) {
1596
+ const override = mcpConfigForProfile(cwd, course);
1597
+ if (!override) {
1598
+ return { error: `Course "${course}" is not a usable profile in .launch-secure.cred.config (missing pat/serverUrl/orgSlug/projectSlug?).` };
1599
+ }
1600
+ return { override };
1601
+ }
1602
+ try {
1603
+ loadMcpConfig(cwd);
1604
+ } catch (err) {
1605
+ return { error: `Cloud sharing needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}` };
1606
+ }
1607
+ return { override: null };
1608
+ }
1609
+ function runWithShareConfig(override, fn) {
1610
+ return override ? withMcpConfig(override, fn) : fn();
1611
+ }
1558
1612
  function escHtml2(s) {
1559
1613
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1560
1614
  }
@@ -1571,31 +1625,49 @@ function readIframeArtifact(cwd, src) {
1571
1625
  return null;
1572
1626
  }
1573
1627
  }
1574
- function renderShareSection(cwd, block) {
1575
- const label = block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
1628
+ function renderShareSection(cwd, block, showLabel = true) {
1629
+ const label = showLabel && block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
1576
1630
  switch (block?.type) {
1577
1631
  case "iframe": {
1578
1632
  const doc = readIframeArtifact(cwd, block.src) ?? "<p>(missing artifact)</p>";
1579
- return `<section class="db">${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe></section>`;
1633
+ return `${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe>`;
1580
1634
  }
1581
1635
  case "rich-html":
1582
1636
  case "html":
1583
- return `<section class="db">${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div></section>`;
1637
+ return `${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div>`;
1584
1638
  case "markdown":
1585
- return `<section class="db">${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div></section>`;
1639
+ return `${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div>`;
1586
1640
  case "mermaid":
1587
- return `<section class="db">${label}<pre class="mermaid">${escHtml2(typeof block.content === "string" ? block.content : "")}</pre></section>`;
1641
+ if (typeof block.renderedSvg === "string" && block.renderedSvg.trim()) {
1642
+ return `${label}<div class="mermaid-svg">${block.renderedSvg}</div>`;
1643
+ }
1644
+ return `${label}<pre class="mermaid">${escHtml2(typeof block.content === "string" ? block.content : "")}</pre>`;
1588
1645
  case "options": {
1589
1646
  const opts = Array.isArray(block.options) ? block.options : [];
1590
- const lis = opts.map(
1591
- (o) => `<li><strong>${escHtml2(String(o?.label ?? o?.id ?? ""))}</strong>${o?.description ? ` \u2014 ${escHtml2(String(o.description))}` : ""}</li>`
1647
+ const cards = opts.map(
1648
+ (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>`
1592
1649
  ).join("");
1593
- return `<section class="db">${label}<ul>${lis}</ul></section>`;
1650
+ return `${label}<div class="db-options">${cards}</div>`;
1594
1651
  }
1595
1652
  default:
1596
- return `<section class="db">${label}</section>`;
1653
+ return label;
1597
1654
  }
1598
1655
  }
1656
+ function buildSharePayload(cwd, session, blocks, svgByIndex = {}) {
1657
+ const enriched = blocks.map((b, i) => {
1658
+ const svg = svgByIndex[String(i)];
1659
+ return b?.type === "mermaid" && typeof svg === "string" && svg.trim() ? { ...b, renderedSvg: svg } : b;
1660
+ });
1661
+ const html = buildShareHtml(cwd, session, enriched);
1662
+ const cloudBlocks = enriched.map((b) => {
1663
+ if (b?.type === "iframe" && typeof b.src === "string") {
1664
+ const content = readIframeArtifact(cwd, b.src);
1665
+ return content ? { ...b, content } : b;
1666
+ }
1667
+ return b;
1668
+ });
1669
+ return { html, cloudBlocks };
1670
+ }
1599
1671
  function buildShareHtml(cwd, session, blocks) {
1600
1672
  if (blocks.length === 1) {
1601
1673
  const only = blocks[0];
@@ -1605,28 +1677,57 @@ function buildShareHtml(cwd, session, blocks) {
1605
1677
  }
1606
1678
  }
1607
1679
  const list = blocks;
1608
- const sections = list.map((b) => renderShareSection(cwd, b)).join("\n");
1680
+ const multi = list.length > 1;
1681
+ const tabBar = multi ? `<nav class="deck-tabs">${list.map(
1682
+ (b, i) => `<button class="deck-tab${i === 0 ? " active" : ""}" data-tab-btn="${i}">${escHtml2(String(b?.label ?? `Tab ${i + 1}`))}</button>`
1683
+ ).join("")}</nav>` : "";
1684
+ const sections = list.map(
1685
+ (b, i) => `<section class="db" data-tab="${i}"${multi && i > 0 ? " hidden" : ""}>${renderShareSection(cwd, b, !multi)}</section>`
1686
+ ).join("\n");
1609
1687
  const hasMd = list.some((b) => b?.type === "markdown");
1610
- const hasMermaid = list.some((b) => b?.type === "mermaid");
1688
+ const hasUnrenderedMermaid = list.some(
1689
+ (b) => b?.type === "mermaid" && !(typeof b?.renderedSvg === "string" && b.renderedSvg.trim())
1690
+ );
1611
1691
  return `<!DOCTYPE html>
1612
1692
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
1613
1693
  <title>${escHtml2(session)}</title>
1694
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,100..1000&display=swap" rel="stylesheet">
1614
1695
  <style>
1615
- body { margin:0; font-family: ui-sans-serif, system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
1696
+ body { margin:0; font-family: 'DM Sans', system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
1616
1697
  .db { padding:20px 24px; border-bottom:1px solid #1e2433; }
1617
1698
  .db-title { font-size:13px; font-weight:600; margin:0 0 12px; color:#9aa4b2; text-transform:uppercase; letter-spacing:.05em; }
1618
1699
  .db-frame { width:100%; height:70vh; border:0; border-radius:8px; background:#fff; }
1619
1700
  .db-html { background:#fff; color:#111; border-radius:8px; padding:16px; overflow:auto; }
1620
1701
  ul { line-height:1.7; }
1621
- .mermaid { background:#fff; border-radius:8px; padding:16px; }
1702
+ /* The embedded SVG is dark-themed (captured from the deck client); keep its
1703
+ backdrop transparent so it sits on the dark page, matching the in-app
1704
+ DeckView \u2014 not a white card. The CDN-source fallback also renders dark. */
1705
+ .mermaid { background:transparent; border-radius:8px; padding:16px; }
1706
+ .mermaid-svg { background:transparent; border-radius:8px; padding:16px; overflow:auto; }
1707
+ .mermaid-svg svg { max-width:100%; height:auto; }
1708
+ .db-options { display:grid; gap:12px; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); }
1709
+ .db-option { border:2px solid #1e2433; border-radius:8px; padding:16px; }
1710
+ .db-option-label { font-size:14px; font-weight:600; color:#e6e9ef; }
1711
+ .db-option-desc { margin-top:4px; font-size:12px; color:#9aa4b2; }
1712
+ .db-option-pre { margin-top:8px; padding:8px; border-radius:6px; background:#11151f; font-size:12px; overflow:auto; }
1713
+ .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; }
1714
+ .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; }
1715
+ .deck-tab:hover { color:#e6e9ef; }
1716
+ .deck-tab.active { color:#c084fc; border-bottom-color:#c084fc; }
1717
+ .db[hidden] { display:none; }
1622
1718
  </style></head>
1623
1719
  <body>
1720
+ ${tabBar}
1624
1721
  ${sections}
1722
+ ${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>` : ""}
1625
1723
  ${hasMd ? `<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
1626
1724
  <script>document.querySelectorAll('.db-md').forEach(function(el){try{el.innerHTML=marked.parse(el.dataset.md||'')}catch(e){el.textContent=el.dataset.md||''}});</script>` : ""}
1627
- ${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>` : ""}
1725
+ ${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>` : ""}
1628
1726
  </body></html>`;
1629
1727
  }
1728
+ function broadcastDiscuss() {
1729
+ broadcastToClients({ type: "discuss_session", sessions: discussSessions.map((d) => d.id) });
1730
+ }
1630
1731
  function consumeRenderError() {
1631
1732
  const err = lastRenderError;
1632
1733
  lastRenderError = null;
@@ -1865,45 +1966,35 @@ async function startDeckServer(opts = {}) {
1865
1966
  return;
1866
1967
  }
1867
1968
  let course;
1969
+ let svgByIndex = {};
1970
+ let publicLink = false;
1868
1971
  try {
1869
1972
  const raw = await readBody(req);
1870
1973
  if (raw.trim()) {
1871
- course = JSON.parse(raw).course?.trim() || void 0;
1974
+ const parsed = JSON.parse(raw);
1975
+ course = parsed.course?.trim() || void 0;
1976
+ if (parsed.svgByIndex && typeof parsed.svgByIndex === "object") svgByIndex = parsed.svgByIndex;
1977
+ publicLink = parsed.publicLink === true;
1872
1978
  }
1873
1979
  } catch {
1874
1980
  }
1875
- let override = null;
1876
- if (course) {
1877
- override = mcpConfigForProfile(cwd, course);
1878
- if (!override) {
1879
- jsonResponse(res, 400, {
1880
- ok: false,
1881
- error: `Course "${course}" is not a usable profile in .launch-secure.cred.config (missing pat/serverUrl/orgSlug/projectSlug?).`
1882
- });
1883
- return;
1884
- }
1885
- } else {
1886
- try {
1887
- loadMcpConfig(cwd);
1888
- } catch (err) {
1889
- jsonResponse(res, 400, {
1890
- ok: false,
1891
- error: `Cloud sharing needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}`
1892
- });
1893
- return;
1894
- }
1981
+ const cfg = resolveShareConfig(cwd, course);
1982
+ if ("error" in cfg) {
1983
+ jsonResponse(res, 400, { ok: false, error: cfg.error });
1984
+ return;
1895
1985
  }
1986
+ const { override } = cfg;
1896
1987
  try {
1897
1988
  const blocks = Array.isArray(persisted.blocks) ? persisted.blocks : [];
1898
- const html = buildShareHtml(cwd, id, blocks);
1989
+ const { html, cloudBlocks } = buildSharePayload(cwd, id, blocks, svgByIndex);
1899
1990
  const types = blocks.map((b) => b?.type).filter(Boolean);
1900
1991
  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.`;
1901
- const input = { title: id, body, html, sessionId: id, blockCount: blocks.length };
1902
- const existing2 = readSyncRecord(cwd, id);
1992
+ const input = { title: id, body, html, blocks: cloudBlocks, sessionId: id, blockCount: blocks.length };
1993
+ const existingSync = readSyncRecord(cwd, id);
1903
1994
  const doShare = async () => {
1904
- if (existing2?.resourceId) {
1995
+ if (existingSync?.resourceId) {
1905
1996
  try {
1906
- const { id: rid2 } = await updateDeck(existing2.resourceId, input);
1997
+ const { id: rid2 } = await updateDeck(existingSync.resourceId, input);
1907
1998
  return { resourceId: rid2, updated: true };
1908
1999
  } catch {
1909
2000
  const { id: rid2 } = await writeDeck(input);
@@ -1913,14 +2004,89 @@ async function startDeckServer(opts = {}) {
1913
2004
  const { id: rid } = await writeDeck(input);
1914
2005
  return { resourceId: rid, updated: false };
1915
2006
  };
1916
- const { resourceId, updated } = override ? await withMcpConfig(override, doShare) : await doShare();
2007
+ const result = await runWithShareConfig(override, async () => {
2008
+ const shared = await doShare();
2009
+ let link = null;
2010
+ let linkError;
2011
+ if (publicLink) {
2012
+ try {
2013
+ const minted = await createDeckShareLink(shared.resourceId);
2014
+ link = { shareUrl: minted.shareUrl, linkId: minted.linkId };
2015
+ } catch (err) {
2016
+ linkError = err instanceof Error ? err.message : String(err);
2017
+ }
2018
+ }
2019
+ return { shared, link, linkError };
2020
+ });
2021
+ if (publicLink && result.linkError && !result.shared.updated) {
2022
+ try {
2023
+ await runWithShareConfig(override, () => deleteDeck(result.shared.resourceId));
2024
+ } catch {
2025
+ }
2026
+ jsonResponse(res, 200, {
2027
+ ok: false,
2028
+ error: `Couldn't create the public link, so the deck wasn't shared: ${result.linkError}`
2029
+ });
2030
+ return;
2031
+ }
2032
+ const publicShareUrl = result.link?.shareUrl ?? existingSync?.publicShareUrl;
2033
+ const publicLinkId = result.link?.linkId ?? existingSync?.publicLinkId;
1917
2034
  writeSyncRecord(cwd, id, {
1918
- resourceId,
2035
+ resourceId: result.shared.resourceId,
1919
2036
  sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
1920
2037
  version: persisted.version,
1921
- course
2038
+ course,
2039
+ ...publicShareUrl ? { publicShareUrl } : {},
2040
+ ...publicLinkId ? { publicLinkId } : {}
2041
+ });
2042
+ jsonResponse(res, 200, {
2043
+ ok: true,
2044
+ resourceId: result.shared.resourceId,
2045
+ updated: result.shared.updated,
2046
+ course,
2047
+ shareUrl: publicShareUrl ?? null,
2048
+ ...result.linkError ? { linkWarning: result.linkError } : {}
1922
2049
  });
1923
- jsonResponse(res, 200, { ok: true, resourceId, updated, course });
2050
+ } catch (err) {
2051
+ jsonResponse(res, 500, { ok: false, error: String(err) });
2052
+ }
2053
+ return;
2054
+ }
2055
+ const publicLinkMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/public-link$/);
2056
+ if (publicLinkMatch && (req.method === "POST" || req.method === "DELETE")) {
2057
+ const id = decodeURIComponent(publicLinkMatch[1]);
2058
+ const sync = readSyncRecord(cwd, id);
2059
+ if (!sync?.resourceId) {
2060
+ jsonResponse(res, 409, { ok: false, error: "Share this deck to the cloud first, then create a public link." });
2061
+ return;
2062
+ }
2063
+ const cfg = resolveShareConfig(cwd, sync.course);
2064
+ if ("error" in cfg) {
2065
+ jsonResponse(res, 400, { ok: false, error: cfg.error });
2066
+ return;
2067
+ }
2068
+ const { override } = cfg;
2069
+ if (req.method === "DELETE") {
2070
+ if (!sync.publicLinkId) {
2071
+ if (sync.publicShareUrl) {
2072
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: void 0, publicLinkId: void 0 });
2073
+ }
2074
+ jsonResponse(res, 200, { ok: true, revoked: false });
2075
+ return;
2076
+ }
2077
+ try {
2078
+ await runWithShareConfig(override, () => revokeDeckShareLink(sync.resourceId, sync.publicLinkId));
2079
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: void 0, publicLinkId: void 0 });
2080
+ jsonResponse(res, 200, { ok: true, revoked: true });
2081
+ } catch (err) {
2082
+ jsonResponse(res, 500, { ok: false, error: String(err) });
2083
+ }
2084
+ return;
2085
+ }
2086
+ try {
2087
+ const minted = await runWithShareConfig(override, () => createDeckShareLink(sync.resourceId));
2088
+ writeSyncRecord(cwd, id, { ...sync, publicShareUrl: minted.shareUrl, publicLinkId: minted.linkId });
2089
+ jsonResponse(res, 200, { ok: true, shareUrl: minted.shareUrl });
1924
2090
  } catch (err) {
1925
2091
  jsonResponse(res, 500, { ok: false, error: String(err) });
1926
2092
  }
@@ -1948,7 +2114,16 @@ async function startDeckServer(opts = {}) {
1948
2114
  }
1949
2115
  jsonResponse(res, 200, { ok: true, deleted: true });
1950
2116
  } catch (err) {
1951
- jsonResponse(res, 500, { ok: false, error: String(err) });
2117
+ const msg = err instanceof Error ? err.message : String(err);
2118
+ if (/not\s*found/i.test(msg)) {
2119
+ try {
2120
+ import_node_fs5.default.rmSync(syncJsonPath(cwd, id), { force: true });
2121
+ } catch {
2122
+ }
2123
+ jsonResponse(res, 200, { ok: true, deleted: false, reason: "already removed" });
2124
+ return;
2125
+ }
2126
+ jsonResponse(res, 500, { ok: false, error: msg });
1952
2127
  }
1953
2128
  return;
1954
2129
  }
@@ -2039,6 +2214,10 @@ async function startDeckServer(opts = {}) {
2039
2214
  deletePersistedSession(cwd, session);
2040
2215
  } catch {
2041
2216
  }
2217
+ const beforePins = discussSessions.length;
2218
+ discussSessions = session === "all" ? [] : discussSessions.filter((d) => d.id !== session);
2219
+ if (discussSessions.length !== beforePins) broadcastDiscuss();
2220
+ if (session === "all" || activeSession?.id === session) activeSession = null;
2042
2221
  jsonResponse(res, 200, { ok: true });
2043
2222
  return;
2044
2223
  }
@@ -2047,6 +2226,16 @@ async function startDeckServer(opts = {}) {
2047
2226
  jsonResponse(res, 200, err ?? null);
2048
2227
  return;
2049
2228
  }
2229
+ if (req.method === "GET" && url2.pathname === "/api/active-session") {
2230
+ jsonResponse(res, 200, {
2231
+ discuss: discussSessions.map((d) => d.id),
2232
+ // ordered = pin number
2233
+ active: activeSession?.id ?? null,
2234
+ focused: activeSession?.focused ?? false,
2235
+ activeAt: activeSession?.at ?? null
2236
+ });
2237
+ return;
2238
+ }
2050
2239
  if (req.method === "GET" && url2.pathname.startsWith("/deck-files/")) {
2051
2240
  const relative = decodeURIComponent(url2.pathname.slice("/deck-files/".length));
2052
2241
  if (relative.includes("..") || relative.startsWith("/")) {
@@ -2119,6 +2308,24 @@ async function startDeckServer(opts = {}) {
2119
2308
  source: msg.source ?? ""
2120
2309
  };
2121
2310
  }
2311
+ if (msg.type === "active_session" && msg.session) {
2312
+ activeSession = {
2313
+ id: msg.session,
2314
+ focused: msg.focused ?? false,
2315
+ at: Date.now()
2316
+ };
2317
+ }
2318
+ if (msg.type === "discuss_session" && msg.session) {
2319
+ const id = msg.session;
2320
+ const idx = discussSessions.findIndex((d) => d.id === id);
2321
+ if (idx >= 0) discussSessions.splice(idx, 1);
2322
+ else discussSessions.push({ id, at: Date.now() });
2323
+ broadcastDiscuss();
2324
+ }
2325
+ if (msg.type === "discuss_clear_all") {
2326
+ discussSessions = [];
2327
+ broadcastDiscuss();
2328
+ }
2122
2329
  } catch {
2123
2330
  }
2124
2331
  });
@@ -2184,7 +2391,7 @@ function runServeCli(argv) {
2184
2391
  process.exit(1);
2185
2392
  });
2186
2393
  }
2187
- var import_node_http, import_node_fs5, import_node_path5, import_ws, DEFAULT_PORT, MAX_PORT_SCAN, MIME_TYPES, pendingFeedback, lastRenderError, wss;
2394
+ var import_node_http, import_node_fs5, import_node_path5, import_ws, DEFAULT_PORT, MAX_PORT_SCAN, MIME_TYPES, pendingFeedback, lastRenderError, activeSession, discussSessions, wss;
2188
2395
  var init_deck_serve = __esm({
2189
2396
  "src/server/deck-serve.ts"() {
2190
2397
  "use strict";
@@ -2214,6 +2421,8 @@ var init_deck_serve = __esm({
2214
2421
  };
2215
2422
  pendingFeedback = /* @__PURE__ */ new Map();
2216
2423
  lastRenderError = null;
2424
+ activeSession = null;
2425
+ discussSessions = [];
2217
2426
  wss = null;
2218
2427
  }
2219
2428
  });
@@ -2473,6 +2682,30 @@ async function handleTool(name, args) {
2473
2682
  }
2474
2683
  return text(JSON.stringify({ running: false }));
2475
2684
  }
2685
+ case "active_session": {
2686
+ const lock = getLiveLock(projectRoot);
2687
+ if (!lock) {
2688
+ return text(JSON.stringify({
2689
+ error: "LaunchDeck server is not running \u2014 nothing is open.",
2690
+ hint: "Start it with start_server, or ask the user to open the deck."
2691
+ }));
2692
+ }
2693
+ try {
2694
+ const resp = await fetch(`${lock.url}/api/active-session`, {
2695
+ signal: AbortSignal.timeout(3e3)
2696
+ });
2697
+ const data = await resp.json();
2698
+ const pins = data.discuss ?? [];
2699
+ const targets = pins.length ? pins : data.active ? [data.active] : [];
2700
+ return text(JSON.stringify({
2701
+ ...data,
2702
+ targets,
2703
+ targetSource: pins.length ? "pinned" : data.active ? "active-tab" : "none"
2704
+ }));
2705
+ } catch (err) {
2706
+ return text(JSON.stringify({ error: `Failed to read active session: ${err}` }));
2707
+ }
2708
+ }
2476
2709
  case "generate_contract": {
2477
2710
  const session = args.session;
2478
2711
  const dir = (0, import_node_path6.join)(projectRoot, LAUNCHSECURE_DIR, "deck-files", session);
@@ -2713,6 +2946,11 @@ var init_deck_mcp = __esm({
2713
2946
  description: "Check whether the LaunchDeck UI server is running.",
2714
2947
  inputSchema: { type: "object", properties: {} }
2715
2948
  },
2949
+ {
2950
+ name: "active_session",
2951
+ description: 'Find out which deck(s) the user is currently looking at or watching. Returns `discuss` (an ORDERED list of sessions the user is explicitly WATCHING via the \u{1F441} eye button \u2014 order = the number shown in the UI; the strongest signal, use these when non-empty), `active` (the tab last shown in the browser), `focused` (whether the deck window itself was focused when `active` was reported \u2014 if false, the user is probably elsewhere and `active` is just the last-viewed tab), and `targets` (the resolved answer to "which deck(s) does the user mean": the watched decks if any, else the active tab). Use this instead of guessing from the session list when the user says "this deck" / "the open one" / "the ones I\'m watching".',
2952
+ inputSchema: { type: "object", properties: {} }
2953
+ },
2716
2954
  {
2717
2955
  name: "generate_contract",
2718
2956
  description: "Generate a contract from a previously pushed blast-radius session.\n\nReads the blast radius manifest from the session's saved files and produces a structured contract with actionable items, acceptance criteria, dependencies, and a verify-unchanged checklist.\n\nReturns JSON contract. Also saves contract.json and contract.md to the session directory.",