@launchsecure/launch-kit 0.0.28 → 0.0.30
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/beacon/beacon.mjs +2759 -1246
- package/dist/beacon/beacon.mjs.map +1 -1
- package/dist/beacon/beacon.umd.js +710 -95
- package/dist/beacon/beacon.umd.js.map +1 -1
- package/dist/beacon/types/core.d.ts +14 -0
- package/dist/beacon/types/core.d.ts.map +1 -0
- package/dist/beacon/types/ctx.d.ts +14 -0
- package/dist/beacon/types/ctx.d.ts.map +1 -0
- package/dist/beacon/types/element.d.ts +16 -48
- package/dist/beacon/types/element.d.ts.map +1 -1
- package/dist/beacon/types/index.d.ts +5 -4
- package/dist/beacon/types/index.d.ts.map +1 -1
- package/dist/beacon/types/internal/annotation-cache.d.ts +10 -0
- package/dist/beacon/types/internal/annotation-cache.d.ts.map +1 -0
- package/dist/beacon/types/internal/element-capture.d.ts +19 -0
- package/dist/beacon/types/internal/element-capture.d.ts.map +1 -0
- package/dist/beacon/types/internal/event-buffer.d.ts +16 -0
- package/dist/beacon/types/internal/event-buffer.d.ts.map +1 -0
- package/dist/beacon/types/internal/framework-detect.d.ts +6 -0
- package/dist/beacon/types/internal/framework-detect.d.ts.map +1 -0
- package/dist/beacon/types/internal/markers.d.ts +17 -0
- package/dist/beacon/types/internal/markers.d.ts.map +1 -0
- package/dist/beacon/types/internal/monitor/capture-dom.d.ts +14 -0
- package/dist/beacon/types/internal/monitor/capture-dom.d.ts.map +1 -0
- package/dist/beacon/types/internal/monitor/capture-network.d.ts +12 -0
- package/dist/beacon/types/internal/monitor/capture-network.d.ts.map +1 -0
- package/dist/beacon/types/internal/monitor/overlay.d.ts +16 -0
- package/dist/beacon/types/internal/monitor/overlay.d.ts.map +1 -0
- package/dist/beacon/types/internal/monitor/session.d.ts +41 -0
- package/dist/beacon/types/internal/monitor/session.d.ts.map +1 -0
- package/dist/beacon/types/{monitor → internal/monitor}/transport.d.ts +3 -3
- package/dist/beacon/types/internal/monitor/transport.d.ts.map +1 -0
- package/dist/beacon/types/{monitor/types.d.ts → internal/monitor/wire.d.ts} +69 -27
- package/dist/beacon/types/internal/monitor/wire.d.ts.map +1 -0
- package/dist/beacon/types/{ui → internal}/pick-mode-overlay.d.ts +4 -5
- package/dist/beacon/types/internal/pick-mode-overlay.d.ts.map +1 -0
- package/dist/beacon/types/{capture → internal}/picker.d.ts +0 -1
- package/dist/beacon/types/internal/picker.d.ts.map +1 -0
- package/dist/beacon/types/{ui → internal}/pin-popover.d.ts +1 -1
- package/dist/beacon/types/internal/pin-popover.d.ts.map +1 -0
- package/dist/beacon/types/{capture → internal}/screenshot.d.ts +1 -0
- package/dist/beacon/types/internal/screenshot.d.ts.map +1 -0
- package/dist/beacon/types/internal/selector.d.ts.map +1 -0
- package/dist/beacon/types/plugins/domEle.d.ts +14 -0
- package/dist/beacon/types/plugins/domEle.d.ts.map +1 -0
- package/dist/beacon/types/plugins/domSS.d.ts +8 -0
- package/dist/beacon/types/plugins/domSS.d.ts.map +1 -0
- package/dist/beacon/types/plugins/errors.d.ts +3 -0
- package/dist/beacon/types/plugins/errors.d.ts.map +1 -0
- package/dist/beacon/types/plugins/index.d.ts +8 -0
- package/dist/beacon/types/plugins/index.d.ts.map +1 -0
- package/dist/beacon/types/plugins/liveMonitor.d.ts +14 -0
- package/dist/beacon/types/plugins/liveMonitor.d.ts.map +1 -0
- package/dist/beacon/types/plugins/metadata.d.ts +3 -0
- package/dist/beacon/types/plugins/metadata.d.ts.map +1 -0
- package/dist/beacon/types/registry.d.ts +33 -0
- package/dist/beacon/types/registry.d.ts.map +1 -0
- package/dist/beacon/types/styles.d.ts +8 -0
- package/dist/beacon/types/styles.d.ts.map +1 -0
- package/dist/beacon/types/transport.d.ts +3 -0
- package/dist/beacon/types/transport.d.ts.map +1 -0
- package/dist/beacon/types/types.d.ts +152 -68
- package/dist/beacon/types/types.d.ts.map +1 -1
- package/dist/beacon/types/ui/dialog.d.ts +53 -0
- package/dist/beacon/types/ui/dialog.d.ts.map +1 -0
- package/dist/beacon/types/ui/form.d.ts +7 -0
- package/dist/beacon/types/ui/form.d.ts.map +1 -0
- package/dist/beacon/types/ui/overlay.d.ts +6 -0
- package/dist/beacon/types/ui/overlay.d.ts.map +1 -0
- package/dist/deck-client/assets/{_baseUniq-W2JQDmje.js → _baseUniq-DCt2IMRR.js} +1 -1
- package/dist/deck-client/assets/{arc-DIBWAId9.js → arc-h-ifqmNR.js} +1 -1
- package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-CAIRMvJK.js → architectureDiagram-Q4EWVU46-C9dITSPv.js} +1 -1
- package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BeNaNiOi.js → blockDiagram-DXYQGD6D-BHuJT34t.js} +1 -1
- package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-B9Ozi62h.js → c4Diagram-AHTNJAMY-CpvMGtDG.js} +1 -1
- package/dist/deck-client/assets/channel-2PZVMiXf.js +1 -0
- package/dist/deck-client/assets/{chunk-4BX2VUAB-D7AZ47dt.js → chunk-4BX2VUAB-B6md1VIm.js} +1 -1
- package/dist/deck-client/assets/{chunk-4TB4RGXK-DnVnNPcI.js → chunk-4TB4RGXK-BmEnX8ik.js} +1 -1
- package/dist/deck-client/assets/{chunk-55IACEB6-UKYs-YNd.js → chunk-55IACEB6-BZPUyZAZ.js} +1 -1
- package/dist/deck-client/assets/{chunk-EDXVE4YY-D43b-SKn.js → chunk-EDXVE4YY-BWwNUK-l.js} +1 -1
- package/dist/deck-client/assets/{chunk-FMBD7UC4-QzBAoyyW.js → chunk-FMBD7UC4-o7gSppGI.js} +1 -1
- package/dist/deck-client/assets/{chunk-OYMX7WX6-Cjif4r6W.js → chunk-OYMX7WX6-C4KoTL5p.js} +1 -1
- package/dist/deck-client/assets/{chunk-QZHKN3VN-CqLDirEI.js → chunk-QZHKN3VN-jkf68sDs.js} +1 -1
- package/dist/deck-client/assets/{chunk-YZCP3GAM-_FQvmMs4.js → chunk-YZCP3GAM-Cd4yBE7o.js} +1 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-Bt8xBAof.js +1 -0
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-Bt8xBAof.js +1 -0
- package/dist/deck-client/assets/clone-BHQryoDl.js +1 -0
- package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-rfrocesE.js → cose-bilkent-S5V4N54A-DeGFUgAV.js} +1 -1
- package/dist/deck-client/assets/{dagre-KV5264BT-Bv_7DJat.js → dagre-KV5264BT-ekcYJuUV.js} +1 -1
- package/dist/deck-client/assets/{diagram-5BDNPKRD-4F1414G5.js → diagram-5BDNPKRD-YHPk4rV2.js} +1 -1
- package/dist/deck-client/assets/{diagram-G4DWMVQ6-C4-Pszqm.js → diagram-G4DWMVQ6-DM-JCd_B.js} +1 -1
- package/dist/deck-client/assets/{diagram-MMDJMWI5-B647TIx9.js → diagram-MMDJMWI5-l5FK1ybk.js} +1 -1
- package/dist/deck-client/assets/{diagram-TYMM5635-BFAqpezd.js → diagram-TYMM5635-CIN4_1-j.js} +1 -1
- package/dist/deck-client/assets/{erDiagram-SMLLAGMA-BfBfrJOC.js → erDiagram-SMLLAGMA-MyinSkEl.js} +1 -1
- package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-DX9YAYes.js → flowDiagram-DWJPFMVM-Dk8nn42x.js} +1 -1
- package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-DCuiy7wF.js → ganttDiagram-T4ZO3ILL-BU1ihicu.js} +1 -1
- package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-CGp1IXUh.js → gitGraphDiagram-UUTBAWPF-BjsTL13C.js} +1 -1
- package/dist/deck-client/assets/{graph-B7g8aoxv.js → graph-DJmh-xi7.js} +1 -1
- package/dist/deck-client/assets/{index-Dg1r-WSN.js → index-KsShfCV-.js} +3 -3
- package/dist/deck-client/assets/{infoDiagram-42DDH7IO-L3fahMkF.js → infoDiagram-42DDH7IO-Dxvy_RB4.js} +1 -1
- package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js → ishikawaDiagram-UXIWVN3A-DPOaNF1l.js} +1 -1
- package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-djTSQZF9.js → journeyDiagram-VCZTEJTY-DMew3K5c.js} +1 -1
- package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CcTHo4CM.js → kanban-definition-6JOO6SKY-csciJFuk.js} +1 -1
- package/dist/deck-client/assets/{layout-mEJiadb7.js → layout-Dg4yyms2.js} +1 -1
- package/dist/deck-client/assets/{linear-XgTKqyRu.js → linear-BA3zU6gq.js} +1 -1
- package/dist/deck-client/assets/{min-Ct9jZdpd.js → min-lz-Ird-p.js} +1 -1
- package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-BaFxCGNU.js → mindmap-definition-QFDTVHPH-CCEN8OQV.js} +1 -1
- package/dist/deck-client/assets/{pieDiagram-DEJITSTG-CIbYYjtw.js → pieDiagram-DEJITSTG-DM6n1HY7.js} +1 -1
- package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-D9EtCOvh.js → quadrantDiagram-34T5L4WZ-_ULoR66n.js} +1 -1
- package/dist/deck-client/assets/{requirementDiagram-MS252O5E-xeni9eVG.js → requirementDiagram-MS252O5E-BuwJs7Tn.js} +1 -1
- package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-LYeknz9h.js → sankeyDiagram-XADWPNL6-BEsuzkW4.js} +1 -1
- package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-RDbsKFZf.js → sequenceDiagram-FGHM5R23-CP2H0YWf.js} +1 -1
- package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-BH1Zjglk.js → stateDiagram-FHFEXIEX-B5Gw_NNL.js} +1 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-4T4wMDXr.js +1 -0
- package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-IFXxKptt.js → timeline-definition-GMOUNBTQ-DsoYydQa.js} +1 -1
- package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D-sLkQs9.js → vennDiagram-DHZGUBPP-Dz8JT_ob.js} +1 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-DGHQ_Ijv.js +162 -0
- package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BTjjuDU3.js → wardleyDiagram-NUSXRM2D-DN1LJMB1.js} +1 -1
- package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-AYbv92n-.js → xychartDiagram-5P7HB3ND-nb0oSfrQ.js} +1 -1
- package/dist/deck-client/index.html +1 -1
- package/dist/server/beacon-monitor-entry.js +548 -6
- package/dist/server/chart-serve.js +920 -249
- package/dist/server/cli.js +1599 -595
- package/dist/server/course-entry.js +3 -3
- package/dist/server/graph-mcp-entry.js +1361 -394
- package/dist/server/init-entry.js +799 -195
- package/dist/server/orbit-entry.js +135 -7
- package/dist/server/parse-worker-entry.js +918 -247
- package/package.json +3 -2
- package/scaffolds/ls-marketplace/.claude-plugin/marketplace.json +4 -4
- package/scaffolds/ls-marketplace/plugins/{ls → kit}/.claude-plugin/plugin.json +1 -10
- package/scaffolds/ls-marketplace/plugins/{ls → kit}/commands/activate-beacon.md +2 -2
- package/scaffolds/ls-marketplace/plugins/kit/commands/activate-statusline.md +46 -0
- package/scaffolds/ls-marketplace/plugins/kit/commands/deactivate-statusline.md +34 -0
- package/scaffolds/ls-marketplace/plugins/{ls → kit}/commands/standup.md +52 -38
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-array.md +107 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-clear.md +94 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-pulse.md +82 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-scan.md +66 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/blast-radius.md +101 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief.md +112 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/course.md +84 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/debug.md +92 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check.md +160 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/diagram.md +134 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/orbit.md +87 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/prototype.md +90 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/recall.md +83 -0
- package/scaffolds/ls-marketplace/plugins/{ls/commands → kit/skills}/show-mcp-status.md +8 -8
- package/scaffolds/ls-marketplace/plugins/kit/skills/wireframe.md +70 -0
- package/scaffolds/statusline/statusline-mcp.sh +204 -0
- package/scaffolds/statusline/statusline-wrapper.sh +50 -0
- package/dist/beacon/types/capture/element.d.ts +0 -3
- package/dist/beacon/types/capture/element.d.ts.map +0 -1
- package/dist/beacon/types/capture/events.d.ts +0 -20
- package/dist/beacon/types/capture/events.d.ts.map +0 -1
- package/dist/beacon/types/capture/framework.d.ts +0 -3
- package/dist/beacon/types/capture/framework.d.ts.map +0 -1
- package/dist/beacon/types/capture/metadata.d.ts +0 -3
- package/dist/beacon/types/capture/metadata.d.ts.map +0 -1
- package/dist/beacon/types/capture/overlay.d.ts +0 -7
- package/dist/beacon/types/capture/overlay.d.ts.map +0 -1
- package/dist/beacon/types/capture/picker.d.ts.map +0 -1
- package/dist/beacon/types/capture/screenshot.d.ts.map +0 -1
- package/dist/beacon/types/capture/selector.d.ts.map +0 -1
- package/dist/beacon/types/monitor/dom.d.ts +0 -13
- package/dist/beacon/types/monitor/dom.d.ts.map +0 -1
- package/dist/beacon/types/monitor/index.d.ts +0 -19
- package/dist/beacon/types/monitor/index.d.ts.map +0 -1
- package/dist/beacon/types/monitor/network.d.ts +0 -12
- package/dist/beacon/types/monitor/network.d.ts.map +0 -1
- package/dist/beacon/types/monitor/transport.d.ts.map +0 -1
- package/dist/beacon/types/monitor/types.d.ts.map +0 -1
- package/dist/beacon/types/transport/submit.d.ts +0 -3
- package/dist/beacon/types/transport/submit.d.ts.map +0 -1
- package/dist/beacon/types/ui/button.d.ts +0 -2
- package/dist/beacon/types/ui/button.d.ts.map +0 -1
- package/dist/beacon/types/ui/drawer.d.ts +0 -33
- package/dist/beacon/types/ui/drawer.d.ts.map +0 -1
- package/dist/beacon/types/ui/icons.d.ts +0 -9
- package/dist/beacon/types/ui/icons.d.ts.map +0 -1
- package/dist/beacon/types/ui/monitor-panel.d.ts +0 -19
- package/dist/beacon/types/ui/monitor-panel.d.ts.map +0 -1
- package/dist/beacon/types/ui/pick-mode-overlay.d.ts.map +0 -1
- package/dist/beacon/types/ui/pin-popover.d.ts.map +0 -1
- package/dist/deck-client/assets/channel-CRdozqbp.js +0 -1
- package/dist/deck-client/assets/classDiagram-6PBFFD2Q-lIZMp57W.js +0 -1
- package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-lIZMp57W.js +0 -1
- package/dist/deck-client/assets/clone-BtWeSTyJ.js +0 -1
- package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +0 -1
- package/dist/deck-client/assets/wardley-RL74JXVD-C010F8l4.js +0 -162
- package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-array.md +0 -92
- package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-clear.md +0 -68
- package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-pulse.md +0 -80
- package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-scan.md +0 -62
- /package/dist/beacon/types/{capture → internal}/selector.d.ts +0 -0
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,12 +30,119 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
33
|
+
// src/server/statusline-install.ts
|
|
34
|
+
var statusline_install_exports = {};
|
|
35
|
+
__export(statusline_install_exports, {
|
|
36
|
+
activateStatusline: () => activateStatusline,
|
|
37
|
+
deactivateStatusline: () => deactivateStatusline
|
|
38
|
+
});
|
|
39
|
+
function readSettings() {
|
|
40
|
+
if (!fs3.existsSync(SETTINGS_PATH)) return null;
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(fs3.readFileSync(SETTINGS_PATH, "utf-8"));
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function writeSettings(s) {
|
|
48
|
+
fs3.mkdirSync(path3.dirname(SETTINGS_PATH), { recursive: true });
|
|
49
|
+
fs3.writeFileSync(SETTINGS_PATH, JSON.stringify(s, null, 2) + "\n", "utf-8");
|
|
50
|
+
}
|
|
51
|
+
function readScaffold(name) {
|
|
52
|
+
const p = path3.resolve(__dirname, "..", "..", "scaffolds", "statusline", name);
|
|
53
|
+
return fs3.readFileSync(p, "utf-8");
|
|
54
|
+
}
|
|
55
|
+
function writeScripts() {
|
|
56
|
+
fs3.mkdirSync(LK_DIR, { recursive: true });
|
|
57
|
+
fs3.writeFileSync(WRAPPER_PATH, readScaffold("statusline-wrapper.sh"), { mode: 493 });
|
|
58
|
+
fs3.writeFileSync(CHIP_PATH, readScaffold("statusline-mcp.sh"), { mode: 493 });
|
|
59
|
+
}
|
|
60
|
+
function wrapperCommand(opts) {
|
|
61
|
+
const env = [];
|
|
62
|
+
if (opts.show) env.push(`LK_STATUSLINE_SHOW=${opts.show}`);
|
|
63
|
+
if (opts.compact) env.push(`LK_STATUSLINE_COMPACT=1`);
|
|
64
|
+
const prefix = env.length > 0 ? env.join(" ") + " " : "";
|
|
65
|
+
return `${prefix}bash ${WRAPPER_PATH}`;
|
|
66
|
+
}
|
|
67
|
+
function activateStatusline(opts = {}) {
|
|
68
|
+
const settings = readSettings();
|
|
69
|
+
if (!settings) {
|
|
70
|
+
return { ok: false, outcome: "no-settings", message: `no ~/.claude/settings.json \u2014 nothing to wrap` };
|
|
71
|
+
}
|
|
72
|
+
const currentCmd = settings.statusLine?.command;
|
|
73
|
+
const alreadyWrapped = typeof currentCmd === "string" && currentCmd.includes(WRAPPER_PATH);
|
|
74
|
+
if (alreadyWrapped) {
|
|
75
|
+
writeScripts();
|
|
76
|
+
if (opts.show !== void 0 || opts.compact !== void 0) {
|
|
77
|
+
const updated = {
|
|
78
|
+
...settings,
|
|
79
|
+
statusLine: { type: "command", command: wrapperCommand(opts) }
|
|
80
|
+
};
|
|
81
|
+
writeSettings(updated);
|
|
82
|
+
const parts = [];
|
|
83
|
+
if (opts.show) parts.push(`--show=${opts.show}`);
|
|
84
|
+
if (opts.compact) parts.push("--compact");
|
|
85
|
+
const desc = parts.length > 0 ? parts.join(" ") : "default (all chips, verbose)";
|
|
86
|
+
return { ok: true, outcome: "refreshed", message: `refreshed chip scripts and updated mode: ${desc}` };
|
|
87
|
+
}
|
|
88
|
+
return { ok: true, outcome: "refreshed", message: "statusline already wrapped \u2014 refreshed chip scripts only" };
|
|
89
|
+
}
|
|
90
|
+
if (!currentCmd) {
|
|
91
|
+
return { ok: false, outcome: "no-statusline", message: "no statusLine.command in ~/.claude/settings.json \u2014 launch-kit only extends an existing statusline" };
|
|
92
|
+
}
|
|
93
|
+
writeScripts();
|
|
94
|
+
const wrapped = {
|
|
95
|
+
...settings,
|
|
96
|
+
statusLine: { type: "command", command: wrapperCommand(opts) },
|
|
97
|
+
[ORIGINAL_KEY]: settings.statusLine
|
|
98
|
+
};
|
|
99
|
+
writeSettings(wrapped);
|
|
100
|
+
const modeParts = [];
|
|
101
|
+
if (opts.show) modeParts.push(`chips: ${opts.show}`);
|
|
102
|
+
if (opts.compact) modeParts.push("compact mode");
|
|
103
|
+
const modeDesc = modeParts.length > 0 ? ` (${modeParts.join(", ")})` : "";
|
|
104
|
+
return { ok: true, outcome: "activated", message: `wrapped statusLine.command${modeDesc}; original stashed under ${ORIGINAL_KEY}` };
|
|
105
|
+
}
|
|
106
|
+
function deactivateStatusline() {
|
|
107
|
+
const settings = readSettings();
|
|
108
|
+
if (!settings) return { ok: false, outcome: "no-settings", message: "no ~/.claude/settings.json" };
|
|
109
|
+
const original = settings[ORIGINAL_KEY];
|
|
110
|
+
if (!original) {
|
|
111
|
+
return { ok: false, outcome: "not-active", message: `no ${ORIGINAL_KEY} in settings.json \u2014 statusline isn't wrapped by launch-kit` };
|
|
112
|
+
}
|
|
113
|
+
const restored = { ...settings, statusLine: original };
|
|
114
|
+
delete restored[ORIGINAL_KEY];
|
|
115
|
+
writeSettings(restored);
|
|
116
|
+
for (const p of [WRAPPER_PATH, CHIP_PATH]) {
|
|
117
|
+
try {
|
|
118
|
+
fs3.unlinkSync(p);
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { ok: true, outcome: "deactivated", message: "restored original statusLine.command" };
|
|
123
|
+
}
|
|
124
|
+
var fs3, path3, import_node_os, LK_DIR, WRAPPER_PATH, CHIP_PATH, SETTINGS_PATH, ORIGINAL_KEY;
|
|
125
|
+
var init_statusline_install = __esm({
|
|
126
|
+
"src/server/statusline-install.ts"() {
|
|
127
|
+
"use strict";
|
|
128
|
+
fs3 = __toESM(require("node:fs"));
|
|
129
|
+
path3 = __toESM(require("node:path"));
|
|
130
|
+
import_node_os = require("node:os");
|
|
131
|
+
LK_DIR = path3.join((0, import_node_os.homedir)(), ".launchsecure");
|
|
132
|
+
WRAPPER_PATH = path3.join(LK_DIR, "statusline-wrapper.sh");
|
|
133
|
+
CHIP_PATH = path3.join(LK_DIR, "statusline-mcp.sh");
|
|
134
|
+
SETTINGS_PATH = path3.join((0, import_node_os.homedir)(), ".claude", "settings.json");
|
|
135
|
+
ORIGINAL_KEY = "_launchKitStatuslineOriginal";
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
26
139
|
// src/server/init-entry.ts
|
|
27
140
|
var import_node_child_process = require("node:child_process");
|
|
28
|
-
var
|
|
141
|
+
var crypto = __toESM(require("node:crypto"));
|
|
142
|
+
var fs4 = __toESM(require("node:fs"));
|
|
29
143
|
var import_node_http = require("node:http");
|
|
30
144
|
var import_node_https = require("node:https");
|
|
31
|
-
var
|
|
145
|
+
var path4 = __toESM(require("node:path"));
|
|
32
146
|
var readline = __toESM(require("node:readline"));
|
|
33
147
|
var import_node_url = require("node:url");
|
|
34
148
|
|
|
@@ -95,12 +209,94 @@ function writeJsonAtomic(absPath, value, mode) {
|
|
|
95
209
|
fs.renameSync(tmp, absPath);
|
|
96
210
|
}
|
|
97
211
|
|
|
212
|
+
// src/server/cred-recovery.ts
|
|
213
|
+
var fs2 = __toESM(require("node:fs"));
|
|
214
|
+
var path2 = __toESM(require("node:path"));
|
|
215
|
+
var LEGACY_CONFIG_FILENAME = ".launch-secure.config";
|
|
216
|
+
function migrateLegacyCredFile(targetDir, opts) {
|
|
217
|
+
const legacy = path2.join(targetDir, LEGACY_CONFIG_FILENAME);
|
|
218
|
+
const dest = path2.join(targetDir, CONFIG_FILENAME);
|
|
219
|
+
if (!fs2.existsSync(legacy) || fs2.existsSync(dest)) return;
|
|
220
|
+
let parsed;
|
|
221
|
+
try {
|
|
222
|
+
parsed = JSON.parse(fs2.readFileSync(legacy, "utf-8"));
|
|
223
|
+
} catch {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const pat = parsed?.pat;
|
|
227
|
+
if (typeof pat !== "string" || !pat.startsWith("ls_pat_")) return;
|
|
228
|
+
if (opts.dryRun) {
|
|
229
|
+
opts.log.dryNote(`would migrate legacy ${LEGACY_CONFIG_FILENAME} \u2192 ${CONFIG_FILENAME} and strip the legacy gitignore line`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
fs2.renameSync(legacy, dest);
|
|
233
|
+
removeGitignoreLine(targetDir, LEGACY_CONFIG_FILENAME, opts);
|
|
234
|
+
opts.log.ok(`migrated legacy ${LEGACY_CONFIG_FILENAME} \u2192 ${CONFIG_FILENAME} (the old name is now reserved for file-backed-config)`);
|
|
235
|
+
}
|
|
236
|
+
function removeGitignoreLine(targetDir, line, opts) {
|
|
237
|
+
const p = path2.join(targetDir, ".gitignore");
|
|
238
|
+
if (!fs2.existsSync(p)) return;
|
|
239
|
+
const before = fs2.readFileSync(p, "utf-8");
|
|
240
|
+
const after = before.split(/\r?\n/).filter((l) => l.trim() !== line).join("\n");
|
|
241
|
+
if (after === before) return;
|
|
242
|
+
if (opts.dryRun) {
|
|
243
|
+
opts.log.dryNote(`would remove "${line}" from .gitignore`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
fs2.writeFileSync(p, after, "utf-8");
|
|
247
|
+
opts.log.ok(`removed ${line} from .gitignore (now reserved for file-backed-config)`);
|
|
248
|
+
}
|
|
249
|
+
function recoverCredFromMcp(targetDir) {
|
|
250
|
+
const p = path2.join(targetDir, ".mcp.json");
|
|
251
|
+
if (!fs2.existsSync(p)) return null;
|
|
252
|
+
let mcp;
|
|
253
|
+
try {
|
|
254
|
+
mcp = JSON.parse(fs2.readFileSync(p, "utf-8"));
|
|
255
|
+
} catch {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const entry = mcp.mcpServers?.["launch-secure"];
|
|
259
|
+
if (!entry?.headers || !entry.url) return null;
|
|
260
|
+
const auth = entry.headers["Authorization"];
|
|
261
|
+
const pat = typeof auth === "string" && auth.startsWith("Bearer ") ? auth.slice("Bearer ".length).trim() : null;
|
|
262
|
+
const orgSlug = entry.headers["X-Org-Slug"];
|
|
263
|
+
const projectSlug = entry.headers["X-Project-Slug"];
|
|
264
|
+
if (!pat || !pat.startsWith("ls_pat_") || !orgSlug || !projectSlug) return null;
|
|
265
|
+
const serverUrl = entry.url.replace(/\/api\/mcp\/project\/?$/, "").replace(/\/+$/, "");
|
|
266
|
+
return { pat, orgSlug, projectSlug, serverUrl };
|
|
267
|
+
}
|
|
268
|
+
function recoverCred(targetDir, opts) {
|
|
269
|
+
migrateLegacyCredFile(targetDir, opts);
|
|
270
|
+
let cred = null;
|
|
271
|
+
try {
|
|
272
|
+
cred = readCredFile(targetDir);
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
if (cred) return { cred, source: "cred-file" };
|
|
276
|
+
if (opts.dryRun) {
|
|
277
|
+
const legacyPath = path2.join(targetDir, LEGACY_CONFIG_FILENAME);
|
|
278
|
+
if (fs2.existsSync(legacyPath)) {
|
|
279
|
+
try {
|
|
280
|
+
const parsed = JSON.parse(fs2.readFileSync(legacyPath, "utf-8"));
|
|
281
|
+
if (typeof parsed?.pat === "string" && parsed.pat.startsWith("ls_pat_")) {
|
|
282
|
+
opts.log.info(`(dry-run) using legacy ${LEGACY_CONFIG_FILENAME} for preview \u2014 a real run would migrate it to ${CONFIG_FILENAME} first`);
|
|
283
|
+
return { cred: parsed, source: "legacy-dry-run" };
|
|
284
|
+
}
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const recovered = recoverCredFromMcp(targetDir);
|
|
290
|
+
if (recovered) return { cred: recovered, source: "mcp" };
|
|
291
|
+
return { cred: null, source: null };
|
|
292
|
+
}
|
|
293
|
+
|
|
98
294
|
// src/server/init-entry.ts
|
|
295
|
+
init_statusline_install();
|
|
99
296
|
var DEFAULT_SERVER_URL = "https://launchsecure-v2.vercel.app";
|
|
100
|
-
var LEGACY_CONFIG_FILENAME = ".launch-secure.config";
|
|
101
297
|
var ONBOARD_SCRIPT_NAME = "onboard";
|
|
102
298
|
var LAUNCH_KIT_PKG = "@launchsecure/launch-kit";
|
|
103
|
-
var
|
|
299
|
+
var LAUNCH_KIT_TOOLS_GUIDE_STATIC_HEAD = `
|
|
104
300
|
Wired in Claude Code (.mcp.json):
|
|
105
301
|
launch-secure \u2014 LS API: work items, comms, secrets, members, board
|
|
106
302
|
launch-chart \u2014 code search + project graph (use instead of grep/glob)
|
|
@@ -115,41 +311,61 @@ Other tools (run on demand via npx):
|
|
|
115
311
|
in-browser monitor. Paste the printed URL into
|
|
116
312
|
the beacon debug panel; events stream to
|
|
117
313
|
.launchsecure/beacon-<token>.ndjson for the
|
|
118
|
-
/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
/ls:activate-beacon \u2014 wire the launch-kit-beacon in-app feedback
|
|
122
|
-
widget into this app (mounts the <launch-kit-
|
|
123
|
-
beacon> Web Component + scaffolds /api/feedback
|
|
124
|
-
forwarding to LaunchSecure Comm Hub)
|
|
125
|
-
/ls:standup \u2014 draft a daily standup from work since the last
|
|
126
|
-
push (chart-grouped themes, work-item linkage,
|
|
127
|
-
release detection) and post to LS Comm Hub as
|
|
128
|
-
a daily_update after you confirm
|
|
129
|
-
/ls:show-mcp-status \u2014 show recall watcher health + last snapshot.
|
|
130
|
-
Add 'full' for expanded report (PID, shadow
|
|
131
|
-
repo size, recent snaps)
|
|
132
|
-
/ls:beacon-scan \u2014 scan recent events from the active
|
|
133
|
-
launch-beacon monitor session. Pass a kind
|
|
134
|
-
(error/click/fetch/route/dialog/probe) and/or
|
|
135
|
-
a limit to filter.
|
|
136
|
-
/ls:beacon-pulse \u2014 most recent error + the N events that preceded
|
|
137
|
-
it. "What was happening just before it broke."
|
|
138
|
-
/ls:beacon-array \u2014 list monitor sessions in .launchsecure/ with
|
|
139
|
-
event counts, last activity, liveness glyph.
|
|
140
|
-
Add 'full' for a per-session expanded report.
|
|
141
|
-
/ls:beacon-clear \u2014 wipe the latest monitor session NDJSON (or
|
|
142
|
-
'all'). Confirms before deleting.
|
|
143
|
-
|
|
314
|
+
/kit:beacon-* commands below to read.
|
|
315
|
+
`;
|
|
316
|
+
var LAUNCH_KIT_TOOLS_GUIDE_STATIC_TAIL = `
|
|
144
317
|
Open this repo in Claude Code; on first open you'll be prompted to install
|
|
145
|
-
the "
|
|
318
|
+
the "launch-secure" marketplace \u2014 accept to enable the commands above.
|
|
319
|
+
`;
|
|
320
|
+
function renderEntries(dir, kindLabel) {
|
|
321
|
+
if (!fs4.existsSync(dir)) return `
|
|
322
|
+
LS slash ${kindLabel}: (scaffold dir not bundled \u2014 this is a packaging bug)
|
|
323
|
+
`;
|
|
324
|
+
const files = fs4.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
325
|
+
if (files.length === 0) return `
|
|
326
|
+
LS slash ${kindLabel}: (none defined)
|
|
327
|
+
`;
|
|
328
|
+
const names = files.map((file) => `/${PLUGIN_ID}:${file.replace(/\.md$/, "")}`);
|
|
329
|
+
const colWidth = Math.max(26, ...names.map((n) => n.length + 2));
|
|
330
|
+
const lines = files.map((file, i) => {
|
|
331
|
+
const text = fs4.readFileSync(path4.join(dir, file), "utf-8");
|
|
332
|
+
const fmMatch = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
333
|
+
const desc = fmMatch?.[1].match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
|
|
334
|
+
return ` ${names[i].padEnd(colWidth)} \u2014 ${desc}`;
|
|
335
|
+
});
|
|
336
|
+
return `
|
|
337
|
+
LS slash ${kindLabel} (run inside Claude Code in this project):
|
|
338
|
+
${lines.join("\n")}
|
|
146
339
|
`;
|
|
340
|
+
}
|
|
341
|
+
function renderLsCommandsSection() {
|
|
342
|
+
const base = path4.resolve(__dirname, "..", "..", "scaffolds", "ls-marketplace", "plugins", "kit");
|
|
343
|
+
return renderEntries(path4.join(base, "commands"), "commands") + renderEntries(path4.join(base, "skills"), "skills");
|
|
344
|
+
}
|
|
345
|
+
function getLaunchKitToolsGuide() {
|
|
346
|
+
return `${LAUNCH_KIT_TOOLS_GUIDE_STATIC_HEAD}${renderLsCommandsSection()}${LAUNCH_KIT_TOOLS_GUIDE_STATIC_TAIL}`;
|
|
347
|
+
}
|
|
147
348
|
var PACKAGE_MANAGERS = [
|
|
148
349
|
{ name: "pnpm", binary: "pnpm", lockfiles: ["pnpm-lock.yaml"], workspaceFiles: ["pnpm-workspace.yaml"], installArgs: ["install"] },
|
|
149
350
|
{ name: "yarn", binary: "yarn", lockfiles: ["yarn.lock"], installArgs: ["install"] },
|
|
150
351
|
{ name: "bun", binary: "bun", lockfiles: ["bun.lockb", "bun.lock"], installArgs: ["install"] },
|
|
151
352
|
{ name: "npm", binary: "npm", lockfiles: ["package-lock.json"], installArgs: ["install"] }
|
|
152
353
|
];
|
|
354
|
+
var KNOWN_BOOL_FLAGS = /* @__PURE__ */ new Set([
|
|
355
|
+
"--help",
|
|
356
|
+
"-h",
|
|
357
|
+
"--no-install",
|
|
358
|
+
"--no-onboard",
|
|
359
|
+
"--no-recall",
|
|
360
|
+
"--no-migrate-safety",
|
|
361
|
+
"--no-ls-marketplace",
|
|
362
|
+
"--no-recall-hook",
|
|
363
|
+
"--refresh-scaffolds",
|
|
364
|
+
"--quiet",
|
|
365
|
+
"--force",
|
|
366
|
+
"--dry-run"
|
|
367
|
+
]);
|
|
368
|
+
var KNOWN_KV_KEYS = /* @__PURE__ */ new Set(["token", "org", "project", "url", "dir", "course"]);
|
|
153
369
|
function parseArgs(argv) {
|
|
154
370
|
const args = {
|
|
155
371
|
token: process.env.LS_PAT ?? null,
|
|
@@ -159,14 +375,21 @@ function parseArgs(argv) {
|
|
|
159
375
|
targetDir: null,
|
|
160
376
|
course: null,
|
|
161
377
|
noInstall: false,
|
|
378
|
+
noOnboard: false,
|
|
162
379
|
noRecall: false,
|
|
163
380
|
noMigrateSafety: false,
|
|
164
381
|
noLsMarketplace: false,
|
|
165
382
|
noRecallHook: false,
|
|
383
|
+
refreshScaffolds: false,
|
|
384
|
+
quiet: false,
|
|
385
|
+
force: false,
|
|
166
386
|
dryRun: false,
|
|
167
387
|
help: false
|
|
168
388
|
};
|
|
169
|
-
|
|
389
|
+
const unknown = [];
|
|
390
|
+
for (let i = 0; i < argv.length; i++) {
|
|
391
|
+
const raw = argv[i];
|
|
392
|
+
if (i === 0 && !raw.startsWith("--") && !raw.startsWith("-")) continue;
|
|
170
393
|
if (raw === "--help" || raw === "-h") {
|
|
171
394
|
args.help = true;
|
|
172
395
|
continue;
|
|
@@ -175,6 +398,10 @@ function parseArgs(argv) {
|
|
|
175
398
|
args.noInstall = true;
|
|
176
399
|
continue;
|
|
177
400
|
}
|
|
401
|
+
if (raw === "--no-onboard") {
|
|
402
|
+
args.noOnboard = true;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
178
405
|
if (raw === "--no-recall") {
|
|
179
406
|
args.noRecall = true;
|
|
180
407
|
continue;
|
|
@@ -191,28 +418,116 @@ function parseArgs(argv) {
|
|
|
191
418
|
args.noRecallHook = true;
|
|
192
419
|
continue;
|
|
193
420
|
}
|
|
421
|
+
if (raw === "--refresh-scaffolds") {
|
|
422
|
+
args.refreshScaffolds = true;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (raw === "--quiet") {
|
|
426
|
+
args.quiet = true;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (raw === "--force") {
|
|
430
|
+
args.force = true;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
194
433
|
if (raw === "--dry-run") {
|
|
195
434
|
args.dryRun = true;
|
|
196
435
|
continue;
|
|
197
436
|
}
|
|
198
437
|
const eq = raw.indexOf("=");
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
438
|
+
if (raw.startsWith("--") && eq > 0) {
|
|
439
|
+
const key = raw.slice(2, eq);
|
|
440
|
+
const val = raw.slice(eq + 1);
|
|
441
|
+
if (key === "token") {
|
|
442
|
+
args.token = val;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
if (key === "org") {
|
|
446
|
+
args.orgSlug = val;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (key === "project") {
|
|
450
|
+
args.projectSlug = val;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (key === "url") {
|
|
454
|
+
args.serverUrl = val.replace(/\/+$/, "");
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
if (key === "dir") {
|
|
458
|
+
args.targetDir = val;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (key === "course") {
|
|
462
|
+
args.course = val;
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
unknown.push(raw);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (raw.startsWith("-")) {
|
|
469
|
+
unknown.push(raw);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
unknown.push(raw);
|
|
473
|
+
}
|
|
474
|
+
if (unknown.length > 0) {
|
|
475
|
+
const knownBool = [...KNOWN_BOOL_FLAGS].join(", ");
|
|
476
|
+
const knownKv = [...KNOWN_KV_KEYS].map((k) => `--${k}=<value>`).join(", ");
|
|
477
|
+
fail(`Unknown argument(s): ${unknown.join(" ")}
|
|
478
|
+
Known boolean flags: ${knownBool}
|
|
479
|
+
Known key=value flags: ${knownKv}`);
|
|
208
480
|
}
|
|
209
481
|
return args;
|
|
210
482
|
}
|
|
483
|
+
function printRefreshHelp() {
|
|
484
|
+
console.log(`launch-kit refresh \u2014 re-apply launch-kit scaffolds in an already-initialized project
|
|
485
|
+
|
|
486
|
+
Usage:
|
|
487
|
+
npx @launchsecure/launch-kit@latest refresh [--dir=<path>] [options]
|
|
488
|
+
|
|
489
|
+
What it does:
|
|
490
|
+
Re-merges .mcp.json with the latest launch-kit entries (preserves your other
|
|
491
|
+
entries), refreshes the launch-secure marketplace tree at .claude/marketplace/
|
|
492
|
+
(picks up any new /kit:* commands), refreshes scripts/ensure-recall.sh and
|
|
493
|
+
the SessionStart hook, and refreshes the migrate-safety scaffold.
|
|
494
|
+
|
|
495
|
+
Reads org/project/serverUrl/pat from the existing .launch-secure.cred.config.
|
|
496
|
+
Does NOT clone, re-install deps, re-prompt for PAT, or re-run the onboard
|
|
497
|
+
script. Use \`init\` for those.
|
|
498
|
+
|
|
499
|
+
Options:
|
|
500
|
+
--dir=<path> Target directory (default: cwd). Must contain a
|
|
501
|
+
valid .launch-secure.cred.config.
|
|
502
|
+
--no-migrate-safety Skip refreshing the migrate-safety scaffold.
|
|
503
|
+
--no-ls-marketplace Skip refreshing the launch-secure marketplace.
|
|
504
|
+
--no-recall-hook Skip refreshing the recall-hook scaffold.
|
|
505
|
+
--refresh-scaffolds Force-overwrite migrate-safety files (default is to
|
|
506
|
+
preserve user edits). Use this to pull updates
|
|
507
|
+
published to @launchsecure/launch-kit.
|
|
508
|
+
--quiet Suppress the post-run tools guide.
|
|
509
|
+
--dry-run Preview every file write without making changes.
|
|
510
|
+
--help Show this help.
|
|
511
|
+
|
|
512
|
+
Tip: prefix the command with @latest (\`launch-kit@latest refresh\`) to force
|
|
513
|
+
npx to pull the newest published version instead of using a cached older one.
|
|
514
|
+
`);
|
|
515
|
+
}
|
|
211
516
|
function printHelp() {
|
|
212
|
-
console.log(`launch-kit
|
|
517
|
+
console.log(`launch-kit \u2014 bootstrap and refresh a LaunchSecure project on this machine
|
|
518
|
+
|
|
519
|
+
Subcommands:
|
|
520
|
+
init Bootstrap a new project (clone, cred file, MCP, scaffolds, install)
|
|
521
|
+
refresh Re-apply scaffolds + MCP entries in an already-initialized project
|
|
522
|
+
(no clone, no install, no PAT prompt \u2014 see \`launch-kit refresh --help\`)
|
|
523
|
+
statusline activate Wrap ~/.claude/settings.json's statusLine.command so MCP daemon
|
|
524
|
+
chips (recall, chart, deck, council) get appended. Refuses to
|
|
525
|
+
create one if none exists \u2014 additive only.
|
|
526
|
+
statusline deactivate Restore the original statusLine.command and remove kit scripts.
|
|
213
527
|
|
|
214
528
|
Usage:
|
|
215
|
-
npx launch-kit init --token=<pat> --org=<orgSlug> --project=<projectSlug> [options]
|
|
529
|
+
npx @launchsecure/launch-kit init --token=<pat> --org=<orgSlug> --project=<projectSlug> [options]
|
|
530
|
+
npx @launchsecure/launch-kit@latest refresh [--dir=<path>] [options]
|
|
216
531
|
|
|
217
532
|
Required:
|
|
218
533
|
--token=<pat> LaunchSecure PAT (ls_pat_...). Or set LS_PAT env var.
|
|
@@ -229,19 +544,34 @@ Options:
|
|
|
229
544
|
becomes active; re-run with a different --course
|
|
230
545
|
and --url to add another (e.g. local + staging).
|
|
231
546
|
Use \`launch-course set <name>\` to switch later.
|
|
232
|
-
--no-install Skip dependency install step
|
|
547
|
+
--no-install Skip dependency install step (also skips the onboard
|
|
548
|
+
script \u2014 install is its prerequisite).
|
|
549
|
+
--no-onboard Skip the onboard script even when install runs.
|
|
233
550
|
--no-recall Skip launch-recall (shadow git backup) scaffold.
|
|
234
551
|
--no-migrate-safety Skip migrate-safety scaffold (pg_dump-before-migrate
|
|
235
552
|
wrapper + GitHub Action + runbook).
|
|
236
|
-
--no-ls-marketplace Skip the Claude Code "
|
|
553
|
+
--no-ls-marketplace Skip the Claude Code "launch-secure" marketplace
|
|
237
554
|
scaffold (.claude/marketplace/ + .claude/settings.json
|
|
238
|
-
wiring \u2014 exposes /
|
|
555
|
+
wiring \u2014 exposes /kit:activate-beacon and future
|
|
239
556
|
ls-namespaced slash commands).
|
|
240
557
|
--no-recall-hook Skip the SessionStart hook scaffold (Claude Code
|
|
241
558
|
hook + scripts/ensure-recall.sh that auto-restarts
|
|
242
559
|
the launch-recall watcher if it died between
|
|
243
560
|
sessions). The hook is the surfacing layer for
|
|
244
561
|
watcher-died-silently scenarios.
|
|
562
|
+
--refresh-scaffolds Force-overwrite migrate-safety scaffold files even
|
|
563
|
+
when they already exist. Default behavior preserves
|
|
564
|
+
user edits; use this to pull updates published to
|
|
565
|
+
@launchsecure/launch-kit (e.g., a newer
|
|
566
|
+
migrate-with-backup.sh).
|
|
567
|
+
--quiet Suppress the post-run tools guide (the long block
|
|
568
|
+
listing /kit:* commands and wired MCPs). Useful for
|
|
569
|
+
idempotent re-runs in CI or scripts.
|
|
570
|
+
--force Skip the auto-delegate-to-refresh check. By default
|
|
571
|
+
init detects an existing bootstrap (cred file +
|
|
572
|
+
launch-secure MCP entry) and runs refresh instead.
|
|
573
|
+
Pass --force to re-init from scratch even when the
|
|
574
|
+
target dir is already set up.
|
|
245
575
|
--dry-run Preview every file write, merge, clone, and install
|
|
246
576
|
command without making any changes. Useful before
|
|
247
577
|
re-running init against a customized project. The
|
|
@@ -265,10 +595,10 @@ What it does:
|
|
|
265
595
|
8. Scaffolds launch-recall (shadow git backup). Skip with --no-recall.
|
|
266
596
|
9. Scaffolds migrate-safety (pg_dump wrapper + GHA backup workflow +
|
|
267
597
|
runbook + .backups/ gitignore line). Skip with --no-migrate-safety.
|
|
268
|
-
10. Scaffolds the Claude Code "
|
|
598
|
+
10. Scaffolds the Claude Code "launch-secure" marketplace at
|
|
269
599
|
.claude/marketplace/ and wires .claude/settings.json so Claude Code
|
|
270
|
-
auto-discovers it and enables the "
|
|
271
|
-
/
|
|
600
|
+
auto-discovers it and enables the "kit" plugin (exposes
|
|
601
|
+
/kit:activate-beacon for wiring the launch-kit-beacon in-app feedback
|
|
272
602
|
widget). Skip with --no-ls-marketplace.
|
|
273
603
|
11. Scaffolds scripts/ensure-recall.sh and appends a SessionStart hook to
|
|
274
604
|
.claude/settings.json that respawns the launch-recall watcher if it
|
|
@@ -278,9 +608,9 @@ What it does:
|
|
|
278
608
|
}
|
|
279
609
|
async function prompt(question) {
|
|
280
610
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
281
|
-
return new Promise((
|
|
611
|
+
return new Promise((resolve3) => rl.question(question, (answer) => {
|
|
282
612
|
rl.close();
|
|
283
|
-
|
|
613
|
+
resolve3(answer.trim());
|
|
284
614
|
}));
|
|
285
615
|
}
|
|
286
616
|
function fail(msg) {
|
|
@@ -310,8 +640,17 @@ function preflight() {
|
|
|
310
640
|
ok(`preflight ok \u2014 node ${process.versions.node}, git present${hasGh ? ", gh present" : ", gh not found (will use git for clone)"}`);
|
|
311
641
|
return { hasGh };
|
|
312
642
|
}
|
|
313
|
-
|
|
314
|
-
|
|
643
|
+
var PROJECT_INFO_TIMEOUT_MS = 3e4;
|
|
644
|
+
var PROJECT_INFO_MAX_ATTEMPTS = 3;
|
|
645
|
+
var ProjectInfoHttpError = class extends Error {
|
|
646
|
+
constructor(status, retryable, message) {
|
|
647
|
+
super(message);
|
|
648
|
+
this.status = status;
|
|
649
|
+
this.retryable = retryable;
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
function attemptProjectInfo(args) {
|
|
653
|
+
return new Promise((resolve3, reject) => {
|
|
315
654
|
const mcpUrl = new import_node_url.URL("/api/mcp/project", args.serverUrl);
|
|
316
655
|
const body = JSON.stringify({
|
|
317
656
|
jsonrpc: "2.0",
|
|
@@ -343,12 +682,24 @@ function callProjectInfo(args) {
|
|
|
343
682
|
res.on("data", (c) => chunks.push(c));
|
|
344
683
|
res.on("end", () => {
|
|
345
684
|
const text = Buffer.concat(chunks).toString("utf-8");
|
|
346
|
-
if (res.statusCode === 401
|
|
347
|
-
reject(new
|
|
685
|
+
if (res.statusCode === 401) {
|
|
686
|
+
reject(new ProjectInfoHttpError(401, false, `PAT rejected (401). Token is invalid or expired. Generate a new PAT at ${args.serverUrl}/settings/tokens and retry.`));
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (res.statusCode === 403) {
|
|
690
|
+
reject(new ProjectInfoHttpError(403, false, `Access denied (403). Token is valid but lacks access to ${args.orgSlug}/${args.projectSlug}. Check membership/permissions on the project.`));
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (res.statusCode === 404) {
|
|
694
|
+
reject(new ProjectInfoHttpError(404, false, `Project not found (404). Verify ${args.orgSlug}/${args.projectSlug} exists at ${args.serverUrl}.`));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (res.statusCode && res.statusCode >= 500) {
|
|
698
|
+
reject(new ProjectInfoHttpError(res.statusCode, true, `LaunchSecure server error ${res.statusCode}: ${text.slice(0, 200)}`));
|
|
348
699
|
return;
|
|
349
700
|
}
|
|
350
701
|
if (res.statusCode && res.statusCode >= 400) {
|
|
351
|
-
reject(new
|
|
702
|
+
reject(new ProjectInfoHttpError(res.statusCode, false, `LaunchSecure responded ${res.statusCode}: ${text.slice(0, 300)}`));
|
|
352
703
|
return;
|
|
353
704
|
}
|
|
354
705
|
let json = text;
|
|
@@ -377,7 +728,7 @@ function callProjectInfo(args) {
|
|
|
377
728
|
return;
|
|
378
729
|
}
|
|
379
730
|
const payload = JSON.parse(inner);
|
|
380
|
-
|
|
731
|
+
resolve3({
|
|
381
732
|
orgSlug: payload.org.slug,
|
|
382
733
|
projectSlug: payload.project.slug,
|
|
383
734
|
projectName: payload.project.name,
|
|
@@ -389,11 +740,34 @@ function callProjectInfo(args) {
|
|
|
389
740
|
});
|
|
390
741
|
}
|
|
391
742
|
);
|
|
392
|
-
req.
|
|
743
|
+
req.setTimeout(PROJECT_INFO_TIMEOUT_MS, () => {
|
|
744
|
+
req.destroy(new Error(`project_info timed out after ${PROJECT_INFO_TIMEOUT_MS / 1e3}s`));
|
|
745
|
+
});
|
|
746
|
+
req.on("error", (err) => {
|
|
747
|
+
const code = err.code;
|
|
748
|
+
const retryable = code === "ECONNRESET" || code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "ENOTFOUND" || code === "EAI_AGAIN" || /timed out/.test(err.message);
|
|
749
|
+
reject(retryable ? new ProjectInfoHttpError(0, true, err.message) : err);
|
|
750
|
+
});
|
|
393
751
|
req.write(body);
|
|
394
752
|
req.end();
|
|
395
753
|
});
|
|
396
754
|
}
|
|
755
|
+
async function callProjectInfo(args) {
|
|
756
|
+
let lastErr;
|
|
757
|
+
for (let attempt = 1; attempt <= PROJECT_INFO_MAX_ATTEMPTS; attempt++) {
|
|
758
|
+
try {
|
|
759
|
+
return await attemptProjectInfo(args);
|
|
760
|
+
} catch (err) {
|
|
761
|
+
lastErr = err;
|
|
762
|
+
const retryable = err instanceof ProjectInfoHttpError && err.retryable;
|
|
763
|
+
if (!retryable || attempt === PROJECT_INFO_MAX_ATTEMPTS) break;
|
|
764
|
+
const delayMs = 1e3 * 2 ** (attempt - 1);
|
|
765
|
+
info(`project_info attempt ${attempt}/${PROJECT_INFO_MAX_ATTEMPTS} failed (${err instanceof Error ? err.message : String(err)}) \u2014 retrying in ${delayMs}ms`);
|
|
766
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
throw lastErr;
|
|
770
|
+
}
|
|
397
771
|
function gitRemoteUrl(dir) {
|
|
398
772
|
const res = (0, import_node_child_process.spawnSync)("git", ["-C", dir, "config", "--get", "remote.origin.url"], { encoding: "utf-8" });
|
|
399
773
|
if (res.status !== 0) return null;
|
|
@@ -411,11 +785,11 @@ function normalizeRepoUrl(url) {
|
|
|
411
785
|
}
|
|
412
786
|
}
|
|
413
787
|
function isGitRepo(dir) {
|
|
414
|
-
return
|
|
788
|
+
return fs4.existsSync(path4.join(dir, ".git"));
|
|
415
789
|
}
|
|
416
790
|
function dirIsEmpty(dir) {
|
|
417
|
-
if (!
|
|
418
|
-
return
|
|
791
|
+
if (!fs4.existsSync(dir)) return true;
|
|
792
|
+
return fs4.readdirSync(dir).length === 0;
|
|
419
793
|
}
|
|
420
794
|
function cloneRepo(repoUrl, targetDir, hasGh) {
|
|
421
795
|
const isGithub = /github\.com/i.test(repoUrl);
|
|
@@ -440,11 +814,14 @@ function cloneRepo(repoUrl, targetDir, hasGh) {
|
|
|
440
814
|
`Clone failed (${cmd} exited ${res.status}). For private repos make sure your GitHub auth is set up: \`gh auth login\` or an SSH key on your GitHub account.`
|
|
441
815
|
);
|
|
442
816
|
}
|
|
817
|
+
if (!fs4.existsSync(path4.join(targetDir, ".git"))) {
|
|
818
|
+
fail(`Clone reported success but .git is missing at ${targetDir}. Possible partial clone, filesystem issue, or sandboxing \u2014 investigate manually.`);
|
|
819
|
+
}
|
|
443
820
|
ok(`cloned to ${targetDir}`);
|
|
444
821
|
}
|
|
445
822
|
function writeConfigFile(targetDir, cfg, courseName) {
|
|
446
|
-
|
|
447
|
-
const p =
|
|
823
|
+
recoverCred(targetDir, getRecoveryOptions());
|
|
824
|
+
const p = path4.join(targetDir, CONFIG_FILENAME);
|
|
448
825
|
const existing = readCredFile(targetDir);
|
|
449
826
|
const isNew = existing === null;
|
|
450
827
|
const isUpdate = !isNew && Boolean(existing?.profiles?.[courseName]);
|
|
@@ -458,38 +835,26 @@ function writeConfigFile(targetDir, cfg, courseName) {
|
|
|
458
835
|
const action = isNew ? "wrote" : isUpdate ? `updated course "${courseName}" in` : `added course "${courseName}" to`;
|
|
459
836
|
ok(`${action} ${CONFIG_FILENAME} (active: ${courseName})`);
|
|
460
837
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
} catch {
|
|
469
|
-
return;
|
|
838
|
+
var recoveryLog = { info, ok, dryNote };
|
|
839
|
+
function getRecoveryOptions() {
|
|
840
|
+
return { dryRun: DRY_RUN, log: recoveryLog };
|
|
841
|
+
}
|
|
842
|
+
function detectExistingBootstrap(targetDir) {
|
|
843
|
+
if (!fs4.existsSync(path4.join(targetDir, CONFIG_FILENAME))) {
|
|
844
|
+
return { bootstrapped: false };
|
|
470
845
|
}
|
|
471
|
-
const
|
|
472
|
-
if (
|
|
473
|
-
|
|
474
|
-
dryNote(`would migrate legacy ${LEGACY_CONFIG_FILENAME} \u2192 ${CONFIG_FILENAME} and strip the legacy gitignore line`);
|
|
475
|
-
return;
|
|
846
|
+
const mcpPath = path4.join(targetDir, ".mcp.json");
|
|
847
|
+
if (!fs4.existsSync(mcpPath)) {
|
|
848
|
+
return { bootstrapped: false };
|
|
476
849
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (!fs2.existsSync(p)) return;
|
|
484
|
-
const before = fs2.readFileSync(p, "utf-8");
|
|
485
|
-
const after = before.split(/\r?\n/).filter((l) => l.trim() !== line).join("\n");
|
|
486
|
-
if (after === before) return;
|
|
487
|
-
if (DRY_RUN) {
|
|
488
|
-
dryNote(`would remove "${line}" from .gitignore`);
|
|
489
|
-
return;
|
|
850
|
+
try {
|
|
851
|
+
const mcp = JSON.parse(fs4.readFileSync(mcpPath, "utf-8"));
|
|
852
|
+
if (mcp.mcpServers?.["launch-secure"]) {
|
|
853
|
+
return { bootstrapped: true, reason: `${CONFIG_FILENAME} present + launch-secure MCP entry in .mcp.json` };
|
|
854
|
+
}
|
|
855
|
+
} catch {
|
|
490
856
|
}
|
|
491
|
-
|
|
492
|
-
ok(`removed ${line} from .gitignore (now reserved for file-backed-config)`);
|
|
857
|
+
return { bootstrapped: false };
|
|
493
858
|
}
|
|
494
859
|
var LAUNCH_SECURE_HEADERS_HELPER = `node -e 'const j=JSON.parse(require("fs").readFileSync(".launch-secure.cred.config","utf-8"));const c=j.profiles&&j.active?j.profiles[j.active]:j;process.stdout.write(JSON.stringify({Authorization:"Bearer "+c.pat,"X-Org-Slug":c.orgSlug,"X-Project-Slug":c.projectSlug}))'`;
|
|
495
860
|
function buildLaunchKitMcpEntries(cfg) {
|
|
@@ -502,6 +867,11 @@ function buildLaunchKitMcpEntries(cfg) {
|
|
|
502
867
|
"launch-chart": {
|
|
503
868
|
command: "npx",
|
|
504
869
|
args: ["-y", "-p", LAUNCH_KIT_PKG, "launch-chart"],
|
|
870
|
+
// Tells launch-chart to also start its HTTP UI alongside the MCP, so
|
|
871
|
+
// users can open the chart viewer at localhost:<port> while queries
|
|
872
|
+
// hit the MCP. Without this, the MCP runs passively (queries work,
|
|
873
|
+
// no UI). I4 deep-merge preserves user-added env keys; this default
|
|
874
|
+
// ensures the auto-serve UX ships out of the box.
|
|
505
875
|
env: { LAUNCH_CHART_AUTOSERVE: "1" }
|
|
506
876
|
},
|
|
507
877
|
"launch-deck": {
|
|
@@ -518,13 +888,29 @@ function buildLaunchKitMcpEntries(cfg) {
|
|
|
518
888
|
}
|
|
519
889
|
};
|
|
520
890
|
}
|
|
891
|
+
function mergeMcpEntry(existing, ours) {
|
|
892
|
+
const merged = { ...existing, ...ours };
|
|
893
|
+
if (existing.headers || ours.headers) {
|
|
894
|
+
const authKeys = /* @__PURE__ */ new Set(["Authorization", "X-Org-Slug", "X-Project-Slug"]);
|
|
895
|
+
const baseHeaders = ours.headersHelper ? Object.fromEntries(Object.entries(existing.headers ?? {}).filter(([k]) => !authKeys.has(k))) : { ...existing.headers ?? {} };
|
|
896
|
+
const combinedHeaders = { ...baseHeaders, ...ours.headers ?? {} };
|
|
897
|
+
if (Object.keys(combinedHeaders).length > 0) merged.headers = combinedHeaders;
|
|
898
|
+
else delete merged.headers;
|
|
899
|
+
}
|
|
900
|
+
if (existing.env || ours.env) {
|
|
901
|
+
const combinedEnv = { ...existing.env ?? {}, ...ours.env ?? {} };
|
|
902
|
+
if (Object.keys(combinedEnv).length > 0) merged.env = combinedEnv;
|
|
903
|
+
else delete merged.env;
|
|
904
|
+
}
|
|
905
|
+
return merged;
|
|
906
|
+
}
|
|
521
907
|
function mergeMcpFile(targetDir, launchKitEntries) {
|
|
522
|
-
const p =
|
|
523
|
-
const hadExisting =
|
|
908
|
+
const p = path4.join(targetDir, ".mcp.json");
|
|
909
|
+
const hadExisting = fs4.existsSync(p);
|
|
524
910
|
let existing = {};
|
|
525
911
|
if (hadExisting) {
|
|
526
912
|
try {
|
|
527
|
-
existing = JSON.parse(
|
|
913
|
+
existing = JSON.parse(fs4.readFileSync(p, "utf-8"));
|
|
528
914
|
} catch (err) {
|
|
529
915
|
fail(`Could not parse existing .mcp.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
530
916
|
}
|
|
@@ -534,24 +920,29 @@ function mergeMcpFile(targetDir, launchKitEntries) {
|
|
|
534
920
|
const overwrites = [];
|
|
535
921
|
const additions = [];
|
|
536
922
|
for (const [name, entry] of Object.entries(launchKitEntries)) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
923
|
+
const existingEntry = merged.mcpServers[name];
|
|
924
|
+
if (existingEntry) {
|
|
925
|
+
overwrites.push(name);
|
|
926
|
+
merged.mcpServers[name] = mergeMcpEntry(existingEntry, entry);
|
|
927
|
+
} else {
|
|
928
|
+
additions.push(name);
|
|
929
|
+
merged.mcpServers[name] = entry;
|
|
930
|
+
}
|
|
540
931
|
}
|
|
541
932
|
if (DRY_RUN) {
|
|
542
933
|
const action2 = hadExisting && existingServerCount > 0 ? "would merge into" : "would write";
|
|
543
934
|
dryNote(`${action2} .mcp.json \u2014 overwriting [${overwrites.join(", ") || "none"}], adding [${additions.join(", ") || "none"}]`);
|
|
544
935
|
return;
|
|
545
936
|
}
|
|
546
|
-
|
|
937
|
+
fs4.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
547
938
|
const action = hadExisting && existingServerCount > 0 ? "merged into" : "wrote";
|
|
548
939
|
ok(`${action} .mcp.json (${Object.keys(launchKitEntries).length} launch-kit entries)`);
|
|
549
940
|
}
|
|
550
941
|
function detectPackageManager(repoDir) {
|
|
551
|
-
const pkgPath =
|
|
552
|
-
if (!
|
|
942
|
+
const pkgPath = path4.join(repoDir, "package.json");
|
|
943
|
+
if (!fs4.existsSync(pkgPath)) return null;
|
|
553
944
|
try {
|
|
554
|
-
const pkg = JSON.parse(
|
|
945
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
555
946
|
if (typeof pkg.packageManager === "string") {
|
|
556
947
|
const name = pkg.packageManager.split("@")[0];
|
|
557
948
|
const match = PACKAGE_MANAGERS.find((p) => p.name === name);
|
|
@@ -560,7 +951,7 @@ function detectPackageManager(repoDir) {
|
|
|
560
951
|
}
|
|
561
952
|
} catch {
|
|
562
953
|
}
|
|
563
|
-
const matches = PACKAGE_MANAGERS.map((pm) => ({ pm, lockfile: pm.lockfiles.find((lf) =>
|
|
954
|
+
const matches = PACKAGE_MANAGERS.map((pm) => ({ pm, lockfile: pm.lockfiles.find((lf) => fs4.existsSync(path4.join(repoDir, lf))) ?? null })).filter((m) => m.lockfile !== null);
|
|
564
955
|
if (matches.length === 1) {
|
|
565
956
|
return { pm: matches[0].pm, source: `lockfile ${matches[0].lockfile}` };
|
|
566
957
|
}
|
|
@@ -569,8 +960,8 @@ function detectPackageManager(repoDir) {
|
|
|
569
960
|
return { pm: matches[0].pm, source: `lockfile ${matches[0].lockfile} (multiple present)` };
|
|
570
961
|
}
|
|
571
962
|
for (const pm of PACKAGE_MANAGERS) {
|
|
572
|
-
if (pm.workspaceFiles?.some((wf) =>
|
|
573
|
-
return { pm, source: `workspace file (${pm.workspaceFiles.find((wf) =>
|
|
963
|
+
if (pm.workspaceFiles?.some((wf) => fs4.existsSync(path4.join(repoDir, wf)))) {
|
|
964
|
+
return { pm, source: `workspace file (${pm.workspaceFiles.find((wf) => fs4.existsSync(path4.join(repoDir, wf)))})` };
|
|
574
965
|
}
|
|
575
966
|
}
|
|
576
967
|
const npm = PACKAGE_MANAGERS.find((p) => p.name === "npm");
|
|
@@ -580,7 +971,7 @@ function runInstall(repoDir, detected) {
|
|
|
580
971
|
const { pm } = detected;
|
|
581
972
|
if (!which(pm.binary)) {
|
|
582
973
|
fail(
|
|
583
|
-
`${pm.name} not found on PATH. Configs and clone are intact. Install ${pm.name} (try \`corepack enable\` if you have Node \u226516), then run: cd ${
|
|
974
|
+
`${pm.name} not found on PATH. Configs and clone are intact. Install ${pm.name} (try \`corepack enable\` if you have Node \u226516), then run: cd ${path4.basename(repoDir)} && ${pm.binary} ${pm.installArgs.join(" ")}`
|
|
584
975
|
);
|
|
585
976
|
}
|
|
586
977
|
info(`running ${pm.binary} ${pm.installArgs.join(" ")} \u2026`);
|
|
@@ -591,16 +982,32 @@ function runInstall(repoDir, detected) {
|
|
|
591
982
|
const res = (0, import_node_child_process.spawnSync)(pm.binary, pm.installArgs, { cwd: repoDir, stdio: "inherit" });
|
|
592
983
|
if (res.status !== 0) {
|
|
593
984
|
fail(
|
|
594
|
-
`${pm.name} install failed (exit ${res.status}).
|
|
985
|
+
`${pm.name} install failed (exit ${res.status}).
|
|
986
|
+
|
|
987
|
+
Half-init state \u2014 install didn't complete, but these files ARE on disk:
|
|
988
|
+
- ${path4.join(repoDir, CONFIG_FILENAME)} (cred file)
|
|
989
|
+
- ${path4.join(repoDir, ".mcp.json")} (5 launch-kit MCP entries merged)
|
|
990
|
+
- ${path4.join(repoDir, ".gitignore")} (cred line appended)
|
|
991
|
+
- clone at ${repoDir}
|
|
992
|
+
|
|
993
|
+
Scaffolds (recall, migrate-safety, marketplace, recall-hook) were NOT yet written.
|
|
994
|
+
|
|
995
|
+
To retry install only:
|
|
996
|
+
cd ${path4.basename(repoDir)} && ${pm.binary} ${pm.installArgs.join(" ")}
|
|
997
|
+
|
|
998
|
+
To re-run init after fixing the install error:
|
|
999
|
+
cd ${path4.basename(repoDir)} && npx @launchsecure/launch-kit init --dir=.
|
|
1000
|
+
|
|
1001
|
+
To fully reset: delete the files listed above and the clone, then re-init.`
|
|
595
1002
|
);
|
|
596
1003
|
}
|
|
597
1004
|
ok(`${pm.name} install complete`);
|
|
598
1005
|
}
|
|
599
1006
|
function hasOnboardScript(repoDir) {
|
|
600
|
-
const pkgPath =
|
|
601
|
-
if (!
|
|
1007
|
+
const pkgPath = path4.join(repoDir, "package.json");
|
|
1008
|
+
if (!fs4.existsSync(pkgPath)) return false;
|
|
602
1009
|
try {
|
|
603
|
-
const pkg = JSON.parse(
|
|
1010
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
604
1011
|
return typeof pkg.scripts?.[ONBOARD_SCRIPT_NAME] === "string";
|
|
605
1012
|
} catch {
|
|
606
1013
|
return false;
|
|
@@ -608,8 +1015,8 @@ function hasOnboardScript(repoDir) {
|
|
|
608
1015
|
}
|
|
609
1016
|
function runRecallInit(repoDir) {
|
|
610
1017
|
info(`scaffolding launch-recall (shadow git backup) \u2026`);
|
|
611
|
-
const recallEntry =
|
|
612
|
-
const useSibling =
|
|
1018
|
+
const recallEntry = path4.resolve(__dirname, "recall-entry.js");
|
|
1019
|
+
const useSibling = fs4.existsSync(recallEntry);
|
|
613
1020
|
const cmd = useSibling ? process.execPath : "npx";
|
|
614
1021
|
const args = useSibling ? [recallEntry, "init"] : ["-y", "-p", LAUNCH_KIT_PKG, "launch-recall", "init"];
|
|
615
1022
|
if (DRY_RUN) {
|
|
@@ -618,7 +1025,7 @@ function runRecallInit(repoDir) {
|
|
|
618
1025
|
}
|
|
619
1026
|
const res = (0, import_node_child_process.spawnSync)(cmd, args, { cwd: repoDir, stdio: "inherit" });
|
|
620
1027
|
if (res.status !== 0) {
|
|
621
|
-
info(`\u26A0 launch-recall init failed (exit ${res.status}). Main onboarding is complete \u2014 you can retry later: cd ${
|
|
1028
|
+
info(`\u26A0 launch-recall init failed (exit ${res.status}). Main onboarding is complete \u2014 you can retry later: cd ${path4.basename(repoDir)} && npx -y -p ${LAUNCH_KIT_PKG} launch-recall init`);
|
|
622
1029
|
return;
|
|
623
1030
|
}
|
|
624
1031
|
ok(`launch-recall ready (shadow git initialized)`);
|
|
@@ -632,14 +1039,14 @@ function runOnboard(repoDir, pm) {
|
|
|
632
1039
|
const res = (0, import_node_child_process.spawnSync)(pm.binary, ["run", ONBOARD_SCRIPT_NAME], { cwd: repoDir, stdio: "inherit" });
|
|
633
1040
|
if (res.status !== 0) {
|
|
634
1041
|
fail(
|
|
635
|
-
`${pm.name} run ${ONBOARD_SCRIPT_NAME} failed (exit ${res.status}). Install completed but the onboard script errored. Fix and retry: cd ${
|
|
1042
|
+
`${pm.name} run ${ONBOARD_SCRIPT_NAME} failed (exit ${res.status}). Install completed but the onboard script errored. Fix and retry: cd ${path4.basename(repoDir)} && ${pm.binary} run ${ONBOARD_SCRIPT_NAME}`
|
|
636
1043
|
);
|
|
637
1044
|
}
|
|
638
1045
|
ok(`${ONBOARD_SCRIPT_NAME} script complete`);
|
|
639
1046
|
}
|
|
640
1047
|
function ensureGitignoreLine(targetDir, line) {
|
|
641
|
-
const p =
|
|
642
|
-
let content =
|
|
1048
|
+
const p = path4.join(targetDir, ".gitignore");
|
|
1049
|
+
let content = fs4.existsSync(p) ? fs4.readFileSync(p, "utf-8") : "";
|
|
643
1050
|
const lines = content.split(/\r?\n/);
|
|
644
1051
|
if (lines.some((l) => l.trim() === line)) return;
|
|
645
1052
|
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
@@ -649,131 +1056,191 @@ function ensureGitignoreLine(targetDir, line) {
|
|
|
649
1056
|
dryNote(`would append "${line}" to .gitignore`);
|
|
650
1057
|
return;
|
|
651
1058
|
}
|
|
652
|
-
|
|
1059
|
+
fs4.writeFileSync(p, content, "utf-8");
|
|
653
1060
|
ok(`appended ${line} to .gitignore`);
|
|
654
1061
|
}
|
|
655
|
-
function copyScaffoldIfMissing(srcPath, destPath, label) {
|
|
656
|
-
if (!fs2.existsSync(srcPath)) return "missing-src";
|
|
657
|
-
if (fs2.existsSync(destPath)) {
|
|
658
|
-
info(`${label} already present \u2014 leaving alone`);
|
|
659
|
-
return "existed";
|
|
660
|
-
}
|
|
661
|
-
if (DRY_RUN) {
|
|
662
|
-
dryNote(`would write ${label}`);
|
|
663
|
-
return "wrote";
|
|
664
|
-
}
|
|
665
|
-
fs2.mkdirSync(path2.dirname(destPath), { recursive: true });
|
|
666
|
-
fs2.copyFileSync(srcPath, destPath);
|
|
667
|
-
try {
|
|
668
|
-
const srcMode = fs2.statSync(srcPath).mode;
|
|
669
|
-
fs2.chmodSync(destPath, srcMode);
|
|
670
|
-
} catch {
|
|
671
|
-
}
|
|
672
|
-
ok(`wrote ${label}`);
|
|
673
|
-
return "wrote";
|
|
674
|
-
}
|
|
675
1062
|
function copyScaffoldDirAlways(srcDir, destDir, labelPrefix) {
|
|
676
|
-
if (!
|
|
677
|
-
for (const entry of
|
|
678
|
-
const srcPath =
|
|
679
|
-
const destPath =
|
|
1063
|
+
if (!fs4.existsSync(srcDir)) return;
|
|
1064
|
+
for (const entry of fs4.readdirSync(srcDir, { withFileTypes: true })) {
|
|
1065
|
+
const srcPath = path4.join(srcDir, entry.name);
|
|
1066
|
+
const destPath = path4.join(destDir, entry.name);
|
|
680
1067
|
const label = labelPrefix ? `${labelPrefix}/${entry.name}` : entry.name;
|
|
681
1068
|
if (entry.isDirectory()) {
|
|
682
1069
|
copyScaffoldDirAlways(srcPath, destPath, label);
|
|
683
1070
|
} else if (entry.isFile()) {
|
|
684
|
-
const existed =
|
|
1071
|
+
const existed = fs4.existsSync(destPath);
|
|
685
1072
|
if (DRY_RUN) {
|
|
686
1073
|
dryNote(`would ${existed ? "refresh" : "write"} ${label}`);
|
|
687
1074
|
continue;
|
|
688
1075
|
}
|
|
689
|
-
|
|
690
|
-
|
|
1076
|
+
fs4.mkdirSync(path4.dirname(destPath), { recursive: true });
|
|
1077
|
+
fs4.copyFileSync(srcPath, destPath);
|
|
691
1078
|
try {
|
|
692
|
-
const srcMode =
|
|
693
|
-
|
|
1079
|
+
const srcMode = fs4.statSync(srcPath).mode;
|
|
1080
|
+
fs4.chmodSync(destPath, srcMode);
|
|
694
1081
|
} catch {
|
|
695
1082
|
}
|
|
696
1083
|
ok(`${existed ? "refreshed" : "wrote"} ${label}`);
|
|
697
1084
|
}
|
|
698
1085
|
}
|
|
699
1086
|
}
|
|
700
|
-
function scaffoldMigrateSafety(targetDir) {
|
|
701
|
-
const scaffoldsRoot =
|
|
702
|
-
if (!
|
|
1087
|
+
function scaffoldMigrateSafety(targetDir, refreshScaffolds = false) {
|
|
1088
|
+
const scaffoldsRoot = path4.resolve(__dirname, "..", "..", "scaffolds", "migrate-safety");
|
|
1089
|
+
if (!fs4.existsSync(scaffoldsRoot)) {
|
|
703
1090
|
info(`\u26A0 migrate-safety scaffolds not found at ${scaffoldsRoot} \u2014 skipping (this is a packaging bug; main onboarding is unaffected)`);
|
|
704
1091
|
return;
|
|
705
1092
|
}
|
|
706
1093
|
const files = [
|
|
707
1094
|
{
|
|
708
|
-
src:
|
|
709
|
-
dest:
|
|
1095
|
+
src: path4.join(scaffoldsRoot, ".github", "workflows", "backup-on-migration.yml"),
|
|
1096
|
+
dest: path4.join(targetDir, ".github", "workflows", "backup-on-migration.yml"),
|
|
710
1097
|
label: ".github/workflows/backup-on-migration.yml"
|
|
711
1098
|
},
|
|
712
1099
|
{
|
|
713
|
-
src:
|
|
714
|
-
dest:
|
|
1100
|
+
src: path4.join(scaffoldsRoot, "scripts", "migrate-with-backup.sh"),
|
|
1101
|
+
dest: path4.join(targetDir, "scripts", "migrate-with-backup.sh"),
|
|
715
1102
|
label: "scripts/migrate-with-backup.sh"
|
|
716
1103
|
},
|
|
717
1104
|
{
|
|
718
|
-
src:
|
|
719
|
-
dest:
|
|
1105
|
+
src: path4.join(scaffoldsRoot, "docs", "migrations-runbook.md"),
|
|
1106
|
+
dest: path4.join(targetDir, "docs", "migrations-runbook.md"),
|
|
720
1107
|
label: "docs/migrations-runbook.md"
|
|
721
1108
|
}
|
|
722
1109
|
];
|
|
723
|
-
info(
|
|
724
|
-
for (const f of files)
|
|
1110
|
+
info(`scaffolding migrate-safety (pg_dump wrapper + GHA backup workflow + runbook)${refreshScaffolds ? " \u2014 --refresh-scaffolds active" : ""} \u2026`);
|
|
1111
|
+
for (const f of files) copyScaffoldDriftAware(f.src, f.dest, f.label, refreshScaffolds);
|
|
725
1112
|
ensureGitignoreLine(targetDir, ".backups/");
|
|
726
1113
|
ok("migrate-safety ready \u2014 see docs/migrations-runbook.md for db:migrate wiring + PROD_DATABASE_URL secret setup");
|
|
727
1114
|
}
|
|
728
|
-
|
|
729
|
-
|
|
1115
|
+
function hashFile(p) {
|
|
1116
|
+
try {
|
|
1117
|
+
return crypto.createHash("sha256").update(fs4.readFileSync(p)).digest("hex");
|
|
1118
|
+
} catch {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function copyScaffoldDriftAware(srcPath, destPath, label, refreshScaffolds) {
|
|
1123
|
+
if (!fs4.existsSync(srcPath)) {
|
|
1124
|
+
info(`\u26A0 scaffold src missing for ${label} \u2014 skipping (packaging bug)`);
|
|
1125
|
+
return "missing-src";
|
|
1126
|
+
}
|
|
1127
|
+
if (!fs4.existsSync(destPath)) {
|
|
1128
|
+
if (DRY_RUN) {
|
|
1129
|
+
dryNote(`would write ${label}`);
|
|
1130
|
+
return "wrote";
|
|
1131
|
+
}
|
|
1132
|
+
fs4.mkdirSync(path4.dirname(destPath), { recursive: true });
|
|
1133
|
+
fs4.copyFileSync(srcPath, destPath);
|
|
1134
|
+
try {
|
|
1135
|
+
fs4.chmodSync(destPath, fs4.statSync(srcPath).mode);
|
|
1136
|
+
} catch {
|
|
1137
|
+
}
|
|
1138
|
+
ok(`wrote ${label}`);
|
|
1139
|
+
return "wrote";
|
|
1140
|
+
}
|
|
1141
|
+
const srcHash = hashFile(srcPath);
|
|
1142
|
+
const destHash = hashFile(destPath);
|
|
1143
|
+
if (srcHash && destHash && srcHash === destHash) {
|
|
1144
|
+
if (DRY_RUN) dryNote(`${label} in sync with shipped scaffold \u2014 no change`);
|
|
1145
|
+
else info(`${label} in sync with shipped scaffold \u2014 no change`);
|
|
1146
|
+
return "in-sync";
|
|
1147
|
+
}
|
|
1148
|
+
if (refreshScaffolds) {
|
|
1149
|
+
if (DRY_RUN) {
|
|
1150
|
+
dryNote(`would refresh ${label} (overrides local edits)`);
|
|
1151
|
+
return "drifted-refreshed";
|
|
1152
|
+
}
|
|
1153
|
+
fs4.copyFileSync(srcPath, destPath);
|
|
1154
|
+
try {
|
|
1155
|
+
fs4.chmodSync(destPath, fs4.statSync(srcPath).mode);
|
|
1156
|
+
} catch {
|
|
1157
|
+
}
|
|
1158
|
+
ok(`refreshed ${label} (overrode local edits \u2014 drift detected before write)`);
|
|
1159
|
+
return "drifted-refreshed";
|
|
1160
|
+
}
|
|
1161
|
+
info(`${label} differs from shipped scaffold (customized or older version) \u2014 preserving. Pass --refresh-scaffolds to overwrite.`);
|
|
1162
|
+
return "drifted-preserved";
|
|
1163
|
+
}
|
|
1164
|
+
var MARKETPLACE_ID = "launch-secure";
|
|
1165
|
+
var PLUGIN_ID = "kit";
|
|
1166
|
+
function isDogfoodMarketplace(targetDir) {
|
|
1167
|
+
const p = path4.join(targetDir, ".claude", "settings.json");
|
|
1168
|
+
if (!fs4.existsSync(p)) return { isDogfood: false };
|
|
1169
|
+
try {
|
|
1170
|
+
const settings = JSON.parse(fs4.readFileSync(p, "utf-8"));
|
|
1171
|
+
const existingPath = settings.extraKnownMarketplaces?.[MARKETPLACE_ID]?.source?.path;
|
|
1172
|
+
if (existingPath && existingPath !== "./.claude/marketplace") {
|
|
1173
|
+
return { isDogfood: true, existingPath };
|
|
1174
|
+
}
|
|
1175
|
+
return { isDogfood: false };
|
|
1176
|
+
} catch {
|
|
1177
|
+
return { isDogfood: false };
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
730
1180
|
function scaffoldLsMarketplace(targetDir) {
|
|
731
|
-
const scaffoldsRoot =
|
|
732
|
-
if (!
|
|
1181
|
+
const scaffoldsRoot = path4.resolve(__dirname, "..", "..", "scaffolds", "ls-marketplace");
|
|
1182
|
+
if (!fs4.existsSync(scaffoldsRoot)) {
|
|
733
1183
|
info(`\u26A0 ls-marketplace scaffolds not found at ${scaffoldsRoot} \u2014 skipping (this is a packaging bug; main onboarding is unaffected)`);
|
|
734
1184
|
return;
|
|
735
1185
|
}
|
|
736
|
-
const
|
|
737
|
-
|
|
1186
|
+
const dogfood = isDogfoodMarketplace(targetDir);
|
|
1187
|
+
if (dogfood.isDogfood) {
|
|
1188
|
+
info(`dogfood marketplace pointer detected (${dogfood.existingPath}) \u2014 skipping copy, only refreshing enabledPlugins`);
|
|
1189
|
+
wireLsSettings(targetDir);
|
|
1190
|
+
ok(`launch-secure marketplace (dogfood) \u2014 Claude Code loads commands from ${dogfood.existingPath}`);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
const marketplaceRoot = path4.join(targetDir, ".claude", "marketplace");
|
|
1194
|
+
info("scaffolding launch-secure marketplace (Claude Code /kit: namespace \u2014 refreshes every /kit:* command found in the scaffold) \u2026");
|
|
738
1195
|
copyScaffoldDirAlways(scaffoldsRoot, marketplaceRoot, ".claude/marketplace");
|
|
739
1196
|
wireLsSettings(targetDir);
|
|
740
|
-
ok(`
|
|
1197
|
+
ok(`launch-secure marketplace ready \u2014 open this repo in Claude Code, approve the "${MARKETPLACE_ID}" marketplace prompt, then try /kit:activate-beacon, /kit:standup, or /kit:show-mcp-status`);
|
|
741
1198
|
}
|
|
742
1199
|
function wireLsSettings(targetDir) {
|
|
743
|
-
const p =
|
|
744
|
-
const hadExisting =
|
|
1200
|
+
const p = path4.join(targetDir, ".claude", "settings.json");
|
|
1201
|
+
const hadExisting = fs4.existsSync(p);
|
|
745
1202
|
let existing = {};
|
|
746
1203
|
if (hadExisting) {
|
|
747
1204
|
try {
|
|
748
|
-
existing = JSON.parse(
|
|
1205
|
+
existing = JSON.parse(fs4.readFileSync(p, "utf-8"));
|
|
749
1206
|
} catch (err) {
|
|
750
1207
|
fail(`Could not parse existing .claude/settings.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
751
1208
|
}
|
|
752
1209
|
}
|
|
753
1210
|
const merged = { ...existing };
|
|
1211
|
+
const existingMarketplacePath = existing.extraKnownMarketplaces?.[MARKETPLACE_ID]?.source?.path;
|
|
1212
|
+
const targetPath = existingMarketplacePath ?? "./.claude/marketplace";
|
|
1213
|
+
if (existingMarketplacePath && existingMarketplacePath !== "./.claude/marketplace") {
|
|
1214
|
+
info(`preserving existing marketplace path: ${existingMarketplacePath} (likely dogfood \u2014 leaving alone)`);
|
|
1215
|
+
}
|
|
754
1216
|
merged.extraKnownMarketplaces = {
|
|
755
1217
|
...existing.extraKnownMarketplaces ?? {},
|
|
756
1218
|
[MARKETPLACE_ID]: {
|
|
757
|
-
source: { source: "directory", path:
|
|
1219
|
+
source: { source: "directory", path: targetPath }
|
|
758
1220
|
}
|
|
759
1221
|
};
|
|
1222
|
+
const pluginKey = `${PLUGIN_ID}@${MARKETPLACE_ID}`;
|
|
1223
|
+
const existingEnabledPlugins = existing.enabledPlugins ?? {};
|
|
760
1224
|
merged.enabledPlugins = {
|
|
761
|
-
...
|
|
762
|
-
|
|
1225
|
+
...existingEnabledPlugins,
|
|
1226
|
+
...pluginKey in existingEnabledPlugins ? {} : { [pluginKey]: true }
|
|
763
1227
|
};
|
|
1228
|
+
if (pluginKey in existingEnabledPlugins && existingEnabledPlugins[pluginKey] === false) {
|
|
1229
|
+
info(`enabledPlugins["${pluginKey}"] is explicitly false \u2014 leaving disabled (re-enable manually to use /kit:* commands)`);
|
|
1230
|
+
}
|
|
764
1231
|
if (DRY_RUN) {
|
|
765
|
-
dryNote(`would ${hadExisting ? "merge into" : "write"} .claude/settings.json (set extraKnownMarketplaces.${MARKETPLACE_ID} + enabledPlugins.${
|
|
1232
|
+
dryNote(`would ${hadExisting ? "merge into" : "write"} .claude/settings.json (set extraKnownMarketplaces.${MARKETPLACE_ID} + enabledPlugins.${PLUGIN_ID}@${MARKETPLACE_ID}; preserves every other key)`);
|
|
766
1233
|
return;
|
|
767
1234
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
ok(`${hadExisting ? "merged into" : "wrote"} .claude/settings.json (extraKnownMarketplaces.${MARKETPLACE_ID} + enabledPlugins.${
|
|
1235
|
+
fs4.mkdirSync(path4.dirname(p), { recursive: true });
|
|
1236
|
+
fs4.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
1237
|
+
ok(`${hadExisting ? "merged into" : "wrote"} .claude/settings.json (extraKnownMarketplaces.${MARKETPLACE_ID} + enabledPlugins.${PLUGIN_ID}@${MARKETPLACE_ID})`);
|
|
771
1238
|
}
|
|
772
1239
|
var RECALL_HOOK_SIGNATURE = "ensure-recall.sh";
|
|
773
1240
|
var RECALL_HOOK_COMMAND = 'bash "${CLAUDE_PROJECT_DIR:-$PWD}/scripts/ensure-recall.sh"';
|
|
774
1241
|
function scaffoldRecallHook(targetDir) {
|
|
775
|
-
const scaffoldsRoot =
|
|
776
|
-
if (!
|
|
1242
|
+
const scaffoldsRoot = path4.resolve(__dirname, "..", "..", "scaffolds", "recall-hook");
|
|
1243
|
+
if (!fs4.existsSync(scaffoldsRoot)) {
|
|
777
1244
|
info(`\u26A0 recall-hook scaffolds not found at ${scaffoldsRoot} \u2014 skipping (this is a packaging bug; main onboarding is unaffected)`);
|
|
778
1245
|
return;
|
|
779
1246
|
}
|
|
@@ -783,12 +1250,12 @@ function scaffoldRecallHook(targetDir) {
|
|
|
783
1250
|
ok("recall-hook ready \u2014 opens with Claude Code will respawn the launch-recall watcher if it died between sessions");
|
|
784
1251
|
}
|
|
785
1252
|
function wireRecallHook(targetDir) {
|
|
786
|
-
const p =
|
|
787
|
-
const hadExisting =
|
|
1253
|
+
const p = path4.join(targetDir, ".claude", "settings.json");
|
|
1254
|
+
const hadExisting = fs4.existsSync(p);
|
|
788
1255
|
let existing = {};
|
|
789
1256
|
if (hadExisting) {
|
|
790
1257
|
try {
|
|
791
|
-
existing = JSON.parse(
|
|
1258
|
+
existing = JSON.parse(fs4.readFileSync(p, "utf-8"));
|
|
792
1259
|
} catch (err) {
|
|
793
1260
|
fail(`Could not parse existing .claude/settings.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
794
1261
|
}
|
|
@@ -816,19 +1283,62 @@ function wireRecallHook(targetDir) {
|
|
|
816
1283
|
dryNote(`would append SessionStart hook to .claude/settings.json (bash scripts/ensure-recall.sh; preserves every other key + existing hooks)`);
|
|
817
1284
|
return;
|
|
818
1285
|
}
|
|
819
|
-
|
|
820
|
-
|
|
1286
|
+
fs4.mkdirSync(path4.dirname(p), { recursive: true });
|
|
1287
|
+
fs4.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
821
1288
|
ok(`appended SessionStart hook to .claude/settings.json (bash scripts/ensure-recall.sh)`);
|
|
822
1289
|
}
|
|
1290
|
+
function tryActivateStatusline() {
|
|
1291
|
+
if (DRY_RUN) {
|
|
1292
|
+
dryNote(`would wrap ~/.claude/settings.json statusLine.command with launch-kit's MCP chip wrapper (skips silently if no statusline configured)`);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const res = activateStatusline();
|
|
1296
|
+
if (res.ok && res.outcome === "activated") {
|
|
1297
|
+
ok(`statusline wrapped \u2014 MCP chips will render alongside your existing statusline next session`);
|
|
1298
|
+
} else if (res.ok && res.outcome === "refreshed") {
|
|
1299
|
+
info(`statusline already wrapped \u2014 refreshed chip scripts`);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
823
1302
|
async function main() {
|
|
1303
|
+
const subcommand = process.argv[2];
|
|
1304
|
+
if (subcommand === "statusline") {
|
|
1305
|
+
const action = process.argv[3];
|
|
1306
|
+
if (!action || action === "--help" || action === "-h") {
|
|
1307
|
+
console.log("usage: launch-kit statusline activate [--show=recall,chart,deck,council] [--compact]");
|
|
1308
|
+
console.log(" launch-kit statusline deactivate");
|
|
1309
|
+
console.log("");
|
|
1310
|
+
console.log(" --show comma-separated subset of chips. Default: all four.");
|
|
1311
|
+
console.log(" --compact collapse to `mcp N/total` (green if all up, red otherwise).");
|
|
1312
|
+
console.log(" Re-run activate with different flags to change in place.");
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
let showArg;
|
|
1316
|
+
let compactArg = false;
|
|
1317
|
+
for (const a of process.argv.slice(4)) {
|
|
1318
|
+
if (a.startsWith("--show=")) showArg = a.slice("--show=".length);
|
|
1319
|
+
else if (a === "--compact") compactArg = true;
|
|
1320
|
+
else fail(`Unknown statusline flag: "${a}". Supported: --show=<csv>, --compact.`);
|
|
1321
|
+
}
|
|
1322
|
+
const { activateStatusline: activateStatusline2, deactivateStatusline: deactivateStatusline2 } = await Promise.resolve().then(() => (init_statusline_install(), statusline_install_exports));
|
|
1323
|
+
let res;
|
|
1324
|
+
if (action === "activate") res = activateStatusline2({ show: showArg, compact: compactArg });
|
|
1325
|
+
else if (action === "deactivate") res = deactivateStatusline2();
|
|
1326
|
+
else fail(`Unknown statusline action: "${action}". Supported: activate, deactivate.`);
|
|
1327
|
+
if (res.ok) ok(`statusline ${res.outcome} \u2014 ${res.message}`);
|
|
1328
|
+
else info(`statusline ${res.outcome} \u2014 ${res.message}`);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
824
1331
|
const args = parseArgs(process.argv.slice(2));
|
|
825
1332
|
if (args.help) {
|
|
826
|
-
|
|
1333
|
+
if (subcommand === "refresh") printRefreshHelp();
|
|
1334
|
+
else printHelp();
|
|
827
1335
|
return;
|
|
828
1336
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1337
|
+
if (!subcommand || subcommand.startsWith("--")) {
|
|
1338
|
+
fail(`missing subcommand. Usage: launch-kit <init|refresh|statusline> [options]. Run with --help.`);
|
|
1339
|
+
}
|
|
1340
|
+
if (subcommand !== "init" && subcommand !== "refresh") {
|
|
1341
|
+
fail(`Unknown subcommand "${subcommand}". Supported: init, refresh, statusline. Run with --help for usage.`);
|
|
832
1342
|
}
|
|
833
1343
|
DRY_RUN = args.dryRun;
|
|
834
1344
|
if (DRY_RUN) {
|
|
@@ -837,6 +1347,99 @@ async function main() {
|
|
|
837
1347
|
info("Lines tagged (dry-run) show what would happen.");
|
|
838
1348
|
info("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
839
1349
|
}
|
|
1350
|
+
if (subcommand === "refresh") return mainRefresh(args);
|
|
1351
|
+
return mainInit(args);
|
|
1352
|
+
}
|
|
1353
|
+
async function mainRefresh(args) {
|
|
1354
|
+
const cwd = process.cwd();
|
|
1355
|
+
const targetDir = path4.resolve(args.targetDir ?? cwd);
|
|
1356
|
+
if (!fs4.existsSync(targetDir)) fail(`target dir does not exist: ${targetDir}`);
|
|
1357
|
+
let cred;
|
|
1358
|
+
let source;
|
|
1359
|
+
try {
|
|
1360
|
+
const recovery = recoverCred(targetDir, getRecoveryOptions());
|
|
1361
|
+
cred = recovery.cred;
|
|
1362
|
+
source = recovery.source;
|
|
1363
|
+
} catch (err) {
|
|
1364
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
1365
|
+
}
|
|
1366
|
+
if (cred && source === "mcp") {
|
|
1367
|
+
info(`recovered cred from .mcp.json launch-secure headers (PAT + org + project + url)`);
|
|
1368
|
+
const courseName = inferCourseName(cred.serverUrl);
|
|
1369
|
+
const nested2 = upsertProfile(null, courseName, cred);
|
|
1370
|
+
if (DRY_RUN) {
|
|
1371
|
+
dryNote(`would write ${CONFIG_FILENAME} from recovered .mcp.json cred (course: ${courseName})`);
|
|
1372
|
+
} else {
|
|
1373
|
+
writeJsonAtomic(path4.join(targetDir, CONFIG_FILENAME), nested2, 384);
|
|
1374
|
+
ok(`wrote ${CONFIG_FILENAME} (course: ${courseName})`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (!cred) {
|
|
1378
|
+
fail(
|
|
1379
|
+
`no ${CONFIG_FILENAME} found at ${targetDir}, and could not recover from .mcp.json. Refresh requires an existing cred or a hardcoded launch-secure MCP entry \u2014 run \`npx @launchsecure/launch-kit init --token=<pat> --org=<org> --project=<project> --dir=${path4.relative(cwd, targetDir) || "."}\` first.`
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
const nested = toNested(cred);
|
|
1383
|
+
if (!nested) fail(`${CONFIG_FILENAME} is malformed or missing required fields (pat/orgSlug/projectSlug/serverUrl).`);
|
|
1384
|
+
const active = nested.profiles[nested.active];
|
|
1385
|
+
if (!active) fail(`${CONFIG_FILENAME} active profile "${nested.active}" is not present in profiles.`);
|
|
1386
|
+
info(`refreshing launch-kit in ${targetDir} (course: ${nested.active}, project: ${active.orgSlug}/${active.projectSlug}) \u2026`);
|
|
1387
|
+
const cfg = { pat: active.pat, orgSlug: active.orgSlug, projectSlug: active.projectSlug, serverUrl: active.serverUrl };
|
|
1388
|
+
mergeMcpFile(targetDir, buildLaunchKitMcpEntries(cfg));
|
|
1389
|
+
ensureGitignoreLine(targetDir, CONFIG_FILENAME);
|
|
1390
|
+
if (!args.noMigrateSafety) scaffoldMigrateSafety(targetDir, args.refreshScaffolds);
|
|
1391
|
+
if (!args.noLsMarketplace) scaffoldLsMarketplace(targetDir);
|
|
1392
|
+
if (!args.noRecallHook) scaffoldRecallHook(targetDir);
|
|
1393
|
+
tryActivateStatusline();
|
|
1394
|
+
console.log("");
|
|
1395
|
+
if (DRY_RUN) {
|
|
1396
|
+
info("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1397
|
+
info(`DRY RUN COMPLETE \u2014 refresh would have applied the above; no files modified.`);
|
|
1398
|
+
info("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
ok(`refresh complete \u2014 restart Claude Code to pick up any new /kit:* commands`);
|
|
1402
|
+
if (!args.quiet) {
|
|
1403
|
+
console.log(`
|
|
1404
|
+
Skipped (refresh never runs these): clone, dependency install, onboard script, launch-recall init. Use \`npx @launchsecure/launch-kit init\` for a full bootstrap.
|
|
1405
|
+
${getLaunchKitToolsGuide()}`);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
async function mainInit(args) {
|
|
1409
|
+
const probeDir = path4.resolve(args.targetDir ?? process.cwd());
|
|
1410
|
+
if (!args.force && fs4.existsSync(probeDir)) {
|
|
1411
|
+
const detection = detectExistingBootstrap(probeDir);
|
|
1412
|
+
if (detection.bootstrapped) {
|
|
1413
|
+
info(`detected existing bootstrap at ${probeDir} (${detection.reason})`);
|
|
1414
|
+
info(`delegating to refresh. Pass --force to re-init from scratch (will re-prompt for PAT if needed).`);
|
|
1415
|
+
return mainRefresh({ ...args, targetDir: probeDir });
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
if (!args.token || !args.orgSlug || !args.projectSlug) {
|
|
1419
|
+
const recoveryDir = path4.resolve(args.targetDir ?? process.cwd());
|
|
1420
|
+
if (fs4.existsSync(recoveryDir)) {
|
|
1421
|
+
const { cred } = recoverCred(recoveryDir, getRecoveryOptions());
|
|
1422
|
+
const nested = cred ? toNested(cred) : null;
|
|
1423
|
+
const recovered = nested ? nested.profiles[nested.active] : cred;
|
|
1424
|
+
if (recovered) {
|
|
1425
|
+
if (!args.token && recovered.pat) {
|
|
1426
|
+
args.token = recovered.pat;
|
|
1427
|
+
info(`recovered --token from existing config in ${recoveryDir}`);
|
|
1428
|
+
}
|
|
1429
|
+
if (!args.orgSlug && recovered.orgSlug) {
|
|
1430
|
+
args.orgSlug = recovered.orgSlug;
|
|
1431
|
+
info(`recovered --org=${recovered.orgSlug} from existing config`);
|
|
1432
|
+
}
|
|
1433
|
+
if (!args.projectSlug && recovered.projectSlug) {
|
|
1434
|
+
args.projectSlug = recovered.projectSlug;
|
|
1435
|
+
info(`recovered --project=${recovered.projectSlug} from existing config`);
|
|
1436
|
+
}
|
|
1437
|
+
if (args.serverUrl === DEFAULT_SERVER_URL && recovered.serverUrl) {
|
|
1438
|
+
args.serverUrl = recovered.serverUrl;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
840
1443
|
if (!args.token) {
|
|
841
1444
|
const t = await prompt("LaunchSecure PAT (ls_pat_\u2026): ");
|
|
842
1445
|
args.token = t || null;
|
|
@@ -861,10 +1464,10 @@ async function main() {
|
|
|
861
1464
|
}
|
|
862
1465
|
const repoUrl = resolved.repositoryUrl;
|
|
863
1466
|
const cwd = process.cwd();
|
|
864
|
-
const targetDir =
|
|
1467
|
+
const targetDir = path4.resolve(args.targetDir ?? path4.join(cwd, resolved.projectSlug));
|
|
865
1468
|
const normalizedRemote = normalizeRepoUrl(repoUrl);
|
|
866
1469
|
let skipClone = false;
|
|
867
|
-
if (
|
|
1470
|
+
if (fs4.existsSync(targetDir)) {
|
|
868
1471
|
if (isGitRepo(targetDir)) {
|
|
869
1472
|
const existingRemote = gitRemoteUrl(targetDir);
|
|
870
1473
|
if (existingRemote && normalizeRepoUrl(existingRemote) === normalizedRemote) {
|
|
@@ -897,14 +1500,15 @@ async function main() {
|
|
|
897
1500
|
installSkippedReason = "no package.json found";
|
|
898
1501
|
} else {
|
|
899
1502
|
runInstall(targetDir, detected);
|
|
900
|
-
if (hasOnboardScript(targetDir)) runOnboard(targetDir, detected.pm);
|
|
1503
|
+
if (!args.noOnboard && hasOnboardScript(targetDir)) runOnboard(targetDir, detected.pm);
|
|
901
1504
|
}
|
|
902
1505
|
const hasOnboard = hasOnboardScript(targetDir);
|
|
903
1506
|
if (!args.noRecall) runRecallInit(targetDir);
|
|
904
|
-
if (!args.noMigrateSafety) scaffoldMigrateSafety(targetDir);
|
|
1507
|
+
if (!args.noMigrateSafety) scaffoldMigrateSafety(targetDir, args.refreshScaffolds);
|
|
905
1508
|
if (!args.noLsMarketplace) scaffoldLsMarketplace(targetDir);
|
|
906
1509
|
if (!args.noRecallHook) scaffoldRecallHook(targetDir);
|
|
907
|
-
|
|
1510
|
+
tryActivateStatusline();
|
|
1511
|
+
const relTarget = path4.relative(cwd, targetDir) || ".";
|
|
908
1512
|
console.log("");
|
|
909
1513
|
if (DRY_RUN) {
|
|
910
1514
|
info("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
@@ -917,20 +1521,20 @@ async function main() {
|
|
|
917
1521
|
ok(`done \u2014 ${resolved.projectName} is ready at ${targetDir}`);
|
|
918
1522
|
if (installSkippedReason) {
|
|
919
1523
|
const installLine = detected ? ` ${detected.pm.binary} ${detected.pm.installArgs.join(" ")}` : ` npm install # or your package manager of choice`;
|
|
920
|
-
const onboardLine = hasOnboard && detected ? `
|
|
1524
|
+
const onboardLine = hasOnboard && detected && !args.noOnboard ? `
|
|
921
1525
|
${detected.pm.binary} run ${ONBOARD_SCRIPT_NAME} # project setup hook` : "";
|
|
922
1526
|
console.log(`
|
|
923
1527
|
Next steps (install skipped: ${installSkippedReason}):
|
|
924
1528
|
cd ${relTarget}
|
|
925
1529
|
${installLine}${onboardLine}
|
|
926
|
-
claude # launch Claude Code (5 MCPs wired)
|
|
927
|
-
${
|
|
928
|
-
} else {
|
|
1530
|
+
claude # launch Claude Code (5 MCPs wired)${args.quiet ? "" : `
|
|
1531
|
+
${getLaunchKitToolsGuide()}`}`);
|
|
1532
|
+
} else if (!args.quiet) {
|
|
929
1533
|
console.log(`
|
|
930
1534
|
Next steps:
|
|
931
1535
|
cd ${relTarget}
|
|
932
1536
|
claude # launch Claude Code (5 MCPs wired)
|
|
933
|
-
${
|
|
1537
|
+
${getLaunchKitToolsGuide()}`);
|
|
934
1538
|
}
|
|
935
1539
|
}
|
|
936
1540
|
main().catch((err) => {
|