@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.
- 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-mvYvzeEJ.js +1 -0
- package/dist/deck-client/assets/arc-CX4ylnp2.js +1 -0
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-BkR-5IRK.js +36 -0
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CUdblaWk.js → blockDiagram-DXYQGD6D-DVNQht7c.js} +2 -2
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-MfAO5lak.js → c4Diagram-AHTNJAMY-Cbq1rlG8.js} +2 -2
- package/dist/deck-client/assets/channel-B9GC-CLn.js +1 -0
- package/dist/deck-client/assets/chunk-4BX2VUAB-D58Co4lU.js +1 -0
- package/dist/deck-client/assets/{chunk-4TB4RGXK-BUJtZ7jO.js → chunk-4TB4RGXK-BYvhTm3d.js} +1 -1
- package/dist/deck-client/assets/chunk-55IACEB6-oWukUhYg.js +1 -0
- package/dist/deck-client/assets/chunk-EDXVE4YY-Cm58kVnZ.js +1 -0
- package/dist/deck-client/assets/{chunk-FMBD7UC4-PnZ9v6ey.js → chunk-FMBD7UC4-Dg-i7kzi.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-DXrWNOsV.js → chunk-OYMX7WX6-C72wigPl.js} +1 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-CLgeuAKw.js +1 -0
- package/dist/deck-client/assets/chunk-YZCP3GAM-HDDlJ5oI.js +1 -0
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CFBvYQ9j.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CFBvYQ9j.js +1 -0
- package/dist/deck-client/assets/clone-n-WQlAGe.js +1 -0
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CUXQKg2M.js +1 -0
- package/dist/deck-client/assets/dagre-KV5264BT-C5M-fVDc.js +4 -0
- package/dist/deck-client/assets/diagram-5BDNPKRD-CcVsQ0S8.js +10 -0
- package/dist/deck-client/assets/diagram-G4DWMVQ6-DJswXyep.js +24 -0
- package/dist/deck-client/assets/diagram-MMDJMWI5-CGT76fm1.js +43 -0
- package/dist/deck-client/assets/diagram-TYMM5635-BBsYUNN6.js +24 -0
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-56pn_93p.js → erDiagram-SMLLAGMA-DKWYEHQS.js} +2 -2
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-BtV3M5xJ.js → flowDiagram-DWJPFMVM-DLuDYIKT.js} +2 -2
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-B19b6Qtj.js +292 -0
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-BYLAfYVS.js +106 -0
- package/dist/deck-client/assets/graph-CfzQUfPh.js +1 -0
- package/dist/deck-client/assets/index-DlwdTgE_.js +892 -0
- package/dist/deck-client/assets/index-evAPhGvM.css +1 -0
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-Dp3mUA9c.js +2 -0
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DXYkdO3T.js → ishikawaDiagram-UXIWVN3A-BhrNX_jI.js} +5 -5
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C2zBr-J5.js → journeyDiagram-VCZTEJTY-B5lJI492.js} +2 -2
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CdoYLS4Z.js → kanban-definition-6JOO6SKY-D9-lmhQf.js} +2 -2
- package/dist/deck-client/assets/layout-CfIe_Su8.js +1 -0
- package/dist/deck-client/assets/linear-09ZFRoh_.js +1 -0
- package/dist/deck-client/assets/mermaid.core-BaQyIOvj.js +309 -0
- package/dist/deck-client/assets/min-CYwCzYaL.js +1 -0
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-oAybLedr.js → mindmap-definition-QFDTVHPH-CouFxf6C.js} +2 -2
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-DMB1ufC0.js +30 -0
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-dtluDZXs.js → quadrantDiagram-34T5L4WZ-CBiOKudN.js} +2 -2
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cq8l7bOl.js → requirementDiagram-MS252O5E-BMc3GJkx.js} +2 -2
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-CxACUncm.js +10 -0
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-CYkd7oQK.js → sequenceDiagram-FGHM5R23-Ch-P3Mzc.js} +2 -2
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX-Cy8n7Yzk.js +1 -0
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-C14VKCzi.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DZIxSyd1.js → timeline-definition-GMOUNBTQ-C2V4sSkm.js} +2 -2
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-Ct4JVRDM.js → vennDiagram-DHZGUBPP-YOqt4VbE.js} +2 -2
- package/dist/deck-client/assets/{wardley-RL74JXVD-V29ycxOW.js → wardley-RL74JXVD-Bxo5x40D.js} +3 -3
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-D-Ua6Cmi.js → wardleyDiagram-NUSXRM2D-DW9SOqbx.js} +2 -2
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-BPCOuRVl.js → xychartDiagram-5P7HB3ND-D-rZvZOL.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 +287 -49
- package/dist/server/deck-serve.js +258 -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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
|
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
|
|
1637
|
+
return `${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div>`;
|
|
1584
1638
|
case "markdown":
|
|
1585
|
-
return
|
|
1639
|
+
return `${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div>`;
|
|
1586
1640
|
case "mermaid":
|
|
1587
|
-
|
|
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
|
|
1591
|
-
(o) => `<
|
|
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
|
|
1650
|
+
return `${label}<div class="db-options">${cards}</div>`;
|
|
1594
1651
|
}
|
|
1595
1652
|
default:
|
|
1596
|
-
return
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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 =
|
|
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
|
|
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 (
|
|
1995
|
+
if (existingSync?.resourceId) {
|
|
1905
1996
|
try {
|
|
1906
|
-
const { id: rid2 } = await updateDeck(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.",
|