@launchsecure/launch-kit 0.0.14 → 0.0.16
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-Dm6IBkiC.js → index-BpQPtTuo.js} +89 -89
- package/dist/chart-client/assets/index-CbZ13AXL.css +1 -0
- package/dist/chart-client/index.html +2 -2
- package/dist/client/assets/index-BCYw64M7.css +32 -0
- package/dist/client/index.html +2 -2
- package/dist/server/chart-serve.js +49 -49
- package/dist/server/cli.js +822 -201
- package/dist/server/graph-mcp-entry.js +687 -103
- package/package.json +1 -1
- package/dist/chart-client/assets/index-l-yyLDX5.css +0 -1
- package/dist/client/assets/index-rlw8dmPR.css +0 -32
- /package/dist/client/assets/{index-CyML1UiJ.js → index-3ENenBk-.js} +0 -0
package/dist/server/cli.js
CHANGED
|
@@ -554,7 +554,7 @@ var require_claude_bridge = __commonJS({
|
|
|
554
554
|
"use strict";
|
|
555
555
|
var { spawn: spawn3 } = require("node-pty");
|
|
556
556
|
var path9 = require("path");
|
|
557
|
-
var
|
|
557
|
+
var fs10 = require("fs");
|
|
558
558
|
var ClaudeBridge = class {
|
|
559
559
|
constructor() {
|
|
560
560
|
this.sessions = /* @__PURE__ */ new Map();
|
|
@@ -572,7 +572,7 @@ var require_claude_bridge = __commonJS({
|
|
|
572
572
|
];
|
|
573
573
|
for (const cmd of possibleCommands) {
|
|
574
574
|
try {
|
|
575
|
-
if (
|
|
575
|
+
if (fs10.existsSync(cmd) || this.commandExists(cmd)) {
|
|
576
576
|
console.log(`Found Claude command at: ${cmd}`);
|
|
577
577
|
return cmd;
|
|
578
578
|
}
|
|
@@ -773,7 +773,7 @@ var require_codex_bridge = __commonJS({
|
|
|
773
773
|
"use strict";
|
|
774
774
|
var { spawn: spawn3 } = require("node-pty");
|
|
775
775
|
var path9 = require("path");
|
|
776
|
-
var
|
|
776
|
+
var fs10 = require("fs");
|
|
777
777
|
var CodexBridge = class {
|
|
778
778
|
constructor() {
|
|
779
779
|
this.sessions = /* @__PURE__ */ new Map();
|
|
@@ -790,7 +790,7 @@ var require_codex_bridge = __commonJS({
|
|
|
790
790
|
];
|
|
791
791
|
for (const cmd of possibleCommands) {
|
|
792
792
|
try {
|
|
793
|
-
if (
|
|
793
|
+
if (fs10.existsSync(cmd) || this.commandExists(cmd)) {
|
|
794
794
|
console.log(`Found Codex command at: ${cmd}`);
|
|
795
795
|
return cmd;
|
|
796
796
|
}
|
|
@@ -966,7 +966,7 @@ var require_agent_bridge = __commonJS({
|
|
|
966
966
|
"use strict";
|
|
967
967
|
var { spawn: spawn3 } = require("node-pty");
|
|
968
968
|
var path9 = require("path");
|
|
969
|
-
var
|
|
969
|
+
var fs10 = require("fs");
|
|
970
970
|
var AgentBridge = class {
|
|
971
971
|
constructor() {
|
|
972
972
|
this.sessions = /* @__PURE__ */ new Map();
|
|
@@ -982,7 +982,7 @@ var require_agent_bridge = __commonJS({
|
|
|
982
982
|
];
|
|
983
983
|
for (const cmd of possibleCommands) {
|
|
984
984
|
try {
|
|
985
|
-
if (
|
|
985
|
+
if (fs10.existsSync(cmd) || this.commandExists(cmd)) {
|
|
986
986
|
console.log(`Found Agent command at: ${cmd}`);
|
|
987
987
|
return cmd;
|
|
988
988
|
}
|
|
@@ -1306,7 +1306,7 @@ var require_script_bridge = __commonJS({
|
|
|
1306
1306
|
var require_session_store = __commonJS({
|
|
1307
1307
|
"../claude-code-web/src/utils/session-store.js"(exports2, module2) {
|
|
1308
1308
|
"use strict";
|
|
1309
|
-
var
|
|
1309
|
+
var fs10 = require("fs").promises;
|
|
1310
1310
|
var path9 = require("path");
|
|
1311
1311
|
var os2 = require("os");
|
|
1312
1312
|
var SessionStore = class {
|
|
@@ -1317,14 +1317,14 @@ var require_session_store = __commonJS({
|
|
|
1317
1317
|
}
|
|
1318
1318
|
async initializeStorage() {
|
|
1319
1319
|
try {
|
|
1320
|
-
await
|
|
1320
|
+
await fs10.mkdir(this.storageDir, { recursive: true });
|
|
1321
1321
|
} catch (error) {
|
|
1322
1322
|
console.error("Failed to create storage directory:", error);
|
|
1323
1323
|
}
|
|
1324
1324
|
}
|
|
1325
1325
|
async saveSessions(sessions) {
|
|
1326
1326
|
try {
|
|
1327
|
-
await
|
|
1327
|
+
await fs10.mkdir(this.storageDir, { recursive: true });
|
|
1328
1328
|
const sessionsArray = Array.from(sessions.entries()).map(([id, session]) => ({
|
|
1329
1329
|
id,
|
|
1330
1330
|
name: session.name || "Unnamed Session",
|
|
@@ -1355,9 +1355,9 @@ var require_session_store = __commonJS({
|
|
|
1355
1355
|
sessions: sessionsArray
|
|
1356
1356
|
};
|
|
1357
1357
|
const tempFile = `${this.sessionsFile}.tmp`;
|
|
1358
|
-
await
|
|
1359
|
-
await
|
|
1360
|
-
await
|
|
1358
|
+
await fs10.writeFile(tempFile, JSON.stringify(data, null, 2));
|
|
1359
|
+
await fs10.mkdir(this.storageDir, { recursive: true });
|
|
1360
|
+
await fs10.rename(tempFile, this.sessionsFile);
|
|
1361
1361
|
return true;
|
|
1362
1362
|
} catch (error) {
|
|
1363
1363
|
console.error("Failed to save sessions:", error.message);
|
|
@@ -1366,8 +1366,8 @@ var require_session_store = __commonJS({
|
|
|
1366
1366
|
}
|
|
1367
1367
|
async loadSessions() {
|
|
1368
1368
|
try {
|
|
1369
|
-
await
|
|
1370
|
-
const data = await
|
|
1369
|
+
await fs10.access(this.sessionsFile);
|
|
1370
|
+
const data = await fs10.readFile(this.sessionsFile, "utf8");
|
|
1371
1371
|
if (!data || !data.trim()) {
|
|
1372
1372
|
console.log("Sessions file is empty, starting fresh");
|
|
1373
1373
|
return /* @__PURE__ */ new Map();
|
|
@@ -1378,7 +1378,7 @@ var require_session_store = __commonJS({
|
|
|
1378
1378
|
} catch (parseError) {
|
|
1379
1379
|
console.error("Sessions file is corrupted, starting fresh:", parseError.message);
|
|
1380
1380
|
try {
|
|
1381
|
-
await
|
|
1381
|
+
await fs10.rename(this.sessionsFile, `${this.sessionsFile}.corrupted.${Date.now()}`);
|
|
1382
1382
|
} catch (renameError) {
|
|
1383
1383
|
}
|
|
1384
1384
|
return /* @__PURE__ */ new Map();
|
|
@@ -1422,7 +1422,7 @@ var require_session_store = __commonJS({
|
|
|
1422
1422
|
}
|
|
1423
1423
|
async clearOldSessions() {
|
|
1424
1424
|
try {
|
|
1425
|
-
await
|
|
1425
|
+
await fs10.unlink(this.sessionsFile);
|
|
1426
1426
|
console.log("Cleared old sessions");
|
|
1427
1427
|
return true;
|
|
1428
1428
|
} catch (error) {
|
|
@@ -1434,9 +1434,9 @@ var require_session_store = __commonJS({
|
|
|
1434
1434
|
}
|
|
1435
1435
|
async getSessionMetadata() {
|
|
1436
1436
|
try {
|
|
1437
|
-
await
|
|
1438
|
-
const stats = await
|
|
1439
|
-
const data = await
|
|
1437
|
+
await fs10.access(this.sessionsFile);
|
|
1438
|
+
const stats = await fs10.stat(this.sessionsFile);
|
|
1439
|
+
const data = await fs10.readFile(this.sessionsFile, "utf8");
|
|
1440
1440
|
const parsed = JSON.parse(data);
|
|
1441
1441
|
return {
|
|
1442
1442
|
exists: true,
|
|
@@ -1461,7 +1461,7 @@ var require_session_store = __commonJS({
|
|
|
1461
1461
|
var require_usage_reader = __commonJS({
|
|
1462
1462
|
"../claude-code-web/src/usage-reader.js"(exports2, module2) {
|
|
1463
1463
|
"use strict";
|
|
1464
|
-
var
|
|
1464
|
+
var fs10 = require("fs").promises;
|
|
1465
1465
|
var path9 = require("path");
|
|
1466
1466
|
var readline = require("readline");
|
|
1467
1467
|
var { createReadStream } = require("fs");
|
|
@@ -1669,12 +1669,12 @@ var require_usage_reader = __commonJS({
|
|
|
1669
1669
|
const projectDirName = cwd.replace(/\//g, "-");
|
|
1670
1670
|
let projectPath = path9.join(this.claudeProjectsPath, projectDirName);
|
|
1671
1671
|
try {
|
|
1672
|
-
await
|
|
1672
|
+
await fs10.access(projectPath);
|
|
1673
1673
|
} catch (err2) {
|
|
1674
1674
|
console.log(`Project directory not found: ${projectPath}`);
|
|
1675
1675
|
return null;
|
|
1676
1676
|
}
|
|
1677
|
-
const files = await
|
|
1677
|
+
const files = await fs10.readdir(projectPath);
|
|
1678
1678
|
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
1679
1679
|
if (jsonlFiles.length === 0) {
|
|
1680
1680
|
return null;
|
|
@@ -1683,7 +1683,7 @@ var require_usage_reader = __commonJS({
|
|
|
1683
1683
|
let mostRecentTime = 0;
|
|
1684
1684
|
for (const file of jsonlFiles) {
|
|
1685
1685
|
const filePath = path9.join(projectPath, file);
|
|
1686
|
-
const stat = await
|
|
1686
|
+
const stat = await fs10.stat(filePath);
|
|
1687
1687
|
if (stat.mtime.getTime() > mostRecentTime) {
|
|
1688
1688
|
mostRecentTime = stat.mtime.getTime();
|
|
1689
1689
|
mostRecentFile = filePath;
|
|
@@ -1698,17 +1698,17 @@ var require_usage_reader = __commonJS({
|
|
|
1698
1698
|
async findJsonlFiles(onlyRecent = false) {
|
|
1699
1699
|
const files = [];
|
|
1700
1700
|
try {
|
|
1701
|
-
const projectDirs = await
|
|
1701
|
+
const projectDirs = await fs10.readdir(this.claudeProjectsPath);
|
|
1702
1702
|
for (const projectDir of projectDirs) {
|
|
1703
1703
|
const projectPath = path9.join(this.claudeProjectsPath, projectDir);
|
|
1704
|
-
const stat = await
|
|
1704
|
+
const stat = await fs10.stat(projectPath);
|
|
1705
1705
|
if (stat.isDirectory()) {
|
|
1706
|
-
const projectFiles = await
|
|
1706
|
+
const projectFiles = await fs10.readdir(projectPath);
|
|
1707
1707
|
const jsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
|
|
1708
1708
|
for (const jsonlFile of jsonlFiles) {
|
|
1709
1709
|
const filePath = path9.join(projectPath, jsonlFile);
|
|
1710
1710
|
if (onlyRecent) {
|
|
1711
|
-
const fileStat = await
|
|
1711
|
+
const fileStat = await fs10.stat(filePath);
|
|
1712
1712
|
const hoursSinceModified = (Date.now() - fileStat.mtime.getTime()) / (1e3 * 60 * 60);
|
|
1713
1713
|
if (hoursSinceModified <= 24) {
|
|
1714
1714
|
files.push(filePath);
|
|
@@ -1871,7 +1871,7 @@ var require_usage_reader = __commonJS({
|
|
|
1871
1871
|
}
|
|
1872
1872
|
const sessionFile = path9.join(this.claudeProjectsPath, path9.basename(process.cwd()).replace(/[^a-zA-Z0-9-]/g, "-"), `${sessionId}.jsonl`);
|
|
1873
1873
|
try {
|
|
1874
|
-
await
|
|
1874
|
+
await fs10.access(sessionFile);
|
|
1875
1875
|
} catch (err2) {
|
|
1876
1876
|
return null;
|
|
1877
1877
|
}
|
|
@@ -2537,7 +2537,7 @@ var require_src = __commonJS({
|
|
|
2537
2537
|
var SessionStore = require_session_store();
|
|
2538
2538
|
var UsageReader = require_usage_reader();
|
|
2539
2539
|
var UsageAnalytics = require_usage_analytics();
|
|
2540
|
-
var
|
|
2540
|
+
var fs10 = require("fs");
|
|
2541
2541
|
function stripAnsi(str) {
|
|
2542
2542
|
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\x1b[()][AB012]/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").replace(/\r\n?/g, "\n").trim();
|
|
2543
2543
|
}
|
|
@@ -2795,7 +2795,7 @@ var require_src = __commonJS({
|
|
|
2795
2795
|
return true;
|
|
2796
2796
|
}
|
|
2797
2797
|
try {
|
|
2798
|
-
const items =
|
|
2798
|
+
const items = fs10.readdirSync(validation.path, { withFileTypes: true });
|
|
2799
2799
|
const showHidden = url.searchParams.get("showHidden") === "true";
|
|
2800
2800
|
const folders = items.filter((item) => item.isDirectory()).filter((item) => !item.name.startsWith(".") || showHidden).map((item) => ({
|
|
2801
2801
|
name: item.name,
|
|
@@ -2830,7 +2830,7 @@ var require_src = __commonJS({
|
|
|
2830
2830
|
res.end(JSON.stringify({ error: validation.error }));
|
|
2831
2831
|
return;
|
|
2832
2832
|
}
|
|
2833
|
-
if (!
|
|
2833
|
+
if (!fs10.existsSync(validation.path) || !fs10.statSync(validation.path).isDirectory()) {
|
|
2834
2834
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2835
2835
|
res.end(JSON.stringify({ error: "Not a valid directory" }));
|
|
2836
2836
|
return;
|
|
@@ -2857,7 +2857,7 @@ var require_src = __commonJS({
|
|
|
2857
2857
|
res.end(JSON.stringify({ error: validation.error }));
|
|
2858
2858
|
return;
|
|
2859
2859
|
}
|
|
2860
|
-
if (!
|
|
2860
|
+
if (!fs10.existsSync(validation.path) || !fs10.statSync(validation.path).isDirectory()) {
|
|
2861
2861
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
2862
2862
|
res.end(JSON.stringify({ error: "Invalid directory path" }));
|
|
2863
2863
|
return;
|
|
@@ -2892,12 +2892,12 @@ var require_src = __commonJS({
|
|
|
2892
2892
|
res.end(JSON.stringify({ message: "Cannot create folder outside the allowed area" }));
|
|
2893
2893
|
return;
|
|
2894
2894
|
}
|
|
2895
|
-
if (
|
|
2895
|
+
if (fs10.existsSync(fullValidation.path)) {
|
|
2896
2896
|
res.writeHead(409, { "Content-Type": "application/json" });
|
|
2897
2897
|
res.end(JSON.stringify({ message: "Folder already exists" }));
|
|
2898
2898
|
return;
|
|
2899
2899
|
}
|
|
2900
|
-
|
|
2900
|
+
fs10.mkdirSync(fullValidation.path, { recursive: true });
|
|
2901
2901
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2902
2902
|
res.end(JSON.stringify({ success: true, path: fullValidation.path }));
|
|
2903
2903
|
} catch (e) {
|
|
@@ -2923,7 +2923,7 @@ var require_src = __commonJS({
|
|
|
2923
2923
|
if (!filePath || filePath === "") filePath = "index.html";
|
|
2924
2924
|
const fullPath = path9.join(__dirname, "public", filePath);
|
|
2925
2925
|
try {
|
|
2926
|
-
if (!
|
|
2926
|
+
if (!fs10.existsSync(fullPath)) return false;
|
|
2927
2927
|
const ext = path9.extname(fullPath);
|
|
2928
2928
|
const mimeTypes = {
|
|
2929
2929
|
".html": "text/html",
|
|
@@ -2935,7 +2935,7 @@ var require_src = __commonJS({
|
|
|
2935
2935
|
".ico": "image/x-icon"
|
|
2936
2936
|
};
|
|
2937
2937
|
const mime = mimeTypes[ext] || "application/octet-stream";
|
|
2938
|
-
const content =
|
|
2938
|
+
const content = fs10.readFileSync(fullPath);
|
|
2939
2939
|
res.writeHead(200, { "Content-Type": mime });
|
|
2940
2940
|
res.end(content);
|
|
2941
2941
|
return true;
|
|
@@ -4363,6 +4363,7 @@ function shutdownTerminalBridge() {
|
|
|
4363
4363
|
// src/server/mcp-config-writer.ts
|
|
4364
4364
|
var import_fs = __toESM(require("fs"));
|
|
4365
4365
|
var import_path = __toESM(require("path"));
|
|
4366
|
+
var MANAGED_SERVERS = ["launch-pod", "launch-chart"];
|
|
4366
4367
|
var writtenPaths = [];
|
|
4367
4368
|
function writeMcpConfigs(opts) {
|
|
4368
4369
|
const { projectDir, token, serverUrl } = opts;
|
|
@@ -4373,7 +4374,10 @@ function writeMcpConfigs(opts) {
|
|
|
4373
4374
|
function removeMcpConfigs() {
|
|
4374
4375
|
for (const filePath of writtenPaths) {
|
|
4375
4376
|
try {
|
|
4376
|
-
if (import_fs.default.existsSync(filePath))
|
|
4377
|
+
if (!import_fs.default.existsSync(filePath)) continue;
|
|
4378
|
+
if (filePath.endsWith(".mcp.json")) {
|
|
4379
|
+
removeClaudeEntries(filePath);
|
|
4380
|
+
} else {
|
|
4377
4381
|
import_fs.default.unlinkSync(filePath);
|
|
4378
4382
|
console.log(`MCP config removed: ${filePath}`);
|
|
4379
4383
|
}
|
|
@@ -4382,46 +4386,88 @@ function removeMcpConfigs() {
|
|
|
4382
4386
|
}
|
|
4383
4387
|
writtenPaths.length = 0;
|
|
4384
4388
|
}
|
|
4389
|
+
function removeClaudeEntries(filePath) {
|
|
4390
|
+
try {
|
|
4391
|
+
const raw = import_fs.default.readFileSync(filePath, "utf-8");
|
|
4392
|
+
const existing = JSON.parse(raw);
|
|
4393
|
+
const servers = existing.mcpServers ?? {};
|
|
4394
|
+
for (const key of MANAGED_SERVERS) {
|
|
4395
|
+
delete servers[key];
|
|
4396
|
+
}
|
|
4397
|
+
if (Object.keys(servers).length === 0 && Object.keys(existing).length <= 1) {
|
|
4398
|
+
import_fs.default.unlinkSync(filePath);
|
|
4399
|
+
console.log(`MCP config removed (empty): ${filePath}`);
|
|
4400
|
+
} else {
|
|
4401
|
+
existing.mcpServers = servers;
|
|
4402
|
+
import_fs.default.writeFileSync(filePath, JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
|
|
4403
|
+
console.log(`MCP config cleaned (LaunchPod entries removed): ${filePath}`);
|
|
4404
|
+
}
|
|
4405
|
+
} catch {
|
|
4406
|
+
try {
|
|
4407
|
+
import_fs.default.unlinkSync(filePath);
|
|
4408
|
+
} catch {
|
|
4409
|
+
}
|
|
4410
|
+
console.log(`MCP config removed: ${filePath}`);
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4385
4413
|
function writeClaudeConfig(projectDir, mcpUrl, token) {
|
|
4386
|
-
const
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
"
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4414
|
+
const filePath = import_path.default.join(projectDir, ".mcp.json");
|
|
4415
|
+
let existing = {};
|
|
4416
|
+
try {
|
|
4417
|
+
if (import_fs.default.existsSync(filePath)) {
|
|
4418
|
+
existing = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
|
|
4419
|
+
}
|
|
4420
|
+
} catch {
|
|
4421
|
+
console.warn(`Warning: existing .mcp.json was malformed, preserving as .mcp.json.bak`);
|
|
4422
|
+
try {
|
|
4423
|
+
import_fs.default.copyFileSync(filePath, filePath + ".bak");
|
|
4424
|
+
} catch {
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
const servers = existing.mcpServers ?? {};
|
|
4428
|
+
servers["launch-pod"] = {
|
|
4429
|
+
type: "http",
|
|
4430
|
+
url: mcpUrl,
|
|
4431
|
+
headers: {
|
|
4432
|
+
Authorization: `Bearer ${token}`
|
|
4399
4433
|
}
|
|
4400
4434
|
};
|
|
4401
|
-
|
|
4402
|
-
|
|
4435
|
+
servers["launch-chart"] = {
|
|
4436
|
+
command: "launch-chart",
|
|
4437
|
+
args: []
|
|
4438
|
+
};
|
|
4439
|
+
existing.mcpServers = servers;
|
|
4440
|
+
import_fs.default.writeFileSync(filePath, JSON.stringify(existing, null, 2) + "\n", { mode: 384 });
|
|
4403
4441
|
writtenPaths.push(filePath);
|
|
4404
|
-
console.log(`Claude Code MCP config
|
|
4442
|
+
console.log(`Claude Code MCP config merged into ${filePath}`);
|
|
4405
4443
|
}
|
|
4406
4444
|
function writeCodexConfig(projectDir, mcpUrl, token) {
|
|
4407
4445
|
const codexDir = import_path.default.join(projectDir, ".codex");
|
|
4408
4446
|
if (!import_fs.default.existsSync(codexDir)) {
|
|
4409
4447
|
import_fs.default.mkdirSync(codexDir, { recursive: true, mode: 448 });
|
|
4410
4448
|
}
|
|
4411
|
-
const
|
|
4449
|
+
const filePath = import_path.default.join(codexDir, "config.toml");
|
|
4450
|
+
let existingContent = "";
|
|
4451
|
+
try {
|
|
4452
|
+
if (import_fs.default.existsSync(filePath)) {
|
|
4453
|
+
existingContent = import_fs.default.readFileSync(filePath, "utf-8");
|
|
4454
|
+
}
|
|
4455
|
+
} catch {
|
|
4456
|
+
}
|
|
4457
|
+
const cleaned = existingContent.replace(/\[mcp_servers\.launch-pod\][^\[]*/, "").replace(/\[mcp_servers\.launch-chart\][^\[]*/, "").trim();
|
|
4458
|
+
const launchPodBlock = [
|
|
4412
4459
|
`[mcp_servers.launch-pod]`,
|
|
4413
4460
|
`url = "${mcpUrl}"`,
|
|
4414
4461
|
`http_headers = { "Authorization" = "Bearer ${token}" }`,
|
|
4415
4462
|
``,
|
|
4416
4463
|
`[mcp_servers.launch-chart]`,
|
|
4417
4464
|
`command = "launch-chart"`,
|
|
4418
|
-
`args = []
|
|
4419
|
-
``
|
|
4465
|
+
`args = []`
|
|
4420
4466
|
].join("\n");
|
|
4421
|
-
const
|
|
4422
|
-
import_fs.default.writeFileSync(filePath,
|
|
4467
|
+
const merged = cleaned ? cleaned + "\n\n" + launchPodBlock + "\n" : launchPodBlock + "\n";
|
|
4468
|
+
import_fs.default.writeFileSync(filePath, merged, { mode: 384 });
|
|
4423
4469
|
writtenPaths.push(filePath);
|
|
4424
|
-
console.log(`Codex MCP config
|
|
4470
|
+
console.log(`Codex MCP config merged into ${filePath}`);
|
|
4425
4471
|
}
|
|
4426
4472
|
|
|
4427
4473
|
// src/server/tracker-poller.ts
|
|
@@ -4528,13 +4574,18 @@ All pipeline data lives in the **database**, not on the local filesystem.
|
|
|
4528
4574
|
---
|
|
4529
4575
|
|
|
4530
4576
|
`;
|
|
4531
|
-
function resolvePrompt(agentId, unitId, inputs) {
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4577
|
+
function resolvePrompt(agentId, unitId, inputs, promptOverride) {
|
|
4578
|
+
let raw;
|
|
4579
|
+
if (promptOverride) {
|
|
4580
|
+
raw = promptOverride;
|
|
4581
|
+
} else {
|
|
4582
|
+
const hyphenated = agentId.replace(/_/g, "-");
|
|
4583
|
+
const filePath = import_path2.default.join(PROMPTS_DIR, `${hyphenated}.md`);
|
|
4584
|
+
if (!import_fs2.default.existsSync(filePath)) {
|
|
4585
|
+
throw new Error(`Prompt file not found for agent "${agentId}": ${filePath}`);
|
|
4586
|
+
}
|
|
4587
|
+
raw = import_fs2.default.readFileSync(filePath, "utf-8");
|
|
4536
4588
|
}
|
|
4537
|
-
const raw = import_fs2.default.readFileSync(filePath, "utf-8");
|
|
4538
4589
|
const unitPreamble = unitId ? `
|
|
4539
4590
|
---
|
|
4540
4591
|
|
|
@@ -4653,7 +4704,7 @@ var NodeTriggerController = class {
|
|
|
4653
4704
|
async startSession(agentId, agent, unitId) {
|
|
4654
4705
|
const key = this.sessionKey(agentId, unitId);
|
|
4655
4706
|
try {
|
|
4656
|
-
const prompt = resolvePrompt(agentId, unitId, agent.in);
|
|
4707
|
+
const prompt = resolvePrompt(agentId, unitId, agent.in, agent.po);
|
|
4657
4708
|
const sessionId = await this.adapter.start({
|
|
4658
4709
|
agentId,
|
|
4659
4710
|
prompt,
|
|
@@ -6906,19 +6957,26 @@ init_config();
|
|
|
6906
6957
|
// src/server/graph/core/resolve-paths.ts
|
|
6907
6958
|
var import_node_fs2 = require("node:fs");
|
|
6908
6959
|
var import_node_path2 = require("node:path");
|
|
6960
|
+
function detectDbDir(rootDir, config) {
|
|
6961
|
+
if (config.paths?.dbDir) return (0, import_node_path2.join)(rootDir, config.paths.dbDir);
|
|
6962
|
+
const prismaDir = (0, import_node_path2.join)(rootDir, "prisma");
|
|
6963
|
+
if ((0, import_node_fs2.existsSync)(prismaDir)) return prismaDir;
|
|
6964
|
+
return null;
|
|
6965
|
+
}
|
|
6909
6966
|
function resolveProjectPaths(rootDir, config) {
|
|
6967
|
+
const dbDir = detectDbDir(rootDir, config);
|
|
6910
6968
|
if (config.paths?.appDir) {
|
|
6911
6969
|
const appDir = (0, import_node_path2.join)(rootDir, config.paths.appDir);
|
|
6912
6970
|
const srcDir = config.paths.srcDir ? (0, import_node_path2.join)(rootDir, config.paths.srcDir) : (0, import_node_path2.dirname)(appDir);
|
|
6913
|
-
return { srcDir, appDir, apiDir: (0, import_node_path2.join)(appDir, "api") };
|
|
6971
|
+
return { srcDir, appDir, apiDir: (0, import_node_path2.join)(appDir, "api"), dbDir };
|
|
6914
6972
|
}
|
|
6915
6973
|
const srcApp = (0, import_node_path2.join)(rootDir, "src", "app");
|
|
6916
6974
|
if ((0, import_node_fs2.existsSync)(srcApp)) {
|
|
6917
|
-
return { srcDir: (0, import_node_path2.join)(rootDir, "src"), appDir: srcApp, apiDir: (0, import_node_path2.join)(srcApp, "api") };
|
|
6975
|
+
return { srcDir: (0, import_node_path2.join)(rootDir, "src"), appDir: srcApp, apiDir: (0, import_node_path2.join)(srcApp, "api"), dbDir };
|
|
6918
6976
|
}
|
|
6919
6977
|
const rootApp = (0, import_node_path2.join)(rootDir, "app");
|
|
6920
6978
|
if ((0, import_node_fs2.existsSync)(rootApp)) {
|
|
6921
|
-
return { srcDir: rootDir, appDir: rootApp, apiDir: (0, import_node_path2.join)(rootApp, "api") };
|
|
6979
|
+
return { srcDir: rootDir, appDir: rootApp, apiDir: (0, import_node_path2.join)(rootApp, "api"), dbDir };
|
|
6922
6980
|
}
|
|
6923
6981
|
return null;
|
|
6924
6982
|
}
|
|
@@ -8282,6 +8340,7 @@ function resolveUrlPath(urlPath, apiPathMap, apiRoutes) {
|
|
|
8282
8340
|
var fetchResolverParser = {
|
|
8283
8341
|
id: "fetch-resolver",
|
|
8284
8342
|
layer: "crosslayer",
|
|
8343
|
+
concern: "api-binding",
|
|
8285
8344
|
detect(_rootDir) {
|
|
8286
8345
|
return true;
|
|
8287
8346
|
},
|
|
@@ -8395,6 +8454,7 @@ function toNodeId2(srcDir, absPath) {
|
|
|
8395
8454
|
var apiAnnotationsParser = {
|
|
8396
8455
|
id: "api-annotations",
|
|
8397
8456
|
layer: "crosslayer",
|
|
8457
|
+
concern: "api-binding",
|
|
8398
8458
|
detect(rootDir) {
|
|
8399
8459
|
return (0, import_node_fs7.existsSync)((0, import_node_path7.join)(rootDir, "src"));
|
|
8400
8460
|
},
|
|
@@ -8481,6 +8541,7 @@ function toNodeId3(srcDir, absPath) {
|
|
|
8481
8541
|
var urlLiteralScannerParser = {
|
|
8482
8542
|
id: "url-literal-scanner",
|
|
8483
8543
|
layer: "crosslayer",
|
|
8544
|
+
concern: "api-binding",
|
|
8484
8545
|
detect(rootDir) {
|
|
8485
8546
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
8486
8547
|
return paths !== null;
|
|
@@ -9092,6 +9153,7 @@ function collectStaticRefsRegex(content, valueLookup, allValues) {
|
|
|
9092
9153
|
var staticRefScannerParser = {
|
|
9093
9154
|
id: "static-ref-scanner",
|
|
9094
9155
|
layer: "crosslayer",
|
|
9156
|
+
concern: "static-ref",
|
|
9095
9157
|
detect(rootDir) {
|
|
9096
9158
|
const paths = resolveProjectPaths(rootDir, loadConfig(rootDir));
|
|
9097
9159
|
return paths !== null;
|
|
@@ -9276,6 +9338,9 @@ function loadCustomParsers(registry, config, rootDir, disabled) {
|
|
|
9276
9338
|
`
|
|
9277
9339
|
);
|
|
9278
9340
|
}
|
|
9341
|
+
if (parser.layer === "crosslayer" && entry.concern && !("concern" in parser && parser.concern)) {
|
|
9342
|
+
parser.concern = entry.concern;
|
|
9343
|
+
}
|
|
9279
9344
|
registry.register(parser);
|
|
9280
9345
|
} catch (err2) {
|
|
9281
9346
|
process.stderr.write(`[launch-chart] failed to load custom parser from ${entry.path}: ${err2}
|
|
@@ -9373,44 +9438,21 @@ function dedupCrossRefs(refs) {
|
|
|
9373
9438
|
}
|
|
9374
9439
|
return result;
|
|
9375
9440
|
}
|
|
9376
|
-
function applyCrossLayerResults(uiOutput, results
|
|
9377
|
-
const allCrossRefs = [...uiOutput.cross_refs];
|
|
9378
|
-
const allFlagged = [...uiOutput.flagged_edges];
|
|
9379
|
-
const allWarnings = [...uiOutput.warnings];
|
|
9380
|
-
const primaryResult = results.find((r) => r.parserId === primaryId);
|
|
9381
|
-
const secondaryResults = results.filter((r) => r.parserId !== primaryId);
|
|
9382
|
-
if (primaryResult) {
|
|
9383
|
-
allCrossRefs.push(...primaryResult.output.cross_refs);
|
|
9384
|
-
allFlagged.push(...primaryResult.output.flagged_edges);
|
|
9385
|
-
allWarnings.push(...primaryResult.output.warnings);
|
|
9386
|
-
}
|
|
9387
|
-
const primarySet = new Set(
|
|
9388
|
-
(primaryResult?.output.cross_refs ?? []).map((r) => `${r.source}|${r.target}|${r.type}`)
|
|
9389
|
-
);
|
|
9390
|
-
for (const sec of secondaryResults) {
|
|
9391
|
-
for (const ref of sec.output.cross_refs) {
|
|
9392
|
-
const key = `${ref.source}|${ref.target}|${ref.type}`;
|
|
9393
|
-
if (primarySet.has(key)) {
|
|
9394
|
-
allCrossRefs.push(ref);
|
|
9395
|
-
} else {
|
|
9396
|
-
allFlagged.push({
|
|
9397
|
-
source: ref.source,
|
|
9398
|
-
target: ref.target,
|
|
9399
|
-
type: "out_of_pattern",
|
|
9400
|
-
label: `API call detected by ${sec.parserId} but not by primary (${primaryId})`,
|
|
9401
|
-
confidence: "medium"
|
|
9402
|
-
});
|
|
9403
|
-
allCrossRefs.push(ref);
|
|
9404
|
-
}
|
|
9405
|
-
}
|
|
9406
|
-
allFlagged.push(...sec.output.flagged_edges);
|
|
9407
|
-
allWarnings.push(...sec.output.warnings);
|
|
9408
|
-
}
|
|
9441
|
+
function applyCrossLayerResults(uiOutput, results) {
|
|
9409
9442
|
return {
|
|
9410
9443
|
...uiOutput,
|
|
9411
|
-
cross_refs: dedupCrossRefs(
|
|
9412
|
-
|
|
9413
|
-
|
|
9444
|
+
cross_refs: dedupCrossRefs([
|
|
9445
|
+
...uiOutput.cross_refs,
|
|
9446
|
+
...results.flatMap((r) => r.output.cross_refs)
|
|
9447
|
+
]),
|
|
9448
|
+
flagged_edges: [
|
|
9449
|
+
...uiOutput.flagged_edges,
|
|
9450
|
+
...results.flatMap((r) => r.output.flagged_edges)
|
|
9451
|
+
],
|
|
9452
|
+
warnings: [
|
|
9453
|
+
...uiOutput.warnings,
|
|
9454
|
+
...results.flatMap((r) => r.output.warnings)
|
|
9455
|
+
]
|
|
9414
9456
|
};
|
|
9415
9457
|
}
|
|
9416
9458
|
|
|
@@ -9449,10 +9491,9 @@ function generateLayer(rootDir, layer) {
|
|
|
9449
9491
|
if (existing) layerOutputs.set(otherLayer, existing);
|
|
9450
9492
|
}
|
|
9451
9493
|
const crossParsers = registry.getCrossLayerParsers();
|
|
9452
|
-
const primaryId = config.parsers?.primary?.crosslayer ?? crossParsers[0]?.id ?? null;
|
|
9453
9494
|
const crossResults = crossParsers.filter((p) => p.detect(rootDir)).map((p) => ({ parserId: p.id, output: p.generate(rootDir, layerOutputs) }));
|
|
9454
9495
|
if (crossResults.length > 0) {
|
|
9455
|
-
merged = applyCrossLayerResults(merged, crossResults
|
|
9496
|
+
merged = applyCrossLayerResults(merged, crossResults);
|
|
9456
9497
|
}
|
|
9457
9498
|
}
|
|
9458
9499
|
return {
|
|
@@ -9499,11 +9540,10 @@ function generateAll(rootDir) {
|
|
|
9499
9540
|
});
|
|
9500
9541
|
}
|
|
9501
9542
|
const crossParsers = registry.getCrossLayerParsers();
|
|
9502
|
-
const primaryId = config.parsers?.primary?.crosslayer ?? crossParsers[0]?.id ?? null;
|
|
9503
9543
|
const crossResults = crossParsers.filter((p) => p.detect(rootDir)).map((p) => ({ parserId: p.id, output: p.generate(rootDir, layerOutputs) }));
|
|
9504
9544
|
if (crossResults.length > 0 && layerOutputs.has("ui")) {
|
|
9505
9545
|
const uiOutput = layerOutputs.get("ui");
|
|
9506
|
-
const merged = applyCrossLayerResults(uiOutput, crossResults
|
|
9546
|
+
const merged = applyCrossLayerResults(uiOutput, crossResults);
|
|
9507
9547
|
layerOutputs.set("ui", merged);
|
|
9508
9548
|
const uiResult = results.find((r) => r.layer === "ui");
|
|
9509
9549
|
if (uiResult) {
|
|
@@ -10110,35 +10150,237 @@ async function handleGraphCommand(subcommand, args) {
|
|
|
10110
10150
|
}
|
|
10111
10151
|
|
|
10112
10152
|
// src/server/graph-mcp.ts
|
|
10113
|
-
var
|
|
10114
|
-
var
|
|
10153
|
+
var import_node_fs19 = require("node:fs");
|
|
10154
|
+
var import_node_path21 = require("node:path");
|
|
10115
10155
|
var import_node_child_process2 = require("node:child_process");
|
|
10116
10156
|
var import_node_os2 = require("node:os");
|
|
10117
10157
|
|
|
10158
|
+
// src/server/blast-radius-builder.ts
|
|
10159
|
+
var import_node_fs15 = __toESM(require("node:fs"));
|
|
10160
|
+
var import_node_path17 = require("node:path");
|
|
10161
|
+
var FALLBACK_DEFAULTS = {
|
|
10162
|
+
rings: [
|
|
10163
|
+
{ id: "modify", name: "Modify", color: "#ff6b00" },
|
|
10164
|
+
{ id: "ripple", name: "Ripple (verify)", color: "#ffff00" },
|
|
10165
|
+
{ id: "create", name: "Create", color: "#00ff00" }
|
|
10166
|
+
],
|
|
10167
|
+
layers: {
|
|
10168
|
+
db: { name: "Database", icon: "database", color: "#cbd5e1" },
|
|
10169
|
+
api: { name: "API", icon: "server", color: "#cbd5e1" },
|
|
10170
|
+
middleware: { name: "Middleware", icon: "shield", color: "#cbd5e1" },
|
|
10171
|
+
ui: { name: "UI", icon: "layout-dashboard", color: "#cbd5e1" },
|
|
10172
|
+
config: { name: "Config / Seed", icon: "settings", color: "#cbd5e1" },
|
|
10173
|
+
shared: { name: "Shared Types", icon: "box", color: "#cbd5e1" }
|
|
10174
|
+
},
|
|
10175
|
+
center: { color: "#ff0000" }
|
|
10176
|
+
};
|
|
10177
|
+
function loadDefaults(rootDir) {
|
|
10178
|
+
const filePath = (0, import_node_path17.join)(rootDir, ".launchsecure", "blast-radius-defaults.json");
|
|
10179
|
+
try {
|
|
10180
|
+
if (import_node_fs15.default.existsSync(filePath)) {
|
|
10181
|
+
const raw = import_node_fs15.default.readFileSync(filePath, "utf-8");
|
|
10182
|
+
return JSON.parse(raw);
|
|
10183
|
+
}
|
|
10184
|
+
} catch {
|
|
10185
|
+
}
|
|
10186
|
+
return FALLBACK_DEFAULTS;
|
|
10187
|
+
}
|
|
10188
|
+
function generateAcceptance(node, inspect) {
|
|
10189
|
+
const criteria = [];
|
|
10190
|
+
const t = node.type?.toLowerCase() ?? "";
|
|
10191
|
+
if (t === "endpoint" || t === "mcp-tool") {
|
|
10192
|
+
const methods = inspect?.methods ?? [];
|
|
10193
|
+
const path9 = inspect?.path ?? node.id;
|
|
10194
|
+
if (methods.length > 0) {
|
|
10195
|
+
criteria.push(`${methods.join("/")} ${path9} still returns correct responses for authorized users`);
|
|
10196
|
+
} else {
|
|
10197
|
+
criteria.push(`${path9} still responds correctly`);
|
|
10198
|
+
}
|
|
10199
|
+
if (inspect?.auth && inspect.auth.includes("withAuth")) {
|
|
10200
|
+
criteria.push("Authentication and authorization still enforced");
|
|
10201
|
+
}
|
|
10202
|
+
if (inspect?.db_models && inspect.db_models.length > 0) {
|
|
10203
|
+
criteria.push(`DB operations on ${inspect.db_models.join(", ")} still work correctly`);
|
|
10204
|
+
}
|
|
10205
|
+
} else if (t === "page" || t === "component" || t === "layout") {
|
|
10206
|
+
criteria.push(`${node.name} renders without errors`);
|
|
10207
|
+
if (inspect?.stateVars && inspect.stateVars.length > 0) {
|
|
10208
|
+
criteria.push("State management still works correctly");
|
|
10209
|
+
}
|
|
10210
|
+
if (inspect?.elements && inspect.elements.length > 5) {
|
|
10211
|
+
criteria.push("All child components render correctly");
|
|
10212
|
+
}
|
|
10213
|
+
} else if (t === "table" || t === "enum") {
|
|
10214
|
+
criteria.push(`${node.name} schema unchanged or migration applies cleanly`);
|
|
10215
|
+
criteria.push("Existing queries against this table still work");
|
|
10216
|
+
} else if (t === "hook") {
|
|
10217
|
+
criteria.push(`${node.name} returns expected shape`);
|
|
10218
|
+
if (inspect?.stateVars && inspect.stateVars.length > 0) {
|
|
10219
|
+
criteria.push(`State variables [${inspect.stateVars.map((s) => s.name).join(", ")}] still returned`);
|
|
10220
|
+
}
|
|
10221
|
+
} else if (t === "context") {
|
|
10222
|
+
criteria.push(`${node.name} provides correct context to consumers`);
|
|
10223
|
+
} else if (t === "lib" || t === "config" || t === "types") {
|
|
10224
|
+
criteria.push(`${node.name} exports still conform to expected interface`);
|
|
10225
|
+
} else if (t === "seed" || t === "seed_role" || t === "seed_permission") {
|
|
10226
|
+
criteria.push("Seed runs without errors");
|
|
10227
|
+
criteria.push("Expected rows created in database");
|
|
10228
|
+
} else {
|
|
10229
|
+
criteria.push("Verify no regression");
|
|
10230
|
+
}
|
|
10231
|
+
return criteria;
|
|
10232
|
+
}
|
|
10233
|
+
function buildManifest(input) {
|
|
10234
|
+
const { mode, title, description, subtitle, blastResults, createNodes, inspectData, defaults } = input;
|
|
10235
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
10236
|
+
const centerNodeIds = /* @__PURE__ */ new Set();
|
|
10237
|
+
for (const result of blastResults) {
|
|
10238
|
+
centerNodeIds.add(result.center.id);
|
|
10239
|
+
for (const node of result.affected) {
|
|
10240
|
+
const existing = nodeMap.get(node.id);
|
|
10241
|
+
if (!existing || node.hop < existing.hop) {
|
|
10242
|
+
nodeMap.set(node.id, node);
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
}
|
|
10246
|
+
for (const id of centerNodeIds) {
|
|
10247
|
+
nodeMap.delete(id);
|
|
10248
|
+
}
|
|
10249
|
+
const manifestNodes = [];
|
|
10250
|
+
for (const result of blastResults) {
|
|
10251
|
+
const c = result.center;
|
|
10252
|
+
if (manifestNodes.some((n) => n.id === c.id)) continue;
|
|
10253
|
+
const inspect = inspectData[c.id];
|
|
10254
|
+
manifestNodes.push({
|
|
10255
|
+
id: c.id,
|
|
10256
|
+
name: c.name,
|
|
10257
|
+
layer: c.layer,
|
|
10258
|
+
ring: "modify",
|
|
10259
|
+
type: c.type,
|
|
10260
|
+
reason: `Direct change target`,
|
|
10261
|
+
acceptance: generateAcceptance(
|
|
10262
|
+
{ id: c.id, name: c.name, type: c.type, layer: c.layer, hop: 0 },
|
|
10263
|
+
inspect
|
|
10264
|
+
)
|
|
10265
|
+
});
|
|
10266
|
+
}
|
|
10267
|
+
for (const [, node] of nodeMap) {
|
|
10268
|
+
const ring = node.hop <= 1 ? "modify" : "ripple";
|
|
10269
|
+
const inspect = inspectData[node.id];
|
|
10270
|
+
const reason = node.hop <= 1 ? `Directly depends on changed node` : `Indirect dependency (${node.hop} hops away)`;
|
|
10271
|
+
manifestNodes.push({
|
|
10272
|
+
id: node.id,
|
|
10273
|
+
name: node.name,
|
|
10274
|
+
layer: node.layer,
|
|
10275
|
+
ring,
|
|
10276
|
+
type: node.type,
|
|
10277
|
+
reason,
|
|
10278
|
+
acceptance: generateAcceptance(node, inspect)
|
|
10279
|
+
});
|
|
10280
|
+
}
|
|
10281
|
+
for (const cn of createNodes) {
|
|
10282
|
+
manifestNodes.push({
|
|
10283
|
+
id: cn.id,
|
|
10284
|
+
name: cn.name,
|
|
10285
|
+
layer: cn.layer,
|
|
10286
|
+
ring: "create",
|
|
10287
|
+
type: cn.type ?? "unknown",
|
|
10288
|
+
reason: cn.reason,
|
|
10289
|
+
acceptance: cn.acceptance ?? ["Verify implementation matches spec"]
|
|
10290
|
+
});
|
|
10291
|
+
}
|
|
10292
|
+
const layerIds = /* @__PURE__ */ new Set();
|
|
10293
|
+
for (const n of manifestNodes) {
|
|
10294
|
+
layerIds.add(n.layer);
|
|
10295
|
+
}
|
|
10296
|
+
const layers = [];
|
|
10297
|
+
for (const id of layerIds) {
|
|
10298
|
+
const def = defaults.layers[id];
|
|
10299
|
+
if (def) {
|
|
10300
|
+
layers.push({ id, name: def.name, icon: def.icon, color: def.color });
|
|
10301
|
+
} else {
|
|
10302
|
+
layers.push({ id, name: id, icon: "box", color: "#cbd5e1" });
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
const edgeSet = /* @__PURE__ */ new Set();
|
|
10306
|
+
const edges = [];
|
|
10307
|
+
const allNodeIds = new Set(manifestNodes.map((n) => n.id));
|
|
10308
|
+
for (const cId of centerNodeIds) {
|
|
10309
|
+
for (const result of blastResults) {
|
|
10310
|
+
for (const affected of result.affected) {
|
|
10311
|
+
if (affected.hop === 1 && result.center.id === cId && allNodeIds.has(affected.id)) {
|
|
10312
|
+
const key = `${cId}->${affected.id}`;
|
|
10313
|
+
if (!edgeSet.has(key)) {
|
|
10314
|
+
edgeSet.add(key);
|
|
10315
|
+
edges.push({ source: cId, target: affected.id });
|
|
10316
|
+
}
|
|
10317
|
+
}
|
|
10318
|
+
}
|
|
10319
|
+
}
|
|
10320
|
+
}
|
|
10321
|
+
for (const result of blastResults) {
|
|
10322
|
+
if (result.edges) {
|
|
10323
|
+
for (const edge of result.edges) {
|
|
10324
|
+
if (allNodeIds.has(edge.source) && allNodeIds.has(edge.target)) {
|
|
10325
|
+
const key = `${edge.source}->${edge.target}`;
|
|
10326
|
+
if (!edgeSet.has(key)) {
|
|
10327
|
+
edgeSet.add(key);
|
|
10328
|
+
edges.push({ source: edge.source, target: edge.target });
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
10333
|
+
}
|
|
10334
|
+
for (const cn of createNodes) {
|
|
10335
|
+
edges.push({ source: "center", target: cn.id });
|
|
10336
|
+
if (cn.connects_to) {
|
|
10337
|
+
for (const targetId of cn.connects_to) {
|
|
10338
|
+
if (allNodeIds.has(targetId) || createNodes.some((c) => c.id === targetId)) {
|
|
10339
|
+
const key = `${cn.id}->${targetId}`;
|
|
10340
|
+
if (!edgeSet.has(key)) {
|
|
10341
|
+
edgeSet.add(key);
|
|
10342
|
+
edges.push({ source: cn.id, target: targetId });
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10345
|
+
}
|
|
10346
|
+
}
|
|
10347
|
+
}
|
|
10348
|
+
return {
|
|
10349
|
+
mode,
|
|
10350
|
+
title,
|
|
10351
|
+
subtitle,
|
|
10352
|
+
layers,
|
|
10353
|
+
rings: defaults.rings,
|
|
10354
|
+
center: { name: title, description },
|
|
10355
|
+
nodes: manifestNodes,
|
|
10356
|
+
edges
|
|
10357
|
+
};
|
|
10358
|
+
}
|
|
10359
|
+
|
|
10118
10360
|
// src/server/lockfile.ts
|
|
10119
10361
|
var import_node_child_process = require("node:child_process");
|
|
10120
|
-
var
|
|
10362
|
+
var import_node_fs16 = require("node:fs");
|
|
10121
10363
|
var import_node_os = require("node:os");
|
|
10122
|
-
var
|
|
10364
|
+
var import_node_path18 = require("node:path");
|
|
10123
10365
|
function lockDir(projectRoot) {
|
|
10124
10366
|
if (projectRoot) {
|
|
10125
|
-
return (0,
|
|
10367
|
+
return (0, import_node_path18.join)(projectRoot, ".launchsecure");
|
|
10126
10368
|
}
|
|
10127
|
-
return (0,
|
|
10369
|
+
return (0, import_node_path18.join)((0, import_node_os.homedir)(), ".launchsecure");
|
|
10128
10370
|
}
|
|
10129
10371
|
function lockPath(projectRoot) {
|
|
10130
|
-
return (0,
|
|
10372
|
+
return (0, import_node_path18.join)(lockDir(projectRoot), "launch-chart.lock");
|
|
10131
10373
|
}
|
|
10132
10374
|
var _activeProjectRoot;
|
|
10133
10375
|
function readLock(projectRoot) {
|
|
10134
10376
|
const root = projectRoot ?? _activeProjectRoot;
|
|
10135
10377
|
const p = lockPath(root);
|
|
10136
|
-
if (!(0,
|
|
10378
|
+
if (!(0, import_node_fs16.existsSync)(p)) {
|
|
10137
10379
|
if (root) {
|
|
10138
10380
|
const globalP = lockPath();
|
|
10139
|
-
if ((0,
|
|
10381
|
+
if ((0, import_node_fs16.existsSync)(globalP)) {
|
|
10140
10382
|
try {
|
|
10141
|
-
const data = JSON.parse((0,
|
|
10383
|
+
const data = JSON.parse((0, import_node_fs16.readFileSync)(globalP, "utf-8"));
|
|
10142
10384
|
if (typeof data.pid === "number" && typeof data.port === "number" && data.cwd === root) {
|
|
10143
10385
|
return data;
|
|
10144
10386
|
}
|
|
@@ -10149,7 +10391,7 @@ function readLock(projectRoot) {
|
|
|
10149
10391
|
return null;
|
|
10150
10392
|
}
|
|
10151
10393
|
try {
|
|
10152
|
-
const data = JSON.parse((0,
|
|
10394
|
+
const data = JSON.parse((0, import_node_fs16.readFileSync)(p, "utf-8"));
|
|
10153
10395
|
if (typeof data.pid !== "number" || typeof data.port !== "number") return null;
|
|
10154
10396
|
return data;
|
|
10155
10397
|
} catch {
|
|
@@ -10186,7 +10428,7 @@ function getLiveLock(projectRoot) {
|
|
|
10186
10428
|
const live = listenerPid !== null ? listenerPid === lock.pid : isPidAlive(lock.pid);
|
|
10187
10429
|
if (!live) {
|
|
10188
10430
|
try {
|
|
10189
|
-
(0,
|
|
10431
|
+
(0, import_node_fs16.unlinkSync)(lockPath(root));
|
|
10190
10432
|
} catch {
|
|
10191
10433
|
}
|
|
10192
10434
|
return null;
|
|
@@ -10196,7 +10438,7 @@ function getLiveLock(projectRoot) {
|
|
|
10196
10438
|
function clearLock(projectRoot) {
|
|
10197
10439
|
const root = projectRoot ?? _activeProjectRoot;
|
|
10198
10440
|
try {
|
|
10199
|
-
(0,
|
|
10441
|
+
(0, import_node_fs16.unlinkSync)(lockPath(root));
|
|
10200
10442
|
} catch {
|
|
10201
10443
|
}
|
|
10202
10444
|
}
|
|
@@ -10205,8 +10447,8 @@ function clearLock(projectRoot) {
|
|
|
10205
10447
|
init_config();
|
|
10206
10448
|
|
|
10207
10449
|
// src/server/graph/core/language-detection.ts
|
|
10208
|
-
var
|
|
10209
|
-
var
|
|
10450
|
+
var import_node_fs17 = require("node:fs");
|
|
10451
|
+
var import_node_path19 = require("node:path");
|
|
10210
10452
|
var EXTENSION_TO_LANGUAGE = {
|
|
10211
10453
|
// Web / Frontend
|
|
10212
10454
|
".ts": "typescript",
|
|
@@ -10318,10 +10560,10 @@ var AUXILIARY_LANGUAGES = /* @__PURE__ */ new Set([
|
|
|
10318
10560
|
]);
|
|
10319
10561
|
function walkForExtensions(dir, extCounts, depth = 0) {
|
|
10320
10562
|
if (depth > 10) return;
|
|
10321
|
-
if (!(0,
|
|
10563
|
+
if (!(0, import_node_fs17.existsSync)(dir)) return;
|
|
10322
10564
|
let entries;
|
|
10323
10565
|
try {
|
|
10324
|
-
entries = (0,
|
|
10566
|
+
entries = (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true });
|
|
10325
10567
|
} catch {
|
|
10326
10568
|
return;
|
|
10327
10569
|
}
|
|
@@ -10329,9 +10571,9 @@ function walkForExtensions(dir, extCounts, depth = 0) {
|
|
|
10329
10571
|
if (entry.name.startsWith(".") && entry.isDirectory()) continue;
|
|
10330
10572
|
if (entry.isDirectory()) {
|
|
10331
10573
|
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
10332
|
-
walkForExtensions((0,
|
|
10574
|
+
walkForExtensions((0, import_node_path19.join)(dir, entry.name), extCounts, depth + 1);
|
|
10333
10575
|
} else {
|
|
10334
|
-
const ext = (0,
|
|
10576
|
+
const ext = (0, import_node_path19.extname)(entry.name).toLowerCase();
|
|
10335
10577
|
if (ext && EXTENSION_TO_LANGUAGE[ext]) {
|
|
10336
10578
|
extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
|
|
10337
10579
|
}
|
|
@@ -10372,13 +10614,13 @@ function detectLanguages(rootDir, supportedLanguages) {
|
|
|
10372
10614
|
}
|
|
10373
10615
|
|
|
10374
10616
|
// src/server/graph/core/audit-core.ts
|
|
10375
|
-
var
|
|
10376
|
-
var
|
|
10617
|
+
var import_node_fs18 = require("node:fs");
|
|
10618
|
+
var import_node_path20 = require("node:path");
|
|
10377
10619
|
function readGraphFile(rootDir, layer) {
|
|
10378
|
-
const filePath = (0,
|
|
10379
|
-
if (!(0,
|
|
10620
|
+
const filePath = (0, import_node_path20.join)(rootDir, ".launchsecure", "graphs", `${layer}.json`);
|
|
10621
|
+
if (!(0, import_node_fs18.existsSync)(filePath)) return null;
|
|
10380
10622
|
try {
|
|
10381
|
-
return JSON.parse((0,
|
|
10623
|
+
return JSON.parse((0, import_node_fs18.readFileSync)(filePath, "utf-8"));
|
|
10382
10624
|
} catch {
|
|
10383
10625
|
return null;
|
|
10384
10626
|
}
|
|
@@ -10422,10 +10664,10 @@ function checkUnprotectedRoutes(rootDir) {
|
|
|
10422
10664
|
const api = readGraphFile(rootDir, "api");
|
|
10423
10665
|
const staticGraph = readGraphFile(rootDir, "static");
|
|
10424
10666
|
if (!api) return buildReport("api", "unprotected_routes", findings);
|
|
10425
|
-
const routePermsPath = (0,
|
|
10667
|
+
const routePermsPath = (0, import_node_path20.join)(rootDir, "src", "config", "route-permissions.ts");
|
|
10426
10668
|
let routePermsContent = "";
|
|
10427
|
-
if ((0,
|
|
10428
|
-
routePermsContent = (0,
|
|
10669
|
+
if ((0, import_node_fs18.existsSync)(routePermsPath)) {
|
|
10670
|
+
routePermsContent = (0, import_node_fs18.readFileSync)(routePermsPath, "utf-8");
|
|
10429
10671
|
}
|
|
10430
10672
|
const registeredRoutes = /* @__PURE__ */ new Set();
|
|
10431
10673
|
const routeEntryRe = /path:\s*'([^']+)'/g;
|
|
@@ -10502,10 +10744,10 @@ function checkUnenforcedPermissions(rootDir) {
|
|
|
10502
10744
|
const staticGraph = readGraphFile(rootDir, "static");
|
|
10503
10745
|
if (!staticGraph) return buildReport("static", "unenforced_permissions", findings);
|
|
10504
10746
|
const permissions = staticGraph.nodes.filter((n) => n.type === "seed_permission").map((n) => ({ id: n.id, key: n.value, name: n.name }));
|
|
10505
|
-
const routePermsPath = (0,
|
|
10747
|
+
const routePermsPath = (0, import_node_path20.join)(rootDir, "src", "config", "route-permissions.ts");
|
|
10506
10748
|
let routePermsContent = "";
|
|
10507
|
-
if ((0,
|
|
10508
|
-
routePermsContent = (0,
|
|
10749
|
+
if ((0, import_node_fs18.existsSync)(routePermsPath)) {
|
|
10750
|
+
routePermsContent = (0, import_node_fs18.readFileSync)(routePermsPath, "utf-8");
|
|
10509
10751
|
}
|
|
10510
10752
|
for (const perm of permissions) {
|
|
10511
10753
|
const regex = new RegExp(`permission:\\s*['"]${perm.key}['"]`);
|
|
@@ -10535,9 +10777,9 @@ function checkHardcodedValues(rootDir) {
|
|
|
10535
10777
|
const seen = /* @__PURE__ */ new Set();
|
|
10536
10778
|
for (const node of api.nodes) {
|
|
10537
10779
|
if (node.type !== "endpoint") continue;
|
|
10538
|
-
const filePath = (0,
|
|
10539
|
-
if (!(0,
|
|
10540
|
-
const content = (0,
|
|
10780
|
+
const filePath = (0, import_node_path20.join)(rootDir, "src", node.id);
|
|
10781
|
+
if (!(0, import_node_fs18.existsSync)(filePath)) continue;
|
|
10782
|
+
const content = (0, import_node_fs18.readFileSync)(filePath, "utf-8");
|
|
10541
10783
|
let m;
|
|
10542
10784
|
allCapsRe.lastIndex = 0;
|
|
10543
10785
|
while ((m = allCapsRe.exec(content)) !== null) {
|
|
@@ -10901,6 +11143,116 @@ Use this when the user asks "is the chart running", "show me the project graph U
|
|
|
10901
11143
|
},
|
|
10902
11144
|
required: ["layer"]
|
|
10903
11145
|
}
|
|
11146
|
+
},
|
|
11147
|
+
{
|
|
11148
|
+
name: "blast_points",
|
|
11149
|
+
description: `Calculate the blast radius for a node \u2014 what depends on it across all project layers. Returns reverse dependencies aggregated with hop distance and summary stats.
|
|
11150
|
+
|
|
11151
|
+
USE THIS when assessing the impact of changing a file, table, or endpoint. Replaces multiple read_graph calls with a single query that:
|
|
11152
|
+
- Traverses REVERSE edges (who imports/depends on this node)
|
|
11153
|
+
- Searches across ALL layers if layer is omitted
|
|
11154
|
+
- Returns affected nodes with hop distance, type, layer, and module
|
|
11155
|
+
- Provides a summary with counts by layer, by hop, and risk assessment
|
|
11156
|
+
|
|
11157
|
+
Example: blast_points(node_id: "server/auth/middleware.ts", hops: 2) \u2192 returns all files that import middleware.ts, and all files that import THOSE files.`,
|
|
11158
|
+
inputSchema: {
|
|
11159
|
+
type: "object",
|
|
11160
|
+
properties: {
|
|
11161
|
+
node_id: {
|
|
11162
|
+
type: "string",
|
|
11163
|
+
description: "The node to analyze (file path, table name, etc.)"
|
|
11164
|
+
},
|
|
11165
|
+
layer: {
|
|
11166
|
+
type: "string",
|
|
11167
|
+
description: "Layer the node lives in (e.g. 'ui', 'api', 'db'). Omit to auto-detect by searching all layers."
|
|
11168
|
+
},
|
|
11169
|
+
hops: {
|
|
11170
|
+
type: "number",
|
|
11171
|
+
description: "Max hops to traverse outward. Default 2."
|
|
11172
|
+
},
|
|
11173
|
+
direction: {
|
|
11174
|
+
type: "string",
|
|
11175
|
+
enum: ["reverse", "both"],
|
|
11176
|
+
description: "'reverse' (default) = only what depends on this node. 'both' = full neighborhood."
|
|
11177
|
+
}
|
|
11178
|
+
},
|
|
11179
|
+
required: ["node_id"]
|
|
11180
|
+
}
|
|
11181
|
+
},
|
|
11182
|
+
{
|
|
11183
|
+
name: "generate_blast_radius",
|
|
11184
|
+
description: `Generate a complete BlastRadiusManifest from graph data \u2014 ready to push to deck.
|
|
11185
|
+
|
|
11186
|
+
Two modes:
|
|
11187
|
+
- **Structural**: single node changed \u2192 auto-discover what's affected via reverse BFS
|
|
11188
|
+
Example: generate_blast_radius({ mode: "structural", node_id: "CommentChannel", title: "CommentChannel refactor" })
|
|
11189
|
+
- **Feature**: new feature \u2192 multiple starting nodes + new nodes to create
|
|
11190
|
+
Example: generate_blast_radius({ mode: "feature", title: "Client Role", description: "...", center_nodes: ["CommentChannel", "ProjectMember"], create_nodes: [{ id: "ChannelMember", name: "ChannelMember table", layer: "db", reason: "..." }] })
|
|
11191
|
+
|
|
11192
|
+
Output is a BlastRadiusManifest JSON that passes directly to the deck tool's blast-radius block.
|
|
11193
|
+
Reads ring/layer/center colors from .launchsecure/blast-radius-defaults.json.
|
|
11194
|
+
Auto-generates acceptance criteria per node using inspect_node AST data.`,
|
|
11195
|
+
inputSchema: {
|
|
11196
|
+
type: "object",
|
|
11197
|
+
properties: {
|
|
11198
|
+
mode: {
|
|
11199
|
+
type: "string",
|
|
11200
|
+
enum: ["structural", "feature"],
|
|
11201
|
+
description: '"structural" = single node changed. "feature" = new feature with multiple nodes.'
|
|
11202
|
+
},
|
|
11203
|
+
title: {
|
|
11204
|
+
type: "string",
|
|
11205
|
+
description: "Title for the blast radius (shown in center node and header)."
|
|
11206
|
+
},
|
|
11207
|
+
description: {
|
|
11208
|
+
type: "string",
|
|
11209
|
+
description: "Description of the change or feature."
|
|
11210
|
+
},
|
|
11211
|
+
subtitle: {
|
|
11212
|
+
type: "string",
|
|
11213
|
+
description: "Optional subtitle shown above title in the viz."
|
|
11214
|
+
},
|
|
11215
|
+
node_id: {
|
|
11216
|
+
type: "string",
|
|
11217
|
+
description: "Structural mode only: the node being changed."
|
|
11218
|
+
},
|
|
11219
|
+
center_nodes: {
|
|
11220
|
+
type: "array",
|
|
11221
|
+
items: { type: "string" },
|
|
11222
|
+
description: "Feature mode: existing graph node IDs that are the starting points for traversal."
|
|
11223
|
+
},
|
|
11224
|
+
create_nodes: {
|
|
11225
|
+
type: "array",
|
|
11226
|
+
items: {
|
|
11227
|
+
type: "object",
|
|
11228
|
+
properties: {
|
|
11229
|
+
id: { type: "string" },
|
|
11230
|
+
name: { type: "string" },
|
|
11231
|
+
layer: { type: "string" },
|
|
11232
|
+
type: { type: "string" },
|
|
11233
|
+
reason: { type: "string" },
|
|
11234
|
+
acceptance: { type: "array", items: { type: "string" } },
|
|
11235
|
+
connects_to: { type: "array", items: { type: "string" }, description: "IDs of existing nodes this new node has FK/relationship edges to." }
|
|
11236
|
+
},
|
|
11237
|
+
required: ["id", "name", "layer", "reason"]
|
|
11238
|
+
},
|
|
11239
|
+
description: "Feature mode: new nodes that need to be created (not in graph yet)."
|
|
11240
|
+
},
|
|
11241
|
+
hops: {
|
|
11242
|
+
type: "number",
|
|
11243
|
+
description: "Max hops for traversal. Default 2. Hop 1 = modify ring, hop 2+ = ripple ring."
|
|
11244
|
+
},
|
|
11245
|
+
push_to_deck: {
|
|
11246
|
+
type: "boolean",
|
|
11247
|
+
description: "If true, pushes the manifest directly to LaunchDeck browser (requires deck server running). Default false."
|
|
11248
|
+
},
|
|
11249
|
+
session: {
|
|
11250
|
+
type: "string",
|
|
11251
|
+
description: "Session name for the deck tab. Required when push_to_deck is true."
|
|
11252
|
+
}
|
|
11253
|
+
},
|
|
11254
|
+
required: ["title"]
|
|
11255
|
+
}
|
|
10904
11256
|
}
|
|
10905
11257
|
];
|
|
10906
11258
|
function matchesSearch(node, query) {
|
|
@@ -11058,6 +11410,263 @@ function neighborhood(graph, centerId, hops, layer, minimal) {
|
|
|
11058
11410
|
const edges = graph.edges.filter((e) => visited.has(e.source) && visited.has(e.target));
|
|
11059
11411
|
return { nodes, edges, budgetExceeded, stoppedAtHop };
|
|
11060
11412
|
}
|
|
11413
|
+
function reverseNeighborhood(graph, centerId, hops, direction) {
|
|
11414
|
+
const center = graph.nodes.find((n) => n.id === centerId);
|
|
11415
|
+
if (!center) return { nodes: /* @__PURE__ */ new Map(), edges: [] };
|
|
11416
|
+
const visited = /* @__PURE__ */ new Map();
|
|
11417
|
+
visited.set(centerId, { node: center, hop: 0 });
|
|
11418
|
+
let frontier = /* @__PURE__ */ new Set([centerId]);
|
|
11419
|
+
for (let h = 0; h < hops; h++) {
|
|
11420
|
+
const next = /* @__PURE__ */ new Set();
|
|
11421
|
+
for (const edge of graph.edges) {
|
|
11422
|
+
if (direction === "reverse") {
|
|
11423
|
+
if (frontier.has(edge.target) && !visited.has(edge.source)) next.add(edge.source);
|
|
11424
|
+
} else {
|
|
11425
|
+
if (frontier.has(edge.source) && !visited.has(edge.target)) next.add(edge.target);
|
|
11426
|
+
if (frontier.has(edge.target) && !visited.has(edge.source)) next.add(edge.source);
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
for (const id of next) {
|
|
11430
|
+
const node = graph.nodes.find((n) => n.id === id);
|
|
11431
|
+
if (node) visited.set(id, { node, hop: h + 1 });
|
|
11432
|
+
}
|
|
11433
|
+
frontier = next;
|
|
11434
|
+
if (frontier.size === 0) break;
|
|
11435
|
+
}
|
|
11436
|
+
const nodeIds = new Set(visited.keys());
|
|
11437
|
+
const edges = graph.edges.filter((e) => nodeIds.has(e.source) && nodeIds.has(e.target));
|
|
11438
|
+
return { nodes: visited, edges };
|
|
11439
|
+
}
|
|
11440
|
+
function handleBlastPoints(args) {
|
|
11441
|
+
const rootDir = process.cwd();
|
|
11442
|
+
const nodeId = args.node_id;
|
|
11443
|
+
const requestedLayer = args.layer;
|
|
11444
|
+
const hops = args.hops ?? 2;
|
|
11445
|
+
const direction = args.direction ?? "reverse";
|
|
11446
|
+
let targetLayer = requestedLayer;
|
|
11447
|
+
if (!targetLayer) {
|
|
11448
|
+
const graphs = readAllGraphs(rootDir);
|
|
11449
|
+
for (const [layer, graph2] of Object.entries(graphs)) {
|
|
11450
|
+
if (graph2 && graph2.nodes.some((n) => n.id === nodeId)) {
|
|
11451
|
+
targetLayer = layer;
|
|
11452
|
+
break;
|
|
11453
|
+
}
|
|
11454
|
+
}
|
|
11455
|
+
if (!targetLayer) {
|
|
11456
|
+
return err(`Node "${nodeId}" not found in any layer. Available layers: ${getAvailableLayers(rootDir).join(", ")}`);
|
|
11457
|
+
}
|
|
11458
|
+
}
|
|
11459
|
+
const graph = readGraph(rootDir, targetLayer);
|
|
11460
|
+
if (!graph) {
|
|
11461
|
+
return err(`No graph for layer "${targetLayer}". Run generate_graph first.`);
|
|
11462
|
+
}
|
|
11463
|
+
const center = graph.nodes.find((n) => n.id === nodeId);
|
|
11464
|
+
if (!center) {
|
|
11465
|
+
return err(`Node "${nodeId}" not found in ${targetLayer} layer.`);
|
|
11466
|
+
}
|
|
11467
|
+
const result = reverseNeighborhood(graph, nodeId, hops, direction);
|
|
11468
|
+
const affected = [];
|
|
11469
|
+
for (const [id, { node, hop }] of result.nodes) {
|
|
11470
|
+
if (id === nodeId) continue;
|
|
11471
|
+
const tags = node.tags;
|
|
11472
|
+
affected.push({
|
|
11473
|
+
id: node.id,
|
|
11474
|
+
name: node.name,
|
|
11475
|
+
type: node.type,
|
|
11476
|
+
layer: targetLayer,
|
|
11477
|
+
hop,
|
|
11478
|
+
module: tags?.module
|
|
11479
|
+
});
|
|
11480
|
+
}
|
|
11481
|
+
const otherLayers = getAvailableLayers(rootDir).filter((l) => l !== targetLayer && l !== "static");
|
|
11482
|
+
for (const otherLayer of otherLayers) {
|
|
11483
|
+
const otherGraph = readGraph(rootDir, otherLayer);
|
|
11484
|
+
if (!otherGraph) continue;
|
|
11485
|
+
for (const edge of otherGraph.edges) {
|
|
11486
|
+
if (edge.target === nodeId || edge.source === nodeId) {
|
|
11487
|
+
const dependentId = edge.target === nodeId ? edge.source : edge.target;
|
|
11488
|
+
if (affected.some((a) => a.id === dependentId)) continue;
|
|
11489
|
+
const depNode = otherGraph.nodes.find((n) => n.id === dependentId);
|
|
11490
|
+
if (depNode) {
|
|
11491
|
+
const tags = depNode.tags;
|
|
11492
|
+
affected.push({
|
|
11493
|
+
id: depNode.id,
|
|
11494
|
+
name: depNode.name,
|
|
11495
|
+
type: depNode.type,
|
|
11496
|
+
layer: otherLayer,
|
|
11497
|
+
hop: 1,
|
|
11498
|
+
module: tags?.module
|
|
11499
|
+
});
|
|
11500
|
+
}
|
|
11501
|
+
}
|
|
11502
|
+
}
|
|
11503
|
+
}
|
|
11504
|
+
const byLayer = {};
|
|
11505
|
+
const byHop = {};
|
|
11506
|
+
const modulesSet = /* @__PURE__ */ new Set();
|
|
11507
|
+
for (const a of affected) {
|
|
11508
|
+
byLayer[a.layer] = (byLayer[a.layer] ?? 0) + 1;
|
|
11509
|
+
byHop[String(a.hop)] = (byHop[String(a.hop)] ?? 0) + 1;
|
|
11510
|
+
if (a.module) modulesSet.add(a.module);
|
|
11511
|
+
}
|
|
11512
|
+
const crossesLayers = Object.keys(byLayer).length > 1;
|
|
11513
|
+
const centerTags = center.tags;
|
|
11514
|
+
return okJson({
|
|
11515
|
+
center: {
|
|
11516
|
+
id: center.id,
|
|
11517
|
+
name: center.name,
|
|
11518
|
+
type: center.type,
|
|
11519
|
+
layer: targetLayer,
|
|
11520
|
+
module: centerTags?.module
|
|
11521
|
+
},
|
|
11522
|
+
affected,
|
|
11523
|
+
summary: {
|
|
11524
|
+
total: affected.length,
|
|
11525
|
+
by_layer: byLayer,
|
|
11526
|
+
by_hop: byHop,
|
|
11527
|
+
modules_touched: Array.from(modulesSet).sort(),
|
|
11528
|
+
crosses_layers: crossesLayers
|
|
11529
|
+
}
|
|
11530
|
+
});
|
|
11531
|
+
}
|
|
11532
|
+
function handleGenerateBlastRadius(args) {
|
|
11533
|
+
const rootDir = process.cwd();
|
|
11534
|
+
const mode = args.mode ?? "structural";
|
|
11535
|
+
const title = args.title;
|
|
11536
|
+
const description = args.description ?? title;
|
|
11537
|
+
const subtitle = args.subtitle;
|
|
11538
|
+
const hops = args.hops ?? 2;
|
|
11539
|
+
const defaults = loadDefaults(rootDir);
|
|
11540
|
+
let centerNodeIds = [];
|
|
11541
|
+
if (mode === "structural") {
|
|
11542
|
+
const nodeId = args.node_id;
|
|
11543
|
+
if (!nodeId) return err("structural mode requires node_id");
|
|
11544
|
+
centerNodeIds = [nodeId];
|
|
11545
|
+
} else {
|
|
11546
|
+
centerNodeIds = args.center_nodes ?? [];
|
|
11547
|
+
if (centerNodeIds.length === 0) return err("feature mode requires center_nodes[]");
|
|
11548
|
+
}
|
|
11549
|
+
const createNodes = args.create_nodes ?? [];
|
|
11550
|
+
const blastResults = [];
|
|
11551
|
+
for (const nodeId of centerNodeIds) {
|
|
11552
|
+
let targetLayer;
|
|
11553
|
+
const graphs = readAllGraphs(rootDir);
|
|
11554
|
+
for (const [layer, graph2] of Object.entries(graphs)) {
|
|
11555
|
+
if (graph2 && graph2.nodes.some((n) => n.id === nodeId)) {
|
|
11556
|
+
targetLayer = layer;
|
|
11557
|
+
break;
|
|
11558
|
+
}
|
|
11559
|
+
}
|
|
11560
|
+
if (!targetLayer) continue;
|
|
11561
|
+
const graph = readGraph(rootDir, targetLayer);
|
|
11562
|
+
if (!graph) continue;
|
|
11563
|
+
const center = graph.nodes.find((n) => n.id === nodeId);
|
|
11564
|
+
if (!center) continue;
|
|
11565
|
+
const result2 = reverseNeighborhood(graph, nodeId, hops, "reverse");
|
|
11566
|
+
const affected = [];
|
|
11567
|
+
for (const [id, { node, hop }] of result2.nodes) {
|
|
11568
|
+
if (id === nodeId) continue;
|
|
11569
|
+
const tags = node.tags;
|
|
11570
|
+
affected.push({ id: node.id, name: node.name, type: node.type, layer: targetLayer, hop, module: tags?.module });
|
|
11571
|
+
}
|
|
11572
|
+
const otherLayers = getAvailableLayers(rootDir).filter((l) => l !== targetLayer && l !== "static");
|
|
11573
|
+
for (const otherLayer of otherLayers) {
|
|
11574
|
+
const otherGraph = readGraph(rootDir, otherLayer);
|
|
11575
|
+
if (!otherGraph) continue;
|
|
11576
|
+
for (const edge of otherGraph.edges) {
|
|
11577
|
+
if (edge.target === nodeId || edge.source === nodeId) {
|
|
11578
|
+
const dependentId = edge.target === nodeId ? edge.source : edge.target;
|
|
11579
|
+
if (affected.some((a) => a.id === dependentId)) continue;
|
|
11580
|
+
const depNode = otherGraph.nodes.find((n) => n.id === dependentId);
|
|
11581
|
+
if (depNode) {
|
|
11582
|
+
const tags = depNode.tags;
|
|
11583
|
+
affected.push({ id: depNode.id, name: depNode.name, type: depNode.type, layer: otherLayer, hop: 1, module: tags?.module });
|
|
11584
|
+
}
|
|
11585
|
+
}
|
|
11586
|
+
}
|
|
11587
|
+
}
|
|
11588
|
+
const centerTags = center.tags;
|
|
11589
|
+
const edges = result2.edges.map((e) => ({ source: e.source, target: e.target }));
|
|
11590
|
+
blastResults.push({
|
|
11591
|
+
center: { id: center.id, name: center.name, type: center.type, layer: targetLayer, module: centerTags?.module },
|
|
11592
|
+
affected,
|
|
11593
|
+
edges
|
|
11594
|
+
});
|
|
11595
|
+
}
|
|
11596
|
+
if (blastResults.length === 0) {
|
|
11597
|
+
return err(`None of the center nodes were found in any graph layer: ${centerNodeIds.join(", ")}`);
|
|
11598
|
+
}
|
|
11599
|
+
const inspectData = {};
|
|
11600
|
+
const allAffectedIds = /* @__PURE__ */ new Set();
|
|
11601
|
+
for (const r of blastResults) {
|
|
11602
|
+
allAffectedIds.add(r.center.id);
|
|
11603
|
+
for (const a of r.affected) allAffectedIds.add(a.id);
|
|
11604
|
+
}
|
|
11605
|
+
const allGraphs = readAllGraphs(rootDir);
|
|
11606
|
+
for (const id of allAffectedIds) {
|
|
11607
|
+
for (const [, graph] of Object.entries(allGraphs)) {
|
|
11608
|
+
if (!graph) continue;
|
|
11609
|
+
const node = graph.nodes.find((n) => n.id === id);
|
|
11610
|
+
if (node) {
|
|
11611
|
+
inspectData[id] = {
|
|
11612
|
+
type: node.type,
|
|
11613
|
+
name: node.name,
|
|
11614
|
+
methods: node.methods,
|
|
11615
|
+
path: node.path ?? node.handler,
|
|
11616
|
+
auth: node.auth,
|
|
11617
|
+
db_models: node.db_models
|
|
11618
|
+
};
|
|
11619
|
+
break;
|
|
11620
|
+
}
|
|
11621
|
+
}
|
|
11622
|
+
}
|
|
11623
|
+
const manifest = buildManifest({
|
|
11624
|
+
mode,
|
|
11625
|
+
title,
|
|
11626
|
+
description,
|
|
11627
|
+
subtitle,
|
|
11628
|
+
blastResults,
|
|
11629
|
+
createNodes,
|
|
11630
|
+
inspectData,
|
|
11631
|
+
defaults
|
|
11632
|
+
});
|
|
11633
|
+
const pushToDeck = args.push_to_deck;
|
|
11634
|
+
const session = args.session;
|
|
11635
|
+
let deckResult;
|
|
11636
|
+
if (pushToDeck) {
|
|
11637
|
+
if (!session) return err("push_to_deck requires a session name");
|
|
11638
|
+
const deckLockPath = (0, import_node_path21.join)(rootDir, ".launchsecure", "launch-deck.lock");
|
|
11639
|
+
if (!(0, import_node_fs19.existsSync)(deckLockPath)) {
|
|
11640
|
+
deckResult = { pushed: false, reason: "Deck server not running (no lock file). Push manually via deck tool." };
|
|
11641
|
+
} else {
|
|
11642
|
+
try {
|
|
11643
|
+
const lock = JSON.parse((0, import_node_fs19.readFileSync)(deckLockPath, "utf-8"));
|
|
11644
|
+
const deckUrl = lock.url;
|
|
11645
|
+
const body = JSON.stringify({
|
|
11646
|
+
session,
|
|
11647
|
+
mode: "show",
|
|
11648
|
+
blocks: [{ type: "blast-radius", label: title, manifest }]
|
|
11649
|
+
});
|
|
11650
|
+
(0, import_node_child_process2.execFileSync)("curl", [
|
|
11651
|
+
"-s",
|
|
11652
|
+
"-X",
|
|
11653
|
+
"POST",
|
|
11654
|
+
deckUrl + "/api/deck",
|
|
11655
|
+
"-H",
|
|
11656
|
+
"Content-Type: application/json",
|
|
11657
|
+
"-d",
|
|
11658
|
+
body
|
|
11659
|
+
], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] });
|
|
11660
|
+
deckResult = { pushed: true, session, url: deckUrl };
|
|
11661
|
+
} catch (e) {
|
|
11662
|
+
deckResult = { pushed: false, reason: `Failed to push to deck: ${e}` };
|
|
11663
|
+
}
|
|
11664
|
+
}
|
|
11665
|
+
}
|
|
11666
|
+
const result = { ...manifest };
|
|
11667
|
+
if (deckResult) result._deck = deckResult;
|
|
11668
|
+
return okJson(result);
|
|
11669
|
+
}
|
|
11061
11670
|
function layerSummary(graph) {
|
|
11062
11671
|
const typeCounts = {};
|
|
11063
11672
|
const moduleCounts = {};
|
|
@@ -11286,12 +11895,12 @@ function handleReadGraph(args) {
|
|
|
11286
11895
|
return okJson(result);
|
|
11287
11896
|
}
|
|
11288
11897
|
function nodeToFilePath(rootDir, layer, nodeId) {
|
|
11289
|
-
if (layer === "ui" || layer === "api") return (0,
|
|
11290
|
-
if (layer === "db") return (0,
|
|
11291
|
-
const withSrc = (0,
|
|
11292
|
-
if ((0,
|
|
11293
|
-
const direct = (0,
|
|
11294
|
-
if ((0,
|
|
11898
|
+
if (layer === "ui" || layer === "api") return (0, import_node_path21.join)(rootDir, "src", nodeId);
|
|
11899
|
+
if (layer === "db") return (0, import_node_path21.join)(rootDir, "prisma", "schema.prisma");
|
|
11900
|
+
const withSrc = (0, import_node_path21.join)(rootDir, "src", nodeId);
|
|
11901
|
+
if ((0, import_node_fs19.existsSync)(withSrc)) return withSrc;
|
|
11902
|
+
const direct = (0, import_node_path21.join)(rootDir, nodeId);
|
|
11903
|
+
if ((0, import_node_fs19.existsSync)(direct)) return direct;
|
|
11295
11904
|
return null;
|
|
11296
11905
|
}
|
|
11297
11906
|
function handleInspectNode(args) {
|
|
@@ -11434,11 +12043,11 @@ function handleGrepNodes(args) {
|
|
|
11434
12043
|
let filesSearched = 0;
|
|
11435
12044
|
let truncated = false;
|
|
11436
12045
|
for (const [filePath, nodeId] of filePaths) {
|
|
11437
|
-
if (!(0,
|
|
12046
|
+
if (!(0, import_node_fs19.existsSync)(filePath)) continue;
|
|
11438
12047
|
filesSearched++;
|
|
11439
12048
|
let content;
|
|
11440
12049
|
try {
|
|
11441
|
-
content = (0,
|
|
12050
|
+
content = (0, import_node_fs19.readFileSync)(filePath, "utf-8");
|
|
11442
12051
|
} catch {
|
|
11443
12052
|
continue;
|
|
11444
12053
|
}
|
|
@@ -11503,11 +12112,11 @@ function handleStartChartServer(args) {
|
|
|
11503
12112
|
});
|
|
11504
12113
|
}
|
|
11505
12114
|
const entryPath = process.argv[1];
|
|
11506
|
-
const logDir = (0,
|
|
11507
|
-
(0,
|
|
11508
|
-
const logPath = (0,
|
|
11509
|
-
const out = (0,
|
|
11510
|
-
const err2 = (0,
|
|
12115
|
+
const logDir = (0, import_node_path21.join)((0, import_node_os2.homedir)(), ".launchsecure");
|
|
12116
|
+
(0, import_node_fs19.mkdirSync)(logDir, { recursive: true });
|
|
12117
|
+
const logPath = (0, import_node_path21.join)(logDir, "launch-chart.log");
|
|
12118
|
+
const out = (0, import_node_fs19.openSync)(logPath, "a");
|
|
12119
|
+
const err2 = (0, import_node_fs19.openSync)(logPath, "a");
|
|
11511
12120
|
const portArgs = args.port ? ["--port", String(args.port)] : [];
|
|
11512
12121
|
const child = (0, import_node_child_process2.spawn)(process.execPath, [entryPath, "serve", ...portArgs], {
|
|
11513
12122
|
detached: true,
|
|
@@ -11619,31 +12228,28 @@ function handleDetectProjectStack() {
|
|
|
11619
12228
|
for (const l of p.layers) availableLayers.add(l);
|
|
11620
12229
|
}
|
|
11621
12230
|
}
|
|
11622
|
-
|
|
12231
|
+
const stats = { calls_api: 0, references_api: 0, annotations: 0 };
|
|
11623
12232
|
const uiGraph = readGraph(rootDir, "ui");
|
|
11624
12233
|
if (uiGraph) {
|
|
11625
12234
|
for (const ref of uiGraph.cross_refs ?? []) {
|
|
11626
12235
|
if (ref.type === "calls_api") stats.calls_api++;
|
|
11627
12236
|
if (ref.type === "references_api") stats.references_api++;
|
|
11628
12237
|
}
|
|
11629
|
-
for (const f of uiGraph.flagged_edges ?? []) {
|
|
11630
|
-
if (f.type === "out_of_pattern") stats.out_of_pattern++;
|
|
11631
|
-
}
|
|
11632
12238
|
}
|
|
11633
|
-
const srcDir = (0,
|
|
11634
|
-
if ((0,
|
|
12239
|
+
const srcDir = (0, import_node_path21.join)(rootDir, "src");
|
|
12240
|
+
if ((0, import_node_fs19.existsSync)(srcDir)) {
|
|
11635
12241
|
const scanDir = (dir) => {
|
|
11636
|
-
if (!(0,
|
|
11637
|
-
for (const entry of (0,
|
|
12242
|
+
if (!(0, import_node_fs19.existsSync)(dir)) return;
|
|
12243
|
+
for (const entry of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
|
|
11638
12244
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
11639
|
-
const full = (0,
|
|
12245
|
+
const full = (0, import_node_path21.join)(dir, entry.name);
|
|
11640
12246
|
if (entry.isDirectory()) {
|
|
11641
12247
|
scanDir(full);
|
|
11642
12248
|
continue;
|
|
11643
12249
|
}
|
|
11644
|
-
if (![".ts", ".tsx"].includes((0,
|
|
12250
|
+
if (![".ts", ".tsx"].includes((0, import_node_path21.extname)(entry.name))) continue;
|
|
11645
12251
|
try {
|
|
11646
|
-
const content = (0,
|
|
12252
|
+
const content = (0, import_node_fs19.readFileSync)(full, "utf-8");
|
|
11647
12253
|
const matches = content.match(/@api\s+(GET|POST|PUT|DELETE|PATCH)\s+\/\S+/g);
|
|
11648
12254
|
if (matches) stats.annotations += matches.length;
|
|
11649
12255
|
} catch {
|
|
@@ -11652,12 +12258,6 @@ function handleDetectProjectStack() {
|
|
|
11652
12258
|
};
|
|
11653
12259
|
scanDir(srcDir);
|
|
11654
12260
|
}
|
|
11655
|
-
let recommendedPrimary = "fetch-resolver";
|
|
11656
|
-
if (stats.annotations > 0 && stats.annotations >= stats.calls_api) {
|
|
11657
|
-
recommendedPrimary = "api-annotations";
|
|
11658
|
-
} else if (stats.calls_api === 0 && stats.references_api > 0) {
|
|
11659
|
-
recommendedPrimary = "url-literal-scanner";
|
|
11660
|
-
}
|
|
11661
12261
|
const supportedLanguages = /* @__PURE__ */ new Map();
|
|
11662
12262
|
supportedLanguages.set("typescript", parserResults.filter((p) => p.detected && p.layers.some((l) => l === "ui" || l === "api")).map((p) => p.id));
|
|
11663
12263
|
supportedLanguages.set("prisma", parserResults.filter((p) => p.detected && p.layers.includes("db")).map((p) => p.id));
|
|
@@ -11668,13 +12268,22 @@ function handleDetectProjectStack() {
|
|
|
11668
12268
|
languages,
|
|
11669
12269
|
parsers: parserResults,
|
|
11670
12270
|
available_layers: [...availableLayers],
|
|
11671
|
-
crosslayer_parsers:
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
12271
|
+
crosslayer_parsers: (() => {
|
|
12272
|
+
const descriptions = {
|
|
12273
|
+
"fetch-resolver": "Detects direct fetch()/api.get() calls with inline URLs",
|
|
12274
|
+
"api-annotations": "Scans for @api METHOD /path annotations in JSDoc/comments",
|
|
12275
|
+
"url-literal-scanner": "Finds /api/... string literals as fallback detection",
|
|
12276
|
+
"static-ref-scanner": "Finds references to static values (enums, permissions, roles)"
|
|
12277
|
+
};
|
|
12278
|
+
const grouped = {};
|
|
12279
|
+
for (const p of registry.getCrossLayerParsers()) {
|
|
12280
|
+
const concern = p.concern ?? "api-binding";
|
|
12281
|
+
if (!grouped[concern]) grouped[concern] = [];
|
|
12282
|
+
grouped[concern].push({ id: p.id, description: descriptions[p.id] ?? p.id });
|
|
12283
|
+
}
|
|
12284
|
+
return grouped;
|
|
12285
|
+
})(),
|
|
11676
12286
|
stats,
|
|
11677
|
-
recommended_primary: recommendedPrimary,
|
|
11678
12287
|
...unsupportedHint ? { unsupported_hint: unsupportedHint } : {},
|
|
11679
12288
|
current_config: Object.keys(config).length > 0 ? config : null,
|
|
11680
12289
|
config_path: ".launchchart.json"
|
|
@@ -11755,6 +12364,14 @@ async function handleMessage(msg) {
|
|
|
11755
12364
|
respond(id ?? null, handleAuditLayer(args));
|
|
11756
12365
|
return;
|
|
11757
12366
|
}
|
|
12367
|
+
if (toolName === "blast_points") {
|
|
12368
|
+
respond(id ?? null, handleBlastPoints(args));
|
|
12369
|
+
return;
|
|
12370
|
+
}
|
|
12371
|
+
if (toolName === "generate_blast_radius") {
|
|
12372
|
+
respond(id ?? null, handleGenerateBlastRadius(args));
|
|
12373
|
+
return;
|
|
12374
|
+
}
|
|
11758
12375
|
respondError(id ?? null, -32601, `Unknown tool: ${toolName}`);
|
|
11759
12376
|
return;
|
|
11760
12377
|
}
|
|
@@ -11806,7 +12423,8 @@ if (import_fs8.default.existsSync(envPath)) {
|
|
|
11806
12423
|
}
|
|
11807
12424
|
}
|
|
11808
12425
|
var LAUNCHSECURE_URL = process.env.LAUNCHSECURE_URL || "http://localhost:4177";
|
|
11809
|
-
var CONFIG_FILE = "launchpod.
|
|
12426
|
+
var CONFIG_FILE = ".launchpod.json";
|
|
12427
|
+
var CONFIG_FILE_LEGACY = "launchpod.config.json";
|
|
11810
12428
|
var REPO_ROOT = process.cwd();
|
|
11811
12429
|
var PROJECT_DIR = REPO_ROOT;
|
|
11812
12430
|
var CREDENTIALS_DIR = import_path8.default.join(PROJECT_DIR, ".launchpod");
|
|
@@ -11842,8 +12460,10 @@ function parseArgs() {
|
|
|
11842
12460
|
port = parseInt(process.env.PORT, 10);
|
|
11843
12461
|
} else {
|
|
11844
12462
|
const configPath = import_path8.default.join(REPO_ROOT, CONFIG_FILE);
|
|
11845
|
-
|
|
11846
|
-
|
|
12463
|
+
const legacyPath = import_path8.default.join(REPO_ROOT, CONFIG_FILE_LEGACY);
|
|
12464
|
+
const resolvedPath = import_fs8.default.existsSync(configPath) ? configPath : import_fs8.default.existsSync(legacyPath) ? legacyPath : null;
|
|
12465
|
+
if (resolvedPath) {
|
|
12466
|
+
const config = JSON.parse(import_fs8.default.readFileSync(resolvedPath, "utf-8"));
|
|
11847
12467
|
if (config.port) port = config.port;
|
|
11848
12468
|
}
|
|
11849
12469
|
}
|
|
@@ -12072,21 +12692,21 @@ function ensureGitRepo(runId) {
|
|
|
12072
12692
|
console.log("Git repo initialized");
|
|
12073
12693
|
}
|
|
12074
12694
|
const branch = `pipeline/${runId}`;
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
}
|
|
12081
|
-
} catch {
|
|
12695
|
+
const worktreeDir = import_path8.default.join(REPO_ROOT, ".launchpod", "worktrees", runId);
|
|
12696
|
+
if (import_fs8.default.existsSync(worktreeDir)) {
|
|
12697
|
+
PROJECT_DIR = worktreeDir;
|
|
12698
|
+
console.log(`Resuming in existing worktree ${worktreeDir}`);
|
|
12699
|
+
return;
|
|
12082
12700
|
}
|
|
12701
|
+
import_fs8.default.mkdirSync(import_path8.default.join(REPO_ROOT, ".launchpod", "worktrees"), { recursive: true });
|
|
12083
12702
|
try {
|
|
12084
|
-
(0, import_child_process4.execSync)(`git
|
|
12085
|
-
console.log(`
|
|
12703
|
+
(0, import_child_process4.execSync)(`git worktree add "${worktreeDir}" "${branch}"`, { cwd: REPO_ROOT, stdio: "pipe" });
|
|
12704
|
+
console.log(`Created worktree from existing branch ${branch}`);
|
|
12086
12705
|
} catch {
|
|
12087
|
-
(0, import_child_process4.execSync)(`git
|
|
12088
|
-
console.log(`Created branch ${branch}`);
|
|
12706
|
+
(0, import_child_process4.execSync)(`git worktree add -b "${branch}" "${worktreeDir}"`, { cwd: REPO_ROOT, stdio: "pipe" });
|
|
12707
|
+
console.log(`Created worktree with new branch ${branch}`);
|
|
12089
12708
|
}
|
|
12709
|
+
PROJECT_DIR = worktreeDir;
|
|
12090
12710
|
}
|
|
12091
12711
|
function normalizeGitUrl(url) {
|
|
12092
12712
|
return url.replace(/\.git$/, "").replace(/^git@github\.com:/, "https://github.com/").replace(/\/$/, "").toLowerCase();
|
|
@@ -12189,7 +12809,8 @@ function parseTrackerData(result) {
|
|
|
12189
12809
|
const runStatus = typeof run?.status === "string" ? run.status : void 0;
|
|
12190
12810
|
return { agents, interventions, runStatus };
|
|
12191
12811
|
}
|
|
12192
|
-
async function startPipelineSystem(creds) {
|
|
12812
|
+
async function startPipelineSystem(creds, capabilities) {
|
|
12813
|
+
const activeCapabilities = capabilities ?? { ...DEFAULT_CAPABILITIES };
|
|
12193
12814
|
try {
|
|
12194
12815
|
writeMcpConfigs({
|
|
12195
12816
|
projectDir: PROJECT_DIR,
|
|
@@ -12455,7 +13076,7 @@ if (!__isMcpMode) {
|
|
|
12455
13076
|
});
|
|
12456
13077
|
authenticated = true;
|
|
12457
13078
|
const savedCreds = loadCredentials();
|
|
12458
|
-
if (savedCreds) startPipelineSystem(savedCreds);
|
|
13079
|
+
if (savedCreds) startPipelineSystem(savedCreds, activeCapabilities);
|
|
12459
13080
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
12460
13081
|
res.end(JSON.stringify({ success: true }));
|
|
12461
13082
|
} catch {
|
|
@@ -12673,7 +13294,7 @@ if (!__isMcpMode) {
|
|
|
12673
13294
|
}
|
|
12674
13295
|
serveIndex(res);
|
|
12675
13296
|
});
|
|
12676
|
-
let
|
|
13297
|
+
let activeCapabilities = { ...DEFAULT_CAPABILITIES };
|
|
12677
13298
|
async function main() {
|
|
12678
13299
|
const { token, serverUrl } = parsedArgs;
|
|
12679
13300
|
if (token) {
|
|
@@ -12690,8 +13311,8 @@ if (!__isMcpMode) {
|
|
|
12690
13311
|
process.exit(1);
|
|
12691
13312
|
}
|
|
12692
13313
|
activeRunId = runId;
|
|
12693
|
-
|
|
12694
|
-
console.log(`Capabilities: workspace_setup=${
|
|
13314
|
+
activeCapabilities = await fetchCapabilities(serverUrl, token);
|
|
13315
|
+
console.log(`Capabilities: workspace_setup=${activeCapabilities.workspace_setup}, terminal=${activeCapabilities.terminal}`);
|
|
12695
13316
|
ensureGitRepo(runId);
|
|
12696
13317
|
await validateRepoUrl(serverUrl, token);
|
|
12697
13318
|
const lock = checkLockfile();
|
|
@@ -12719,13 +13340,13 @@ if (!__isMcpMode) {
|
|
|
12719
13340
|
const existingCreds = loadCredentials();
|
|
12720
13341
|
if (existingCreds) {
|
|
12721
13342
|
if (!token) {
|
|
12722
|
-
|
|
13343
|
+
activeCapabilities = await fetchCapabilities(existingCreds.serverUrl, existingCreds.token);
|
|
12723
13344
|
}
|
|
12724
|
-
if (
|
|
13345
|
+
if (activeCapabilities.terminal) {
|
|
12725
13346
|
initTerminalBridge(server, PROJECT_DIR, import_path8.default.join(PROJECT_DIR, ".launchpod", "sessions"));
|
|
12726
13347
|
}
|
|
12727
|
-
startPipelineSystem(existingCreds);
|
|
12728
|
-
} else if (
|
|
13348
|
+
startPipelineSystem(existingCreds, activeCapabilities);
|
|
13349
|
+
} else if (activeCapabilities.terminal) {
|
|
12729
13350
|
initTerminalBridge(server, PROJECT_DIR, import_path8.default.join(PROJECT_DIR, ".launchpod", "sessions"));
|
|
12730
13351
|
}
|
|
12731
13352
|
}
|