@launchsecure/launch-kit 0.0.39 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/chart-client/assets/index-CWJFFDPu.css +1 -0
  2. package/dist/chart-client/index.html +2 -2
  3. package/dist/client/assets/index-CTzFcfGV.css +32 -0
  4. package/dist/client/index.html +2 -2
  5. package/dist/council-client/assets/index-ArgRc5mN.css +1 -0
  6. package/dist/council-client/index.html +2 -2
  7. package/dist/deck-client/assets/{_baseUniq-DOrnEQMI.js → _baseUniq-BZP7n41F.js} +1 -1
  8. package/dist/deck-client/assets/{arc-DOWK7V3m.js → arc-31biU3Az.js} +1 -1
  9. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-DPhzvk7q.js → architectureDiagram-Q4EWVU46-DHg6Ss--.js} +1 -1
  10. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-CwAGy9lU.js → blockDiagram-DXYQGD6D-CUdblaWk.js} +1 -1
  11. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-L_g_SS21.js → c4Diagram-AHTNJAMY-MfAO5lak.js} +1 -1
  12. package/dist/deck-client/assets/channel-BBkRLdnC.js +1 -0
  13. package/dist/deck-client/assets/{chunk-4BX2VUAB-RKm0LXpu.js → chunk-4BX2VUAB-DQ1MrGgN.js} +1 -1
  14. package/dist/deck-client/assets/{chunk-4TB4RGXK-Bk0FUbxU.js → chunk-4TB4RGXK-BUJtZ7jO.js} +1 -1
  15. package/dist/deck-client/assets/{chunk-55IACEB6-Cl3hja-M.js → chunk-55IACEB6-BdSnXB6g.js} +1 -1
  16. package/dist/deck-client/assets/{chunk-EDXVE4YY-CNIMQCV2.js → chunk-EDXVE4YY-94yZIUI8.js} +1 -1
  17. package/dist/deck-client/assets/{chunk-FMBD7UC4-DqOvWr1k.js → chunk-FMBD7UC4-PnZ9v6ey.js} +1 -1
  18. package/dist/deck-client/assets/{chunk-OYMX7WX6-1Kd7yK5u.js → chunk-OYMX7WX6-DXrWNOsV.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-QZHKN3VN-6_kraYpP.js → chunk-QZHKN3VN-CsIGIDKX.js} +1 -1
  20. package/dist/deck-client/assets/{chunk-YZCP3GAM-FgAwIWlo.js → chunk-YZCP3GAM-DVkBO9tn.js} +1 -1
  21. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-DFCaeF-7.js +1 -0
  22. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-DFCaeF-7.js +1 -0
  23. package/dist/deck-client/assets/clone-GCEVRScB.js +1 -0
  24. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-CigVnnPr.js → cose-bilkent-S5V4N54A-m126Oh3b.js} +1 -1
  25. package/dist/deck-client/assets/{dagre-KV5264BT-DHZXTktX.js → dagre-KV5264BT-C2aig8U5.js} +1 -1
  26. package/dist/deck-client/assets/{diagram-5BDNPKRD-H5k0eauU.js → diagram-5BDNPKRD-CKpoRfGn.js} +1 -1
  27. package/dist/deck-client/assets/{diagram-G4DWMVQ6-Bg3dFhSY.js → diagram-G4DWMVQ6-Cjh115Ep.js} +1 -1
  28. package/dist/deck-client/assets/{diagram-MMDJMWI5-CQLC410N.js → diagram-MMDJMWI5-DKlBv_2L.js} +1 -1
  29. package/dist/deck-client/assets/{diagram-TYMM5635-DFTCHVkP.js → diagram-TYMM5635-CdBh4cEn.js} +1 -1
  30. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-aiv9GZnL.js → erDiagram-SMLLAGMA-56pn_93p.js} +1 -1
  31. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-C6Fhvtsy.js → flowDiagram-DWJPFMVM-BtV3M5xJ.js} +1 -1
  32. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-DSaGMPM4.js → ganttDiagram-T4ZO3ILL-DTIsC6Zg.js} +1 -1
  33. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-DMjL2Vix.js → gitGraphDiagram-UUTBAWPF-CJYeyCLe.js} +1 -1
  34. package/dist/deck-client/assets/{graph-B7Vn5lkK.js → graph-BDvMu1Ss.js} +1 -1
  35. package/dist/deck-client/assets/index-D4eSxcBn.css +1 -0
  36. package/dist/deck-client/assets/{index-BD36e-tD.js → index-QnGVE9PZ.js} +72 -72
  37. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-mNi4iygG.js → infoDiagram-42DDH7IO-BWyKJnpW.js} +1 -1
  38. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-BwCUmUVt.js → ishikawaDiagram-UXIWVN3A-DXYkdO3T.js} +1 -1
  39. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-C6qoqJmJ.js → journeyDiagram-VCZTEJTY-C2zBr-J5.js} +1 -1
  40. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-Dz1Tt3sA.js → kanban-definition-6JOO6SKY-CdoYLS4Z.js} +1 -1
  41. package/dist/deck-client/assets/{layout-CZTyRhOG.js → layout-vOnxnCQU.js} +1 -1
  42. package/dist/deck-client/assets/{linear--7n7iEvd.js → linear-B0J0WCGz.js} +1 -1
  43. package/dist/deck-client/assets/{min-Bh130DN8.js → min-B0AXlT9L.js} +1 -1
  44. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-CfXcK1qH.js → mindmap-definition-QFDTVHPH-oAybLedr.js} +1 -1
  45. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DjVHLAVw.js → pieDiagram-DEJITSTG-BjHyHxGk.js} +1 -1
  46. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-CXwvZ1i1.js → quadrantDiagram-34T5L4WZ-dtluDZXs.js} +1 -1
  47. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-Cl6xm0fR.js → requirementDiagram-MS252O5E-Cq8l7bOl.js} +1 -1
  48. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BOH9sLyh.js → sankeyDiagram-XADWPNL6-C1Vih91z.js} +1 -1
  49. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-BC1MYBn6.js → sequenceDiagram-FGHM5R23-CYkd7oQK.js} +1 -1
  50. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-kNp9bv8K.js → stateDiagram-FHFEXIEX-CtyG8wBK.js} +1 -1
  51. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BLyKWfcN.js +1 -0
  52. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-DKnITsD4.js → timeline-definition-GMOUNBTQ-DZIxSyd1.js} +1 -1
  53. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-BdajXRrh.js → vennDiagram-DHZGUBPP-Ct4JVRDM.js} +1 -1
  54. package/dist/deck-client/assets/wardley-RL74JXVD-V29ycxOW.js +162 -0
  55. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-B2hDCDl2.js → wardleyDiagram-NUSXRM2D-D-Ua6Cmi.js} +1 -1
  56. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CvnYFs51.js → xychartDiagram-5P7HB3ND-BPCOuRVl.js} +1 -1
  57. package/dist/deck-client/index.html +2 -2
  58. package/dist/server/beacon-monitor-entry.js +106 -24
  59. package/dist/server/chart-serve.js +544 -247
  60. package/dist/server/cli.js +743 -324
  61. package/dist/server/council-entry.js +16 -3
  62. package/dist/server/council-serve.js +15 -2
  63. package/dist/server/deck-mcp-entry.js +267 -107
  64. package/dist/server/deck-serve.js +98 -22
  65. package/dist/server/graph-mcp-entry.js +866 -357
  66. package/dist/server/orbit-entry.js +91 -7
  67. package/dist/server/recall-entry.js +94 -12
  68. package/dist/server/rover-entry.js +1 -1
  69. package/package.json +1 -1
  70. package/scaffolds/statusline/statusline-mcp.sh +68 -19
  71. package/scaffolds/statusline/statusline-wrapper.sh +12 -9
  72. package/dist/chart-client/assets/index-ysGpLeOW.css +0 -1
  73. package/dist/client/assets/index-CMN3tlGP.css +0 -32
  74. package/dist/council-client/assets/index-ChmNX6bZ.css +0 -1
  75. package/dist/deck-client/assets/channel-DqiACUUq.js +0 -1
  76. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-D23cq2C3.js +0 -1
  77. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-D23cq2C3.js +0 -1
  78. package/dist/deck-client/assets/clone-C7jSigGq.js +0 -1
  79. package/dist/deck-client/assets/index-CGbNOpk9.css +0 -1
  80. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-hRsAFc2t.js +0 -1
  81. package/dist/deck-client/assets/wardley-RL74JXVD-BL802-su.js +0 -162
  82. /package/dist/chart-client/assets/{index-BlsuXuQ1.js → index-Dzlj-oCj.js} +0 -0
  83. /package/dist/client/assets/{index-BA7BHBWT.js → index-tTg_ezUF.js} +0 -0
  84. /package/dist/council-client/assets/{index-jjBWyhry.js → index-B1v46vTE.js} +0 -0
