@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.
- package/dist/chart-client/assets/index-Dd6IotOZ.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-DE0uje6k.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/council-client/assets/index-CGYusOCK.css +1 -0
- package/dist/council-client/assets/{index-B1v46vTE.js → index-DkTFX53U.js} +1 -1
- package/dist/council-client/index.html +2 -2
- package/dist/deck-client/assets/_baseUniq-BrhDuG3C.js +1 -0
- package/dist/deck-client/assets/arc-DXtPHMhw.js +1 -0
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-Cs9IdLQQ.js +36 -0
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CUdblaWk.js → blockDiagram-DXYQGD6D--wpvoBAt.js} +2 -2
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-MfAO5lak.js → c4Diagram-AHTNJAMY-BNIKE8-Z.js} +2 -2
- package/dist/deck-client/assets/channel-Bb5wIjTD.js +1 -0
- package/dist/deck-client/assets/chunk-4BX2VUAB-Bz3EWmUo.js +1 -0
- package/dist/deck-client/assets/{chunk-4TB4RGXK-BUJtZ7jO.js → chunk-4TB4RGXK-D55BBvVZ.js} +1 -1
- package/dist/deck-client/assets/chunk-55IACEB6-BzjEcoTi.js +1 -0
- package/dist/deck-client/assets/chunk-EDXVE4YY-CH1vs4Lu.js +1 -0
- package/dist/deck-client/assets/{chunk-FMBD7UC4-PnZ9v6ey.js → chunk-FMBD7UC4-CVRnaGM0.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-DXrWNOsV.js → chunk-OYMX7WX6-BCTV_aEZ.js} +1 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-BqM8r4ee.js +1 -0
- package/dist/deck-client/assets/chunk-YZCP3GAM-Rhbb691A.js +1 -0
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CN-ZYMbT.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CN-ZYMbT.js +1 -0
- package/dist/deck-client/assets/clone-D6e7poKG.js +1 -0
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-fWyBR9Nn.js +1 -0
- package/dist/deck-client/assets/dagre-KV5264BT-Ds0Sqext.js +4 -0
- package/dist/deck-client/assets/diagram-5BDNPKRD-BGz_X007.js +10 -0
- package/dist/deck-client/assets/diagram-G4DWMVQ6-C78jeb-r.js +24 -0
- package/dist/deck-client/assets/diagram-MMDJMWI5-BdoHW8mF.js +43 -0
- package/dist/deck-client/assets/diagram-TYMM5635-C8i9zc-U.js +24 -0
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-56pn_93p.js → erDiagram-SMLLAGMA-DplAZ-yG.js} +2 -2
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BtV3M5xJ.js → flowDiagram-DWJPFMVM-F3N9yTAB.js} +2 -2
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DebdM4S5.js +292 -0
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-htCk0oPz.js +106 -0
- package/dist/deck-client/assets/graph-Dtxc2PT4.js +1 -0
- package/dist/deck-client/assets/index-CwAiam97.js +892 -0
- package/dist/deck-client/assets/index-evAPhGvM.css +1 -0
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-gTxegviJ.js +2 -0
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DXYkdO3T.js → ishikawaDiagram-UXIWVN3A-DYSEsfUK.js} +5 -5
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C2zBr-J5.js → journeyDiagram-VCZTEJTY-DgVc1q4D.js} +2 -2
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CdoYLS4Z.js → kanban-definition-6JOO6SKY-DcOf3i9N.js} +2 -2
- package/dist/deck-client/assets/layout-CHP9HIw4.js +1 -0
- package/dist/deck-client/assets/linear-XjVb7x4Q.js +1 -0
- package/dist/deck-client/assets/mermaid.core-bouKOnsR.js +309 -0
- package/dist/deck-client/assets/min-Jl4GV9DB.js +1 -0
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-oAybLedr.js → mindmap-definition-QFDTVHPH-FXXf4cJx.js} +2 -2
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-Dl4plCTi.js +30 -0
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-dtluDZXs.js → quadrantDiagram-34T5L4WZ-BeOEeelg.js} +2 -2
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cq8l7bOl.js → requirementDiagram-MS252O5E-CBh1jchM.js} +2 -2
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-BK6gG-ub.js +10 -0
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CYkd7oQK.js → sequenceDiagram-FGHM5R23-BtQwzsF5.js} +2 -2
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX--kLQOi8R.js +1 -0
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CHEPjgB0.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DZIxSyd1.js → timeline-definition-GMOUNBTQ-CQ_-CLh7.js} +2 -2
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-Ct4JVRDM.js → vennDiagram-DHZGUBPP-DBhKYAFT.js} +2 -2
- package/dist/deck-client/assets/{wardley-RL74JXVD-V29ycxOW.js → wardley-RL74JXVD-DLKe29q9.js} +3 -3
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-D-Ua6Cmi.js → wardleyDiagram-NUSXRM2D-Brb8ezsV.js} +2 -2
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-BPCOuRVl.js → xychartDiagram-5P7HB3ND-LogZ0Az9.js} +2 -2
- package/dist/deck-client/index.html +2 -2
- package/dist/server/cli.js +488 -215
- package/dist/server/council-entry.js +7 -1
- package/dist/server/council-serve.js +7 -1
- package/dist/server/deck-mcp-entry.js +423 -50
- package/dist/server/deck-serve.js +350 -48
- package/dist/server/graph-mcp-entry.js +331 -37
- package/dist/server/init-entry.js +17 -7
- package/dist/server/rover-entry.js +1 -1
- package/package.json +1 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/comms/SKILL.md +34 -1
- package/scaffolds/ls-marketplace/plugins/kit/skills/gen-test/SKILL.md +126 -0
- package/dist/chart-client/assets/index-CWJFFDPu.css +0 -1
- package/dist/client/assets/index-CTzFcfGV.css +0 -32
- package/dist/council-client/assets/index-ArgRc5mN.css +0 -1
- package/dist/deck-client/assets/_baseUniq-BZP7n41F.js +0 -1
- package/dist/deck-client/assets/arc-31biU3Az.js +0 -1
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DHg6Ss--.js +0 -36
- package/dist/deck-client/assets/channel-BBkRLdnC.js +0 -1
- package/dist/deck-client/assets/chunk-4BX2VUAB-DQ1MrGgN.js +0 -1
- package/dist/deck-client/assets/chunk-55IACEB6-BdSnXB6g.js +0 -1
- package/dist/deck-client/assets/chunk-EDXVE4YY-94yZIUI8.js +0 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-CsIGIDKX.js +0 -1
- package/dist/deck-client/assets/chunk-YZCP3GAM-DVkBO9tn.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-DFCaeF-7.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-DFCaeF-7.js +0 -1
- package/dist/deck-client/assets/clone-GCEVRScB.js +0 -1
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-m126Oh3b.js +0 -1
- package/dist/deck-client/assets/dagre-KV5264BT-C2aig8U5.js +0 -4
- package/dist/deck-client/assets/diagram-5BDNPKRD-CKpoRfGn.js +0 -10
- package/dist/deck-client/assets/diagram-G4DWMVQ6-Cjh115Ep.js +0 -24
- package/dist/deck-client/assets/diagram-MMDJMWI5-DKlBv_2L.js +0 -43
- package/dist/deck-client/assets/diagram-TYMM5635-CdBh4cEn.js +0 -24
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DTIsC6Zg.js +0 -292
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-CJYeyCLe.js +0 -106
- package/dist/deck-client/assets/graph-BDvMu1Ss.js +0 -1
- package/dist/deck-client/assets/index-D4eSxcBn.css +0 -1
- package/dist/deck-client/assets/index-QnGVE9PZ.js +0 -1196
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-BWyKJnpW.js +0 -2
- package/dist/deck-client/assets/layout-vOnxnCQU.js +0 -1
- package/dist/deck-client/assets/linear-B0J0WCGz.js +0 -1
- package/dist/deck-client/assets/min-B0AXlT9L.js +0 -1
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-BjHyHxGk.js +0 -30
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-C1Vih91z.js +0 -10
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX-CtyG8wBK.js +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BLyKWfcN.js +0 -1
- /package/dist/chart-client/assets/{index-Dzlj-oCj.js → index-CrYM1-ac.js} +0 -0
- /package/dist/client/assets/{index-tTg_ezUF.js → index-BoIjawzY.js} +0 -0
|
@@ -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
|
-
|
|
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,49 @@ async function updateDeck(commentId, input) {
|
|
|
1454
1462
|
async function deleteDeck(commentId) {
|
|
1455
1463
|
await callTool("communication_delete", { comment_id: commentId });
|
|
1456
1464
|
}
|
|
1465
|
+
function authorLabel(c) {
|
|
1466
|
+
return c.author?.name || c.author?.email || "unknown";
|
|
1467
|
+
}
|
|
1468
|
+
async function readCloudDeck(commentId) {
|
|
1469
|
+
const result = await callTool("communication_read", {
|
|
1470
|
+
resource_type: "deck",
|
|
1471
|
+
limit: 50
|
|
1472
|
+
});
|
|
1473
|
+
const deck = result?.comments?.find((c) => c.id === commentId);
|
|
1474
|
+
if (!deck) return null;
|
|
1475
|
+
return {
|
|
1476
|
+
id: deck.id,
|
|
1477
|
+
title: deck.title ?? "(untitled deck)",
|
|
1478
|
+
author: authorLabel(deck),
|
|
1479
|
+
html: typeof deck.fields?.deckHtml === "string" ? deck.fields.deckHtml : null,
|
|
1480
|
+
blocks: Array.isArray(deck.fields?.deckBlocks) && deck.fields.deckBlocks.length > 0 ? deck.fields.deckBlocks : null
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
function currentMcpOrigin() {
|
|
1484
|
+
const config = _config;
|
|
1485
|
+
if (!config) throw new Error("MCP config not loaded \u2014 call loadMcpConfig first");
|
|
1486
|
+
return new URL(config.url).origin;
|
|
1487
|
+
}
|
|
1488
|
+
async function createDeckShareLink(commentId) {
|
|
1489
|
+
const result = await callTool("deck_share_link", {
|
|
1490
|
+
comment_id: commentId,
|
|
1491
|
+
action: "create"
|
|
1492
|
+
});
|
|
1493
|
+
const token = result.link.token;
|
|
1494
|
+
return {
|
|
1495
|
+
linkId: result.link.id,
|
|
1496
|
+
token,
|
|
1497
|
+
shareUrl: `${currentMcpOrigin()}/s/deck/${token}`,
|
|
1498
|
+
reused: Boolean(result.reused)
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
async function revokeDeckShareLink(commentId, linkId) {
|
|
1502
|
+
await callTool("deck_share_link", {
|
|
1503
|
+
comment_id: commentId,
|
|
1504
|
+
action: "revoke",
|
|
1505
|
+
link_id: linkId
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1457
1508
|
var import_node_fs4, import_node_path4, _config, _requestId, _sessionId, _overrideLock;
|
|
1458
1509
|
var init_mcp_client = __esm({
|
|
1459
1510
|
"src/server/mcp-client.ts"() {
|
|
@@ -1472,6 +1523,7 @@ var init_mcp_client = __esm({
|
|
|
1472
1523
|
var deck_serve_exports = {};
|
|
1473
1524
|
__export(deck_serve_exports, {
|
|
1474
1525
|
broadcastToClients: () => broadcastToClients,
|
|
1526
|
+
buildSharePayload: () => buildSharePayload,
|
|
1475
1527
|
consumeRenderError: () => consumeRenderError,
|
|
1476
1528
|
createFeedbackWaiter: () => createFeedbackWaiter,
|
|
1477
1529
|
resolveFeedback: () => resolveFeedback,
|
|
@@ -1484,6 +1536,10 @@ function sessionDir(cwd, session) {
|
|
|
1484
1536
|
function sessionJsonPath(cwd, session) {
|
|
1485
1537
|
return import_node_path5.default.join(sessionDir(cwd, session), "session.json");
|
|
1486
1538
|
}
|
|
1539
|
+
function sessionIdFromCloud(title, fallbackId) {
|
|
1540
|
+
const slug = title.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 80);
|
|
1541
|
+
return slug || `deck-${fallbackId.slice(0, 8)}`;
|
|
1542
|
+
}
|
|
1487
1543
|
function readPersistedSession(cwd, session) {
|
|
1488
1544
|
try {
|
|
1489
1545
|
const raw = import_node_fs5.default.readFileSync(sessionJsonPath(cwd, session), "utf-8");
|
|
@@ -1527,7 +1583,11 @@ function listPersistedSessions(cwd) {
|
|
|
1527
1583
|
createdAt: persisted.createdAt,
|
|
1528
1584
|
updatedAt: persisted.updatedAt,
|
|
1529
1585
|
shared: Boolean(sync),
|
|
1530
|
-
stale: Boolean(sync) && persisted.version > (sync?.version ?? 0)
|
|
1586
|
+
stale: Boolean(sync) && persisted.version > (sync?.version ?? 0),
|
|
1587
|
+
shareUrl: sync?.publicShareUrl,
|
|
1588
|
+
course: sync?.course,
|
|
1589
|
+
watching: sync?.watching,
|
|
1590
|
+
watchedFrom: sync?.watchedFrom
|
|
1531
1591
|
});
|
|
1532
1592
|
}
|
|
1533
1593
|
sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
@@ -1555,6 +1615,24 @@ function readSyncRecord(cwd, session) {
|
|
|
1555
1615
|
function writeSyncRecord(cwd, session, rec) {
|
|
1556
1616
|
import_node_fs5.default.writeFileSync(syncJsonPath(cwd, session), JSON.stringify(rec, null, 2));
|
|
1557
1617
|
}
|
|
1618
|
+
function resolveShareConfig(cwd, course) {
|
|
1619
|
+
if (course) {
|
|
1620
|
+
const override = mcpConfigForProfile(cwd, course);
|
|
1621
|
+
if (!override) {
|
|
1622
|
+
return { error: `Course "${course}" is not a usable profile in .launch-secure.cred.config (missing pat/serverUrl/orgSlug/projectSlug?).` };
|
|
1623
|
+
}
|
|
1624
|
+
return { override };
|
|
1625
|
+
}
|
|
1626
|
+
try {
|
|
1627
|
+
loadMcpConfig(cwd);
|
|
1628
|
+
} catch (err) {
|
|
1629
|
+
return { error: `Cloud sharing needs an active LaunchSecure course (.launch-secure.cred.config) or a "launch-secure" entry in .mcp.json: ${String(err)}` };
|
|
1630
|
+
}
|
|
1631
|
+
return { override: null };
|
|
1632
|
+
}
|
|
1633
|
+
function runWithShareConfig(override, fn) {
|
|
1634
|
+
return override ? withMcpConfig(override, fn) : fn();
|
|
1635
|
+
}
|
|
1558
1636
|
function escHtml2(s) {
|
|
1559
1637
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1560
1638
|
}
|
|
@@ -1571,31 +1649,49 @@ function readIframeArtifact(cwd, src) {
|
|
|
1571
1649
|
return null;
|
|
1572
1650
|
}
|
|
1573
1651
|
}
|
|
1574
|
-
function renderShareSection(cwd, block) {
|
|
1575
|
-
const label = block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
|
|
1652
|
+
function renderShareSection(cwd, block, showLabel = true) {
|
|
1653
|
+
const label = showLabel && block?.label ? `<h2 class="db-title">${escHtml2(String(block.label))}</h2>` : "";
|
|
1576
1654
|
switch (block?.type) {
|
|
1577
1655
|
case "iframe": {
|
|
1578
1656
|
const doc = readIframeArtifact(cwd, block.src) ?? "<p>(missing artifact)</p>";
|
|
1579
|
-
return
|
|
1657
|
+
return `${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe>`;
|
|
1580
1658
|
}
|
|
1581
1659
|
case "rich-html":
|
|
1582
1660
|
case "html":
|
|
1583
|
-
return
|
|
1661
|
+
return `${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div>`;
|
|
1584
1662
|
case "markdown":
|
|
1585
|
-
return
|
|
1663
|
+
return `${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div>`;
|
|
1586
1664
|
case "mermaid":
|
|
1587
|
-
|
|
1665
|
+
if (typeof block.renderedSvg === "string" && block.renderedSvg.trim()) {
|
|
1666
|
+
return `${label}<div class="mermaid-svg">${block.renderedSvg}</div>`;
|
|
1667
|
+
}
|
|
1668
|
+
return `${label}<pre class="mermaid">${escHtml2(typeof block.content === "string" ? block.content : "")}</pre>`;
|
|
1588
1669
|
case "options": {
|
|
1589
1670
|
const opts = Array.isArray(block.options) ? block.options : [];
|
|
1590
|
-
const
|
|
1591
|
-
(o) => `<
|
|
1671
|
+
const cards = opts.map(
|
|
1672
|
+
(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
1673
|
).join("");
|
|
1593
|
-
return
|
|
1674
|
+
return `${label}<div class="db-options">${cards}</div>`;
|
|
1594
1675
|
}
|
|
1595
1676
|
default:
|
|
1596
|
-
return
|
|
1677
|
+
return label;
|
|
1597
1678
|
}
|
|
1598
1679
|
}
|
|
1680
|
+
function buildSharePayload(cwd, session, blocks, svgByIndex = {}) {
|
|
1681
|
+
const enriched = blocks.map((b, i) => {
|
|
1682
|
+
const svg = svgByIndex[String(i)];
|
|
1683
|
+
return b?.type === "mermaid" && typeof svg === "string" && svg.trim() ? { ...b, renderedSvg: svg } : b;
|
|
1684
|
+
});
|
|
1685
|
+
const html = buildShareHtml(cwd, session, enriched);
|
|
1686
|
+
const cloudBlocks = enriched.map((b) => {
|
|
1687
|
+
if (b?.type === "iframe" && typeof b.src === "string") {
|
|
1688
|
+
const content = readIframeArtifact(cwd, b.src);
|
|
1689
|
+
return content ? { ...b, content } : b;
|
|
1690
|
+
}
|
|
1691
|
+
return b;
|
|
1692
|
+
});
|
|
1693
|
+
return { html, cloudBlocks };
|
|
1694
|
+
}
|
|
1599
1695
|
function buildShareHtml(cwd, session, blocks) {
|
|
1600
1696
|
if (blocks.length === 1) {
|
|
1601
1697
|
const only = blocks[0];
|
|
@@ -1605,28 +1701,57 @@ function buildShareHtml(cwd, session, blocks) {
|
|
|
1605
1701
|
}
|
|
1606
1702
|
}
|
|
1607
1703
|
const list = blocks;
|
|
1608
|
-
const
|
|
1704
|
+
const multi = list.length > 1;
|
|
1705
|
+
const tabBar = multi ? `<nav class="deck-tabs">${list.map(
|
|
1706
|
+
(b, i) => `<button class="deck-tab${i === 0 ? " active" : ""}" data-tab-btn="${i}">${escHtml2(String(b?.label ?? `Tab ${i + 1}`))}</button>`
|
|
1707
|
+
).join("")}</nav>` : "";
|
|
1708
|
+
const sections = list.map(
|
|
1709
|
+
(b, i) => `<section class="db" data-tab="${i}"${multi && i > 0 ? " hidden" : ""}>${renderShareSection(cwd, b, !multi)}</section>`
|
|
1710
|
+
).join("\n");
|
|
1609
1711
|
const hasMd = list.some((b) => b?.type === "markdown");
|
|
1610
|
-
const
|
|
1712
|
+
const hasUnrenderedMermaid = list.some(
|
|
1713
|
+
(b) => b?.type === "mermaid" && !(typeof b?.renderedSvg === "string" && b.renderedSvg.trim())
|
|
1714
|
+
);
|
|
1611
1715
|
return `<!DOCTYPE html>
|
|
1612
1716
|
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1613
1717
|
<title>${escHtml2(session)}</title>
|
|
1718
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,100..1000&display=swap" rel="stylesheet">
|
|
1614
1719
|
<style>
|
|
1615
|
-
body { margin:0; font-family:
|
|
1720
|
+
body { margin:0; font-family: 'DM Sans', system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
|
|
1616
1721
|
.db { padding:20px 24px; border-bottom:1px solid #1e2433; }
|
|
1617
1722
|
.db-title { font-size:13px; font-weight:600; margin:0 0 12px; color:#9aa4b2; text-transform:uppercase; letter-spacing:.05em; }
|
|
1618
1723
|
.db-frame { width:100%; height:70vh; border:0; border-radius:8px; background:#fff; }
|
|
1619
1724
|
.db-html { background:#fff; color:#111; border-radius:8px; padding:16px; overflow:auto; }
|
|
1620
1725
|
ul { line-height:1.7; }
|
|
1621
|
-
|
|
1726
|
+
/* The embedded SVG is dark-themed (captured from the deck client); keep its
|
|
1727
|
+
backdrop transparent so it sits on the dark page, matching the in-app
|
|
1728
|
+
DeckView \u2014 not a white card. The CDN-source fallback also renders dark. */
|
|
1729
|
+
.mermaid { background:transparent; border-radius:8px; padding:16px; }
|
|
1730
|
+
.mermaid-svg { background:transparent; border-radius:8px; padding:16px; overflow:auto; }
|
|
1731
|
+
.mermaid-svg svg { max-width:100%; height:auto; }
|
|
1732
|
+
.db-options { display:grid; gap:12px; grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); }
|
|
1733
|
+
.db-option { border:2px solid #1e2433; border-radius:8px; padding:16px; }
|
|
1734
|
+
.db-option-label { font-size:14px; font-weight:600; color:#e6e9ef; }
|
|
1735
|
+
.db-option-desc { margin-top:4px; font-size:12px; color:#9aa4b2; }
|
|
1736
|
+
.db-option-pre { margin-top:8px; padding:8px; border-radius:6px; background:#11151f; font-size:12px; overflow:auto; }
|
|
1737
|
+
.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; }
|
|
1738
|
+
.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; }
|
|
1739
|
+
.deck-tab:hover { color:#e6e9ef; }
|
|
1740
|
+
.deck-tab.active { color:#c084fc; border-bottom-color:#c084fc; }
|
|
1741
|
+
.db[hidden] { display:none; }
|
|
1622
1742
|
</style></head>
|
|
1623
1743
|
<body>
|
|
1744
|
+
${tabBar}
|
|
1624
1745
|
${sections}
|
|
1746
|
+
${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
1747
|
${hasMd ? `<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
1626
1748
|
<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
|
-
${
|
|
1749
|
+
${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
1750
|
</body></html>`;
|
|
1629
1751
|
}
|
|
1752
|
+
function broadcastDiscuss() {
|
|
1753
|
+
broadcastToClients({ type: "discuss_session", sessions: discussSessions.map((d) => d.id) });
|
|
1754
|
+
}
|
|
1630
1755
|
function consumeRenderError() {
|
|
1631
1756
|
const err = lastRenderError;
|
|
1632
1757
|
lastRenderError = null;
|
|
@@ -1856,6 +1981,74 @@ async function startDeckServer(opts = {}) {
|
|
|
1856
1981
|
jsonResponse(res, 200, { sessions: listPersistedSessions(cwd) });
|
|
1857
1982
|
return;
|
|
1858
1983
|
}
|
|
1984
|
+
const pullMatch = url2.pathname.match(/^\/api\/cloud-decks\/([^/]+)\/pull$/);
|
|
1985
|
+
if (pullMatch && req.method === "POST") {
|
|
1986
|
+
const deckId = decodeURIComponent(pullMatch[1]);
|
|
1987
|
+
let course;
|
|
1988
|
+
try {
|
|
1989
|
+
const raw = await readBody(req);
|
|
1990
|
+
if (raw.trim()) {
|
|
1991
|
+
const parsed = JSON.parse(raw);
|
|
1992
|
+
course = parsed.course?.trim() || void 0;
|
|
1993
|
+
}
|
|
1994
|
+
} catch {
|
|
1995
|
+
}
|
|
1996
|
+
const cfg = resolveShareConfig(cwd, course);
|
|
1997
|
+
if ("error" in cfg) {
|
|
1998
|
+
jsonResponse(res, 400, { ok: false, error: cfg.error });
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
try {
|
|
2002
|
+
const deck = await runWithShareConfig(cfg.override, () => readCloudDeck(deckId));
|
|
2003
|
+
if (!deck) {
|
|
2004
|
+
jsonResponse(res, 404, { ok: false, error: `No shared deck "${deckId}" in this course.` });
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
if (!deck.html && !deck.blocks) {
|
|
2008
|
+
jsonResponse(res, 422, { ok: false, error: "That deck carries no viewable content (no deckHtml or deckBlocks)." });
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
const session = sessionIdFromCloud(deck.title, deck.id);
|
|
2012
|
+
const sessionEncoded = encodeURIComponent(session);
|
|
2013
|
+
let blocks;
|
|
2014
|
+
if (deck.html) {
|
|
2015
|
+
const dir = sessionDir(cwd, session);
|
|
2016
|
+
import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
2017
|
+
import_node_fs5.default.writeFileSync(import_node_path5.default.join(dir, "imported.html"), deck.html);
|
|
2018
|
+
blocks = [{ type: "iframe", label: deck.title, src: `/deck-files/${sessionEncoded}/imported.html` }];
|
|
2019
|
+
} else {
|
|
2020
|
+
blocks = deck.blocks;
|
|
2021
|
+
}
|
|
2022
|
+
const persisted = writePersistedSession(cwd, session, { mode: "show", blocks });
|
|
2023
|
+
writeSyncRecord(cwd, session, {
|
|
2024
|
+
resourceId: deck.id,
|
|
2025
|
+
sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2026
|
+
version: persisted.version,
|
|
2027
|
+
course,
|
|
2028
|
+
watching: true,
|
|
2029
|
+
watchedFrom: deck.author
|
|
2030
|
+
});
|
|
2031
|
+
broadcastToClients({
|
|
2032
|
+
type: "session",
|
|
2033
|
+
session,
|
|
2034
|
+
mode: "show",
|
|
2035
|
+
blocks,
|
|
2036
|
+
version: persisted.version
|
|
2037
|
+
});
|
|
2038
|
+
jsonResponse(res, 200, {
|
|
2039
|
+
ok: true,
|
|
2040
|
+
session,
|
|
2041
|
+
deckId: deck.id,
|
|
2042
|
+
title: deck.title,
|
|
2043
|
+
author: deck.author,
|
|
2044
|
+
blockCount: blocks.length,
|
|
2045
|
+
course
|
|
2046
|
+
});
|
|
2047
|
+
} catch (err) {
|
|
2048
|
+
jsonResponse(res, 500, { ok: false, error: String(err) });
|
|
2049
|
+
}
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
1859
2052
|
const shareMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/share$/);
|
|
1860
2053
|
if (shareMatch && req.method === "POST") {
|
|
1861
2054
|
const id = decodeURIComponent(shareMatch[1]);
|
|
@@ -1865,45 +2058,35 @@ async function startDeckServer(opts = {}) {
|
|
|
1865
2058
|
return;
|
|
1866
2059
|
}
|
|
1867
2060
|
let course;
|
|
2061
|
+
let svgByIndex = {};
|
|
2062
|
+
let publicLink = false;
|
|
1868
2063
|
try {
|
|
1869
2064
|
const raw = await readBody(req);
|
|
1870
2065
|
if (raw.trim()) {
|
|
1871
|
-
|
|
2066
|
+
const parsed = JSON.parse(raw);
|
|
2067
|
+
course = parsed.course?.trim() || void 0;
|
|
2068
|
+
if (parsed.svgByIndex && typeof parsed.svgByIndex === "object") svgByIndex = parsed.svgByIndex;
|
|
2069
|
+
publicLink = parsed.publicLink === true;
|
|
1872
2070
|
}
|
|
1873
2071
|
} catch {
|
|
1874
2072
|
}
|
|
1875
|
-
|
|
1876
|
-
if (
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
-
}
|
|
2073
|
+
const cfg = resolveShareConfig(cwd, course);
|
|
2074
|
+
if ("error" in cfg) {
|
|
2075
|
+
jsonResponse(res, 400, { ok: false, error: cfg.error });
|
|
2076
|
+
return;
|
|
1895
2077
|
}
|
|
2078
|
+
const { override } = cfg;
|
|
1896
2079
|
try {
|
|
1897
2080
|
const blocks = Array.isArray(persisted.blocks) ? persisted.blocks : [];
|
|
1898
|
-
const html =
|
|
2081
|
+
const { html, cloudBlocks } = buildSharePayload(cwd, id, blocks, svgByIndex);
|
|
1899
2082
|
const types = blocks.map((b) => b?.type).filter(Boolean);
|
|
1900
2083
|
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
|
|
2084
|
+
const input = { title: id, body, html, blocks: cloudBlocks, sessionId: id, blockCount: blocks.length };
|
|
2085
|
+
const existingSync = readSyncRecord(cwd, id);
|
|
1903
2086
|
const doShare = async () => {
|
|
1904
|
-
if (
|
|
2087
|
+
if (existingSync?.resourceId) {
|
|
1905
2088
|
try {
|
|
1906
|
-
const { id: rid2 } = await updateDeck(
|
|
2089
|
+
const { id: rid2 } = await updateDeck(existingSync.resourceId, input);
|
|
1907
2090
|
return { resourceId: rid2, updated: true };
|
|
1908
2091
|
} catch {
|
|
1909
2092
|
const { id: rid2 } = await writeDeck(input);
|
|
@@ -1913,14 +2096,89 @@ async function startDeckServer(opts = {}) {
|
|
|
1913
2096
|
const { id: rid } = await writeDeck(input);
|
|
1914
2097
|
return { resourceId: rid, updated: false };
|
|
1915
2098
|
};
|
|
1916
|
-
const
|
|
2099
|
+
const result = await runWithShareConfig(override, async () => {
|
|
2100
|
+
const shared = await doShare();
|
|
2101
|
+
let link = null;
|
|
2102
|
+
let linkError;
|
|
2103
|
+
if (publicLink) {
|
|
2104
|
+
try {
|
|
2105
|
+
const minted = await createDeckShareLink(shared.resourceId);
|
|
2106
|
+
link = { shareUrl: minted.shareUrl, linkId: minted.linkId };
|
|
2107
|
+
} catch (err) {
|
|
2108
|
+
linkError = err instanceof Error ? err.message : String(err);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
return { shared, link, linkError };
|
|
2112
|
+
});
|
|
2113
|
+
if (publicLink && result.linkError && !result.shared.updated) {
|
|
2114
|
+
try {
|
|
2115
|
+
await runWithShareConfig(override, () => deleteDeck(result.shared.resourceId));
|
|
2116
|
+
} catch {
|
|
2117
|
+
}
|
|
2118
|
+
jsonResponse(res, 200, {
|
|
2119
|
+
ok: false,
|
|
2120
|
+
error: `Couldn't create the public link, so the deck wasn't shared: ${result.linkError}`
|
|
2121
|
+
});
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
const publicShareUrl = result.link?.shareUrl ?? existingSync?.publicShareUrl;
|
|
2125
|
+
const publicLinkId = result.link?.linkId ?? existingSync?.publicLinkId;
|
|
1917
2126
|
writeSyncRecord(cwd, id, {
|
|
1918
|
-
resourceId,
|
|
2127
|
+
resourceId: result.shared.resourceId,
|
|
1919
2128
|
sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1920
2129
|
version: persisted.version,
|
|
1921
|
-
course
|
|
2130
|
+
course,
|
|
2131
|
+
...publicShareUrl ? { publicShareUrl } : {},
|
|
2132
|
+
...publicLinkId ? { publicLinkId } : {}
|
|
2133
|
+
});
|
|
2134
|
+
jsonResponse(res, 200, {
|
|
2135
|
+
ok: true,
|
|
2136
|
+
resourceId: result.shared.resourceId,
|
|
2137
|
+
updated: result.shared.updated,
|
|
2138
|
+
course,
|
|
2139
|
+
shareUrl: publicShareUrl ?? null,
|
|
2140
|
+
...result.linkError ? { linkWarning: result.linkError } : {}
|
|
1922
2141
|
});
|
|
1923
|
-
|
|
2142
|
+
} catch (err) {
|
|
2143
|
+
jsonResponse(res, 500, { ok: false, error: String(err) });
|
|
2144
|
+
}
|
|
2145
|
+
return;
|
|
2146
|
+
}
|
|
2147
|
+
const publicLinkMatch = url2.pathname.match(/^\/api\/sessions\/([^/]+)\/public-link$/);
|
|
2148
|
+
if (publicLinkMatch && (req.method === "POST" || req.method === "DELETE")) {
|
|
2149
|
+
const id = decodeURIComponent(publicLinkMatch[1]);
|
|
2150
|
+
const sync = readSyncRecord(cwd, id);
|
|
2151
|
+
if (!sync?.resourceId) {
|
|
2152
|
+
jsonResponse(res, 409, { ok: false, error: "Share this deck to the cloud first, then create a public link." });
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
const cfg = resolveShareConfig(cwd, sync.course);
|
|
2156
|
+
if ("error" in cfg) {
|
|
2157
|
+
jsonResponse(res, 400, { ok: false, error: cfg.error });
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
const { override } = cfg;
|
|
2161
|
+
if (req.method === "DELETE") {
|
|
2162
|
+
if (!sync.publicLinkId) {
|
|
2163
|
+
if (sync.publicShareUrl) {
|
|
2164
|
+
writeSyncRecord(cwd, id, { ...sync, publicShareUrl: void 0, publicLinkId: void 0 });
|
|
2165
|
+
}
|
|
2166
|
+
jsonResponse(res, 200, { ok: true, revoked: false });
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
try {
|
|
2170
|
+
await runWithShareConfig(override, () => revokeDeckShareLink(sync.resourceId, sync.publicLinkId));
|
|
2171
|
+
writeSyncRecord(cwd, id, { ...sync, publicShareUrl: void 0, publicLinkId: void 0 });
|
|
2172
|
+
jsonResponse(res, 200, { ok: true, revoked: true });
|
|
2173
|
+
} catch (err) {
|
|
2174
|
+
jsonResponse(res, 500, { ok: false, error: String(err) });
|
|
2175
|
+
}
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
try {
|
|
2179
|
+
const minted = await runWithShareConfig(override, () => createDeckShareLink(sync.resourceId));
|
|
2180
|
+
writeSyncRecord(cwd, id, { ...sync, publicShareUrl: minted.shareUrl, publicLinkId: minted.linkId });
|
|
2181
|
+
jsonResponse(res, 200, { ok: true, shareUrl: minted.shareUrl });
|
|
1924
2182
|
} catch (err) {
|
|
1925
2183
|
jsonResponse(res, 500, { ok: false, error: String(err) });
|
|
1926
2184
|
}
|
|
@@ -1948,7 +2206,16 @@ async function startDeckServer(opts = {}) {
|
|
|
1948
2206
|
}
|
|
1949
2207
|
jsonResponse(res, 200, { ok: true, deleted: true });
|
|
1950
2208
|
} catch (err) {
|
|
1951
|
-
|
|
2209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2210
|
+
if (/not\s*found/i.test(msg)) {
|
|
2211
|
+
try {
|
|
2212
|
+
import_node_fs5.default.rmSync(syncJsonPath(cwd, id), { force: true });
|
|
2213
|
+
} catch {
|
|
2214
|
+
}
|
|
2215
|
+
jsonResponse(res, 200, { ok: true, deleted: false, reason: "already removed" });
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
jsonResponse(res, 500, { ok: false, error: msg });
|
|
1952
2219
|
}
|
|
1953
2220
|
return;
|
|
1954
2221
|
}
|
|
@@ -2039,6 +2306,10 @@ async function startDeckServer(opts = {}) {
|
|
|
2039
2306
|
deletePersistedSession(cwd, session);
|
|
2040
2307
|
} catch {
|
|
2041
2308
|
}
|
|
2309
|
+
const beforePins = discussSessions.length;
|
|
2310
|
+
discussSessions = session === "all" ? [] : discussSessions.filter((d) => d.id !== session);
|
|
2311
|
+
if (discussSessions.length !== beforePins) broadcastDiscuss();
|
|
2312
|
+
if (session === "all" || activeSession?.id === session) activeSession = null;
|
|
2042
2313
|
jsonResponse(res, 200, { ok: true });
|
|
2043
2314
|
return;
|
|
2044
2315
|
}
|
|
@@ -2047,6 +2318,16 @@ async function startDeckServer(opts = {}) {
|
|
|
2047
2318
|
jsonResponse(res, 200, err ?? null);
|
|
2048
2319
|
return;
|
|
2049
2320
|
}
|
|
2321
|
+
if (req.method === "GET" && url2.pathname === "/api/active-session") {
|
|
2322
|
+
jsonResponse(res, 200, {
|
|
2323
|
+
discuss: discussSessions.map((d) => d.id),
|
|
2324
|
+
// ordered = pin number
|
|
2325
|
+
active: activeSession?.id ?? null,
|
|
2326
|
+
focused: activeSession?.focused ?? false,
|
|
2327
|
+
activeAt: activeSession?.at ?? null
|
|
2328
|
+
});
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2050
2331
|
if (req.method === "GET" && url2.pathname.startsWith("/deck-files/")) {
|
|
2051
2332
|
const relative = decodeURIComponent(url2.pathname.slice("/deck-files/".length));
|
|
2052
2333
|
if (relative.includes("..") || relative.startsWith("/")) {
|
|
@@ -2119,6 +2400,24 @@ async function startDeckServer(opts = {}) {
|
|
|
2119
2400
|
source: msg.source ?? ""
|
|
2120
2401
|
};
|
|
2121
2402
|
}
|
|
2403
|
+
if (msg.type === "active_session" && msg.session) {
|
|
2404
|
+
activeSession = {
|
|
2405
|
+
id: msg.session,
|
|
2406
|
+
focused: msg.focused ?? false,
|
|
2407
|
+
at: Date.now()
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
if (msg.type === "discuss_session" && msg.session) {
|
|
2411
|
+
const id = msg.session;
|
|
2412
|
+
const idx = discussSessions.findIndex((d) => d.id === id);
|
|
2413
|
+
if (idx >= 0) discussSessions.splice(idx, 1);
|
|
2414
|
+
else discussSessions.push({ id, at: Date.now() });
|
|
2415
|
+
broadcastDiscuss();
|
|
2416
|
+
}
|
|
2417
|
+
if (msg.type === "discuss_clear_all") {
|
|
2418
|
+
discussSessions = [];
|
|
2419
|
+
broadcastDiscuss();
|
|
2420
|
+
}
|
|
2122
2421
|
} catch {
|
|
2123
2422
|
}
|
|
2124
2423
|
});
|
|
@@ -2184,7 +2483,7 @@ function runServeCli(argv) {
|
|
|
2184
2483
|
process.exit(1);
|
|
2185
2484
|
});
|
|
2186
2485
|
}
|
|
2187
|
-
var import_node_http, import_node_fs5, import_node_path5, import_ws, DEFAULT_PORT, MAX_PORT_SCAN, MIME_TYPES, pendingFeedback, lastRenderError, wss;
|
|
2486
|
+
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
2487
|
var init_deck_serve = __esm({
|
|
2189
2488
|
"src/server/deck-serve.ts"() {
|
|
2190
2489
|
"use strict";
|
|
@@ -2214,6 +2513,8 @@ var init_deck_serve = __esm({
|
|
|
2214
2513
|
};
|
|
2215
2514
|
pendingFeedback = /* @__PURE__ */ new Map();
|
|
2216
2515
|
lastRenderError = null;
|
|
2516
|
+
activeSession = null;
|
|
2517
|
+
discussSessions = [];
|
|
2217
2518
|
wss = null;
|
|
2218
2519
|
}
|
|
2219
2520
|
});
|
|
@@ -2227,7 +2528,7 @@ function httpGet(url) {
|
|
|
2227
2528
|
return new Promise((resolve, reject) => {
|
|
2228
2529
|
const parsed = new URL(url);
|
|
2229
2530
|
const req = import_node_http2.default.request(
|
|
2230
|
-
{ hostname: parsed.hostname, port: parsed.port, path: parsed.pathname, method: "GET" },
|
|
2531
|
+
{ hostname: parsed.hostname, port: parsed.port, path: parsed.pathname + parsed.search, method: "GET" },
|
|
2231
2532
|
(res) => {
|
|
2232
2533
|
let buf = "";
|
|
2233
2534
|
res.on("data", (chunk) => {
|
|
@@ -2473,6 +2774,30 @@ async function handleTool(name, args) {
|
|
|
2473
2774
|
}
|
|
2474
2775
|
return text(JSON.stringify({ running: false }));
|
|
2475
2776
|
}
|
|
2777
|
+
case "active_session": {
|
|
2778
|
+
const lock = getLiveLock(projectRoot);
|
|
2779
|
+
if (!lock) {
|
|
2780
|
+
return text(JSON.stringify({
|
|
2781
|
+
error: "LaunchDeck server is not running \u2014 nothing is open.",
|
|
2782
|
+
hint: "Start it with start_server, or ask the user to open the deck."
|
|
2783
|
+
}));
|
|
2784
|
+
}
|
|
2785
|
+
try {
|
|
2786
|
+
const resp = await fetch(`${lock.url}/api/active-session`, {
|
|
2787
|
+
signal: AbortSignal.timeout(3e3)
|
|
2788
|
+
});
|
|
2789
|
+
const data = await resp.json();
|
|
2790
|
+
const pins = data.discuss ?? [];
|
|
2791
|
+
const targets = pins.length ? pins : data.active ? [data.active] : [];
|
|
2792
|
+
return text(JSON.stringify({
|
|
2793
|
+
...data,
|
|
2794
|
+
targets,
|
|
2795
|
+
targetSource: pins.length ? "pinned" : data.active ? "active-tab" : "none"
|
|
2796
|
+
}));
|
|
2797
|
+
} catch (err) {
|
|
2798
|
+
return text(JSON.stringify({ error: `Failed to read active session: ${err}` }));
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2476
2801
|
case "generate_contract": {
|
|
2477
2802
|
const session = args.session;
|
|
2478
2803
|
const dir = (0, import_node_path6.join)(projectRoot, LAUNCHSECURE_DIR, "deck-files", session);
|
|
@@ -2495,6 +2820,31 @@ async function handleTool(name, args) {
|
|
|
2495
2820
|
return text(JSON.stringify({ error: `Failed to generate contract: ${err}` }));
|
|
2496
2821
|
}
|
|
2497
2822
|
}
|
|
2823
|
+
case "deck_pull": {
|
|
2824
|
+
const lock = getLiveLock(projectRoot);
|
|
2825
|
+
if (!lock) {
|
|
2826
|
+
return text(JSON.stringify({ error: "LaunchDeck server is not running. Call start_server first." }));
|
|
2827
|
+
}
|
|
2828
|
+
const deckId = args.deck?.trim();
|
|
2829
|
+
if (!deckId) {
|
|
2830
|
+
return text(JSON.stringify({ error: "Provide the deck's comment id (from launch-secure deck_list / watched_decks)." }));
|
|
2831
|
+
}
|
|
2832
|
+
const course = args.course?.trim();
|
|
2833
|
+
try {
|
|
2834
|
+
const resp = await httpPost(`${lock.url}/api/cloud-decks/${encodeURIComponent(deckId)}/pull`, course ? { course } : {});
|
|
2835
|
+
const parsed = JSON.parse(resp);
|
|
2836
|
+
if (parsed.ok === false) {
|
|
2837
|
+
return text(JSON.stringify({ error: parsed.error ?? "Failed to pull the deck." }));
|
|
2838
|
+
}
|
|
2839
|
+
return text(JSON.stringify({
|
|
2840
|
+
...parsed,
|
|
2841
|
+
url: lock.url,
|
|
2842
|
+
action: "Deck pulled into a local tab. Open the LaunchDeck browser to view it; it can now be \u{1F441}-watched like any local deck."
|
|
2843
|
+
}));
|
|
2844
|
+
} catch (err) {
|
|
2845
|
+
return text(JSON.stringify({ error: `Failed to pull deck: ${err}` }));
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2498
2848
|
default:
|
|
2499
2849
|
return text(`Unknown tool: ${name}`);
|
|
2500
2850
|
}
|
|
@@ -2713,6 +3063,11 @@ var init_deck_mcp = __esm({
|
|
|
2713
3063
|
description: "Check whether the LaunchDeck UI server is running.",
|
|
2714
3064
|
inputSchema: { type: "object", properties: {} }
|
|
2715
3065
|
},
|
|
3066
|
+
{
|
|
3067
|
+
name: "active_session",
|
|
3068
|
+
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".',
|
|
3069
|
+
inputSchema: { type: "object", properties: {} }
|
|
3070
|
+
},
|
|
2716
3071
|
{
|
|
2717
3072
|
name: "generate_contract",
|
|
2718
3073
|
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.",
|
|
@@ -2726,6 +3081,24 @@ var init_deck_mcp = __esm({
|
|
|
2726
3081
|
},
|
|
2727
3082
|
required: ["session"]
|
|
2728
3083
|
}
|
|
3084
|
+
},
|
|
3085
|
+
{
|
|
3086
|
+
name: "deck_pull",
|
|
3087
|
+
description: "Pull a cloud-shared deck DOWN into a local LaunchDeck session tab \u2014 the inverse of publishing a deck up. After this, the deck is an ordinary local tab the user can view, \u{1F441}-watch, download, or re-pull. Takes the deck's comment id \u2014 get it from launch-secure's deck_list or watched_decks. The deck is reconstructed from its shared HTML snapshot as one self-contained block (view-faithful; not re-editable block-by-block). Requires the LaunchDeck server to be running (start_server first). Note: listing shared decks and reading watch state live on the launch-secure MCP (deck_list / watched_decks), not here.",
|
|
3088
|
+
inputSchema: {
|
|
3089
|
+
type: "object",
|
|
3090
|
+
properties: {
|
|
3091
|
+
deck: {
|
|
3092
|
+
type: "string",
|
|
3093
|
+
description: "Deck comment id (from launch-secure deck_list / watched_decks)."
|
|
3094
|
+
},
|
|
3095
|
+
course: {
|
|
3096
|
+
type: "string",
|
|
3097
|
+
description: "Optional course (cred profile) to read from. Omit for the active course."
|
|
3098
|
+
}
|
|
3099
|
+
},
|
|
3100
|
+
required: ["deck"]
|
|
3101
|
+
}
|
|
2729
3102
|
}
|
|
2730
3103
|
];
|
|
2731
3104
|
}
|