@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.
Files changed (195) hide show
  1. package/dist/beacon/beacon.mjs +2759 -1246
  2. package/dist/beacon/beacon.mjs.map +1 -1
  3. package/dist/beacon/beacon.umd.js +710 -95
  4. package/dist/beacon/beacon.umd.js.map +1 -1
  5. package/dist/beacon/types/core.d.ts +14 -0
  6. package/dist/beacon/types/core.d.ts.map +1 -0
  7. package/dist/beacon/types/ctx.d.ts +14 -0
  8. package/dist/beacon/types/ctx.d.ts.map +1 -0
  9. package/dist/beacon/types/element.d.ts +16 -48
  10. package/dist/beacon/types/element.d.ts.map +1 -1
  11. package/dist/beacon/types/index.d.ts +5 -4
  12. package/dist/beacon/types/index.d.ts.map +1 -1
  13. package/dist/beacon/types/internal/annotation-cache.d.ts +10 -0
  14. package/dist/beacon/types/internal/annotation-cache.d.ts.map +1 -0
  15. package/dist/beacon/types/internal/element-capture.d.ts +19 -0
  16. package/dist/beacon/types/internal/element-capture.d.ts.map +1 -0
  17. package/dist/beacon/types/internal/event-buffer.d.ts +16 -0
  18. package/dist/beacon/types/internal/event-buffer.d.ts.map +1 -0
  19. package/dist/beacon/types/internal/framework-detect.d.ts +6 -0
  20. package/dist/beacon/types/internal/framework-detect.d.ts.map +1 -0
  21. package/dist/beacon/types/internal/markers.d.ts +17 -0
  22. package/dist/beacon/types/internal/markers.d.ts.map +1 -0
  23. package/dist/beacon/types/internal/monitor/capture-dom.d.ts +14 -0
  24. package/dist/beacon/types/internal/monitor/capture-dom.d.ts.map +1 -0
  25. package/dist/beacon/types/internal/monitor/capture-network.d.ts +12 -0
  26. package/dist/beacon/types/internal/monitor/capture-network.d.ts.map +1 -0
  27. package/dist/beacon/types/internal/monitor/overlay.d.ts +16 -0
  28. package/dist/beacon/types/internal/monitor/overlay.d.ts.map +1 -0
  29. package/dist/beacon/types/internal/monitor/session.d.ts +41 -0
  30. package/dist/beacon/types/internal/monitor/session.d.ts.map +1 -0
  31. package/dist/beacon/types/{monitor → internal/monitor}/transport.d.ts +3 -3
  32. package/dist/beacon/types/internal/monitor/transport.d.ts.map +1 -0
  33. package/dist/beacon/types/{monitor/types.d.ts → internal/monitor/wire.d.ts} +69 -27
  34. package/dist/beacon/types/internal/monitor/wire.d.ts.map +1 -0
  35. package/dist/beacon/types/{ui → internal}/pick-mode-overlay.d.ts +4 -5
  36. package/dist/beacon/types/internal/pick-mode-overlay.d.ts.map +1 -0
  37. package/dist/beacon/types/{capture → internal}/picker.d.ts +0 -1
  38. package/dist/beacon/types/internal/picker.d.ts.map +1 -0
  39. package/dist/beacon/types/{ui → internal}/pin-popover.d.ts +1 -1
  40. package/dist/beacon/types/internal/pin-popover.d.ts.map +1 -0
  41. package/dist/beacon/types/{capture → internal}/screenshot.d.ts +1 -0
  42. package/dist/beacon/types/internal/screenshot.d.ts.map +1 -0
  43. package/dist/beacon/types/internal/selector.d.ts.map +1 -0
  44. package/dist/beacon/types/plugins/domEle.d.ts +14 -0
  45. package/dist/beacon/types/plugins/domEle.d.ts.map +1 -0
  46. package/dist/beacon/types/plugins/domSS.d.ts +8 -0
  47. package/dist/beacon/types/plugins/domSS.d.ts.map +1 -0
  48. package/dist/beacon/types/plugins/errors.d.ts +3 -0
  49. package/dist/beacon/types/plugins/errors.d.ts.map +1 -0
  50. package/dist/beacon/types/plugins/index.d.ts +8 -0
  51. package/dist/beacon/types/plugins/index.d.ts.map +1 -0
  52. package/dist/beacon/types/plugins/liveMonitor.d.ts +14 -0
  53. package/dist/beacon/types/plugins/liveMonitor.d.ts.map +1 -0
  54. package/dist/beacon/types/plugins/metadata.d.ts +3 -0
  55. package/dist/beacon/types/plugins/metadata.d.ts.map +1 -0
  56. package/dist/beacon/types/registry.d.ts +33 -0
  57. package/dist/beacon/types/registry.d.ts.map +1 -0
  58. package/dist/beacon/types/styles.d.ts +8 -0
  59. package/dist/beacon/types/styles.d.ts.map +1 -0
  60. package/dist/beacon/types/transport.d.ts +3 -0
  61. package/dist/beacon/types/transport.d.ts.map +1 -0
  62. package/dist/beacon/types/types.d.ts +152 -68
  63. package/dist/beacon/types/types.d.ts.map +1 -1
  64. package/dist/beacon/types/ui/dialog.d.ts +53 -0
  65. package/dist/beacon/types/ui/dialog.d.ts.map +1 -0
  66. package/dist/beacon/types/ui/form.d.ts +7 -0
  67. package/dist/beacon/types/ui/form.d.ts.map +1 -0
  68. package/dist/beacon/types/ui/overlay.d.ts +6 -0
  69. package/dist/beacon/types/ui/overlay.d.ts.map +1 -0
  70. package/dist/deck-client/assets/{_baseUniq-W2JQDmje.js → _baseUniq-DCt2IMRR.js} +1 -1
  71. package/dist/deck-client/assets/{arc-DIBWAId9.js → arc-h-ifqmNR.js} +1 -1
  72. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-CAIRMvJK.js → architectureDiagram-Q4EWVU46-C9dITSPv.js} +1 -1
  73. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BeNaNiOi.js → blockDiagram-DXYQGD6D-BHuJT34t.js} +1 -1
  74. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-B9Ozi62h.js → c4Diagram-AHTNJAMY-CpvMGtDG.js} +1 -1
  75. package/dist/deck-client/assets/channel-2PZVMiXf.js +1 -0
  76. package/dist/deck-client/assets/{chunk-4BX2VUAB-D7AZ47dt.js → chunk-4BX2VUAB-B6md1VIm.js} +1 -1
  77. package/dist/deck-client/assets/{chunk-4TB4RGXK-DnVnNPcI.js → chunk-4TB4RGXK-BmEnX8ik.js} +1 -1
  78. package/dist/deck-client/assets/{chunk-55IACEB6-UKYs-YNd.js → chunk-55IACEB6-BZPUyZAZ.js} +1 -1
  79. package/dist/deck-client/assets/{chunk-EDXVE4YY-D43b-SKn.js → chunk-EDXVE4YY-BWwNUK-l.js} +1 -1
  80. package/dist/deck-client/assets/{chunk-FMBD7UC4-QzBAoyyW.js → chunk-FMBD7UC4-o7gSppGI.js} +1 -1
  81. package/dist/deck-client/assets/{chunk-OYMX7WX6-Cjif4r6W.js → chunk-OYMX7WX6-C4KoTL5p.js} +1 -1
  82. package/dist/deck-client/assets/{chunk-QZHKN3VN-CqLDirEI.js → chunk-QZHKN3VN-jkf68sDs.js} +1 -1
  83. package/dist/deck-client/assets/{chunk-YZCP3GAM-_FQvmMs4.js → chunk-YZCP3GAM-Cd4yBE7o.js} +1 -1
  84. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-Bt8xBAof.js +1 -0
  85. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-Bt8xBAof.js +1 -0
  86. package/dist/deck-client/assets/clone-BHQryoDl.js +1 -0
  87. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-rfrocesE.js → cose-bilkent-S5V4N54A-DeGFUgAV.js} +1 -1
  88. package/dist/deck-client/assets/{dagre-KV5264BT-Bv_7DJat.js → dagre-KV5264BT-ekcYJuUV.js} +1 -1
  89. package/dist/deck-client/assets/{diagram-5BDNPKRD-4F1414G5.js → diagram-5BDNPKRD-YHPk4rV2.js} +1 -1
  90. package/dist/deck-client/assets/{diagram-G4DWMVQ6-C4-Pszqm.js → diagram-G4DWMVQ6-DM-JCd_B.js} +1 -1
  91. package/dist/deck-client/assets/{diagram-MMDJMWI5-B647TIx9.js → diagram-MMDJMWI5-l5FK1ybk.js} +1 -1
  92. package/dist/deck-client/assets/{diagram-TYMM5635-BFAqpezd.js → diagram-TYMM5635-CIN4_1-j.js} +1 -1
  93. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-BfBfrJOC.js → erDiagram-SMLLAGMA-MyinSkEl.js} +1 -1
  94. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-DX9YAYes.js → flowDiagram-DWJPFMVM-Dk8nn42x.js} +1 -1
  95. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-DCuiy7wF.js → ganttDiagram-T4ZO3ILL-BU1ihicu.js} +1 -1
  96. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-CGp1IXUh.js → gitGraphDiagram-UUTBAWPF-BjsTL13C.js} +1 -1
  97. package/dist/deck-client/assets/{graph-B7g8aoxv.js → graph-DJmh-xi7.js} +1 -1
  98. package/dist/deck-client/assets/{index-Dg1r-WSN.js → index-KsShfCV-.js} +3 -3
  99. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-L3fahMkF.js → infoDiagram-42DDH7IO-Dxvy_RB4.js} +1 -1
  100. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js → ishikawaDiagram-UXIWVN3A-DPOaNF1l.js} +1 -1
  101. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-djTSQZF9.js → journeyDiagram-VCZTEJTY-DMew3K5c.js} +1 -1
  102. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-CcTHo4CM.js → kanban-definition-6JOO6SKY-csciJFuk.js} +1 -1
  103. package/dist/deck-client/assets/{layout-mEJiadb7.js → layout-Dg4yyms2.js} +1 -1
  104. package/dist/deck-client/assets/{linear-XgTKqyRu.js → linear-BA3zU6gq.js} +1 -1
  105. package/dist/deck-client/assets/{min-Ct9jZdpd.js → min-lz-Ird-p.js} +1 -1
  106. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-BaFxCGNU.js → mindmap-definition-QFDTVHPH-CCEN8OQV.js} +1 -1
  107. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-CIbYYjtw.js → pieDiagram-DEJITSTG-DM6n1HY7.js} +1 -1
  108. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-D9EtCOvh.js → quadrantDiagram-34T5L4WZ-_ULoR66n.js} +1 -1
  109. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-xeni9eVG.js → requirementDiagram-MS252O5E-BuwJs7Tn.js} +1 -1
  110. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-LYeknz9h.js → sankeyDiagram-XADWPNL6-BEsuzkW4.js} +1 -1
  111. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-RDbsKFZf.js → sequenceDiagram-FGHM5R23-CP2H0YWf.js} +1 -1
  112. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-BH1Zjglk.js → stateDiagram-FHFEXIEX-B5Gw_NNL.js} +1 -1
  113. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-4T4wMDXr.js +1 -0
  114. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-IFXxKptt.js → timeline-definition-GMOUNBTQ-DsoYydQa.js} +1 -1
  115. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D-sLkQs9.js → vennDiagram-DHZGUBPP-Dz8JT_ob.js} +1 -1
  116. package/dist/deck-client/assets/wardley-RL74JXVD-DGHQ_Ijv.js +162 -0
  117. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BTjjuDU3.js → wardleyDiagram-NUSXRM2D-DN1LJMB1.js} +1 -1
  118. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-AYbv92n-.js → xychartDiagram-5P7HB3ND-nb0oSfrQ.js} +1 -1
  119. package/dist/deck-client/index.html +1 -1
  120. package/dist/server/beacon-monitor-entry.js +548 -6
  121. package/dist/server/chart-serve.js +920 -249
  122. package/dist/server/cli.js +1599 -595
  123. package/dist/server/course-entry.js +3 -3
  124. package/dist/server/graph-mcp-entry.js +1361 -394
  125. package/dist/server/init-entry.js +799 -195
  126. package/dist/server/orbit-entry.js +135 -7
  127. package/dist/server/parse-worker-entry.js +918 -247
  128. package/package.json +3 -2
  129. package/scaffolds/ls-marketplace/.claude-plugin/marketplace.json +4 -4
  130. package/scaffolds/ls-marketplace/plugins/{ls → kit}/.claude-plugin/plugin.json +1 -10
  131. package/scaffolds/ls-marketplace/plugins/{ls → kit}/commands/activate-beacon.md +2 -2
  132. package/scaffolds/ls-marketplace/plugins/kit/commands/activate-statusline.md +46 -0
  133. package/scaffolds/ls-marketplace/plugins/kit/commands/deactivate-statusline.md +34 -0
  134. package/scaffolds/ls-marketplace/plugins/{ls → kit}/commands/standup.md +52 -38
  135. package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-array.md +107 -0
  136. package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-clear.md +94 -0
  137. package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-pulse.md +82 -0
  138. package/scaffolds/ls-marketplace/plugins/kit/skills/beacon-scan.md +66 -0
  139. package/scaffolds/ls-marketplace/plugins/kit/skills/blast-radius.md +101 -0
  140. package/scaffolds/ls-marketplace/plugins/kit/skills/brief.md +112 -0
  141. package/scaffolds/ls-marketplace/plugins/kit/skills/course.md +84 -0
  142. package/scaffolds/ls-marketplace/plugins/kit/skills/debug.md +92 -0
  143. package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check.md +160 -0
  144. package/scaffolds/ls-marketplace/plugins/kit/skills/diagram.md +134 -0
  145. package/scaffolds/ls-marketplace/plugins/kit/skills/orbit.md +87 -0
  146. package/scaffolds/ls-marketplace/plugins/kit/skills/prototype.md +90 -0
  147. package/scaffolds/ls-marketplace/plugins/kit/skills/recall.md +83 -0
  148. package/scaffolds/ls-marketplace/plugins/{ls/commands → kit/skills}/show-mcp-status.md +8 -8
  149. package/scaffolds/ls-marketplace/plugins/kit/skills/wireframe.md +70 -0
  150. package/scaffolds/statusline/statusline-mcp.sh +204 -0
  151. package/scaffolds/statusline/statusline-wrapper.sh +50 -0
  152. package/dist/beacon/types/capture/element.d.ts +0 -3
  153. package/dist/beacon/types/capture/element.d.ts.map +0 -1
  154. package/dist/beacon/types/capture/events.d.ts +0 -20
  155. package/dist/beacon/types/capture/events.d.ts.map +0 -1
  156. package/dist/beacon/types/capture/framework.d.ts +0 -3
  157. package/dist/beacon/types/capture/framework.d.ts.map +0 -1
  158. package/dist/beacon/types/capture/metadata.d.ts +0 -3
  159. package/dist/beacon/types/capture/metadata.d.ts.map +0 -1
  160. package/dist/beacon/types/capture/overlay.d.ts +0 -7
  161. package/dist/beacon/types/capture/overlay.d.ts.map +0 -1
  162. package/dist/beacon/types/capture/picker.d.ts.map +0 -1
  163. package/dist/beacon/types/capture/screenshot.d.ts.map +0 -1
  164. package/dist/beacon/types/capture/selector.d.ts.map +0 -1
  165. package/dist/beacon/types/monitor/dom.d.ts +0 -13
  166. package/dist/beacon/types/monitor/dom.d.ts.map +0 -1
  167. package/dist/beacon/types/monitor/index.d.ts +0 -19
  168. package/dist/beacon/types/monitor/index.d.ts.map +0 -1
  169. package/dist/beacon/types/monitor/network.d.ts +0 -12
  170. package/dist/beacon/types/monitor/network.d.ts.map +0 -1
  171. package/dist/beacon/types/monitor/transport.d.ts.map +0 -1
  172. package/dist/beacon/types/monitor/types.d.ts.map +0 -1
  173. package/dist/beacon/types/transport/submit.d.ts +0 -3
  174. package/dist/beacon/types/transport/submit.d.ts.map +0 -1
  175. package/dist/beacon/types/ui/button.d.ts +0 -2
  176. package/dist/beacon/types/ui/button.d.ts.map +0 -1
  177. package/dist/beacon/types/ui/drawer.d.ts +0 -33
  178. package/dist/beacon/types/ui/drawer.d.ts.map +0 -1
  179. package/dist/beacon/types/ui/icons.d.ts +0 -9
  180. package/dist/beacon/types/ui/icons.d.ts.map +0 -1
  181. package/dist/beacon/types/ui/monitor-panel.d.ts +0 -19
  182. package/dist/beacon/types/ui/monitor-panel.d.ts.map +0 -1
  183. package/dist/beacon/types/ui/pick-mode-overlay.d.ts.map +0 -1
  184. package/dist/beacon/types/ui/pin-popover.d.ts.map +0 -1
  185. package/dist/deck-client/assets/channel-CRdozqbp.js +0 -1
  186. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-lIZMp57W.js +0 -1
  187. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-lIZMp57W.js +0 -1
  188. package/dist/deck-client/assets/clone-BtWeSTyJ.js +0 -1
  189. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +0 -1
  190. package/dist/deck-client/assets/wardley-RL74JXVD-C010F8l4.js +0 -162
  191. package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-array.md +0 -92
  192. package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-clear.md +0 -68
  193. package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-pulse.md +0 -80
  194. package/scaffolds/ls-marketplace/plugins/ls/commands/beacon-scan.md +0 -62
  195. /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 fs2 = __toESM(require("node:fs"));
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 path2 = __toESM(require("node:path"));
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 LAUNCH_KIT_TOOLS_GUIDE = `
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
- /ls:beacon-* commands below to read.
119
-
120
- LS slash commands (run inside Claude Code in this project):
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 "launchsecure" marketplace \u2014 accept to enable the commands above.
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
- for (const raw of argv) {
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 (!raw.startsWith("--") || eq < 0) continue;
200
- const key = raw.slice(2, eq);
201
- const val = raw.slice(eq + 1);
202
- if (key === "token") args.token = val;
203
- else if (key === "org") args.orgSlug = val;
204
- else if (key === "project") args.projectSlug = val;
205
- else if (key === "url") args.serverUrl = val.replace(/\/+$/, "");
206
- else if (key === "dir") args.targetDir = val;
207
- else if (key === "course") args.course = val;
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 init \u2014 bootstrap a LaunchSecure project on this machine
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 "launchsecure" marketplace
553
+ --no-ls-marketplace Skip the Claude Code "launch-secure" marketplace
237
554
  scaffold (.claude/marketplace/ + .claude/settings.json
238
- wiring \u2014 exposes /ls:activate-beacon and future
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 "launchsecure" marketplace at
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 "ls" plugin (exposes
271
- /ls:activate-beacon for wiring the launch-kit-beacon in-app feedback
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((resolve2) => rl.question(question, (answer) => {
611
+ return new Promise((resolve3) => rl.question(question, (answer) => {
282
612
  rl.close();
283
- resolve2(answer.trim());
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
- function callProjectInfo(args) {
314
- return new Promise((resolve2, reject) => {
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 || res.statusCode === 403) {
347
- reject(new Error(`PAT rejected (${res.statusCode}). Check token + that it has access to ${args.orgSlug}/${args.projectSlug}.`));
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 Error(`LaunchSecure responded ${res.statusCode}: ${text.slice(0, 300)}`));
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
- resolve2({
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.on("error", reject);
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 fs2.existsSync(path2.join(dir, ".git"));
788
+ return fs4.existsSync(path4.join(dir, ".git"));
415
789
  }
416
790
  function dirIsEmpty(dir) {
417
- if (!fs2.existsSync(dir)) return true;
418
- return fs2.readdirSync(dir).length === 0;
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
- migrateLegacyCredFile(targetDir);
447
- const p = path2.join(targetDir, CONFIG_FILENAME);
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
- function migrateLegacyCredFile(targetDir) {
462
- const legacy = path2.join(targetDir, LEGACY_CONFIG_FILENAME);
463
- const dest = path2.join(targetDir, CONFIG_FILENAME);
464
- if (!fs2.existsSync(legacy) || fs2.existsSync(dest)) return;
465
- let parsed;
466
- try {
467
- parsed = JSON.parse(fs2.readFileSync(legacy, "utf-8"));
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 pat = parsed?.pat;
472
- if (typeof pat !== "string" || !pat.startsWith("ls_pat_")) return;
473
- if (DRY_RUN) {
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
- fs2.renameSync(legacy, dest);
478
- removeGitignoreLine(targetDir, LEGACY_CONFIG_FILENAME);
479
- ok(`migrated legacy ${LEGACY_CONFIG_FILENAME} \u2192 ${CONFIG_FILENAME} (the old name is now reserved for file-backed-config)`);
480
- }
481
- function removeGitignoreLine(targetDir, line) {
482
- const p = path2.join(targetDir, ".gitignore");
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
- fs2.writeFileSync(p, after, "utf-8");
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 = path2.join(targetDir, ".mcp.json");
523
- const hadExisting = fs2.existsSync(p);
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(fs2.readFileSync(p, "utf-8"));
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
- if (merged.mcpServers[name]) overwrites.push(name);
538
- else additions.push(name);
539
- merged.mcpServers[name] = entry;
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
- fs2.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
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 = path2.join(repoDir, "package.json");
552
- if (!fs2.existsSync(pkgPath)) return null;
942
+ const pkgPath = path4.join(repoDir, "package.json");
943
+ if (!fs4.existsSync(pkgPath)) return null;
553
944
  try {
554
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
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) => fs2.existsSync(path2.join(repoDir, lf))) ?? null })).filter((m) => m.lockfile !== null);
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) => fs2.existsSync(path2.join(repoDir, wf)))) {
573
- return { pm, source: `workspace file (${pm.workspaceFiles.find((wf) => fs2.existsSync(path2.join(repoDir, 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 ${path2.basename(repoDir)} && ${pm.binary} ${pm.installArgs.join(" ")}`
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}). Configs and clone are intact \u2014 fix the underlying error and retry: cd ${path2.basename(repoDir)} && ${pm.binary} ${pm.installArgs.join(" ")}`
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 = path2.join(repoDir, "package.json");
601
- if (!fs2.existsSync(pkgPath)) return false;
1007
+ const pkgPath = path4.join(repoDir, "package.json");
1008
+ if (!fs4.existsSync(pkgPath)) return false;
602
1009
  try {
603
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
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 = path2.resolve(__dirname, "recall-entry.js");
612
- const useSibling = fs2.existsSync(recallEntry);
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 ${path2.basename(repoDir)} && npx -y -p ${LAUNCH_KIT_PKG} launch-recall init`);
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 ${path2.basename(repoDir)} && ${pm.binary} run ${ONBOARD_SCRIPT_NAME}`
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 = path2.join(targetDir, ".gitignore");
642
- let content = fs2.existsSync(p) ? fs2.readFileSync(p, "utf-8") : "";
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
- fs2.writeFileSync(p, content, "utf-8");
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 (!fs2.existsSync(srcDir)) return;
677
- for (const entry of fs2.readdirSync(srcDir, { withFileTypes: true })) {
678
- const srcPath = path2.join(srcDir, entry.name);
679
- const destPath = path2.join(destDir, entry.name);
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 = fs2.existsSync(destPath);
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
- fs2.mkdirSync(path2.dirname(destPath), { recursive: true });
690
- fs2.copyFileSync(srcPath, destPath);
1076
+ fs4.mkdirSync(path4.dirname(destPath), { recursive: true });
1077
+ fs4.copyFileSync(srcPath, destPath);
691
1078
  try {
692
- const srcMode = fs2.statSync(srcPath).mode;
693
- fs2.chmodSync(destPath, srcMode);
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 = path2.resolve(__dirname, "..", "..", "scaffolds", "migrate-safety");
702
- if (!fs2.existsSync(scaffoldsRoot)) {
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: path2.join(scaffoldsRoot, ".github", "workflows", "backup-on-migration.yml"),
709
- dest: path2.join(targetDir, ".github", "workflows", "backup-on-migration.yml"),
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: path2.join(scaffoldsRoot, "scripts", "migrate-with-backup.sh"),
714
- dest: path2.join(targetDir, "scripts", "migrate-with-backup.sh"),
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: path2.join(scaffoldsRoot, "docs", "migrations-runbook.md"),
719
- dest: path2.join(targetDir, "docs", "migrations-runbook.md"),
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("scaffolding migrate-safety (pg_dump wrapper + GHA backup workflow + runbook) \u2026");
724
- for (const f of files) copyScaffoldIfMissing(f.src, f.dest, f.label);
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
- var MARKETPLACE_ID = "launchsecure";
729
- var LS_PLUGIN_ID = "ls";
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 = path2.resolve(__dirname, "..", "..", "scaffolds", "ls-marketplace");
732
- if (!fs2.existsSync(scaffoldsRoot)) {
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 marketplaceRoot = path2.join(targetDir, ".claude", "marketplace");
737
- info("scaffolding ls marketplace (Claude Code /ls: namespace \u2014 refreshes every /ls:* command found in the scaffold) \u2026");
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(`ls marketplace ready \u2014 open this repo in Claude Code, approve the "${MARKETPLACE_ID}" marketplace prompt, then try /ls:activate-beacon, /ls:standup, or /ls:show-mcp-status`);
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 = path2.join(targetDir, ".claude", "settings.json");
744
- const hadExisting = fs2.existsSync(p);
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(fs2.readFileSync(p, "utf-8"));
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: "./.claude/marketplace" }
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
- ...existing.enabledPlugins ?? {},
762
- [`${LS_PLUGIN_ID}@${MARKETPLACE_ID}`]: true
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.${LS_PLUGIN_ID}@${MARKETPLACE_ID}; preserves every other key)`);
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
- fs2.mkdirSync(path2.dirname(p), { recursive: true });
769
- fs2.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
770
- ok(`${hadExisting ? "merged into" : "wrote"} .claude/settings.json (extraKnownMarketplaces.${MARKETPLACE_ID} + enabledPlugins.${LS_PLUGIN_ID}@${MARKETPLACE_ID})`);
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 = path2.resolve(__dirname, "..", "..", "scaffolds", "recall-hook");
776
- if (!fs2.existsSync(scaffoldsRoot)) {
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 = path2.join(targetDir, ".claude", "settings.json");
787
- const hadExisting = fs2.existsSync(p);
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(fs2.readFileSync(p, "utf-8"));
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
- fs2.mkdirSync(path2.dirname(p), { recursive: true });
820
- fs2.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
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
- printHelp();
1333
+ if (subcommand === "refresh") printRefreshHelp();
1334
+ else printHelp();
827
1335
  return;
828
1336
  }
829
- const subcommand = process.argv[2];
830
- if (subcommand && subcommand !== "init" && !subcommand.startsWith("--")) {
831
- fail(`Unknown subcommand "${subcommand}". Only "init" is supported. Run with --help for usage.`);
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 = path2.resolve(args.targetDir ?? path2.join(cwd, resolved.projectSlug));
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 (fs2.existsSync(targetDir)) {
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
- const relTarget = path2.relative(cwd, targetDir) || ".";
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
- ${LAUNCH_KIT_TOOLS_GUIDE}`);
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
- ${LAUNCH_KIT_TOOLS_GUIDE}`);
1537
+ ${getLaunchKitToolsGuide()}`);
934
1538
  }
935
1539
  }
936
1540
  main().catch((err) => {