@@ -1385,7 +1385,7 @@ function rewriteEnvFile(filePath, rewrites) {
1385
1385
  const out = lines.map((line) => {
1386
1386
  const m = LINE_RE.exec(line);
1387
1387
  if (!m) return line;
1388
- const [, indent, key, sep, rawValue] = m;
1388
+ const [, indent, key, sep2, rawValue] = m;
1389
1389
  if (!(key in rewrites)) return line;
1390
1390
  touched.add(key);
1391
1391
  const { quote, inner } = stripQuotes2(rawValue);
@@ -1396,7 +1396,7 @@ function rewriteEnvFile(filePath, rewrites) {
1396
1396
  return line;
1397
1397
  }
1398
1398
  result.changed.push(key);
1399
- return `${indent}${key}${sep}${quote}${next2}${quote}`;
1399
+ return `${indent}${key}${sep2}${quote}${next2}${quote}`;
1400
1400
  });
1401
1401
  for (const key of Object.keys(rewrites)) {
1402
1402
  if (!touched.has(key)) result.missing.push(key);
@@ -3060,10 +3060,93 @@ var init_orbit_mcp = __esm({
3060
3060
  });
3061
3061
 
3062
3062
  // src/server/orbit-entry.ts
3063
- var import_node_fs13 = require("node:fs");
3064
- var import_node_path10 = require("node:path");
3063
+ var import_node_fs14 = require("node:fs");
3064
+ var import_node_path11 = require("node:path");
3065
3065
  init_orchestrator();
3066
3066
  init_logger();
3067
+
3068
+ // src/server/prune-npx-cache.ts
3069
+ var import_node_fs13 = require("node:fs");
3070
+ var import_node_os5 = require("node:os");
3071
+ var import_node_path10 = require("node:path");
3072
+ var import_node_util = require("node:util");
3073
+ var PKG = "@launchsecure/launch-kit";
3074
+ var readFileAsync = (0, import_node_util.promisify)(import_node_fs13.readFile);
3075
+ var readdirAsync = (0, import_node_util.promisify)(import_node_fs13.readdir);
3076
+ var rmAsync = (0, import_node_util.promisify)(import_node_fs13.rm);
3077
+ var realpathAsync = (0, import_node_util.promisify)(import_node_fs13.realpath);
3078
+ function npxCacheRoot() {
3079
+ const cache = process.env.npm_config_cache;
3080
+ if (cache && cache.trim()) return (0, import_node_path10.join)(cache, "_npx");
3081
+ if (process.platform === "win32") {
3082
+ const localAppData = process.env.LOCALAPPDATA;
3083
+ if (localAppData) return (0, import_node_path10.join)(localAppData, "npm-cache", "_npx");
3084
+ }
3085
+ return (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".npm", "_npx");
3086
+ }
3087
+ function ownVersion() {
3088
+ let dir = __dirname;
3089
+ for (let i = 0; i < 8; i++) {
3090
+ try {
3091
+ const pkg = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(dir, "package.json"), "utf8"));
3092
+ if (pkg && pkg.name === PKG) return typeof pkg.version === "string" ? pkg.version : null;
3093
+ } catch {
3094
+ }
3095
+ const parent = (0, import_node_path10.dirname)(dir);
3096
+ if (parent === dir) break;
3097
+ dir = parent;
3098
+ }
3099
+ return null;
3100
+ }
3101
+ async function pruneStaleNpxCache() {
3102
+ try {
3103
+ const version = ownVersion();
3104
+ if (!version) return;
3105
+ const root = npxCacheRoot();
3106
+ let hashes;
3107
+ try {
3108
+ hashes = await readdirAsync(root);
3109
+ } catch {
3110
+ return;
3111
+ }
3112
+ const selfPath = await realpathAsync(process.argv[1] || __dirname).catch(() => __dirname);
3113
+ let reaped = 0;
3114
+ for (const hash of hashes) {
3115
+ const dir = (0, import_node_path10.join)(root, hash);
3116
+ if (selfPath === dir || selfPath.startsWith(dir + import_node_path10.sep)) continue;
3117
+ let ver;
3118
+ try {
3119
+ const lkPkg = JSON.parse(
3120
+ await readFileAsync((0, import_node_path10.join)(dir, "node_modules", PKG, "package.json"), "utf8")
3121
+ );
3122
+ ver = lkPkg.version;
3123
+ } catch {
3124
+ continue;
3125
+ }
3126
+ if (ver === version) continue;
3127
+ try {
3128
+ const top = JSON.parse(await readFileAsync((0, import_node_path10.join)(dir, "package.json"), "utf8"));
3129
+ const spec = top?.dependencies?.[PKG];
3130
+ if (typeof spec === "string" && spec.startsWith("file:")) continue;
3131
+ } catch {
3132
+ }
3133
+ try {
3134
+ await rmAsync(dir, { recursive: true, force: true });
3135
+ reaped++;
3136
+ } catch {
3137
+ }
3138
+ }
3139
+ if (reaped > 0) {
3140
+ process.stderr.write(
3141
+ `[launch-kit] npx-cache: reaped ${reaped} stale copy(ies), kept v${version}
3142
+ `
3143
+ );
3144
+ }
3145
+ } catch {
3146
+ }
3147
+ }
3148
+
3149
+ // src/server/orbit-entry.ts
3067
3150
  function parseFlags(argv) {
3068
3151
  const positional = [];
3069
3152
  const flags = { emitCd: false, noBackup: false, skipGates: [], deep: false, all: false, json: false, force: false };
@@ -3099,6 +3182,7 @@ function parseFlags(argv) {
3099
3182
  return { positional, flags };
3100
3183
  }
3101
3184
  async function main() {
3185
+ void pruneStaleNpxCache();
3102
3186
  const argv = process.argv.slice(2);
3103
3187
  const subcommand = argv[0];
3104
3188
  if (!subcommand || subcommand === "mcp") {
@@ -3328,8 +3412,8 @@ function formatRollup(reports) {
3328
3412
  return out.join("\n") + "\n";
3329
3413
  }
3330
3414
  function runInit(projectRoot) {
3331
- const path = (0, import_node_path10.join)(projectRoot, "orbit.json");
3332
- if ((0, import_node_fs13.existsSync)(path)) {
3415
+ const path = (0, import_node_path11.join)(projectRoot, "orbit.json");
3416
+ if ((0, import_node_fs14.existsSync)(path)) {
3333
3417
  process.stderr.write(`[launch-orbit] orbit.json already exists at ${path}
3334
3418
  `);
3335
3419
  process.exit(1);
@@ -3384,7 +3468,7 @@ function runInit(projectRoot) {
3384
3468
  full: { resources: ["db", "ports"] }
3385
3469
  }
3386
3470
  };
3387
- (0, import_node_fs13.writeFileSync)(path, JSON.stringify(starter, null, 2) + "\n", "utf-8");
3471
+ (0, import_node_fs14.writeFileSync)(path, JSON.stringify(starter, null, 2) + "\n", "utf-8");
3388
3472
  process.stdout.write(`\u2713 wrote ${path}
3389
3473
  `);
3390
3474
  }
@@ -284,14 +284,14 @@ function pidFilePath(recallDir) {
284
284
  function existingWatcherPid() {
285
285
  const { recallDir } = resolveRecallPaths();
286
286
  const pf = pidFilePath(recallDir);
287
- if (!(0, import_node_fs.existsSync)(pf)) return null;
288
- const pid = Number((0, import_node_fs.readFileSync)(pf, "utf-8").trim());
287
+ if (!(0, import_node_fs2.existsSync)(pf)) return null;
288
+ const pid = Number((0, import_node_fs2.readFileSync)(pf, "utf-8").trim());
289
289
  if (!Number.isFinite(pid) || pid <= 0) return null;
290
290
  return isPidAlive(pid) ? pid : null;
291
291
  }
292
292
  function ensureWatcher() {
293
293
  const { gitDir, workTree } = resolveRecallPaths();
294
- if (!(0, import_node_fs.existsSync)(gitDir)) {
294
+ if (!(0, import_node_fs2.existsSync)(gitDir)) {
295
295
  process.stderr.write(
296
296
  `[launch-recall mcp] shadow repo missing at ${gitDir} \u2014 run \`launch-recall init\` first. MCP tools will still serve read-only queries, but nothing will be captured.
297
297
  `
@@ -358,7 +358,7 @@ async function handleTool(name, args) {
358
358
  function doctorTool() {
359
359
  const { gitDir, recallDir } = resolveRecallPaths();
360
360
  const checks = [];
361
- const shadowOk = (0, import_node_fs.existsSync)(gitDir);
361
+ const shadowOk = (0, import_node_fs2.existsSync)(gitDir);
362
362
  checks.push({
363
363
  name: "shadow_repo",
364
364
  ok: shadowOk,
@@ -391,7 +391,7 @@ function doctorTool() {
391
391
  }
392
392
  function reportTool() {
393
393
  const { gitDir, recallDir, workTree } = resolveRecallPaths();
394
- if (!(0, import_node_fs.existsSync)(gitDir)) return { error: "shadow repo not initialised" };
394
+ if (!(0, import_node_fs2.existsSync)(gitDir)) return { error: "shadow repo not initialised" };
395
395
  let totalSnapshots = 0;
396
396
  try {
397
397
  const out = (0, import_node_child_process2.execFileSync)("git", [`--git-dir=${gitDir}`, "rev-list", "--count", "HEAD"], {
@@ -425,9 +425,9 @@ function reportTool() {
425
425
  }
426
426
  let config = null;
427
427
  const cfgPath = `${workTree}/.launch-recall.json`;
428
- if ((0, import_node_fs.existsSync)(cfgPath)) {
428
+ if ((0, import_node_fs2.existsSync)(cfgPath)) {
429
429
  try {
430
- config = JSON.parse((0, import_node_fs.readFileSync)(cfgPath, "utf-8"));
430
+ config = JSON.parse((0, import_node_fs2.readFileSync)(cfgPath, "utf-8"));
431
431
  } catch {
432
432
  }
433
433
  }
@@ -435,7 +435,7 @@ function reportTool() {
435
435
  }
436
436
  function historyTool(path7, limit) {
437
437
  const { gitDir, workTree } = resolveRecallPaths();
438
- if (!(0, import_node_fs.existsSync)(gitDir)) return { error: "shadow repo not initialised", path: path7, snapshots: [] };
438
+ if (!(0, import_node_fs2.existsSync)(gitDir)) return { error: "shadow repo not initialised", path: path7, snapshots: [] };
439
439
  const rel = path7.startsWith("/") ? path7.replace(`${workTree}/`, "").replace(/^\/+/, "") : path7;
440
440
  const snapshots = [];
441
441
  try {
@@ -456,7 +456,7 @@ function statusTool() {
456
456
  const { gitDir, workTree } = resolveRecallPaths();
457
457
  const pid = existingWatcherPid();
458
458
  let lastSnapshotAt = null;
459
- if ((0, import_node_fs.existsSync)(gitDir)) {
459
+ if ((0, import_node_fs2.existsSync)(gitDir)) {
460
460
  try {
461
461
  lastSnapshotAt = (0, import_node_child_process2.execFileSync)("git", [`--git-dir=${gitDir}`, "log", "-1", "--format=%aI"], {
462
462
  encoding: "utf-8",
@@ -551,14 +551,14 @@ function startRecallMcpServer() {
551
551
  process.stderr.write("[launch-recall mcp] stdin closed, exiting\n");
552
552
  teardown();
553
553
  });
554
- void import_node_fs.statSync;
554
+ void import_node_fs2.statSync;
555
555
  }
556
- var import_node_child_process2, import_node_fs, SERVER_INFO, TOOLS, childWatcher;
556
+ var import_node_child_process2, import_node_fs2, SERVER_INFO, TOOLS, childWatcher;
557
557
  var init_recall_mcp = __esm({
558
558
  "src/server/recall-mcp.ts"() {
559
559
  "use strict";
560
560
  import_node_child_process2 = require("node:child_process");
561
- import_node_fs = require("node:fs");
561
+ import_node_fs2 = require("node:fs");
562
562
  init_paths();
563
563
  SERVER_INFO = { name: "launch-recall", version: "0.0.1" };
564
564
  TOOLS = [
@@ -1355,6 +1355,87 @@ var init_uninstall = __esm({
1355
1355
  }
1356
1356
  });
1357
1357
 
1358
+ // src/server/prune-npx-cache.ts
1359
+ var import_node_fs = require("node:fs");
1360
+ var import_node_os = require("node:os");
1361
+ var import_node_path = require("node:path");
1362
+ var import_node_util = require("node:util");
1363
+ var PKG = "@launchsecure/launch-kit";
1364
+ var readFileAsync = (0, import_node_util.promisify)(import_node_fs.readFile);
1365
+ var readdirAsync = (0, import_node_util.promisify)(import_node_fs.readdir);
1366
+ var rmAsync = (0, import_node_util.promisify)(import_node_fs.rm);
1367
+ var realpathAsync = (0, import_node_util.promisify)(import_node_fs.realpath);
1368
+ function npxCacheRoot() {
1369
+ const cache = process.env.npm_config_cache;
1370
+ if (cache && cache.trim()) return (0, import_node_path.join)(cache, "_npx");
1371
+ if (process.platform === "win32") {
1372
+ const localAppData = process.env.LOCALAPPDATA;
1373
+ if (localAppData) return (0, import_node_path.join)(localAppData, "npm-cache", "_npx");
1374
+ }
1375
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".npm", "_npx");
1376
+ }
1377
+ function ownVersion() {
1378
+ let dir = __dirname;
1379
+ for (let i = 0; i < 8; i++) {
1380
+ try {
1381
+ const pkg = JSON.parse((0, import_node_fs.readFileSync)((0, import_node_path.join)(dir, "package.json"), "utf8"));
1382
+ if (pkg && pkg.name === PKG) return typeof pkg.version === "string" ? pkg.version : null;
1383
+ } catch {
1384
+ }
1385
+ const parent = (0, import_node_path.dirname)(dir);
1386
+ if (parent === dir) break;
1387
+ dir = parent;
1388
+ }
1389
+ return null;
1390
+ }
1391
+ async function pruneStaleNpxCache() {
1392
+ try {
1393
+ const version = ownVersion();
1394
+ if (!version) return;
1395
+ const root = npxCacheRoot();
1396
+ let hashes;
1397
+ try {
1398
+ hashes = await readdirAsync(root);
1399
+ } catch {
1400
+ return;
1401
+ }
1402
+ const selfPath = await realpathAsync(process.argv[1] || __dirname).catch(() => __dirname);
1403
+ let reaped = 0;
1404
+ for (const hash of hashes) {
1405
+ const dir = (0, import_node_path.join)(root, hash);
1406
+ if (selfPath === dir || selfPath.startsWith(dir + import_node_path.sep)) continue;
1407
+ let ver;
1408
+ try {
1409
+ const lkPkg = JSON.parse(
1410
+ await readFileAsync((0, import_node_path.join)(dir, "node_modules", PKG, "package.json"), "utf8")
1411
+ );
1412
+ ver = lkPkg.version;
1413
+ } catch {
1414
+ continue;
1415
+ }
1416
+ if (ver === version) continue;
1417
+ try {
1418
+ const top = JSON.parse(await readFileAsync((0, import_node_path.join)(dir, "package.json"), "utf8"));
1419
+ const spec = top?.dependencies?.[PKG];
1420
+ if (typeof spec === "string" && spec.startsWith("file:")) continue;
1421
+ } catch {
1422
+ }
1423
+ try {
1424
+ await rmAsync(dir, { recursive: true, force: true });
1425
+ reaped++;
1426
+ } catch {
1427
+ }
1428
+ }
1429
+ if (reaped > 0) {
1430
+ process.stderr.write(
1431
+ `[launch-kit] npx-cache: reaped ${reaped} stale copy(ies), kept v${version}
1432
+ `
1433
+ );
1434
+ }
1435
+ } catch {
1436
+ }
1437
+ }
1438
+
1358
1439
  // src/server/recall-entry.ts
1359
1440
  function logStderr(msg) {
1360
1441
  process.stderr.write(`[launch-recall] ${msg}
@@ -1383,6 +1464,7 @@ function printUsage() {
1383
1464
  );
1384
1465
  }
1385
1466
  async function main() {
1467
+ void pruneStaleNpxCache();
1386
1468
  const argv = process.argv.slice(2);
1387
1469
  const subcommand = argv[0];
1388
1470
  if (!subcommand || subcommand === "--help" || subcommand === "-h") {
@@ -608,7 +608,7 @@ var require_package = __commonJS({
608
608
  "package.json"(exports2, module2) {
609
609
  module2.exports = {
610
610
  name: "@launchsecure/launch-kit",
611
- version: "0.0.39",
611
+ version: "0.0.40",
612
612
  description: "LaunchSecure toolkit \u2014 launch-sequencer (pipeline runner + terminal bridge), launch-radar (feedback webhook receiver), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup). launch-pod is the container image these run inside.",
613
613
  license: "MIT",
614
614
  author: "LaunchSecure - AutomateWithUs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchsecure/launch-kit",
3
- "version": "0.0.39",
3
+ "version": "0.0.40",
4
4
  "description": "LaunchSecure toolkit — launch-sequencer (pipeline runner + terminal bridge), launch-radar (feedback webhook receiver), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup). launch-pod is the container image these run inside.",
5
5
  "license": "MIT",
6
6
  "author": "LaunchSecure - AutomateWithUs",
@@ -8,6 +8,8 @@
8
8
  # statusline-mcp.sh --show=recall,chart # only the listed chips
9
9
  # statusline-mcp.sh --compact # collapse to `mcp <up>/<total>`
10
10
  # green when all up, red if any down
11
+ # statusline-mcp.sh --course # print only the active-course line
12
+ # (`course: <profile> → <org>/<project>`)
11
13
  #
12
14
  # `secure` differs from the others: recall/chart/deck/council are local
13
15
  # daemons probed via a pidfile/lockfile/freshness file, but launch-secure is
@@ -22,10 +24,12 @@ set -u
22
24
 
23
25
  show=""
24
26
  compact=0
27
+ course_mode=0
25
28
  for arg in "$@"; do
26
29
  case "$arg" in
27
30
  --show=*) show="${arg#--show=}" ;;
28
31
  --compact) compact=1 ;;
32
+ --course) course_mode=1 ;;
29
33
  esac
30
34
  done
31
35
  [ -z "$show" ] && show="recall,chart,deck,council,secure"
@@ -40,6 +44,14 @@ ORANGE=$'\033[33m'
40
44
  RED=$'\033[31m'
41
45
  RESET=$'\033[0m'
42
46
 
47
+ # Course-line palette: three distinct shades of orange (256-color) for the
48
+ # course / org / project segments, plus a DIM ` | ` separator that matches the
49
+ # user's first-line separator style (^[[2m).
50
+ COURSE_C1=$'\033[38;5;214m' # amber — course (profile)
51
+ COURSE_C2=$'\033[38;5;208m' # orange — org
52
+ COURSE_C3=$'\033[38;5;166m' # burnt — project
53
+ COURSE_SEP=$'\033[0m\033[2m | \033[0m'
54
+
43
55
  find_project_root() {
44
56
  # `.recall` only exists at the repo root (launch-recall init creates it
45
57
  # there). `.launchsecure` can appear in subdirs too (e.g. packages/cli
@@ -184,23 +196,24 @@ chip_council() {
184
196
  _state="orange"; _display="${ORANGE}council${RESET}"
185
197
  }
186
198
 
187
- # Pull the active profile's serverUrl out of .launch-secure.cred.config without
188
- # jq (the chip script stays coreutils-only). Handles both shapes the cred file
189
- # can take — profiled ({active, profiles:{<name>:{serverUrl}}}) and flat
190
- # ({serverUrl}) — mirroring the .mcp.json headersHelper's own fallback.
191
- extract_secure_url() {
192
- local cred="$1" active
199
+ # Pull a string field out of the active profile in .launch-secure.cred.config
200
+ # without jq (the chip script stays coreutils-only). Handles both shapes the
201
+ # cred file can take — profiled ({active, profiles:{<name>:{...}}}) and flat
202
+ # ({...}) — mirroring the .mcp.json headersHelper's own fallback. $2 is the key
203
+ # (serverUrl, orgSlug, projectSlug).
204
+ extract_profile_field() {
205
+ local cred="$1" field="$2" active
193
206
  active=$(grep -o '"active"[[:space:]]*:[[:space:]]*"[^"]*"' "$cred" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
194
- awk -v prof="$active" '
207
+ awk -v prof="$active" -v field="$field" '
195
208
  function opens(s){ return gsub(/{/,"&",s) }
196
209
  function closes(s){ return gsub(/}/,"&",s) }
197
- BEGIN { inprof = (prof == "") ? 1 : 0; depth = 0 }
210
+ BEGIN { inprof = (prof == "") ? 1 : 0; depth = 0; pat = "\"" field "\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" }
198
211
  {
199
212
  if (prof != "" && !inprof) {
200
213
  if (index($0, "\"" prof "\"") > 0 && index($0, "{") > 0) { inprof = 1; depth = opens($0) - closes($0) }
201
214
  next
202
215
  }
203
- if (match($0, /"serverUrl"[[:space:]]*:[[:space:]]*"[^"]*"/)) {
216
+ if (match($0, pat)) {
204
217
  s = substr($0, RSTART, RLENGTH)
205
218
  sub(/.*:[[:space:]]*"/, "", s); sub(/".*/, "", s)
206
219
  print s; exit
@@ -210,12 +223,42 @@ extract_secure_url() {
210
223
  ' "$cred" 2>/dev/null
211
224
  }
212
225
 
226
+ extract_secure_url() { extract_profile_field "$1" "serverUrl"; }
227
+
228
+ # Emit the active-course line: `course: <profile> → <org>/<project>`. Sourced
229
+ # from the same cred file the secure chip reads, so it always agrees with which
230
+ # org/project this session's MCP calls (decks, comments, secrets) target. Each
231
+ # segment gets its own orange shade; the ` | ` matches the first-line separator.
232
+ # Silent when no cred/slugs exist.
233
+ render_course_line() {
234
+ local cred="$PROJECT_ROOT/.launch-secure.cred.config"
235
+ [ -f "$cred" ] || return 0
236
+ local active org project parts=""
237
+ active=$(grep -o '"active"[[:space:]]*:[[:space:]]*"[^"]*"' "$cred" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
238
+ org=$(extract_profile_field "$cred" "orgSlug")
239
+ project=$(extract_profile_field "$cred" "projectSlug")
240
+ # Labeled, pipe-joined segments — each appears only when its value exists.
241
+ # Each segment carries its own orange shade; separators match the first line.
242
+ if [ -n "$active" ]; then parts="${COURSE_C1}course: ${active}${RESET}"; fi
243
+ if [ -n "$org" ]; then
244
+ [ -n "$parts" ] && parts="${parts}${COURSE_SEP}"
245
+ parts="${parts}${COURSE_C2}org: ${org}${RESET}"
246
+ fi
247
+ if [ -n "$project" ]; then
248
+ [ -n "$parts" ] && parts="${parts}${COURSE_SEP}"
249
+ parts="${parts}${COURSE_C3}project: ${project}${RESET}"
250
+ fi
251
+ [ -z "$parts" ] && return 0
252
+ printf '%s' "$parts"
253
+ }
254
+
213
255
  _render_secure() {
214
- # $1 = state (green|red|unknown), $2 = label (active profile name)
256
+ # $1 = state (green|red|unknown). The active profile name lives on the course
257
+ # line (see render_course_line), so the chip stays a bare reachability light.
215
258
  case "$1" in
216
- green) _state="green"; _display="${GREEN}secure(${2})${RESET}" ;;
217
- unknown) _state="orange"; _display="${ORANGE}secure(${2})${RESET}" ;;
218
- *) _state="red"; _display="${RED}secure(${2})${RESET}" ;;
259
+ green) _state="green"; _display="${GREEN}secure${RESET}" ;;
260
+ unknown) _state="orange"; _display="${ORANGE}secure${RESET}" ;;
261
+ *) _state="red"; _display="${RED}secure${RESET}" ;;
219
262
  esac
220
263
  }
221
264
 
@@ -223,11 +266,9 @@ chip_secure() {
223
266
  local cred="$PROJECT_ROOT/.launch-secure.cred.config"
224
267
  if [ ! -f "$cred" ]; then _state="orange"; _display="${ORANGE}secure(?)${RESET}"; return; fi
225
268
 
226
- local active url label
227
- active=$(grep -o '"active"[[:space:]]*:[[:space:]]*"[^"]*"' "$cred" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
269
+ local url
228
270
  url=$(extract_secure_url "$cred")
229
- if [ -z "$url" ]; then _state="orange"; _display="${ORANGE}secure(?)${RESET}"; return; fi
230
- label="${active:-secure}"
271
+ if [ -z "$url" ]; then _state="orange"; _display="${ORANGE}secure${RESET}"; return; fi
231
272
 
232
273
  # Cache keyed on the URL so a course-switch (prod↔local) invalidates instantly.
233
274
  local cache="$PROJECT_ROOT/.launchsecure/.statusline-secure.cache"
@@ -236,7 +277,7 @@ chip_secure() {
236
277
  local c_ts c_url c_state
237
278
  IFS='|' read -r c_ts c_url c_state < "$cache" 2>/dev/null || true
238
279
  if [ -n "${c_ts:-}" ] && [ "${c_url:-}" = "$url" ] && [ $((now - c_ts)) -lt "$SECURE_CACHE_TTL" ]; then
239
- _render_secure "$c_state" "$label"; return
280
+ _render_secure "$c_state"; return
240
281
  fi
241
282
  fi
242
283
 
@@ -249,9 +290,17 @@ chip_secure() {
249
290
  if [ -n "$code" ] && [ "$code" != "000" ]; then state="green"; else state="red"; fi
250
291
  fi
251
292
  printf '%s|%s|%s\n' "$now" "$url" "$state" > "$cache" 2>/dev/null || true
252
- _render_secure "$state" "$label"
293
+ _render_secure "$state"
253
294
  }
254
295
 
296
+ # --course mode: print only the active-course line and exit. The wrapper calls
297
+ # this separately so the course renders on its own line between the user's
298
+ # statusline and the MCP chips, regardless of compact/show.
299
+ if [ "$course_mode" = "1" ]; then
300
+ render_course_line
301
+ exit 0
302
+ fi
303
+
255
304
  show_list=$(echo "$show" | tr ',' ' ')
256
305
  total=0
257
306
  up=0
@@ -27,6 +27,7 @@ if command -v jq >/dev/null 2>&1; then
27
27
  fi
28
28
 
29
29
  chips=""
30
+ course=""
30
31
  if [ -x "$HOME/.launchsecure/statusline-mcp.sh" ]; then
31
32
  # Build args array conditionally; `set -u` + empty `"${args[@]}"` would
32
33
  # error on bash 4 (default macOS bash), so test length before expanding.
@@ -38,20 +39,22 @@ if [ -x "$HOME/.launchsecure/statusline-mcp.sh" ]; then
38
39
  else
39
40
  chips=$(LK_STATUSLINE_CWD="$cwd" "$HOME/.launchsecure/statusline-mcp.sh" "${args[@]}" 2>/dev/null || true)
40
41
  fi
42
+ # Active-course line (`course: <profile> → <org>/<project>`), independent of
43
+ # show/compact — surfaces which org/project this session's MCP calls target.
44
+ course=$(LK_STATUSLINE_CWD="$cwd" "$HOME/.launchsecure/statusline-mcp.sh" --course 2>/dev/null || true)
41
45
  fi
42
46
 
43
- # Layout: by default the chips go on their OWN line below the user's original
44
- # statusline (Claude Code renders each stdout newline as a new row). Set
47
+ # Layout: by default each segment goes on its OWN line (Claude Code renders each
48
+ # stdout newline as a new row), in order: original · course · chips. Set
45
49
  # LK_STATUSLINE_INLINE=1 to keep the old single-line ` | `-joined layout.
46
50
  if [ "${LK_STATUSLINE_INLINE:-0}" = "1" ]; then
47
51
  sep=$'\033[2m | \033[0m'
48
52
  else
49
53
  sep=$'\n'
50
54
  fi
51
- if [ -n "$original_output" ] && [ -n "$chips" ]; then
52
- printf '%s%s%s' "$original_output" "$sep" "$chips"
53
- elif [ -n "$original_output" ]; then
54
- printf '%s' "$original_output"
55
- elif [ -n "$chips" ]; then
56
- printf '%s' "$chips"
57
- fi
55
+ out=""
56
+ for seg in "$original_output" "$course" "$chips"; do
57
+ [ -z "$seg" ] && continue
58
+ if [ -z "$out" ]; then out="$seg"; else out="${out}${sep}${seg}"; fi
59
+ done
60
+ printf '%s' "$out"