@launchsecure/launch-kit 0.0.39 → 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-jjBWyhry.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-CwAGy9lU.js → blockDiagram-DXYQGD6D-DVNQht7c.js} +2 -2
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-L_g_SS21.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-Bk0FUbxU.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-DqOvWr1k.js → chunk-FMBD7UC4-Dg-i7kzi.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-1Kd7yK5u.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-aiv9GZnL.js → erDiagram-SMLLAGMA-DKWYEHQS.js} +2 -2
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-C6Fhvtsy.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-BwCUmUVt.js → ishikawaDiagram-UXIWVN3A-BhrNX_jI.js} +5 -5
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C6qoqJmJ.js → journeyDiagram-VCZTEJTY-B5lJI492.js} +2 -2
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-Dz1Tt3sA.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-CfXcK1qH.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-CXwvZ1i1.js → quadrantDiagram-34T5L4WZ-CBiOKudN.js} +2 -2
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cl6xm0fR.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-BC1MYBn6.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-DKnITsD4.js → timeline-definition-GMOUNBTQ-C2V4sSkm.js} +2 -2
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-BdajXRrh.js → vennDiagram-DHZGUBPP-YOqt4VbE.js} +2 -2
- package/dist/deck-client/assets/wardley-RL74JXVD-Bxo5x40D.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-B2hDCDl2.js → wardleyDiagram-NUSXRM2D-DW9SOqbx.js} +2 -2
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CvnYFs51.js → xychartDiagram-5P7HB3ND-D-rZvZOL.js} +2 -2
- package/dist/deck-client/index.html +2 -2
- package/dist/server/beacon-monitor-entry.js +106 -24
- package/dist/server/chart-serve.js +544 -247
- package/dist/server/cli.js +1016 -324
- package/dist/server/council-entry.js +23 -4
- package/dist/server/council-serve.js +22 -3
- package/dist/server/deck-mcp-entry.js +523 -125
- package/dist/server/deck-serve.js +326 -40
- package/dist/server/graph-mcp-entry.js +1160 -357
- package/dist/server/init-entry.js +17 -7
- package/dist/server/orbit-entry.js +91 -7
- package/dist/server/recall-entry.js +94 -12
- 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/scaffolds/statusline/statusline-mcp.sh +68 -19
- package/scaffolds/statusline/statusline-wrapper.sh +12 -9
- package/dist/chart-client/assets/index-ysGpLeOW.css +0 -1
- package/dist/client/assets/index-CMN3tlGP.css +0 -32
- package/dist/council-client/assets/index-ChmNX6bZ.css +0 -1
- package/dist/deck-client/assets/_baseUniq-DOrnEQMI.js +0 -1
- package/dist/deck-client/assets/arc-DOWK7V3m.js +0 -1
- package/dist/deck-client/assets/architectureDiagram-Q4EWVU46-DPhzvk7q.js +0 -36
- package/dist/deck-client/assets/channel-DqiACUUq.js +0 -1
- package/dist/deck-client/assets/chunk-4BX2VUAB-RKm0LXpu.js +0 -1
- package/dist/deck-client/assets/chunk-55IACEB6-Cl3hja-M.js +0 -1
- package/dist/deck-client/assets/chunk-EDXVE4YY-CNIMQCV2.js +0 -1
- package/dist/deck-client/assets/chunk-QZHKN3VN-6_kraYpP.js +0 -1
- package/dist/deck-client/assets/chunk-YZCP3GAM-FgAwIWlo.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-D23cq2C3.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-D23cq2C3.js +0 -1
- package/dist/deck-client/assets/clone-C7jSigGq.js +0 -1
- package/dist/deck-client/assets/cose-bilkent-S5V4N54A-CigVnnPr.js +0 -1
- package/dist/deck-client/assets/dagre-KV5264BT-DHZXTktX.js +0 -4
- package/dist/deck-client/assets/diagram-5BDNPKRD-H5k0eauU.js +0 -10
- package/dist/deck-client/assets/diagram-G4DWMVQ6-Bg3dFhSY.js +0 -24
- package/dist/deck-client/assets/diagram-MMDJMWI5-CQLC410N.js +0 -43
- package/dist/deck-client/assets/diagram-TYMM5635-DFTCHVkP.js +0 -24
- package/dist/deck-client/assets/ganttDiagram-T4ZO3ILL-DSaGMPM4.js +0 -292
- package/dist/deck-client/assets/gitGraphDiagram-UUTBAWPF-DMjL2Vix.js +0 -106
- package/dist/deck-client/assets/graph-B7Vn5lkK.js +0 -1
- package/dist/deck-client/assets/index-BD36e-tD.js +0 -1196
- package/dist/deck-client/assets/index-CGbNOpk9.css +0 -1
- package/dist/deck-client/assets/infoDiagram-42DDH7IO-mNi4iygG.js +0 -2
- package/dist/deck-client/assets/layout-CZTyRhOG.js +0 -1
- package/dist/deck-client/assets/linear--7n7iEvd.js +0 -1
- package/dist/deck-client/assets/min-Bh130DN8.js +0 -1
- package/dist/deck-client/assets/pieDiagram-DEJITSTG-DjVHLAVw.js +0 -30
- package/dist/deck-client/assets/sankeyDiagram-XADWPNL6-BOH9sLyh.js +0 -10
- package/dist/deck-client/assets/stateDiagram-FHFEXIEX-kNp9bv8K.js +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-hRsAFc2t.js +0 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-BL802-su.js +0 -162
- /package/dist/chart-client/assets/{index-BlsuXuQ1.js → index-CrYM1-ac.js} +0 -0
- /package/dist/client/assets/{index-BA7BHBWT.js → index-BoIjawzY.js} +0 -0
|
@@ -128,24 +128,24 @@ var init_deck_lockfile = __esm({
|
|
|
128
128
|
|
|
129
129
|
// src/server/deck-config.ts
|
|
130
130
|
function loadDeckConfig(rootDir) {
|
|
131
|
-
const configPath = (0,
|
|
132
|
-
if (!(0,
|
|
131
|
+
const configPath = (0, import_node_path3.join)(rootDir, CONFIG_FILENAME);
|
|
132
|
+
if (!(0, import_node_fs3.existsSync)(configPath)) return {};
|
|
133
133
|
try {
|
|
134
|
-
return JSON.parse((0,
|
|
134
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(configPath, "utf-8"));
|
|
135
135
|
} catch {
|
|
136
136
|
return {};
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
function saveDeckConfig(rootDir, config) {
|
|
140
|
-
const configPath = (0,
|
|
141
|
-
(0,
|
|
140
|
+
const configPath = (0, import_node_path3.join)(rootDir, CONFIG_FILENAME);
|
|
141
|
+
(0, import_node_fs3.writeFileSync)(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
142
142
|
}
|
|
143
|
-
var
|
|
143
|
+
var import_node_fs3, import_node_path3, CONFIG_FILENAME;
|
|
144
144
|
var init_deck_config = __esm({
|
|
145
145
|
"src/server/deck-config.ts"() {
|
|
146
146
|
"use strict";
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
import_node_fs3 = require("node:fs");
|
|
148
|
+
import_node_path3 = require("node:path");
|
|
149
149
|
CONFIG_FILENAME = ".launchdeck.json";
|
|
150
150
|
}
|
|
151
151
|
});
|
|
@@ -1250,7 +1250,7 @@ var init_cred_shape = __esm({
|
|
|
1250
1250
|
});
|
|
1251
1251
|
|
|
1252
1252
|
// src/server/mcp-client.ts
|
|
1253
|
-
function
|
|
1253
|
+
function mcpConfigForProfile(projectRoot, name) {
|
|
1254
1254
|
let cred;
|
|
1255
1255
|
try {
|
|
1256
1256
|
cred = readCredFile(projectRoot);
|
|
@@ -1260,7 +1260,7 @@ function mcpConfigFromCourse(projectRoot) {
|
|
|
1260
1260
|
if (!cred) return null;
|
|
1261
1261
|
const nested = toNested(cred);
|
|
1262
1262
|
if (!nested) return null;
|
|
1263
|
-
const profile = nested.profiles[
|
|
1263
|
+
const profile = nested.profiles[name];
|
|
1264
1264
|
if (!profile?.pat || !profile.serverUrl || !profile.orgSlug || !profile.projectSlug) {
|
|
1265
1265
|
return null;
|
|
1266
1266
|
}
|
|
@@ -1273,6 +1273,37 @@ function mcpConfigFromCourse(projectRoot) {
|
|
|
1273
1273
|
}
|
|
1274
1274
|
};
|
|
1275
1275
|
}
|
|
1276
|
+
function mcpConfigFromCourse(projectRoot) {
|
|
1277
|
+
let cred;
|
|
1278
|
+
try {
|
|
1279
|
+
cred = readCredFile(projectRoot);
|
|
1280
|
+
} catch {
|
|
1281
|
+
return null;
|
|
1282
|
+
}
|
|
1283
|
+
if (!cred) return null;
|
|
1284
|
+
const nested = toNested(cred);
|
|
1285
|
+
if (!nested) return null;
|
|
1286
|
+
return mcpConfigForProfile(projectRoot, nested.active);
|
|
1287
|
+
}
|
|
1288
|
+
function listCourses(projectRoot) {
|
|
1289
|
+
let cred;
|
|
1290
|
+
try {
|
|
1291
|
+
cred = readCredFile(projectRoot);
|
|
1292
|
+
} catch {
|
|
1293
|
+
return [];
|
|
1294
|
+
}
|
|
1295
|
+
if (!cred) return [];
|
|
1296
|
+
const nested = toNested(cred);
|
|
1297
|
+
if (!nested) return [];
|
|
1298
|
+
return Object.entries(nested.profiles).map(([name, p]) => ({
|
|
1299
|
+
name,
|
|
1300
|
+
org: p.orgSlug ?? "",
|
|
1301
|
+
project: p.projectSlug ?? "",
|
|
1302
|
+
serverUrl: p.serverUrl ?? "",
|
|
1303
|
+
active: name === nested.active,
|
|
1304
|
+
usable: Boolean(p.pat && p.serverUrl && p.orgSlug && p.projectSlug)
|
|
1305
|
+
}));
|
|
1306
|
+
}
|
|
1276
1307
|
function loadMcpConfig(projectRoot) {
|
|
1277
1308
|
if (_config) return _config;
|
|
1278
1309
|
const fromCourse = mcpConfigFromCourse(projectRoot);
|
|
@@ -1280,13 +1311,13 @@ function loadMcpConfig(projectRoot) {
|
|
|
1280
1311
|
_config = fromCourse;
|
|
1281
1312
|
return _config;
|
|
1282
1313
|
}
|
|
1283
|
-
const mcpPath = (0,
|
|
1284
|
-
if (!(0,
|
|
1314
|
+
const mcpPath = (0, import_node_path4.join)(projectRoot, ".mcp.json");
|
|
1315
|
+
if (!(0, import_node_fs4.existsSync)(mcpPath)) {
|
|
1285
1316
|
throw new Error(
|
|
1286
1317
|
`No active course in ${CONFIG_FILENAME2} and no .mcp.json at ${mcpPath}. Run \`launch-course\` or bootstrap with \`launch-kit init\`.`
|
|
1287
1318
|
);
|
|
1288
1319
|
}
|
|
1289
|
-
const raw = JSON.parse((0,
|
|
1320
|
+
const raw = JSON.parse((0, import_node_fs4.readFileSync)(mcpPath, "utf-8"));
|
|
1290
1321
|
const entry = raw.mcpServers["launch-secure"];
|
|
1291
1322
|
if (!entry?.url) {
|
|
1292
1323
|
throw new Error(
|
|
@@ -1299,6 +1330,23 @@ function loadMcpConfig(projectRoot) {
|
|
|
1299
1330
|
};
|
|
1300
1331
|
return _config;
|
|
1301
1332
|
}
|
|
1333
|
+
function withMcpConfig(override, fn) {
|
|
1334
|
+
const run = async () => {
|
|
1335
|
+
const prevConfig = _config;
|
|
1336
|
+
const prevSession = _sessionId;
|
|
1337
|
+
_config = override;
|
|
1338
|
+
_sessionId = null;
|
|
1339
|
+
try {
|
|
1340
|
+
return await fn();
|
|
1341
|
+
} finally {
|
|
1342
|
+
_config = prevConfig;
|
|
1343
|
+
_sessionId = prevSession;
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
const result = _overrideLock.then(run, run);
|
|
1347
|
+
_overrideLock = result.then(() => void 0, () => void 0);
|
|
1348
|
+
return result;
|
|
1349
|
+
}
|
|
1302
1350
|
function parseSSE(text2) {
|
|
1303
1351
|
const lines = text2.split("\n");
|
|
1304
1352
|
for (const line of lines) {
|
|
@@ -1373,7 +1421,13 @@ async function callTool(toolName, args) {
|
|
|
1373
1421
|
}
|
|
1374
1422
|
const textContent = result.result?.content?.[0]?.text;
|
|
1375
1423
|
if (!textContent) return null;
|
|
1376
|
-
|
|
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
|
+
}
|
|
1377
1431
|
}
|
|
1378
1432
|
async function writeDeck(input) {
|
|
1379
1433
|
const result = await callTool("communication_write", {
|
|
@@ -1382,6 +1436,7 @@ async function writeDeck(input) {
|
|
|
1382
1436
|
resource_type: "deck",
|
|
1383
1437
|
fields: {
|
|
1384
1438
|
deckHtml: input.html,
|
|
1439
|
+
deckBlocks: input.blocks,
|
|
1385
1440
|
sessionId: input.sessionId,
|
|
1386
1441
|
blockCount: input.blockCount,
|
|
1387
1442
|
sharedFrom: "launch-deck"
|
|
@@ -1396,6 +1451,7 @@ async function updateDeck(commentId, input) {
|
|
|
1396
1451
|
body: input.body,
|
|
1397
1452
|
fields: {
|
|
1398
1453
|
deckHtml: input.html,
|
|
1454
|
+
deckBlocks: input.blocks,
|
|
1399
1455
|
sessionId: input.sessionId,
|
|
1400
1456
|
blockCount: input.blockCount,
|
|
1401
1457
|
sharedFrom: "launch-deck"
|
|
@@ -1406,16 +1462,42 @@ async function updateDeck(commentId, input) {
|
|
|
1406
1462
|
async function deleteDeck(commentId) {
|
|
1407
1463
|
await callTool("communication_delete", { comment_id: commentId });
|
|
1408
1464
|
}
|
|
1409
|
-
|
|
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
|
+
}
|
|
1490
|
+
var import_node_fs4, import_node_path4, _config, _requestId, _sessionId, _overrideLock;
|
|
1410
1491
|
var init_mcp_client = __esm({
|
|
1411
1492
|
"src/server/mcp-client.ts"() {
|
|
1412
1493
|
"use strict";
|
|
1413
|
-
|
|
1414
|
-
|
|
1494
|
+
import_node_fs4 = require("node:fs");
|
|
1495
|
+
import_node_path4 = require("node:path");
|
|
1415
1496
|
init_cred_shape();
|
|
1416
1497
|
_config = null;
|
|
1417
1498
|
_requestId = 0;
|
|
1418
1499
|
_sessionId = null;
|
|
1500
|
+
_overrideLock = Promise.resolve();
|
|
1419
1501
|
}
|
|
1420
1502
|
});
|
|
1421
1503
|
|
|
@@ -1423,6 +1505,7 @@ var init_mcp_client = __esm({
|
|
|
1423
1505
|
var deck_serve_exports = {};
|
|
1424
1506
|
__export(deck_serve_exports, {
|
|
1425
1507
|
broadcastToClients: () => broadcastToClients,
|
|
1508
|
+
buildSharePayload: () => buildSharePayload,
|
|
1426
1509
|
consumeRenderError: () => consumeRenderError,
|
|
1427
1510
|
createFeedbackWaiter: () => createFeedbackWaiter,
|
|
1428
1511
|
resolveFeedback: () => resolveFeedback,
|
|
@@ -1430,14 +1513,14 @@ __export(deck_serve_exports, {
|
|
|
1430
1513
|
startDeckServer: () => startDeckServer
|
|
1431
1514
|
});
|
|
1432
1515
|
function sessionDir(cwd, session) {
|
|
1433
|
-
return
|
|
1516
|
+
return import_node_path5.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", session);
|
|
1434
1517
|
}
|
|
1435
1518
|
function sessionJsonPath(cwd, session) {
|
|
1436
|
-
return
|
|
1519
|
+
return import_node_path5.default.join(sessionDir(cwd, session), "session.json");
|
|
1437
1520
|
}
|
|
1438
1521
|
function readPersistedSession(cwd, session) {
|
|
1439
1522
|
try {
|
|
1440
|
-
const raw =
|
|
1523
|
+
const raw = import_node_fs5.default.readFileSync(sessionJsonPath(cwd, session), "utf-8");
|
|
1441
1524
|
return JSON.parse(raw);
|
|
1442
1525
|
} catch {
|
|
1443
1526
|
return null;
|
|
@@ -1445,7 +1528,7 @@ function readPersistedSession(cwd, session) {
|
|
|
1445
1528
|
}
|
|
1446
1529
|
function writePersistedSession(cwd, session, payload) {
|
|
1447
1530
|
const dir = sessionDir(cwd, session);
|
|
1448
|
-
|
|
1531
|
+
import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
1449
1532
|
const existing = readPersistedSession(cwd, session);
|
|
1450
1533
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1451
1534
|
const next = {
|
|
@@ -1457,13 +1540,13 @@ function writePersistedSession(cwd, session, payload) {
|
|
|
1457
1540
|
createdAt: existing?.createdAt ?? now,
|
|
1458
1541
|
updatedAt: now
|
|
1459
1542
|
};
|
|
1460
|
-
|
|
1543
|
+
import_node_fs5.default.writeFileSync(sessionJsonPath(cwd, session), JSON.stringify(next, null, 2));
|
|
1461
1544
|
return next;
|
|
1462
1545
|
}
|
|
1463
1546
|
function listPersistedSessions(cwd) {
|
|
1464
|
-
const base =
|
|
1465
|
-
if (!
|
|
1466
|
-
const entries =
|
|
1547
|
+
const base = import_node_path5.default.join(cwd, LAUNCHSECURE_DIR, "deck-files");
|
|
1548
|
+
if (!import_node_fs5.default.existsSync(base)) return [];
|
|
1549
|
+
const entries = import_node_fs5.default.readdirSync(base, { withFileTypes: true });
|
|
1467
1550
|
const sessions = [];
|
|
1468
1551
|
for (const entry of entries) {
|
|
1469
1552
|
if (!entry.isDirectory()) continue;
|
|
@@ -1478,33 +1561,53 @@ function listPersistedSessions(cwd) {
|
|
|
1478
1561
|
createdAt: persisted.createdAt,
|
|
1479
1562
|
updatedAt: persisted.updatedAt,
|
|
1480
1563
|
shared: Boolean(sync),
|
|
1481
|
-
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
|
|
1482
1567
|
});
|
|
1483
1568
|
}
|
|
1484
1569
|
sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1485
1570
|
return sessions;
|
|
1486
1571
|
}
|
|
1487
1572
|
function deletePersistedSession(cwd, session) {
|
|
1488
|
-
const base =
|
|
1573
|
+
const base = import_node_path5.default.join(cwd, LAUNCHSECURE_DIR, "deck-files");
|
|
1489
1574
|
if (session === "all") {
|
|
1490
|
-
if (
|
|
1575
|
+
if (import_node_fs5.default.existsSync(base)) import_node_fs5.default.rmSync(base, { recursive: true, force: true });
|
|
1491
1576
|
return;
|
|
1492
1577
|
}
|
|
1493
1578
|
const dir = sessionDir(cwd, session);
|
|
1494
|
-
if (
|
|
1579
|
+
if (import_node_fs5.default.existsSync(dir)) import_node_fs5.default.rmSync(dir, { recursive: true, force: true });
|
|
1495
1580
|
}
|
|
1496
1581
|
function syncJsonPath(cwd, session) {
|
|
1497
|
-
return
|
|
1582
|
+
return import_node_path5.default.join(sessionDir(cwd, session), ".sync.json");
|
|
1498
1583
|
}
|
|
1499
1584
|
function readSyncRecord(cwd, session) {
|
|
1500
1585
|
try {
|
|
1501
|
-
return JSON.parse(
|
|
1586
|
+
return JSON.parse(import_node_fs5.default.readFileSync(syncJsonPath(cwd, session), "utf-8"));
|
|
1502
1587
|
} catch {
|
|
1503
1588
|
return null;
|
|
1504
1589
|
}
|
|
1505
1590
|
}
|
|
1506
1591
|
function writeSyncRecord(cwd, session, rec) {
|
|
1507
|
-
|
|
1592
|
+
import_node_fs5.default.writeFileSync(syncJsonPath(cwd, session), JSON.stringify(rec, null, 2));
|
|
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();
|
|
1508
1611
|
}
|
|
1509
1612
|
function escHtml2(s) {
|
|
1510
1613
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
@@ -1517,36 +1620,54 @@ function readIframeArtifact(cwd, src) {
|
|
|
1517
1620
|
const rel = decodeURIComponent(src.slice("/deck-files/".length));
|
|
1518
1621
|
if (rel.includes("..")) return null;
|
|
1519
1622
|
try {
|
|
1520
|
-
return
|
|
1623
|
+
return import_node_fs5.default.readFileSync(import_node_path5.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", rel), "utf-8");
|
|
1521
1624
|
} catch {
|
|
1522
1625
|
return null;
|
|
1523
1626
|
}
|
|
1524
1627
|
}
|
|
1525
|
-
function renderShareSection(cwd, block) {
|
|
1526
|
-
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>` : "";
|
|
1527
1630
|
switch (block?.type) {
|
|
1528
1631
|
case "iframe": {
|
|
1529
1632
|
const doc = readIframeArtifact(cwd, block.src) ?? "<p>(missing artifact)</p>";
|
|
1530
|
-
return
|
|
1633
|
+
return `${label}<iframe class="db-frame" sandbox="allow-scripts allow-popups" srcdoc="${escAttr(doc)}"></iframe>`;
|
|
1531
1634
|
}
|
|
1532
1635
|
case "rich-html":
|
|
1533
1636
|
case "html":
|
|
1534
|
-
return
|
|
1637
|
+
return `${label}<div class="db-html">${typeof block.content === "string" ? block.content : ""}</div>`;
|
|
1535
1638
|
case "markdown":
|
|
1536
|
-
return
|
|
1639
|
+
return `${label}<div class="db-md" data-md="${escAttr(typeof block.content === "string" ? block.content : "")}"></div>`;
|
|
1537
1640
|
case "mermaid":
|
|
1538
|
-
|
|
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>`;
|
|
1539
1645
|
case "options": {
|
|
1540
1646
|
const opts = Array.isArray(block.options) ? block.options : [];
|
|
1541
|
-
const
|
|
1542
|
-
(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>`
|
|
1543
1649
|
).join("");
|
|
1544
|
-
return
|
|
1650
|
+
return `${label}<div class="db-options">${cards}</div>`;
|
|
1545
1651
|
}
|
|
1546
1652
|
default:
|
|
1547
|
-
return
|
|
1653
|
+
return label;
|
|
1548
1654
|
}
|
|
1549
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
|
+
}
|
|
1550
1671
|
function buildShareHtml(cwd, session, blocks) {
|
|
1551
1672
|
if (blocks.length === 1) {
|
|
1552
1673
|
const only = blocks[0];
|
|
@@ -1556,28 +1677,57 @@ function buildShareHtml(cwd, session, blocks) {
|
|
|
1556
1677
|
}
|
|
1557
1678
|
}
|
|
1558
1679
|
const list = blocks;
|
|
1559
|
-
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");
|
|
1560
1687
|
const hasMd = list.some((b) => b?.type === "markdown");
|
|
1561
|
-
const
|
|
1688
|
+
const hasUnrenderedMermaid = list.some(
|
|
1689
|
+
(b) => b?.type === "mermaid" && !(typeof b?.renderedSvg === "string" && b.renderedSvg.trim())
|
|
1690
|
+
);
|
|
1562
1691
|
return `<!DOCTYPE html>
|
|
1563
1692
|
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1564
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">
|
|
1565
1695
|
<style>
|
|
1566
|
-
body { margin:0; font-family:
|
|
1696
|
+
body { margin:0; font-family: 'DM Sans', system-ui, sans-serif; background:#0b0e14; color:#e6e9ef; }
|
|
1567
1697
|
.db { padding:20px 24px; border-bottom:1px solid #1e2433; }
|
|
1568
1698
|
.db-title { font-size:13px; font-weight:600; margin:0 0 12px; color:#9aa4b2; text-transform:uppercase; letter-spacing:.05em; }
|
|
1569
1699
|
.db-frame { width:100%; height:70vh; border:0; border-radius:8px; background:#fff; }
|
|
1570
1700
|
.db-html { background:#fff; color:#111; border-radius:8px; padding:16px; overflow:auto; }
|
|
1571
1701
|
ul { line-height:1.7; }
|
|
1572
|
-
|
|
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; }
|
|
1573
1718
|
</style></head>
|
|
1574
1719
|
<body>
|
|
1720
|
+
${tabBar}
|
|
1575
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>` : ""}
|
|
1576
1723
|
${hasMd ? `<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
1577
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>` : ""}
|
|
1578
|
-
${
|
|
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>` : ""}
|
|
1579
1726
|
</body></html>`;
|
|
1580
1727
|
}
|
|
1728
|
+
function broadcastDiscuss() {
|
|
1729
|
+
broadcastToClients({ type: "discuss_session", sessions: discussSessions.map((d) => d.id) });
|
|
1730
|
+
}
|
|
1581
1731
|
function consumeRenderError() {
|
|
1582
1732
|
const err = lastRenderError;
|
|
1583
1733
|
lastRenderError = null;
|
|
@@ -1614,23 +1764,23 @@ function broadcastToClients(message) {
|
|
|
1614
1764
|
}
|
|
1615
1765
|
}
|
|
1616
1766
|
function serveStatic(res, filePath) {
|
|
1617
|
-
if (!
|
|
1618
|
-
const ext =
|
|
1767
|
+
if (!import_node_fs5.default.existsSync(filePath) || !import_node_fs5.default.statSync(filePath).isFile()) return false;
|
|
1768
|
+
const ext = import_node_path5.default.extname(filePath).toLowerCase();
|
|
1619
1769
|
const mime = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
1620
1770
|
res.writeHead(200, { "Content-Type": mime, "Cache-Control": "no-cache" });
|
|
1621
|
-
|
|
1771
|
+
import_node_fs5.default.createReadStream(filePath).pipe(res);
|
|
1622
1772
|
return true;
|
|
1623
1773
|
}
|
|
1624
1774
|
function resolveClientDir() {
|
|
1625
|
-
const sibling =
|
|
1626
|
-
if (
|
|
1627
|
-
const devDist =
|
|
1628
|
-
if (
|
|
1775
|
+
const sibling = import_node_path5.default.join(__dirname, "..", "deck-client");
|
|
1776
|
+
if (import_node_fs5.default.existsSync(import_node_path5.default.join(sibling, "assets"))) return sibling;
|
|
1777
|
+
const devDist = import_node_path5.default.join(__dirname, "..", "..", "dist", "deck-client");
|
|
1778
|
+
if (import_node_fs5.default.existsSync(import_node_path5.default.join(devDist, "assets"))) return devDist;
|
|
1629
1779
|
return sibling;
|
|
1630
1780
|
}
|
|
1631
1781
|
function serveIndex(res, clientDir) {
|
|
1632
|
-
const indexPath =
|
|
1633
|
-
if (!
|
|
1782
|
+
const indexPath = import_node_path5.default.join(clientDir, "index.html");
|
|
1783
|
+
if (!import_node_fs5.default.existsSync(indexPath)) {
|
|
1634
1784
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
1635
1785
|
res.end(`LaunchDeck client bundle not found at ${clientDir}. Run 'npm run build:client'.`);
|
|
1636
1786
|
return;
|
|
@@ -1713,6 +1863,10 @@ async function startDeckServer(opts = {}) {
|
|
|
1713
1863
|
jsonResponse(res, 200, { config: cfg });
|
|
1714
1864
|
return;
|
|
1715
1865
|
}
|
|
1866
|
+
if (req.method === "GET" && url2.pathname === "/api/deck/courses") {
|
|
1867
|
+
jsonResponse(res, 200, { courses: listCourses(cwd) });
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1716
1870
|
if (req.method === "POST" && url2.pathname === "/api/deck/config") {
|
|
1717
1871
|
try {
|
|
1718
1872
|
const body = JSON.parse(await readBody(req));
|
|
@@ -1734,15 +1888,15 @@ async function startDeckServer(opts = {}) {
|
|
|
1734
1888
|
const baseUrl = `/deck-files/${sessionEncoded}`;
|
|
1735
1889
|
const html = generateBlastRadiusHtml(block.manifest, baseUrl);
|
|
1736
1890
|
const report = generateBlastRadiusReport(block.manifest);
|
|
1737
|
-
const dir =
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1891
|
+
const dir = import_node_path5.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", body.session);
|
|
1892
|
+
import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
1893
|
+
import_node_fs5.default.writeFileSync(import_node_path5.default.join(dir, "blast-radius.html"), html);
|
|
1894
|
+
import_node_fs5.default.writeFileSync(import_node_path5.default.join(dir, "blast-radius-report.md"), report);
|
|
1895
|
+
import_node_fs5.default.writeFileSync(import_node_path5.default.join(dir, "blast-radius-manifest.json"), JSON.stringify(block.manifest, null, 2));
|
|
1742
1896
|
const contractData = generateContract(block.manifest);
|
|
1743
1897
|
const contractMd = contractToMarkdown(contractData);
|
|
1744
|
-
|
|
1745
|
-
|
|
1898
|
+
import_node_fs5.default.writeFileSync(import_node_path5.default.join(dir, "contract.json"), JSON.stringify(contractData, null, 2));
|
|
1899
|
+
import_node_fs5.default.writeFileSync(import_node_path5.default.join(dir, "contract.md"), contractMd);
|
|
1746
1900
|
block.type = "iframe";
|
|
1747
1901
|
block.src = `${baseUrl}/blast-radius.html`;
|
|
1748
1902
|
delete block.manifest;
|
|
@@ -1769,9 +1923,9 @@ async function startDeckServer(opts = {}) {
|
|
|
1769
1923
|
const sessionEncoded = encodeURIComponent(body.session);
|
|
1770
1924
|
const baseUrl = `/deck-files/${sessionEncoded}`;
|
|
1771
1925
|
const filename = `${slugify(block.label)}.html`;
|
|
1772
|
-
const dir =
|
|
1773
|
-
|
|
1774
|
-
|
|
1926
|
+
const dir = import_node_path5.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", body.session);
|
|
1927
|
+
import_node_fs5.default.mkdirSync(dir, { recursive: true });
|
|
1928
|
+
import_node_fs5.default.writeFileSync(import_node_path5.default.join(dir, filename), html);
|
|
1775
1929
|
block.type = "iframe";
|
|
1776
1930
|
block.src = `${baseUrl}/${filename}`;
|
|
1777
1931
|
delete block.content;
|
|
@@ -1811,40 +1965,128 @@ async function startDeckServer(opts = {}) {
|
|
|
1811
1965
|
jsonResponse(res, 404, { ok: false, error: "session not found" });
|
|
1812
1966
|
return;
|
|
1813
1967
|
}
|
|
1968
|
+
let course;
|
|
1969
|
+
let svgByIndex = {};
|
|
1970
|
+
let publicLink = false;
|
|
1814
1971
|
try {
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1972
|
+
const raw = await readBody(req);
|
|
1973
|
+
if (raw.trim()) {
|
|
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;
|
|
1978
|
+
}
|
|
1979
|
+
} catch {
|
|
1980
|
+
}
|
|
1981
|
+
const cfg = resolveShareConfig(cwd, course);
|
|
1982
|
+
if ("error" in cfg) {
|
|
1983
|
+
jsonResponse(res, 400, { ok: false, error: cfg.error });
|
|
1821
1984
|
return;
|
|
1822
1985
|
}
|
|
1986
|
+
const { override } = cfg;
|
|
1823
1987
|
try {
|
|
1824
1988
|
const blocks = Array.isArray(persisted.blocks) ? persisted.blocks : [];
|
|
1825
|
-
const html =
|
|
1989
|
+
const { html, cloudBlocks } = buildSharePayload(cwd, id, blocks, svgByIndex);
|
|
1826
1990
|
const types = blocks.map((b) => b?.type).filter(Boolean);
|
|
1827
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.`;
|
|
1828
|
-
const input = { title: id, body, html, sessionId: id, blockCount: blocks.length };
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1992
|
+
const input = { title: id, body, html, blocks: cloudBlocks, sessionId: id, blockCount: blocks.length };
|
|
1993
|
+
const existingSync = readSyncRecord(cwd, id);
|
|
1994
|
+
const doShare = async () => {
|
|
1995
|
+
if (existingSync?.resourceId) {
|
|
1996
|
+
try {
|
|
1997
|
+
const { id: rid2 } = await updateDeck(existingSync.resourceId, input);
|
|
1998
|
+
return { resourceId: rid2, updated: true };
|
|
1999
|
+
} catch {
|
|
2000
|
+
const { id: rid2 } = await writeDeck(input);
|
|
2001
|
+
return { resourceId: rid2, updated: false };
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
const { id: rid } = await writeDeck(input);
|
|
2005
|
+
return { resourceId: rid, updated: false };
|
|
2006
|
+
};
|
|
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) {
|
|
1833
2022
|
try {
|
|
1834
|
-
(
|
|
1835
|
-
updated = true;
|
|
2023
|
+
await runWithShareConfig(override, () => deleteDeck(result.shared.resourceId));
|
|
1836
2024
|
} catch {
|
|
1837
|
-
({ id: resourceId } = await writeDeck(input));
|
|
1838
2025
|
}
|
|
1839
|
-
|
|
1840
|
-
|
|
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;
|
|
1841
2031
|
}
|
|
2032
|
+
const publicShareUrl = result.link?.shareUrl ?? existingSync?.publicShareUrl;
|
|
2033
|
+
const publicLinkId = result.link?.linkId ?? existingSync?.publicLinkId;
|
|
1842
2034
|
writeSyncRecord(cwd, id, {
|
|
1843
|
-
resourceId,
|
|
2035
|
+
resourceId: result.shared.resourceId,
|
|
1844
2036
|
sharedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1845
|
-
version: persisted.version
|
|
2037
|
+
version: persisted.version,
|
|
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 } : {}
|
|
1846
2049
|
});
|
|
1847
|
-
|
|
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 });
|
|
1848
2090
|
} catch (err) {
|
|
1849
2091
|
jsonResponse(res, 500, { ok: false, error: String(err) });
|
|
1850
2092
|
}
|
|
@@ -1867,12 +2109,21 @@ async function startDeckServer(opts = {}) {
|
|
|
1867
2109
|
try {
|
|
1868
2110
|
await deleteDeck(sync.resourceId);
|
|
1869
2111
|
try {
|
|
1870
|
-
|
|
2112
|
+
import_node_fs5.default.rmSync(syncJsonPath(cwd, id), { force: true });
|
|
1871
2113
|
} catch {
|
|
1872
2114
|
}
|
|
1873
2115
|
jsonResponse(res, 200, { ok: true, deleted: true });
|
|
1874
2116
|
} catch (err) {
|
|
1875
|
-
|
|
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 });
|
|
1876
2127
|
}
|
|
1877
2128
|
return;
|
|
1878
2129
|
}
|
|
@@ -1963,6 +2214,10 @@ async function startDeckServer(opts = {}) {
|
|
|
1963
2214
|
deletePersistedSession(cwd, session);
|
|
1964
2215
|
} catch {
|
|
1965
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;
|
|
1966
2221
|
jsonResponse(res, 200, { ok: true });
|
|
1967
2222
|
return;
|
|
1968
2223
|
}
|
|
@@ -1971,6 +2226,16 @@ async function startDeckServer(opts = {}) {
|
|
|
1971
2226
|
jsonResponse(res, 200, err ?? null);
|
|
1972
2227
|
return;
|
|
1973
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
|
+
}
|
|
1974
2239
|
if (req.method === "GET" && url2.pathname.startsWith("/deck-files/")) {
|
|
1975
2240
|
const relative = decodeURIComponent(url2.pathname.slice("/deck-files/".length));
|
|
1976
2241
|
if (relative.includes("..") || relative.startsWith("/")) {
|
|
@@ -1978,7 +2243,7 @@ async function startDeckServer(opts = {}) {
|
|
|
1978
2243
|
res.end("Forbidden");
|
|
1979
2244
|
return;
|
|
1980
2245
|
}
|
|
1981
|
-
const filePath =
|
|
2246
|
+
const filePath = import_node_path5.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", relative);
|
|
1982
2247
|
if (serveStatic(res, filePath)) return;
|
|
1983
2248
|
res.writeHead(404);
|
|
1984
2249
|
res.end("Not found");
|
|
@@ -1991,25 +2256,25 @@ async function startDeckServer(opts = {}) {
|
|
|
1991
2256
|
res.end("Forbidden");
|
|
1992
2257
|
return;
|
|
1993
2258
|
}
|
|
1994
|
-
const filePath =
|
|
1995
|
-
if (!
|
|
2259
|
+
const filePath = import_node_path5.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", relative);
|
|
2260
|
+
if (!import_node_fs5.default.existsSync(filePath) || !import_node_fs5.default.statSync(filePath).isFile()) {
|
|
1996
2261
|
res.writeHead(404);
|
|
1997
2262
|
res.end("Not found");
|
|
1998
2263
|
return;
|
|
1999
2264
|
}
|
|
2000
|
-
const ext =
|
|
2265
|
+
const ext = import_node_path5.default.extname(filePath).toLowerCase();
|
|
2001
2266
|
const mime = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
2002
|
-
const filename =
|
|
2267
|
+
const filename = import_node_path5.default.basename(filePath);
|
|
2003
2268
|
res.writeHead(200, {
|
|
2004
2269
|
"Content-Type": mime,
|
|
2005
2270
|
"Content-Disposition": `attachment; filename="${filename}"`,
|
|
2006
2271
|
"Cache-Control": "no-cache"
|
|
2007
2272
|
});
|
|
2008
|
-
|
|
2273
|
+
import_node_fs5.default.createReadStream(filePath).pipe(res);
|
|
2009
2274
|
return;
|
|
2010
2275
|
}
|
|
2011
2276
|
if (url2.pathname !== "/") {
|
|
2012
|
-
const staticPath =
|
|
2277
|
+
const staticPath = import_node_path5.default.join(clientDir, url2.pathname);
|
|
2013
2278
|
if (serveStatic(res, staticPath)) return;
|
|
2014
2279
|
}
|
|
2015
2280
|
serveIndex(res, clientDir);
|
|
@@ -2043,6 +2308,24 @@ async function startDeckServer(opts = {}) {
|
|
|
2043
2308
|
source: msg.source ?? ""
|
|
2044
2309
|
};
|
|
2045
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
|
+
}
|
|
2046
2329
|
} catch {
|
|
2047
2330
|
}
|
|
2048
2331
|
});
|
|
@@ -2108,13 +2391,13 @@ function runServeCli(argv) {
|
|
|
2108
2391
|
process.exit(1);
|
|
2109
2392
|
});
|
|
2110
2393
|
}
|
|
2111
|
-
var import_node_http,
|
|
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;
|
|
2112
2395
|
var init_deck_serve = __esm({
|
|
2113
2396
|
"src/server/deck-serve.ts"() {
|
|
2114
2397
|
"use strict";
|
|
2115
2398
|
import_node_http = __toESM(require("node:http"));
|
|
2116
|
-
|
|
2117
|
-
|
|
2399
|
+
import_node_fs5 = __toESM(require("node:fs"));
|
|
2400
|
+
import_node_path5 = __toESM(require("node:path"));
|
|
2118
2401
|
import_ws = require("ws");
|
|
2119
2402
|
init_launch_kit_paths();
|
|
2120
2403
|
init_deck_lockfile();
|
|
@@ -2138,6 +2421,8 @@ var init_deck_serve = __esm({
|
|
|
2138
2421
|
};
|
|
2139
2422
|
pendingFeedback = /* @__PURE__ */ new Map();
|
|
2140
2423
|
lastRenderError = null;
|
|
2424
|
+
activeSession = null;
|
|
2425
|
+
discussSessions = [];
|
|
2141
2426
|
wss = null;
|
|
2142
2427
|
}
|
|
2143
2428
|
});
|
|
@@ -2208,15 +2493,15 @@ async function handleTool(name, args) {
|
|
|
2208
2493
|
const baseUrl = `/deck-files/${sessionEncoded}`;
|
|
2209
2494
|
const html = generateBlastRadiusHtml(block.manifest, baseUrl);
|
|
2210
2495
|
const report = generateBlastRadiusReport(block.manifest);
|
|
2211
|
-
const dir = (0,
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2496
|
+
const dir = (0, import_node_path6.join)(projectRoot, LAUNCHSECURE_DIR, "deck-files", session);
|
|
2497
|
+
import_node_fs6.default.mkdirSync(dir, { recursive: true });
|
|
2498
|
+
import_node_fs6.default.writeFileSync((0, import_node_path6.join)(dir, "blast-radius.html"), html);
|
|
2499
|
+
import_node_fs6.default.writeFileSync((0, import_node_path6.join)(dir, "blast-radius-report.md"), report);
|
|
2500
|
+
import_node_fs6.default.writeFileSync((0, import_node_path6.join)(dir, "blast-radius-manifest.json"), JSON.stringify(block.manifest, null, 2));
|
|
2216
2501
|
const contractData = generateContract(block.manifest);
|
|
2217
2502
|
const contractMd = contractToMarkdown(contractData);
|
|
2218
|
-
|
|
2219
|
-
|
|
2503
|
+
import_node_fs6.default.writeFileSync((0, import_node_path6.join)(dir, "contract.json"), JSON.stringify(contractData, null, 2));
|
|
2504
|
+
import_node_fs6.default.writeFileSync((0, import_node_path6.join)(dir, "contract.md"), contractMd);
|
|
2220
2505
|
block.type = "iframe";
|
|
2221
2506
|
block.src = `${baseUrl}/blast-radius.html`;
|
|
2222
2507
|
delete block.manifest;
|
|
@@ -2243,9 +2528,9 @@ async function handleTool(name, args) {
|
|
|
2243
2528
|
const sessionEncoded = encodeURIComponent(session);
|
|
2244
2529
|
const baseUrl = `/deck-files/${sessionEncoded}`;
|
|
2245
2530
|
const filename = `${slugify(block.label)}.html`;
|
|
2246
|
-
const dir = (0,
|
|
2247
|
-
|
|
2248
|
-
|
|
2531
|
+
const dir = (0, import_node_path6.join)(projectRoot, LAUNCHSECURE_DIR, "deck-files", session);
|
|
2532
|
+
import_node_fs6.default.mkdirSync(dir, { recursive: true });
|
|
2533
|
+
import_node_fs6.default.writeFileSync((0, import_node_path6.join)(dir, filename), html);
|
|
2249
2534
|
block.type = "iframe";
|
|
2250
2535
|
block.src = `${baseUrl}/${filename}`;
|
|
2251
2536
|
delete block.content;
|
|
@@ -2339,11 +2624,11 @@ async function handleTool(name, args) {
|
|
|
2339
2624
|
}));
|
|
2340
2625
|
}
|
|
2341
2626
|
try {
|
|
2342
|
-
const logDir = (0,
|
|
2343
|
-
(0,
|
|
2344
|
-
const logPath = (0,
|
|
2345
|
-
const out = (0,
|
|
2346
|
-
const err = (0,
|
|
2627
|
+
const logDir = (0, import_node_path6.join)((0, import_node_os3.homedir)(), LAUNCHSECURE_DIR);
|
|
2628
|
+
(0, import_node_fs7.mkdirSync)(logDir, { recursive: true });
|
|
2629
|
+
const logPath = (0, import_node_path6.join)(logDir, "launch-deck.log");
|
|
2630
|
+
const out = (0, import_node_fs7.openSync)(logPath, "a");
|
|
2631
|
+
const err = (0, import_node_fs7.openSync)(logPath, "a");
|
|
2347
2632
|
const entryPath = process.argv[1];
|
|
2348
2633
|
const config = loadDeckConfig(projectRoot);
|
|
2349
2634
|
const resolvedPort = args.port ?? config.port;
|
|
@@ -2397,23 +2682,47 @@ async function handleTool(name, args) {
|
|
|
2397
2682
|
}
|
|
2398
2683
|
return text(JSON.stringify({ running: false }));
|
|
2399
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
|
+
}
|
|
2400
2709
|
case "generate_contract": {
|
|
2401
2710
|
const session = args.session;
|
|
2402
|
-
const dir = (0,
|
|
2403
|
-
const manifestPath = (0,
|
|
2404
|
-
if (!
|
|
2711
|
+
const dir = (0, import_node_path6.join)(projectRoot, LAUNCHSECURE_DIR, "deck-files", session);
|
|
2712
|
+
const manifestPath = (0, import_node_path6.join)(dir, "blast-radius-manifest.json");
|
|
2713
|
+
if (!import_node_fs6.default.existsSync(manifestPath)) {
|
|
2405
2714
|
return text(JSON.stringify({
|
|
2406
2715
|
error: `No blast radius manifest found for session "${session}". Push a blast-radius block first.`,
|
|
2407
2716
|
hint: "Use the deck tool with a blast-radius block type to create a session."
|
|
2408
2717
|
}));
|
|
2409
2718
|
}
|
|
2410
2719
|
try {
|
|
2411
|
-
const raw =
|
|
2720
|
+
const raw = import_node_fs6.default.readFileSync(manifestPath, "utf-8");
|
|
2412
2721
|
const manifest = JSON.parse(raw);
|
|
2413
2722
|
const contract = generateContract(manifest);
|
|
2414
2723
|
const markdown = contractToMarkdown(contract);
|
|
2415
|
-
|
|
2416
|
-
|
|
2724
|
+
import_node_fs6.default.writeFileSync((0, import_node_path6.join)(dir, "contract.json"), JSON.stringify(contract, null, 2));
|
|
2725
|
+
import_node_fs6.default.writeFileSync((0, import_node_path6.join)(dir, "contract.md"), markdown);
|
|
2417
2726
|
return text(JSON.stringify(contract));
|
|
2418
2727
|
} catch (err) {
|
|
2419
2728
|
return text(JSON.stringify({ error: `Failed to generate contract: ${err}` }));
|
|
@@ -2504,16 +2813,16 @@ function startDeckMcpServer() {
|
|
|
2504
2813
|
process.exit(0);
|
|
2505
2814
|
});
|
|
2506
2815
|
}
|
|
2507
|
-
var import_node_http2,
|
|
2816
|
+
var import_node_http2, import_node_fs6, import_node_path6, import_node_child_process2, import_node_fs7, import_node_os3, SERVER_INFO, TOOLS;
|
|
2508
2817
|
var init_deck_mcp = __esm({
|
|
2509
2818
|
"src/server/deck-mcp.ts"() {
|
|
2510
2819
|
"use strict";
|
|
2511
2820
|
import_node_http2 = __toESM(require("node:http"));
|
|
2512
|
-
|
|
2513
|
-
|
|
2821
|
+
import_node_fs6 = __toESM(require("node:fs"));
|
|
2822
|
+
import_node_path6 = require("node:path");
|
|
2514
2823
|
import_node_child_process2 = require("node:child_process");
|
|
2515
|
-
|
|
2516
|
-
|
|
2824
|
+
import_node_fs7 = require("node:fs");
|
|
2825
|
+
import_node_os3 = require("node:os");
|
|
2517
2826
|
init_launch_kit_paths();
|
|
2518
2827
|
init_deck_lockfile();
|
|
2519
2828
|
init_deck_config();
|
|
@@ -2637,6 +2946,11 @@ var init_deck_mcp = __esm({
|
|
|
2637
2946
|
description: "Check whether the LaunchDeck UI server is running.",
|
|
2638
2947
|
inputSchema: { type: "object", properties: {} }
|
|
2639
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
|
+
},
|
|
2640
2954
|
{
|
|
2641
2955
|
name: "generate_contract",
|
|
2642
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.",
|
|
@@ -2657,8 +2971,92 @@ var init_deck_mcp = __esm({
|
|
|
2657
2971
|
|
|
2658
2972
|
// src/server/deck-mcp-entry.ts
|
|
2659
2973
|
init_deck_lockfile();
|
|
2974
|
+
|
|
2975
|
+
// src/server/prune-npx-cache.ts
|
|
2976
|
+
var import_node_fs2 = require("node:fs");
|
|
2977
|
+
var import_node_os2 = require("node:os");
|
|
2978
|
+
var import_node_path2 = require("node:path");
|
|
2979
|
+
var import_node_util = require("node:util");
|
|
2980
|
+
var PKG = "@launchsecure/launch-kit";
|
|
2981
|
+
var readFileAsync = (0, import_node_util.promisify)(import_node_fs2.readFile);
|
|
2982
|
+
var readdirAsync = (0, import_node_util.promisify)(import_node_fs2.readdir);
|
|
2983
|
+
var rmAsync = (0, import_node_util.promisify)(import_node_fs2.rm);
|
|
2984
|
+
var realpathAsync = (0, import_node_util.promisify)(import_node_fs2.realpath);
|
|
2985
|
+
function npxCacheRoot() {
|
|
2986
|
+
const cache = process.env.npm_config_cache;
|
|
2987
|
+
if (cache && cache.trim()) return (0, import_node_path2.join)(cache, "_npx");
|
|
2988
|
+
if (process.platform === "win32") {
|
|
2989
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
2990
|
+
if (localAppData) return (0, import_node_path2.join)(localAppData, "npm-cache", "_npx");
|
|
2991
|
+
}
|
|
2992
|
+
return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".npm", "_npx");
|
|
2993
|
+
}
|
|
2994
|
+
function ownVersion() {
|
|
2995
|
+
let dir = __dirname;
|
|
2996
|
+
for (let i = 0; i < 8; i++) {
|
|
2997
|
+
try {
|
|
2998
|
+
const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(dir, "package.json"), "utf8"));
|
|
2999
|
+
if (pkg && pkg.name === PKG) return typeof pkg.version === "string" ? pkg.version : null;
|
|
3000
|
+
} catch {
|
|
3001
|
+
}
|
|
3002
|
+
const parent = (0, import_node_path2.dirname)(dir);
|
|
3003
|
+
if (parent === dir) break;
|
|
3004
|
+
dir = parent;
|
|
3005
|
+
}
|
|
3006
|
+
return null;
|
|
3007
|
+
}
|
|
3008
|
+
async function pruneStaleNpxCache() {
|
|
3009
|
+
try {
|
|
3010
|
+
const version = ownVersion();
|
|
3011
|
+
if (!version) return;
|
|
3012
|
+
const root = npxCacheRoot();
|
|
3013
|
+
let hashes;
|
|
3014
|
+
try {
|
|
3015
|
+
hashes = await readdirAsync(root);
|
|
3016
|
+
} catch {
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
const selfPath = await realpathAsync(process.argv[1] || __dirname).catch(() => __dirname);
|
|
3020
|
+
let reaped = 0;
|
|
3021
|
+
for (const hash of hashes) {
|
|
3022
|
+
const dir = (0, import_node_path2.join)(root, hash);
|
|
3023
|
+
if (selfPath === dir || selfPath.startsWith(dir + import_node_path2.sep)) continue;
|
|
3024
|
+
let ver;
|
|
3025
|
+
try {
|
|
3026
|
+
const lkPkg = JSON.parse(
|
|
3027
|
+
await readFileAsync((0, import_node_path2.join)(dir, "node_modules", PKG, "package.json"), "utf8")
|
|
3028
|
+
);
|
|
3029
|
+
ver = lkPkg.version;
|
|
3030
|
+
} catch {
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
3033
|
+
if (ver === version) continue;
|
|
3034
|
+
try {
|
|
3035
|
+
const top = JSON.parse(await readFileAsync((0, import_node_path2.join)(dir, "package.json"), "utf8"));
|
|
3036
|
+
const spec = top?.dependencies?.[PKG];
|
|
3037
|
+
if (typeof spec === "string" && spec.startsWith("file:")) continue;
|
|
3038
|
+
} catch {
|
|
3039
|
+
}
|
|
3040
|
+
try {
|
|
3041
|
+
await rmAsync(dir, { recursive: true, force: true });
|
|
3042
|
+
reaped++;
|
|
3043
|
+
} catch {
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
if (reaped > 0) {
|
|
3047
|
+
process.stderr.write(
|
|
3048
|
+
`[launch-kit] npx-cache: reaped ${reaped} stale copy(ies), kept v${version}
|
|
3049
|
+
`
|
|
3050
|
+
);
|
|
3051
|
+
}
|
|
3052
|
+
} catch {
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
// src/server/deck-mcp-entry.ts
|
|
2660
3057
|
async function main() {
|
|
2661
3058
|
setProjectRoot(process.cwd());
|
|
3059
|
+
void pruneStaleNpxCache();
|
|
2662
3060
|
const argv = process.argv.slice(2);
|
|
2663
3061
|
const subcommand = argv[0];
|
|
2664
3062
|
if (subcommand === "serve") {
|