@launchsecure/launch-kit 0.0.32 → 0.0.33

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 (85) hide show
  1. package/dist/chart-client/assets/{index-B__ARB8k.js → index-DFu2xIrM.js} +2 -2
  2. package/dist/chart-client/assets/index-DpKO9p0s.css +1 -0
  3. package/dist/chart-client/index.html +2 -2
  4. package/dist/client/assets/{index-h8kMzVtG.js → index-Cbw6bVdx.js} +2 -2
  5. package/dist/client/assets/index-Dv6dD2zY.css +32 -0
  6. package/dist/client/index.html +2 -2
  7. package/dist/council-client/assets/index-AqQ9Sei6.css +1 -0
  8. package/dist/council-client/assets/{index-CWaDcsFR.js → index-CAsmGTzg.js} +2 -2
  9. package/dist/council-client/index.html +2 -2
  10. package/dist/deck-client/assets/{_baseUniq-C7GsHvgg.js → _baseUniq-BiVx0WO_.js} +1 -1
  11. package/dist/deck-client/assets/{arc-CSrZRINY.js → arc-DGMkiEzS.js} +1 -1
  12. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-zoB-G17J.js → architectureDiagram-Q4EWVU46-Y2WRmHtk.js} +1 -1
  13. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-BRjjtYH6.js → blockDiagram-DXYQGD6D-_Lbfu5BQ.js} +1 -1
  14. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-C3D3sd2U.js → c4Diagram-AHTNJAMY-CTqpYTBX.js} +1 -1
  15. package/dist/deck-client/assets/channel-DB6LxW_l.js +1 -0
  16. package/dist/deck-client/assets/{chunk-4BX2VUAB-DhpDMOPO.js → chunk-4BX2VUAB-liEIbPHs.js} +1 -1
  17. package/dist/deck-client/assets/{chunk-4TB4RGXK-BIRgPXRl.js → chunk-4TB4RGXK-CCc6lYvL.js} +1 -1
  18. package/dist/deck-client/assets/{chunk-55IACEB6-BF24dwDZ.js → chunk-55IACEB6-D02jJUR2.js} +1 -1
  19. package/dist/deck-client/assets/{chunk-EDXVE4YY-CW75Y61B.js → chunk-EDXVE4YY-BFmGMbLD.js} +1 -1
  20. package/dist/deck-client/assets/{chunk-FMBD7UC4-B5-oyL79.js → chunk-FMBD7UC4-6wFLOVcJ.js} +1 -1
  21. package/dist/deck-client/assets/{chunk-OYMX7WX6-BB2bHe_Q.js → chunk-OYMX7WX6-Bnr8RiBf.js} +1 -1
  22. package/dist/deck-client/assets/{chunk-QZHKN3VN-D80eZO4B.js → chunk-QZHKN3VN-Ct82MksJ.js} +1 -1
  23. package/dist/deck-client/assets/{chunk-YZCP3GAM-Dz9787p_.js → chunk-YZCP3GAM-BXmN1diQ.js} +1 -1
  24. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-g944ZyG8.js +1 -0
  25. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-g944ZyG8.js +1 -0
  26. package/dist/deck-client/assets/clone-DiIRH1pI.js +1 -0
  27. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-MQjiZLcL.js → cose-bilkent-S5V4N54A-CmQCT-mH.js} +1 -1
  28. package/dist/deck-client/assets/{dagre-KV5264BT-DG4EcLpJ.js → dagre-KV5264BT-DDdSa9EX.js} +1 -1
  29. package/dist/deck-client/assets/{diagram-5BDNPKRD-1n7hM3Gc.js → diagram-5BDNPKRD-Bccks2xJ.js} +1 -1
  30. package/dist/deck-client/assets/{diagram-G4DWMVQ6-CYMarncV.js → diagram-G4DWMVQ6-CPPNgxmQ.js} +1 -1
  31. package/dist/deck-client/assets/{diagram-MMDJMWI5-DSisoipe.js → diagram-MMDJMWI5-KrD300pS.js} +1 -1
  32. package/dist/deck-client/assets/{diagram-TYMM5635-Btnq49OJ.js → diagram-TYMM5635-DefnLuQf.js} +1 -1
  33. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-Cu2Hb_Tz.js → erDiagram-SMLLAGMA-DI9FfnFP.js} +1 -1
  34. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-CGJzUzsO.js → flowDiagram-DWJPFMVM-twKyd3Fx.js} +1 -1
  35. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-D9sqGUBT.js → ganttDiagram-T4ZO3ILL-Wau3jhBr.js} +1 -1
  36. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-C0QwX2od.js → gitGraphDiagram-UUTBAWPF-D9GgYXwb.js} +1 -1
  37. package/dist/deck-client/assets/{graph-CcBjOQCl.js → graph-BhNLzyXS.js} +1 -1
  38. package/dist/deck-client/assets/index-B-YQq5b5.css +1 -0
  39. package/dist/deck-client/assets/{index-0arwoc0z.js → index-BtQBaQ7s.js} +3 -3
  40. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-DTimhhhS.js → infoDiagram-42DDH7IO-TylGlSG-.js} +1 -1
  41. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DxOxg_B4.js → ishikawaDiagram-UXIWVN3A-DAT8icpg.js} +1 -1
  42. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-Bpq0qa4j.js → journeyDiagram-VCZTEJTY-D3v_XL72.js} +1 -1
  43. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-aTIrpcVO.js → kanban-definition-6JOO6SKY-DNUOBiNr.js} +1 -1
  44. package/dist/deck-client/assets/{layout-DqglLR2E.js → layout-COfodgwF.js} +1 -1
  45. package/dist/deck-client/assets/{linear-D5GxehPc.js → linear-DmTsuIvK.js} +1 -1
  46. package/dist/deck-client/assets/{min-DXLfSREq.js → min-BW1F7i1D.js} +1 -1
  47. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-mO5Vys7I.js → mindmap-definition-QFDTVHPH-CErFzKWl.js} +1 -1
  48. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-Dm0gzdAr.js → pieDiagram-DEJITSTG-DW5F757o.js} +1 -1
  49. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-Daq7j3qD.js → quadrantDiagram-34T5L4WZ-B1S2-TfI.js} +1 -1
  50. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-CmwV95um.js → requirementDiagram-MS252O5E-BY5BAR-5.js} +1 -1
  51. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BOYl3Nkf.js → sankeyDiagram-XADWPNL6-CE1Cp9HS.js} +1 -1
  52. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-BuUjhIcW.js → sequenceDiagram-FGHM5R23-IaHnbKye.js} +1 -1
  53. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-LUZ_uwio.js → stateDiagram-FHFEXIEX-CwPJm9hU.js} +1 -1
  54. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-DQYa2M1q.js +1 -0
  55. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-CDUxCCAW.js → timeline-definition-GMOUNBTQ-DVFGGSgN.js} +1 -1
  56. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-BRb24Tf7.js → vennDiagram-DHZGUBPP-C1194MJi.js} +1 -1
  57. package/dist/deck-client/assets/wardley-RL74JXVD-CHZiUbBa.js +162 -0
  58. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-BLGlYrQz.js → wardleyDiagram-NUSXRM2D-hpwdFfGj.js} +1 -1
  59. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-De31MSnk.js → xychartDiagram-5P7HB3ND-DYkotwy8.js} +1 -1
  60. package/dist/deck-client/index.html +2 -2
  61. package/dist/server/cli.js +91 -13
  62. package/dist/server/council-entry.js +0 -0
  63. package/dist/server/fb-wizard.js +0 -0
  64. package/dist/server/init-entry.js +740 -221
  65. package/dist/server/radar-docker-init-entry.js +239 -0
  66. package/dist/server/radar-entrypoint-entry.js +99 -0
  67. package/dist/server/radar-teardown-entry.js +477 -0
  68. package/dist/server/recall-entry.js +4 -1
  69. package/package.json +22 -23
  70. package/scaffolds/ls-marketplace/plugins/kit/commands/activate-statusline.md +5 -5
  71. package/scaffolds/ls-marketplace/plugins/kit/skills/ship/SKILL.md +274 -0
  72. package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +0 -0
  73. package/scaffolds/recall-hook/scripts/ensure-recall.sh +0 -0
  74. package/scaffolds/statusline/statusline-mcp.sh +82 -2
  75. package/scaffolds/statusline/statusline-wrapper.sh +8 -1
  76. package/dist/chart-client/assets/index-CDIhdgWg.css +0 -1
  77. package/dist/client/assets/index-CfW4n40I.css +0 -32
  78. package/dist/council-client/assets/index-CZim6x1u.css +0 -1
  79. package/dist/deck-client/assets/channel-8ReQnQfH.js +0 -1
  80. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-cRxTeGkK.js +0 -1
  81. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-cRxTeGkK.js +0 -1
  82. package/dist/deck-client/assets/clone-LSHZ3K6R.js +0 -1
  83. package/dist/deck-client/assets/index-BlTlhxFW.css +0 -1
  84. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CnnRwE5D.js +0 -1
  85. package/dist/deck-client/assets/wardley-RL74JXVD-B0BYyVBY.js +0 -162
@@ -30,6 +30,217 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  mod
31
31
  ));
32
32
 
33
+ // src/server/cred-shape.ts
34
+ function inferCourseName(serverUrl) {
35
+ try {
36
+ const host = new URL(serverUrl).hostname.toLowerCase();
37
+ if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) return "local";
38
+ if (host.includes("staging")) return "staging";
39
+ if (host.endsWith(".vercel.app")) return "prod";
40
+ return host.split(".")[0] || "default";
41
+ } catch {
42
+ return "default";
43
+ }
44
+ }
45
+ function toNested(cred) {
46
+ if (cred.profiles && cred.active && cred.profiles[cred.active]) {
47
+ return { active: cred.active, profiles: cred.profiles };
48
+ }
49
+ if (!cred.pat || !cred.orgSlug || !cred.projectSlug || !cred.serverUrl) {
50
+ return null;
51
+ }
52
+ const name = inferCourseName(cred.serverUrl);
53
+ return {
54
+ active: name,
55
+ profiles: {
56
+ [name]: {
57
+ pat: cred.pat,
58
+ orgSlug: cred.orgSlug,
59
+ projectSlug: cred.projectSlug,
60
+ serverUrl: cred.serverUrl
61
+ }
62
+ }
63
+ };
64
+ }
65
+ function upsertProfile(existing, name, profile) {
66
+ const base = existing ? toNested(existing) ?? { active: name, profiles: {} } : { active: name, profiles: {} };
67
+ return {
68
+ active: name,
69
+ profiles: { ...base.profiles, [name]: profile }
70
+ };
71
+ }
72
+ function readCredFile(repoRoot) {
73
+ const p = path.join(repoRoot, CONFIG_FILENAME);
74
+ if (!fs.existsSync(p)) return null;
75
+ try {
76
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
77
+ } catch (err) {
78
+ throw new Error(`could not parse ${CONFIG_FILENAME}: ${err instanceof Error ? err.message : String(err)}`);
79
+ }
80
+ }
81
+ function writeJsonAtomic(absPath, value, mode) {
82
+ const tmp = `${absPath}.tmp`;
83
+ fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n", "utf-8");
84
+ if (mode !== void 0) {
85
+ try {
86
+ fs.chmodSync(tmp, mode);
87
+ } catch {
88
+ }
89
+ }
90
+ fs.renameSync(tmp, absPath);
91
+ }
92
+ var fs, path, CONFIG_FILENAME;
93
+ var init_cred_shape = __esm({
94
+ "src/server/cred-shape.ts"() {
95
+ "use strict";
96
+ fs = __toESM(require("node:fs"));
97
+ path = __toESM(require("node:path"));
98
+ CONFIG_FILENAME = ".launch-secure.cred.config";
99
+ }
100
+ });
101
+
102
+ // src/server/secrets-pull.ts
103
+ var secrets_pull_exports = {};
104
+ __export(secrets_pull_exports, {
105
+ runSecretsPull: () => runSecretsPull
106
+ });
107
+ async function runSecretsPull(opts) {
108
+ const cred = readCredFile(opts.targetDir);
109
+ if (!cred) {
110
+ throw new Error(
111
+ `No ${CONFIG_FILENAME} found in ${opts.targetDir}. Run \`launch-kit init\` first.`
112
+ );
113
+ }
114
+ const nested = toNested(cred);
115
+ if (!nested) {
116
+ throw new Error(`${CONFIG_FILENAME} is missing required fields (pat, orgSlug, projectSlug, serverUrl).`);
117
+ }
118
+ const profile = nested.profiles[nested.active];
119
+ if (!profile) {
120
+ throw new Error(`Active profile "${nested.active}" not found in ${CONFIG_FILENAME}.`);
121
+ }
122
+ const envsRaw = await callMcp(profile, "environments_list", {});
123
+ const envsResult = Array.isArray(envsRaw) ? { environments: envsRaw, defaultPullEnvSlug: null } : envsRaw;
124
+ const envSlug = resolveEnvSlug(opts.envOverride, envsResult);
125
+ if (!envSlug) {
126
+ const list = envsResult.environments.map((e) => e.slug).join(", ") || "(none)";
127
+ throw new Error(
128
+ `No env specified and no default set. Available: ${list}. Pass --env=<slug>, set $LS_ENV, or set a project-level default at ${profile.serverUrl}/${profile.orgSlug}/projects/${profile.projectSlug}/settings.`
129
+ );
130
+ }
131
+ const pullResult = await callMcp(profile, "secrets_pull_env", { env_slug: envSlug });
132
+ const filePath = path3.join(opts.targetDir, opts.fileName);
133
+ writeEnvFile(filePath, pullResult.vars);
134
+ console.log(
135
+ `\u2714 wrote ${pullResult.vars.length} secrets from "${pullResult.env.slug}" \u2192 ${path3.relative(opts.targetDir, filePath) || opts.fileName}`
136
+ );
137
+ }
138
+ function resolveEnvSlug(override, envs) {
139
+ if (override) return override;
140
+ if (process.env.LS_ENV) return process.env.LS_ENV;
141
+ if (envs.defaultPullEnvSlug) return envs.defaultPullEnvSlug;
142
+ if (envs.environments.length === 1) return envs.environments[0].slug;
143
+ return null;
144
+ }
145
+ function writeEnvFile(filePath, vars) {
146
+ const lines = vars.map((v) => `${v.key}=${formatValue(v.value)}`);
147
+ fs3.writeFileSync(filePath, lines.join("\n") + "\n", "utf-8");
148
+ try {
149
+ fs3.chmodSync(filePath, 384);
150
+ } catch {
151
+ }
152
+ }
153
+ function formatValue(v) {
154
+ if (v === "") return "";
155
+ if (/[\s"'\\$`#=]/.test(v)) {
156
+ return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"`;
157
+ }
158
+ return v;
159
+ }
160
+ async function callMcp(profile, toolName, args) {
161
+ return new Promise((resolve3, reject) => {
162
+ const mcpUrl = new import_node_url.URL("/api/mcp/project", profile.serverUrl);
163
+ const body = JSON.stringify({
164
+ jsonrpc: "2.0",
165
+ id: 1,
166
+ method: "tools/call",
167
+ params: { name: toolName, arguments: args }
168
+ });
169
+ const requester = mcpUrl.protocol === "https:" ? import_node_https.request : import_node_http.request;
170
+ const req = requester(
171
+ {
172
+ host: mcpUrl.hostname,
173
+ port: mcpUrl.port || (mcpUrl.protocol === "https:" ? 443 : 80),
174
+ path: mcpUrl.pathname,
175
+ method: "POST",
176
+ headers: {
177
+ "Content-Type": "application/json",
178
+ "Accept": "application/json, text/event-stream",
179
+ "Content-Length": String(Buffer.byteLength(body)),
180
+ "Authorization": `Bearer ${profile.pat}`,
181
+ "X-Org-Slug": profile.orgSlug,
182
+ "X-Project-Slug": profile.projectSlug
183
+ }
184
+ },
185
+ (res) => {
186
+ const chunks = [];
187
+ res.on("data", (c2) => chunks.push(c2));
188
+ res.on("end", () => {
189
+ const text = Buffer.concat(chunks).toString("utf-8");
190
+ if (res.statusCode === 401) {
191
+ return reject(new Error(`PAT rejected (401). Regenerate at ${profile.serverUrl}/settings/tokens.`));
192
+ }
193
+ if (res.statusCode === 403) {
194
+ return reject(new Error(`Access denied (403). PAT lacks the scope required for ${toolName}.`));
195
+ }
196
+ if (res.statusCode && res.statusCode >= 400) {
197
+ return reject(new Error(`LaunchSecure ${res.statusCode}: ${text.slice(0, 300)}`));
198
+ }
199
+ let json = text;
200
+ if (text.startsWith("event:") || text.includes("\ndata: ")) {
201
+ for (const line of text.split("\n")) {
202
+ if (line.startsWith("data: ")) {
203
+ json = line.slice(6);
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ try {
209
+ const parsed = JSON.parse(json);
210
+ if (parsed.error) return reject(new Error(`${toolName}: ${parsed.error.message ?? "unknown"}`));
211
+ const inner = parsed.result?.content?.[0]?.text;
212
+ if (!inner) return reject(new Error(`${toolName} returned no content`));
213
+ if (inner.startsWith("\u2500\u2500 Error")) {
214
+ const firstLine = inner.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("\u2500\u2500"))[0];
215
+ return reject(new Error(`${toolName}: ${firstLine ?? inner}`));
216
+ }
217
+ resolve3(JSON.parse(inner));
218
+ } catch (err) {
219
+ reject(new Error(`Could not parse ${toolName} response: ${err instanceof Error ? err.message : String(err)}`));
220
+ }
221
+ });
222
+ }
223
+ );
224
+ req.setTimeout(TIMEOUT_MS, () => req.destroy(new Error(`${toolName} timed out after ${TIMEOUT_MS / 1e3}s`)));
225
+ req.on("error", reject);
226
+ req.write(body);
227
+ req.end();
228
+ });
229
+ }
230
+ var fs3, path3, import_node_http, import_node_https, import_node_url, TIMEOUT_MS;
231
+ var init_secrets_pull = __esm({
232
+ "src/server/secrets-pull.ts"() {
233
+ "use strict";
234
+ fs3 = __toESM(require("node:fs"));
235
+ path3 = __toESM(require("node:path"));
236
+ import_node_http = require("node:http");
237
+ import_node_https = require("node:https");
238
+ import_node_url = require("node:url");
239
+ init_cred_shape();
240
+ TIMEOUT_MS = 15e3;
241
+ }
242
+ });
243
+
33
244
  // src/server/statusline-install.ts
34
245
  var statusline_install_exports = {};
35
246
  __export(statusline_install_exports, {
@@ -37,25 +248,25 @@ __export(statusline_install_exports, {
37
248
  deactivateStatusline: () => deactivateStatusline
38
249
  });
39
250
  function readSettings() {
40
- if (!fs3.existsSync(SETTINGS_PATH)) return null;
251
+ if (!fs4.existsSync(SETTINGS_PATH)) return null;
41
252
  try {
42
- return JSON.parse(fs3.readFileSync(SETTINGS_PATH, "utf-8"));
253
+ return JSON.parse(fs4.readFileSync(SETTINGS_PATH, "utf-8"));
43
254
  } catch {
44
255
  return null;
45
256
  }
46
257
  }
47
258
  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");
259
+ fs4.mkdirSync(path4.dirname(SETTINGS_PATH), { recursive: true });
260
+ fs4.writeFileSync(SETTINGS_PATH, JSON.stringify(s, null, 2) + "\n", "utf-8");
50
261
  }
51
262
  function readScaffold(name) {
52
- const p = path3.resolve(__dirname, "..", "..", "scaffolds", "statusline", name);
53
- return fs3.readFileSync(p, "utf-8");
263
+ const p = path4.resolve(__dirname, "..", "..", "scaffolds", "statusline", name);
264
+ return fs4.readFileSync(p, "utf-8");
54
265
  }
55
266
  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 });
267
+ fs4.mkdirSync(LK_DIR, { recursive: true });
268
+ fs4.writeFileSync(WRAPPER_PATH, readScaffold("statusline-wrapper.sh"), { mode: 493 });
269
+ fs4.writeFileSync(CHIP_PATH, readScaffold("statusline-mcp.sh"), { mode: 493 });
59
270
  }
60
271
  function wrapperCommand(opts) {
61
272
  const env = [];
@@ -115,103 +326,313 @@ function deactivateStatusline() {
115
326
  writeSettings(restored);
116
327
  for (const p of [WRAPPER_PATH, CHIP_PATH]) {
117
328
  try {
118
- fs3.unlinkSync(p);
329
+ fs4.unlinkSync(p);
119
330
  } catch {
120
331
  }
121
332
  }
122
333
  return { ok: true, outcome: "deactivated", message: "restored original statusLine.command" };
123
334
  }
124
- var fs3, path3, import_node_os, LK_DIR, WRAPPER_PATH, CHIP_PATH, SETTINGS_PATH, ORIGINAL_KEY;
335
+ var fs4, path4, import_node_os, LK_DIR, WRAPPER_PATH, CHIP_PATH, SETTINGS_PATH, ORIGINAL_KEY;
125
336
  var init_statusline_install = __esm({
126
337
  "src/server/statusline-install.ts"() {
127
338
  "use strict";
128
- fs3 = __toESM(require("node:fs"));
129
- path3 = __toESM(require("node:path"));
339
+ fs4 = __toESM(require("node:fs"));
340
+ path4 = __toESM(require("node:path"));
130
341
  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");
342
+ LK_DIR = path4.join((0, import_node_os.homedir)(), ".launchsecure");
343
+ WRAPPER_PATH = path4.join(LK_DIR, "statusline-wrapper.sh");
344
+ CHIP_PATH = path4.join(LK_DIR, "statusline-mcp.sh");
345
+ SETTINGS_PATH = path4.join((0, import_node_os.homedir)(), ".claude", "settings.json");
135
346
  ORIGINAL_KEY = "_launchKitStatuslineOriginal";
136
347
  }
137
348
  });
138
349
 
139
- // src/server/init-entry.ts
140
- var import_node_child_process = require("node:child_process");
141
- var crypto = __toESM(require("node:crypto"));
142
- var fs4 = __toESM(require("node:fs"));
143
- var import_node_http = require("node:http");
144
- var import_node_https = require("node:https");
145
- var path4 = __toESM(require("node:path"));
146
- var readline = __toESM(require("node:readline"));
147
- var import_node_url = require("node:url");
148
-
149
- // src/server/cred-shape.ts
150
- var fs = __toESM(require("node:fs"));
151
- var path = __toESM(require("node:path"));
152
- var CONFIG_FILENAME = ".launch-secure.cred.config";
153
- function inferCourseName(serverUrl) {
350
+ // src/server/radar/mcp.ts
351
+ function parseBody(text) {
352
+ if (!text) return {};
353
+ if (text.startsWith("event:") || text.includes("\ndata: ")) {
354
+ for (const line of text.split("\n")) {
355
+ if (line.startsWith("data: ")) {
356
+ try {
357
+ return JSON.parse(line.slice(6));
358
+ } catch {
359
+ }
360
+ }
361
+ }
362
+ }
154
363
  try {
155
- const host = new URL(serverUrl).hostname.toLowerCase();
156
- if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) return "local";
157
- if (host.includes("staging")) return "staging";
158
- if (host.endsWith(".vercel.app")) return "prod";
159
- return host.split(".")[0] || "default";
364
+ return JSON.parse(text);
160
365
  } catch {
161
- return "default";
366
+ return {};
162
367
  }
163
368
  }
164
- function toNested(cred) {
165
- if (cred.profiles && cred.active && cred.profiles[cred.active]) {
166
- return { active: cred.active, profiles: cred.profiles };
167
- }
168
- if (!cred.pat || !cred.orgSlug || !cred.projectSlug || !cred.serverUrl) {
169
- return null;
170
- }
171
- const name = inferCourseName(cred.serverUrl);
172
- return {
173
- active: name,
174
- profiles: {
175
- [name]: {
176
- pat: cred.pat,
177
- orgSlug: cred.orgSlug,
178
- projectSlug: cred.projectSlug,
179
- serverUrl: cred.serverUrl
369
+ var import_node_https2, import_node_http2, ProjectMcpClient;
370
+ var init_mcp = __esm({
371
+ "src/server/radar/mcp.ts"() {
372
+ "use strict";
373
+ import_node_https2 = require("node:https");
374
+ import_node_http2 = require("node:http");
375
+ ProjectMcpClient = class {
376
+ constructor(opts) {
377
+ this.initialized = false;
378
+ this.callId = 1;
379
+ this.mcpUrl = new URL("/api/mcp/project", opts.serverUrl);
380
+ this.headers = {
381
+ "Content-Type": "application/json",
382
+ "Accept": "application/json, text/event-stream",
383
+ "Authorization": `Bearer ${opts.pat}`
384
+ };
385
+ if (opts.orgSlug) this.headers["X-Org-Slug"] = opts.orgSlug;
386
+ if (opts.projectSlug) this.headers["X-Project-Slug"] = opts.projectSlug;
180
387
  }
181
- }
182
- };
183
- }
184
- function upsertProfile(existing, name, profile) {
185
- const base = existing ? toNested(existing) ?? { active: name, profiles: {} } : { active: name, profiles: {} };
186
- return {
187
- active: name,
188
- profiles: { ...base.profiles, [name]: profile }
189
- };
388
+ async call(toolName, args) {
389
+ await this.ensureInitialized();
390
+ const body = JSON.stringify({
391
+ jsonrpc: "2.0",
392
+ id: this.callId++,
393
+ method: "tools/call",
394
+ params: { name: toolName, arguments: args }
395
+ });
396
+ const resp = await this.send(body);
397
+ const parsed = parseBody(resp.body);
398
+ if (parsed.error) {
399
+ throw new Error(`MCP ${toolName} failed: ${parsed.error.message ?? JSON.stringify(parsed.error)}`);
400
+ }
401
+ const text = parsed.result?.content?.[0]?.text;
402
+ if (!text) return parsed.result;
403
+ if (text.startsWith("\u2500\u2500 Error")) {
404
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
405
+ const reason = lines.find((l) => !l.startsWith("\u2500\u2500")) ?? text;
406
+ throw new Error(`MCP ${toolName} failed: ${reason}`);
407
+ }
408
+ try {
409
+ return JSON.parse(text);
410
+ } catch {
411
+ return text;
412
+ }
413
+ }
414
+ async ensureInitialized() {
415
+ if (this.initialized) return;
416
+ const initBody = JSON.stringify({
417
+ jsonrpc: "2.0",
418
+ id: this.callId++,
419
+ method: "initialize",
420
+ params: {
421
+ protocolVersion: "2025-03-26",
422
+ capabilities: {},
423
+ clientInfo: { name: "launchpod-feedback-agent", version: "0.0.1" }
424
+ }
425
+ });
426
+ const initResp = await this.send(initBody);
427
+ if (initResp.sessionId) this.sessionId = initResp.sessionId;
428
+ const notifBody = JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" });
429
+ await this.send(notifBody);
430
+ this.initialized = true;
431
+ }
432
+ send(body) {
433
+ return new Promise((resolve3, reject) => {
434
+ const headers = {
435
+ ...this.headers,
436
+ "Content-Length": String(Buffer.byteLength(body))
437
+ };
438
+ if (this.sessionId) headers["Mcp-Session-Id"] = this.sessionId;
439
+ const requester = this.mcpUrl.protocol === "https:" ? import_node_https2.request : import_node_http2.request;
440
+ const req = requester(
441
+ {
442
+ host: this.mcpUrl.hostname,
443
+ port: this.mcpUrl.port || (this.mcpUrl.protocol === "https:" ? 443 : 80),
444
+ path: this.mcpUrl.pathname + this.mcpUrl.search,
445
+ method: "POST",
446
+ headers
447
+ },
448
+ (res) => {
449
+ const chunks = [];
450
+ res.on("data", (c2) => chunks.push(c2));
451
+ res.on("end", () => {
452
+ const text = Buffer.concat(chunks).toString("utf-8");
453
+ if (res.statusCode && res.statusCode >= 400) {
454
+ reject(new Error(`MCP HTTP ${res.statusCode}: ${text}`));
455
+ return;
456
+ }
457
+ const sid = res.headers["mcp-session-id"];
458
+ resolve3({ body: text, sessionId: typeof sid === "string" ? sid : void 0 });
459
+ });
460
+ }
461
+ );
462
+ req.on("error", reject);
463
+ req.write(body);
464
+ req.end();
465
+ });
466
+ }
467
+ };
468
+ }
469
+ });
470
+
471
+ // src/server/radar-docker-init-entry.ts
472
+ var radar_docker_init_entry_exports = {};
473
+ function fail(message) {
474
+ console.error(message);
475
+ process.exit(1);
190
476
  }
191
- function readCredFile(repoRoot) {
192
- const p = path.join(repoRoot, CONFIG_FILENAME);
193
- if (!fs.existsSync(p)) return null;
477
+ function requireEnv(name) {
478
+ const v = process.env[name];
479
+ if (!v) fail(`ERROR: ${name} is required but not set`);
480
+ return v;
481
+ }
482
+ function run2(cmd, args, stdio = "inherit") {
483
+ const r = (0, import_node_child_process2.spawnSync)(cmd, args, { stdio });
484
+ return r.status ?? 1;
485
+ }
486
+ async function setupFromCloud() {
487
+ const pat = requireEnv("LS_PAT");
488
+ const serverUrl = process.env.LS_SERVER_URL ?? "https://launchsecure-v2.vercel.app";
489
+ const orgSlug = process.env.LS_ORG_SLUG;
490
+ const projectSlug = process.env.LS_PROJECT_SLUG;
491
+ const mcp = new ProjectMcpClient({ serverUrl, pat, orgSlug, projectSlug });
492
+ let bundle;
194
493
  try {
195
- return JSON.parse(fs.readFileSync(p, "utf-8"));
494
+ bundle = await mcp.call("radar_bootstrap_get", {});
196
495
  } catch (err) {
197
- throw new Error(`could not parse ${CONFIG_FILENAME}: ${err instanceof Error ? err.message : String(err)}`);
496
+ fail(`[entrypoint] radar_bootstrap_get failed (${err instanceof Error ? err.message : String(err)}) \u2014 check LS_PAT has mcp:radar:bootstrap scope and is scoped to the right org/project.`);
497
+ }
498
+ if (!process.env.LS_ORG_SLUG) process.env.LS_ORG_SLUG = bundle.orgSlug;
499
+ if (!process.env.LS_PROJECT_SLUG) process.env.LS_PROJECT_SLUG = bundle.projectSlug;
500
+ if (!process.env.GIT_USER_NAME) process.env.GIT_USER_NAME = bundle.gitName;
501
+ if (!process.env.GIT_USER_EMAIL) process.env.GIT_USER_EMAIL = bundle.gitEmail;
502
+ if (!process.env.GH_TOKEN && bundle.githubToken) process.env.GH_TOKEN = bundle.githubToken;
503
+ if (!process.env.GH_TOKEN) {
504
+ fail(`[entrypoint] no GH_TOKEN available \u2014 user has not connected GitHub (githubTokenStatus=${bundle.githubTokenStatus}). Connect GitHub in LS or pre-set GH_TOKEN in the container env.`);
505
+ }
506
+ console.log(`[entrypoint] bundle from cloud: org=${process.env.LS_ORG_SLUG} project=${process.env.LS_PROJECT_SLUG} git=${process.env.GIT_USER_NAME} <${process.env.GIT_USER_EMAIL}> github=${bundle.githubTokenStatus.toLowerCase()}`);
507
+ }
508
+ function setupClaudeCredentials() {
509
+ const home = process.env.HOME ?? "/home/launchpod";
510
+ const claudeDir = (0, import_node_path.join)(home, ".claude");
511
+ (0, import_node_fs.mkdirSync)(claudeDir, { recursive: true });
512
+ const decoded = Buffer.from(requireEnv("CLAUDE_CREDENTIALS_B64"), "base64").toString("utf8");
513
+ const credsPath = (0, import_node_path.join)(claudeDir, ".credentials.json");
514
+ (0, import_node_fs.writeFileSync)(credsPath, decoded);
515
+ (0, import_node_fs.chmodSync)(credsPath, 384);
516
+ const configPath = (0, import_node_path.join)(home, ".claude.json");
517
+ let cfg = {};
518
+ if ((0, import_node_fs.existsSync)(configPath)) {
519
+ try {
520
+ cfg = JSON.parse((0, import_node_fs.readFileSync)(configPath, "utf8"));
521
+ } catch {
522
+ cfg = {};
523
+ }
198
524
  }
525
+ cfg.hasCompletedOnboarding = true;
526
+ cfg.lastOnboardingVersion = cfg.lastOnboardingVersion ?? "2.1.159";
527
+ cfg.numStartups = (cfg.numStartups ?? 0) + 1;
528
+ cfg.installMethod = cfg.installMethod ?? "global";
529
+ (0, import_node_fs.writeFileSync)(configPath, JSON.stringify(cfg, null, 2));
530
+ (0, import_node_fs.chmodSync)(configPath, 384);
531
+ }
532
+ function setupGitAndGh() {
533
+ const name = process.env.GIT_USER_NAME ?? "Radar Bot";
534
+ const email = process.env.GIT_USER_EMAIL ?? "radar@launchpod.local";
535
+ const status = run2("launch-kit", ["setup-git", `--identity=${name} <${email}>`]);
536
+ if (status !== 0) fail(`[entrypoint] launch-kit setup-git failed (status ${status})`);
537
+ }
538
+ function initWorkspaceIfEmpty() {
539
+ process.chdir("/workspace");
540
+ if ((0, import_node_fs.existsSync)(".git")) {
541
+ console.log("[entrypoint] /workspace already initialized \u2014 skipping init");
542
+ return;
543
+ }
544
+ console.log("[entrypoint] /workspace is empty \u2014 running launch-kit init");
545
+ const status = run2("launch-kit", [
546
+ "init",
547
+ `--token=${requireEnv("LS_PAT")}`,
548
+ `--org=${requireEnv("LS_ORG_SLUG")}`,
549
+ `--project=${requireEnv("LS_PROJECT_SLUG")}`,
550
+ `--url=${process.env.LS_SERVER_URL ?? "https://launchsecure-v2.vercel.app"}`,
551
+ `--dir=/workspace`
552
+ ]);
553
+ if (status !== 0) fail(`[entrypoint] launch-kit init failed (status ${status})`);
199
554
  }
200
- function writeJsonAtomic(absPath, value, mode) {
201
- const tmp = `${absPath}.tmp`;
202
- fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + "\n", "utf-8");
203
- if (mode !== void 0) {
555
+ function execLaunchPodRadar() {
556
+ console.log("[entrypoint] starting launch-pod radar");
557
+ const child = (0, import_node_child_process2.spawn)("launch-pod", ["radar"], { stdio: "inherit" });
558
+ const forward = (sig) => () => {
204
559
  try {
205
- fs.chmodSync(tmp, mode);
560
+ child.kill(sig);
206
561
  } catch {
207
562
  }
563
+ };
564
+ process.on("SIGTERM", forward("SIGTERM"));
565
+ process.on("SIGINT", forward("SIGINT"));
566
+ process.on("SIGHUP", forward("SIGHUP"));
567
+ child.on("exit", (code, signal) => {
568
+ if (signal) process.kill(process.pid, signal);
569
+ else process.exit(code ?? 0);
570
+ });
571
+ }
572
+ async function main() {
573
+ for (const k of REQUIRED_ENV) requireEnv(k);
574
+ await setupFromCloud();
575
+ setupClaudeCredentials();
576
+ setupGitAndGh();
577
+ initWorkspaceIfEmpty();
578
+ execLaunchPodRadar();
579
+ }
580
+ var import_node_child_process2, import_node_fs, import_node_path, REQUIRED_ENV;
581
+ var init_radar_docker_init_entry = __esm({
582
+ "src/server/radar-docker-init-entry.ts"() {
583
+ "use strict";
584
+ import_node_child_process2 = require("node:child_process");
585
+ import_node_fs = require("node:fs");
586
+ import_node_path = require("node:path");
587
+ init_mcp();
588
+ REQUIRED_ENV = [
589
+ "CLAUDE_CREDENTIALS_B64",
590
+ "LS_PAT"
591
+ ];
592
+ main().catch((err) => {
593
+ console.error(`[entrypoint] fatal: ${err instanceof Error ? err.message : String(err)}`);
594
+ process.exit(1);
595
+ });
208
596
  }
209
- fs.renameSync(tmp, absPath);
597
+ });
598
+
599
+ // src/server/init-entry.ts
600
+ var import_node_child_process3 = require("node:child_process");
601
+ var crypto = __toESM(require("node:crypto"));
602
+ var fs5 = __toESM(require("node:fs"));
603
+ var import_node_http3 = require("node:http");
604
+ var import_node_https3 = require("node:https");
605
+ var path5 = __toESM(require("node:path"));
606
+ var readline = __toESM(require("node:readline"));
607
+ var import_node_url2 = require("node:url");
608
+ init_cred_shape();
609
+
610
+ // src/server/git-bot-config.ts
611
+ var import_node_child_process = require("node:child_process");
612
+ function run(cmd, args, stdio = "inherit") {
613
+ return (0, import_node_child_process.spawnSync)(cmd, args, { stdio }).status ?? 1;
614
+ }
615
+ function configureGitForBot(identity) {
616
+ if (process.env.GH_TOKEN) {
617
+ run("gh", ["auth", "setup-git"]);
618
+ }
619
+ run("git", ["config", "--global", "user.name", identity.name]);
620
+ run("git", ["config", "--global", "user.email", identity.email]);
621
+ run("git", ["config", "--global", "init.defaultBranch", "main"]);
622
+ run("git", ["config", "--global", "pull.rebase", "false"]);
623
+ }
624
+ function parseGitIdentityFlag(value, flagName = "--git-identity") {
625
+ const m = value.match(/^\s*(.+?)\s*<\s*([^>]+?)\s*>\s*$/);
626
+ if (!m) {
627
+ throw new Error(`${flagName} must be in the form "Name <email>" (got: ${value})`);
628
+ }
629
+ return { name: m[1], email: m[2] };
210
630
  }
211
631
 
212
632
  // src/server/cred-recovery.ts
213
633
  var fs2 = __toESM(require("node:fs"));
214
634
  var path2 = __toESM(require("node:path"));
635
+ init_cred_shape();
215
636
  var LEGACY_CONFIG_FILENAME = ".launch-secure.config";
216
637
  function migrateLegacyCredFile(targetDir, opts) {
217
638
  const legacy = path2.join(targetDir, LEGACY_CONFIG_FILENAME);
@@ -292,6 +713,7 @@ function recoverCred(targetDir, opts) {
292
713
  }
293
714
 
294
715
  // src/server/init-entry.ts
716
+ init_secrets_pull();
295
717
  init_statusline_install();
296
718
  var DEFAULT_SERVER_URL = "https://launchsecure-v2.vercel.app";
297
719
  var ONBOARD_SCRIPT_NAME = "onboard";
@@ -363,12 +785,12 @@ var LAUNCH_KIT_TOOLS_GUIDE_STATIC_TAIL = `
363
785
  `;
364
786
  function listEntries(dir, kind) {
365
787
  if (kind === "commands") {
366
- return fs4.readdirSync(dir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f.replace(/\.md$/, ""), file: path4.join(dir, f) }));
788
+ return fs5.readdirSync(dir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f.replace(/\.md$/, ""), file: path5.join(dir, f) }));
367
789
  }
368
- return fs4.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && fs4.existsSync(path4.join(dir, e.name, "SKILL.md"))).map((e) => e.name).sort().map((name) => ({ name, file: path4.join(dir, name, "SKILL.md") }));
790
+ return fs5.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && fs5.existsSync(path5.join(dir, e.name, "SKILL.md"))).map((e) => e.name).sort().map((name) => ({ name, file: path5.join(dir, name, "SKILL.md") }));
369
791
  }
370
792
  function renderEntries(dir, kind) {
371
- if (!fs4.existsSync(dir)) return `
793
+ if (!fs5.existsSync(dir)) return `
372
794
  LS slash ${kind}: (scaffold dir not bundled \u2014 this is a packaging bug)
373
795
  `;
374
796
  const entries = listEntries(dir, kind);
@@ -378,7 +800,7 @@ LS slash ${kind}: (none defined)
378
800
  const names = entries.map((e) => `/${PLUGIN_ID}:${e.name}`);
379
801
  const colWidth = Math.max(26, ...names.map((n) => n.length + 2));
380
802
  const lines = entries.map((entry, i) => {
381
- const text = fs4.readFileSync(entry.file, "utf-8");
803
+ const text = fs5.readFileSync(entry.file, "utf-8");
382
804
  const fmMatch = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
383
805
  const desc = fmMatch?.[1].match(/^description:\s*(.+)$/m)?.[1]?.trim() ?? "";
384
806
  return ` ${names[i].padEnd(colWidth)} \u2014 ${desc}`;
@@ -389,8 +811,8 @@ ${lines.join("\n")}
389
811
  `;
390
812
  }
391
813
  function renderLsCommandsSection() {
392
- const base = path4.resolve(__dirname, "..", "..", "scaffolds", "ls-marketplace", "plugins", "kit");
393
- return renderEntries(path4.join(base, "commands"), "commands") + renderEntries(path4.join(base, "skills"), "skills");
814
+ const base = path5.resolve(__dirname, "..", "..", "scaffolds", "ls-marketplace", "plugins", "kit");
815
+ return renderEntries(path5.join(base, "commands"), "commands") + renderEntries(path5.join(base, "skills"), "skills");
394
816
  }
395
817
  function getLaunchKitToolsGuide() {
396
818
  return `${LAUNCH_KIT_TOOLS_GUIDE_STATIC_HEAD}${renderLsCommandsSection()}${LAUNCH_KIT_TOOLS_GUIDE_STATIC_TAIL}`;
@@ -418,7 +840,7 @@ var KNOWN_BOOL_FLAGS = /* @__PURE__ */ new Set([
418
840
  "--guide",
419
841
  "--no-guide"
420
842
  ]);
421
- var KNOWN_KV_KEYS = /* @__PURE__ */ new Set(["token", "org", "project", "url", "dir", "course"]);
843
+ var KNOWN_KV_KEYS = /* @__PURE__ */ new Set(["token", "org", "project", "url", "dir", "course", "git-identity"]);
422
844
  function parseArgs(argv) {
423
845
  const args = {
424
846
  token: process.env.LS_PAT ?? null,
@@ -426,6 +848,7 @@ function parseArgs(argv) {
426
848
  projectSlug: null,
427
849
  serverUrl: DEFAULT_SERVER_URL,
428
850
  targetDir: null,
851
+ gitIdentity: null,
429
852
  course: null,
430
853
  noInstall: false,
431
854
  noOnboard: false,
@@ -529,6 +952,14 @@ function parseArgs(argv) {
529
952
  args.course = val;
530
953
  continue;
531
954
  }
955
+ if (key === "git-identity") {
956
+ try {
957
+ args.gitIdentity = parseGitIdentityFlag(val);
958
+ } catch (err) {
959
+ fail2(err instanceof Error ? err.message : String(err));
960
+ }
961
+ continue;
962
+ }
532
963
  unknown.push(raw);
533
964
  continue;
534
965
  }
@@ -541,7 +972,7 @@ function parseArgs(argv) {
541
972
  if (unknown.length > 0) {
542
973
  const knownBool = [...KNOWN_BOOL_FLAGS].join(", ");
543
974
  const knownKv = [...KNOWN_KV_KEYS].map((k) => `--${k}=<value>`).join(", ");
544
- fail(`Unknown argument(s): ${unknown.join(" ")}
975
+ fail2(`Unknown argument(s): ${unknown.join(" ")}
545
976
  Known boolean flags: ${knownBool}
546
977
  Known key=value flags: ${knownKv}`);
547
978
  }
@@ -594,6 +1025,12 @@ Subcommands:
594
1025
  init Bootstrap a new project (clone, cred file, MCP, scaffolds, install)
595
1026
  refresh Re-apply scaffolds + MCP entries in an already-initialized project
596
1027
  (no clone, no install, no PAT prompt \u2014 see \`launch-kit refresh --help\`)
1028
+ setup-git Configure git identity + gh credential helper in one
1029
+ shot. Use in containers / CI where init isn't needed.
1030
+ \`launch-kit setup-git --identity="Name <email>"\`.
1031
+ secrets pull Fetch decrypted secrets from cloud LS and write a .env file.
1032
+ Env resolved from --env \u2192 $LS_ENV \u2192 server-side project default \u2192
1033
+ single-env auto-pick. See \`launch-kit secrets --help\`.
597
1034
  statusline activate Wrap ~/.claude/settings.json's statusLine.command so MCP daemon
598
1035
  chips (recall, chart, deck, council) get appended. Refuses to
599
1036
  create one if none exists \u2014 additive only.
@@ -618,6 +1055,12 @@ Options:
618
1055
  becomes active; re-run with a different --course
619
1056
  and --url to add another (e.g. local + staging).
620
1057
  Use \`launch-course set <name>\` to switch later.
1058
+ --git-identity="N <e>" Non-interactive git identity for service-account /
1059
+ CI / Docker runs. Configures git user.name, user.email,
1060
+ init.defaultBranch=main, pull.rebase=false; also
1061
+ wires GH_TOKEN into git's credential helper via
1062
+ \`gh auth setup-git\` when GH_TOKEN is set. Example:
1063
+ --git-identity="Radar Bot <radar@launchpod.local>".
621
1064
  --no-install Skip dependency install step (also skips the onboard
622
1065
  script \u2014 install is its prerequisite).
623
1066
  --no-onboard Skip the onboard script even when install runs.
@@ -695,7 +1138,7 @@ async function prompt(question) {
695
1138
  resolve3(answer.trim());
696
1139
  }));
697
1140
  }
698
- function fail(msg) {
1141
+ function fail2(msg) {
699
1142
  console.error(`[launch-kit] \u2717 ${msg}`);
700
1143
  process.exit(1);
701
1144
  }
@@ -711,14 +1154,14 @@ function dryNote(msg) {
711
1154
  console.log(`[launch-kit] (dry-run) ${msg}`);
712
1155
  }
713
1156
  function which(bin) {
714
- const res = (0, import_node_child_process.spawnSync)(process.platform === "win32" ? "where" : "which", [bin], { encoding: "utf-8" });
1157
+ const res = (0, import_node_child_process3.spawnSync)(process.platform === "win32" ? "where" : "which", [bin], { encoding: "utf-8" });
715
1158
  if (res.status !== 0) return null;
716
1159
  return res.stdout.split(/\r?\n/)[0]?.trim() || null;
717
1160
  }
718
1161
  function preflight() {
719
1162
  const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
720
- if (nodeMajor < 18) fail(`Node.js >= 18 required (current: ${process.versions.node}).`);
721
- if (!which("git")) fail("git not found in PATH. Install git: https://git-scm.com/downloads");
1163
+ if (nodeMajor < 18) fail2(`Node.js >= 18 required (current: ${process.versions.node}).`);
1164
+ if (!which("git")) fail2("git not found in PATH. Install git: https://git-scm.com/downloads");
722
1165
  const hasGh = which("gh") !== null;
723
1166
  ok(`preflight ok \u2014 node ${process.versions.node}, git present${hasGh ? ", gh present" : ", gh not found (will use git for clone)"}`);
724
1167
  return { hasGh };
@@ -734,7 +1177,7 @@ var ProjectInfoHttpError = class extends Error {
734
1177
  };
735
1178
  function attemptProjectInfo(args) {
736
1179
  return new Promise((resolve3, reject) => {
737
- const mcpUrl = new import_node_url.URL("/api/mcp/project", args.serverUrl);
1180
+ const mcpUrl = new import_node_url2.URL("/api/mcp/project", args.serverUrl);
738
1181
  const body = JSON.stringify({
739
1182
  jsonrpc: "2.0",
740
1183
  id: 1,
@@ -744,7 +1187,7 @@ function attemptProjectInfo(args) {
744
1187
  arguments: { org_slug: args.orgSlug, project_slug: args.projectSlug }
745
1188
  }
746
1189
  });
747
- const requester = mcpUrl.protocol === "https:" ? import_node_https.request : import_node_http.request;
1190
+ const requester = mcpUrl.protocol === "https:" ? import_node_https3.request : import_node_http3.request;
748
1191
  const req = requester(
749
1192
  {
750
1193
  host: mcpUrl.hostname,
@@ -852,7 +1295,7 @@ async function callProjectInfo(args) {
852
1295
  throw lastErr;
853
1296
  }
854
1297
  function gitRemoteUrl(dir) {
855
- const res = (0, import_node_child_process.spawnSync)("git", ["-C", dir, "config", "--get", "remote.origin.url"], { encoding: "utf-8" });
1298
+ const res = (0, import_node_child_process3.spawnSync)("git", ["-C", dir, "config", "--get", "remote.origin.url"], { encoding: "utf-8" });
856
1299
  if (res.status !== 0) return null;
857
1300
  return res.stdout.trim() || null;
858
1301
  }
@@ -861,18 +1304,18 @@ function normalizeRepoUrl(url) {
861
1304
  const sshMatch = u.match(/^git@([^:]+):(.+)$/);
862
1305
  if (sshMatch) u = `https://${sshMatch[1]}/${sshMatch[2]}`;
863
1306
  try {
864
- const parsed = new import_node_url.URL(u);
1307
+ const parsed = new import_node_url2.URL(u);
865
1308
  return `${parsed.protocol}//${parsed.host.toLowerCase()}${parsed.pathname}`;
866
1309
  } catch {
867
1310
  return u;
868
1311
  }
869
1312
  }
870
1313
  function isGitRepo(dir) {
871
- return fs4.existsSync(path4.join(dir, ".git"));
1314
+ return fs5.existsSync(path5.join(dir, ".git"));
872
1315
  }
873
1316
  function dirIsEmpty(dir) {
874
- if (!fs4.existsSync(dir)) return true;
875
- return fs4.readdirSync(dir).length === 0;
1317
+ if (!fs5.existsSync(dir)) return true;
1318
+ return fs5.readdirSync(dir).length === 0;
876
1319
  }
877
1320
  function cloneRepo(repoUrl, targetDir, hasGh) {
878
1321
  const isGithub = /github\.com/i.test(repoUrl);
@@ -891,20 +1334,20 @@ function cloneRepo(repoUrl, targetDir, hasGh) {
891
1334
  dryNote(`would run: ${cmd} ${args.join(" ")}`);
892
1335
  return;
893
1336
  }
894
- const res = (0, import_node_child_process.spawnSync)(cmd, args, { stdio: "inherit" });
1337
+ const res = (0, import_node_child_process3.spawnSync)(cmd, args, { stdio: "inherit" });
895
1338
  if (res.status !== 0) {
896
- fail(
1339
+ fail2(
897
1340
  `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.`
898
1341
  );
899
1342
  }
900
- if (!fs4.existsSync(path4.join(targetDir, ".git"))) {
901
- fail(`Clone reported success but .git is missing at ${targetDir}. Possible partial clone, filesystem issue, or sandboxing \u2014 investigate manually.`);
1343
+ if (!fs5.existsSync(path5.join(targetDir, ".git"))) {
1344
+ fail2(`Clone reported success but .git is missing at ${targetDir}. Possible partial clone, filesystem issue, or sandboxing \u2014 investigate manually.`);
902
1345
  }
903
1346
  ok(`cloned to ${targetDir}`);
904
1347
  }
905
1348
  function writeConfigFile(targetDir, cfg, courseName) {
906
1349
  recoverCred(targetDir, getRecoveryOptions());
907
- const p = path4.join(targetDir, CONFIG_FILENAME);
1350
+ const p = path5.join(targetDir, CONFIG_FILENAME);
908
1351
  const existing = readCredFile(targetDir);
909
1352
  const isNew = existing === null;
910
1353
  const isUpdate = !isNew && Boolean(existing?.profiles?.[courseName]);
@@ -923,15 +1366,15 @@ function getRecoveryOptions() {
923
1366
  return { dryRun: DRY_RUN, log: recoveryLog };
924
1367
  }
925
1368
  function detectExistingBootstrap(targetDir) {
926
- if (!fs4.existsSync(path4.join(targetDir, CONFIG_FILENAME))) {
1369
+ if (!fs5.existsSync(path5.join(targetDir, CONFIG_FILENAME))) {
927
1370
  return { bootstrapped: false };
928
1371
  }
929
- const mcpPath = path4.join(targetDir, ".mcp.json");
930
- if (!fs4.existsSync(mcpPath)) {
1372
+ const mcpPath = path5.join(targetDir, ".mcp.json");
1373
+ if (!fs5.existsSync(mcpPath)) {
931
1374
  return { bootstrapped: false };
932
1375
  }
933
1376
  try {
934
- const mcp = JSON.parse(fs4.readFileSync(mcpPath, "utf-8"));
1377
+ const mcp = JSON.parse(fs5.readFileSync(mcpPath, "utf-8"));
935
1378
  if (mcp.mcpServers?.["launch-secure"]) {
936
1379
  return { bootstrapped: true, reason: `${CONFIG_FILENAME} present + launch-secure MCP entry in .mcp.json` };
937
1380
  }
@@ -949,13 +1392,7 @@ function buildLaunchKitMcpEntries(cfg) {
949
1392
  },
950
1393
  "launch-chart": {
951
1394
  command: "npx",
952
- args: ["-y", "-p", LAUNCH_KIT_PKG, "launch-chart"],
953
- // Tells launch-chart to also start its HTTP UI alongside the MCP, so
954
- // users can open the chart viewer at localhost:<port> while queries
955
- // hit the MCP. Without this, the MCP runs passively (queries work,
956
- // no UI). I4 deep-merge preserves user-added env keys; this default
957
- // ensures the auto-serve UX ships out of the box.
958
- env: { LAUNCH_CHART_AUTOSERVE: "1" }
1395
+ args: ["-y", "-p", LAUNCH_KIT_PKG, "launch-chart"]
959
1396
  },
960
1397
  "launch-deck": {
961
1398
  command: "npx",
@@ -988,14 +1425,14 @@ function mergeMcpEntry(existing, ours) {
988
1425
  return merged;
989
1426
  }
990
1427
  function mergeMcpFile(targetDir, launchKitEntries) {
991
- const p = path4.join(targetDir, ".mcp.json");
992
- const hadExisting = fs4.existsSync(p);
1428
+ const p = path5.join(targetDir, ".mcp.json");
1429
+ const hadExisting = fs5.existsSync(p);
993
1430
  let existing = {};
994
1431
  if (hadExisting) {
995
1432
  try {
996
- existing = JSON.parse(fs4.readFileSync(p, "utf-8"));
1433
+ existing = JSON.parse(fs5.readFileSync(p, "utf-8"));
997
1434
  } catch (err) {
998
- fail(`Could not parse existing .mcp.json: ${err instanceof Error ? err.message : String(err)}`);
1435
+ fail2(`Could not parse existing .mcp.json: ${err instanceof Error ? err.message : String(err)}`);
999
1436
  }
1000
1437
  }
1001
1438
  const existingServerCount = Object.keys(existing.mcpServers ?? {}).length;
@@ -1017,17 +1454,17 @@ function mergeMcpFile(targetDir, launchKitEntries) {
1017
1454
  dryNote(`${action} .mcp.json \u2014 overwriting [${overwrites.join(", ") || "none"}], adding [${additions.join(", ") || "none"}]`);
1018
1455
  return { status: "skipped", summary: "(dry-run)" };
1019
1456
  }
1020
- fs4.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1457
+ fs5.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1021
1458
  const verb = hadExisting && existingServerCount > 0 ? "merged" : "wrote";
1022
1459
  ok(`${verb === "merged" ? "merged into" : "wrote"} .mcp.json (${Object.keys(launchKitEntries).length} launch-kit entries)`);
1023
1460
  const total = Object.keys(launchKitEntries).length;
1024
1461
  return { status: "ok", summary: `${verb} ${total} entries` };
1025
1462
  }
1026
1463
  function detectPackageManager(repoDir) {
1027
- const pkgPath = path4.join(repoDir, "package.json");
1028
- if (!fs4.existsSync(pkgPath)) return null;
1464
+ const pkgPath = path5.join(repoDir, "package.json");
1465
+ if (!fs5.existsSync(pkgPath)) return null;
1029
1466
  try {
1030
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
1467
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
1031
1468
  if (typeof pkg.packageManager === "string") {
1032
1469
  const name = pkg.packageManager.split("@")[0];
1033
1470
  const match = PACKAGE_MANAGERS.find((p) => p.name === name);
@@ -1036,7 +1473,7 @@ function detectPackageManager(repoDir) {
1036
1473
  }
1037
1474
  } catch {
1038
1475
  }
1039
- const matches = PACKAGE_MANAGERS.map((pm) => ({ pm, lockfile: pm.lockfiles.find((lf) => fs4.existsSync(path4.join(repoDir, lf))) ?? null })).filter((m) => m.lockfile !== null);
1476
+ const matches = PACKAGE_MANAGERS.map((pm) => ({ pm, lockfile: pm.lockfiles.find((lf) => fs5.existsSync(path5.join(repoDir, lf))) ?? null })).filter((m) => m.lockfile !== null);
1040
1477
  if (matches.length === 1) {
1041
1478
  return { pm: matches[0].pm, source: `lockfile ${matches[0].lockfile}` };
1042
1479
  }
@@ -1045,8 +1482,8 @@ function detectPackageManager(repoDir) {
1045
1482
  return { pm: matches[0].pm, source: `lockfile ${matches[0].lockfile} (multiple present)` };
1046
1483
  }
1047
1484
  for (const pm of PACKAGE_MANAGERS) {
1048
- if (pm.workspaceFiles?.some((wf) => fs4.existsSync(path4.join(repoDir, wf)))) {
1049
- return { pm, source: `workspace file (${pm.workspaceFiles.find((wf) => fs4.existsSync(path4.join(repoDir, wf)))})` };
1485
+ if (pm.workspaceFiles?.some((wf) => fs5.existsSync(path5.join(repoDir, wf)))) {
1486
+ return { pm, source: `workspace file (${pm.workspaceFiles.find((wf) => fs5.existsSync(path5.join(repoDir, wf)))})` };
1050
1487
  }
1051
1488
  }
1052
1489
  const npm = PACKAGE_MANAGERS.find((p) => p.name === "npm");
@@ -1055,8 +1492,8 @@ function detectPackageManager(repoDir) {
1055
1492
  function runInstall(repoDir, detected) {
1056
1493
  const { pm } = detected;
1057
1494
  if (!which(pm.binary)) {
1058
- fail(
1059
- `${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(" ")}`
1495
+ fail2(
1496
+ `${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 ${path5.basename(repoDir)} && ${pm.binary} ${pm.installArgs.join(" ")}`
1060
1497
  );
1061
1498
  }
1062
1499
  info(`running ${pm.binary} ${pm.installArgs.join(" ")} \u2026`);
@@ -1064,24 +1501,24 @@ function runInstall(repoDir, detected) {
1064
1501
  dryNote(`would run: ${pm.binary} ${pm.installArgs.join(" ")} (cwd: ${repoDir})`);
1065
1502
  return;
1066
1503
  }
1067
- const res = (0, import_node_child_process.spawnSync)(pm.binary, pm.installArgs, { cwd: repoDir, stdio: "inherit" });
1504
+ const res = (0, import_node_child_process3.spawnSync)(pm.binary, pm.installArgs, { cwd: repoDir, stdio: "inherit" });
1068
1505
  if (res.status !== 0) {
1069
- fail(
1506
+ fail2(
1070
1507
  `${pm.name} install failed (exit ${res.status}).
1071
1508
 
1072
1509
  Half-init state \u2014 install didn't complete, but these files ARE on disk:
1073
- - ${path4.join(repoDir, CONFIG_FILENAME)} (cred file)
1074
- - ${path4.join(repoDir, ".mcp.json")} (5 launch-kit MCP entries merged)
1075
- - ${path4.join(repoDir, ".gitignore")} (cred line appended)
1510
+ - ${path5.join(repoDir, CONFIG_FILENAME)} (cred file)
1511
+ - ${path5.join(repoDir, ".mcp.json")} (5 launch-kit MCP entries merged)
1512
+ - ${path5.join(repoDir, ".gitignore")} (cred line appended)
1076
1513
  - clone at ${repoDir}
1077
1514
 
1078
1515
  Scaffolds (recall, migrate-safety, marketplace, recall-hook) were NOT yet written.
1079
1516
 
1080
1517
  To retry install only:
1081
- cd ${path4.basename(repoDir)} && ${pm.binary} ${pm.installArgs.join(" ")}
1518
+ cd ${path5.basename(repoDir)} && ${pm.binary} ${pm.installArgs.join(" ")}
1082
1519
 
1083
1520
  To re-run init after fixing the install error:
1084
- cd ${path4.basename(repoDir)} && npx @launchsecure/launch-kit init --dir=.
1521
+ cd ${path5.basename(repoDir)} && npx @launchsecure/launch-kit init --dir=.
1085
1522
 
1086
1523
  To fully reset: delete the files listed above and the clone, then re-init.`
1087
1524
  );
@@ -1089,10 +1526,10 @@ To fully reset: delete the files listed above and the clone, then re-init.`
1089
1526
  ok(`${pm.name} install complete`);
1090
1527
  }
1091
1528
  function hasOnboardScript(repoDir) {
1092
- const pkgPath = path4.join(repoDir, "package.json");
1093
- if (!fs4.existsSync(pkgPath)) return false;
1529
+ const pkgPath = path5.join(repoDir, "package.json");
1530
+ if (!fs5.existsSync(pkgPath)) return false;
1094
1531
  try {
1095
- const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
1532
+ const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
1096
1533
  return typeof pkg.scripts?.[ONBOARD_SCRIPT_NAME] === "string";
1097
1534
  } catch {
1098
1535
  return false;
@@ -1100,17 +1537,17 @@ function hasOnboardScript(repoDir) {
1100
1537
  }
1101
1538
  function runRecallInit(repoDir) {
1102
1539
  info(`scaffolding launch-recall (shadow git backup) \u2026`);
1103
- const recallEntry = path4.resolve(__dirname, "recall-entry.js");
1104
- const useSibling = fs4.existsSync(recallEntry);
1540
+ const recallEntry = path5.resolve(__dirname, "recall-entry.js");
1541
+ const useSibling = fs5.existsSync(recallEntry);
1105
1542
  const cmd = useSibling ? process.execPath : "npx";
1106
1543
  const args = useSibling ? [recallEntry, "init"] : ["-y", "-p", LAUNCH_KIT_PKG, "launch-recall", "init"];
1107
1544
  if (DRY_RUN) {
1108
1545
  dryNote(`would run launch-recall init: ${cmd} ${args.join(" ")} (cwd: ${repoDir})`);
1109
1546
  return;
1110
1547
  }
1111
- const res = (0, import_node_child_process.spawnSync)(cmd, args, { cwd: repoDir, stdio: "inherit" });
1548
+ const res = (0, import_node_child_process3.spawnSync)(cmd, args, { cwd: repoDir, stdio: "inherit" });
1112
1549
  if (res.status !== 0) {
1113
- 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`);
1550
+ info(`\u26A0 launch-recall init failed (exit ${res.status}). Main onboarding is complete \u2014 you can retry later: cd ${path5.basename(repoDir)} && npx -y -p ${LAUNCH_KIT_PKG} launch-recall init`);
1114
1551
  return;
1115
1552
  }
1116
1553
  ok(`launch-recall ready (shadow git initialized)`);
@@ -1121,17 +1558,17 @@ function runOnboard(repoDir, pm) {
1121
1558
  dryNote(`would run: ${pm.binary} run ${ONBOARD_SCRIPT_NAME} (cwd: ${repoDir})`);
1122
1559
  return;
1123
1560
  }
1124
- const res = (0, import_node_child_process.spawnSync)(pm.binary, ["run", ONBOARD_SCRIPT_NAME], { cwd: repoDir, stdio: "inherit" });
1561
+ const res = (0, import_node_child_process3.spawnSync)(pm.binary, ["run", ONBOARD_SCRIPT_NAME], { cwd: repoDir, stdio: "inherit" });
1125
1562
  if (res.status !== 0) {
1126
- fail(
1127
- `${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}`
1563
+ fail2(
1564
+ `${pm.name} run ${ONBOARD_SCRIPT_NAME} failed (exit ${res.status}). Install completed but the onboard script errored. Fix and retry: cd ${path5.basename(repoDir)} && ${pm.binary} run ${ONBOARD_SCRIPT_NAME}`
1128
1565
  );
1129
1566
  }
1130
1567
  ok(`${ONBOARD_SCRIPT_NAME} script complete`);
1131
1568
  }
1132
1569
  function ensureGitignoreLine(targetDir, line) {
1133
- const p = path4.join(targetDir, ".gitignore");
1134
- let content = fs4.existsSync(p) ? fs4.readFileSync(p, "utf-8") : "";
1570
+ const p = path5.join(targetDir, ".gitignore");
1571
+ let content = fs5.existsSync(p) ? fs5.readFileSync(p, "utf-8") : "";
1135
1572
  const lines = content.split(/\r?\n/);
1136
1573
  if (lines.some((l) => l.trim() === line)) return;
1137
1574
  if (content.length && !content.endsWith("\n")) content += "\n";
@@ -1141,28 +1578,28 @@ function ensureGitignoreLine(targetDir, line) {
1141
1578
  dryNote(`would append "${line}" to .gitignore`);
1142
1579
  return;
1143
1580
  }
1144
- fs4.writeFileSync(p, content, "utf-8");
1581
+ fs5.writeFileSync(p, content, "utf-8");
1145
1582
  ok(`appended ${line} to .gitignore`);
1146
1583
  }
1147
1584
  function copyScaffoldDirAlways(srcDir, destDir, labelPrefix) {
1148
- if (!fs4.existsSync(srcDir)) return;
1149
- for (const entry of fs4.readdirSync(srcDir, { withFileTypes: true })) {
1150
- const srcPath = path4.join(srcDir, entry.name);
1151
- const destPath = path4.join(destDir, entry.name);
1585
+ if (!fs5.existsSync(srcDir)) return;
1586
+ for (const entry of fs5.readdirSync(srcDir, { withFileTypes: true })) {
1587
+ const srcPath = path5.join(srcDir, entry.name);
1588
+ const destPath = path5.join(destDir, entry.name);
1152
1589
  const label = labelPrefix ? `${labelPrefix}/${entry.name}` : entry.name;
1153
1590
  if (entry.isDirectory()) {
1154
1591
  copyScaffoldDirAlways(srcPath, destPath, label);
1155
1592
  } else if (entry.isFile()) {
1156
- const existed = fs4.existsSync(destPath);
1593
+ const existed = fs5.existsSync(destPath);
1157
1594
  if (DRY_RUN) {
1158
1595
  dryNote(`would ${existed ? "refresh" : "write"} ${label}`);
1159
1596
  continue;
1160
1597
  }
1161
- fs4.mkdirSync(path4.dirname(destPath), { recursive: true });
1162
- fs4.copyFileSync(srcPath, destPath);
1598
+ fs5.mkdirSync(path5.dirname(destPath), { recursive: true });
1599
+ fs5.copyFileSync(srcPath, destPath);
1163
1600
  try {
1164
- const srcMode = fs4.statSync(srcPath).mode;
1165
- fs4.chmodSync(destPath, srcMode);
1601
+ const srcMode = fs5.statSync(srcPath).mode;
1602
+ fs5.chmodSync(destPath, srcMode);
1166
1603
  } catch {
1167
1604
  }
1168
1605
  ok(`${existed ? "refreshed" : "wrote"} ${label}`);
@@ -1170,25 +1607,25 @@ function copyScaffoldDirAlways(srcDir, destDir, labelPrefix) {
1170
1607
  }
1171
1608
  }
1172
1609
  function scaffoldMigrateSafety(targetDir, refreshScaffolds = false) {
1173
- const scaffoldsRoot = path4.resolve(__dirname, "..", "..", "scaffolds", "migrate-safety");
1174
- if (!fs4.existsSync(scaffoldsRoot)) {
1610
+ const scaffoldsRoot = path5.resolve(__dirname, "..", "..", "scaffolds", "migrate-safety");
1611
+ if (!fs5.existsSync(scaffoldsRoot)) {
1175
1612
  warn(`migrate-safety scaffolds missing at ${scaffoldsRoot} \u2014 packaging bug; main onboarding unaffected`);
1176
1613
  return { status: "warn", summary: "scaffolds missing (packaging bug)" };
1177
1614
  }
1178
1615
  const files = [
1179
1616
  {
1180
- src: path4.join(scaffoldsRoot, ".github", "workflows", "backup-on-migration.yml"),
1181
- dest: path4.join(targetDir, ".github", "workflows", "backup-on-migration.yml"),
1617
+ src: path5.join(scaffoldsRoot, ".github", "workflows", "backup-on-migration.yml"),
1618
+ dest: path5.join(targetDir, ".github", "workflows", "backup-on-migration.yml"),
1182
1619
  label: ".github/workflows/backup-on-migration.yml"
1183
1620
  },
1184
1621
  {
1185
- src: path4.join(scaffoldsRoot, "scripts", "migrate-with-backup.sh"),
1186
- dest: path4.join(targetDir, "scripts", "migrate-with-backup.sh"),
1622
+ src: path5.join(scaffoldsRoot, "scripts", "migrate-with-backup.sh"),
1623
+ dest: path5.join(targetDir, "scripts", "migrate-with-backup.sh"),
1187
1624
  label: "scripts/migrate-with-backup.sh"
1188
1625
  },
1189
1626
  {
1190
- src: path4.join(scaffoldsRoot, "docs", "migrations-runbook.md"),
1191
- dest: path4.join(targetDir, "docs", "migrations-runbook.md"),
1627
+ src: path5.join(scaffoldsRoot, "docs", "migrations-runbook.md"),
1628
+ dest: path5.join(targetDir, "docs", "migrations-runbook.md"),
1192
1629
  label: "docs/migrations-runbook.md"
1193
1630
  }
1194
1631
  ];
@@ -1223,25 +1660,25 @@ function summarizeFileCounts(counts) {
1223
1660
  }
1224
1661
  function hashFile(p) {
1225
1662
  try {
1226
- return crypto.createHash("sha256").update(fs4.readFileSync(p)).digest("hex");
1663
+ return crypto.createHash("sha256").update(fs5.readFileSync(p)).digest("hex");
1227
1664
  } catch {
1228
1665
  return null;
1229
1666
  }
1230
1667
  }
1231
1668
  function copyScaffoldDriftAware(srcPath, destPath, label, refreshScaffolds) {
1232
- if (!fs4.existsSync(srcPath)) {
1669
+ if (!fs5.existsSync(srcPath)) {
1233
1670
  info(`\u26A0 scaffold src missing for ${label} \u2014 skipping (packaging bug)`);
1234
1671
  return "missing-src";
1235
1672
  }
1236
- if (!fs4.existsSync(destPath)) {
1673
+ if (!fs5.existsSync(destPath)) {
1237
1674
  if (DRY_RUN) {
1238
1675
  dryNote(`would write ${label}`);
1239
1676
  return "wrote";
1240
1677
  }
1241
- fs4.mkdirSync(path4.dirname(destPath), { recursive: true });
1242
- fs4.copyFileSync(srcPath, destPath);
1678
+ fs5.mkdirSync(path5.dirname(destPath), { recursive: true });
1679
+ fs5.copyFileSync(srcPath, destPath);
1243
1680
  try {
1244
- fs4.chmodSync(destPath, fs4.statSync(srcPath).mode);
1681
+ fs5.chmodSync(destPath, fs5.statSync(srcPath).mode);
1245
1682
  } catch {
1246
1683
  }
1247
1684
  ok(`wrote ${label}`);
@@ -1259,9 +1696,9 @@ function copyScaffoldDriftAware(srcPath, destPath, label, refreshScaffolds) {
1259
1696
  dryNote(`would refresh ${label} (overrides local edits)`);
1260
1697
  return "drifted-refreshed";
1261
1698
  }
1262
- fs4.copyFileSync(srcPath, destPath);
1699
+ fs5.copyFileSync(srcPath, destPath);
1263
1700
  try {
1264
- fs4.chmodSync(destPath, fs4.statSync(srcPath).mode);
1701
+ fs5.chmodSync(destPath, fs5.statSync(srcPath).mode);
1265
1702
  } catch {
1266
1703
  }
1267
1704
  ok(`refreshed ${label} (overrode local edits \u2014 drift detected before write)`);
@@ -1273,10 +1710,10 @@ function copyScaffoldDriftAware(srcPath, destPath, label, refreshScaffolds) {
1273
1710
  var MARKETPLACE_ID = "launch-secure";
1274
1711
  var PLUGIN_ID = "kit";
1275
1712
  function isDogfoodMarketplace(targetDir) {
1276
- const p = path4.join(targetDir, ".claude", "settings.json");
1277
- if (!fs4.existsSync(p)) return { isDogfood: false };
1713
+ const p = path5.join(targetDir, ".claude", "settings.json");
1714
+ if (!fs5.existsSync(p)) return { isDogfood: false };
1278
1715
  try {
1279
- const settings = JSON.parse(fs4.readFileSync(p, "utf-8"));
1716
+ const settings = JSON.parse(fs5.readFileSync(p, "utf-8"));
1280
1717
  const existingPath = settings.extraKnownMarketplaces?.[MARKETPLACE_ID]?.source?.path;
1281
1718
  if (existingPath && existingPath !== "./.claude/marketplace") {
1282
1719
  return { isDogfood: true, existingPath };
@@ -1287,8 +1724,8 @@ function isDogfoodMarketplace(targetDir) {
1287
1724
  }
1288
1725
  }
1289
1726
  function scaffoldLsMarketplace(targetDir) {
1290
- const scaffoldsRoot = path4.resolve(__dirname, "..", "..", "scaffolds", "ls-marketplace");
1291
- if (!fs4.existsSync(scaffoldsRoot)) {
1727
+ const scaffoldsRoot = path5.resolve(__dirname, "..", "..", "scaffolds", "ls-marketplace");
1728
+ if (!fs5.existsSync(scaffoldsRoot)) {
1292
1729
  warn(`ls-marketplace scaffolds missing at ${scaffoldsRoot} \u2014 packaging bug`);
1293
1730
  return { status: "warn", summary: "scaffolds missing (packaging bug)" };
1294
1731
  }
@@ -1299,7 +1736,7 @@ function scaffoldLsMarketplace(targetDir) {
1299
1736
  ok(`launch-secure marketplace (dogfood) \u2014 Claude Code loads commands from ${dogfood.existingPath}`);
1300
1737
  return { status: "ok", summary: `dogfood pointer (${dogfood.existingPath})` };
1301
1738
  }
1302
- const marketplaceRoot = path4.join(targetDir, ".claude", "marketplace");
1739
+ const marketplaceRoot = path5.join(targetDir, ".claude", "marketplace");
1303
1740
  info("scaffolding launch-secure marketplace (Claude Code /kit: namespace \u2014 refreshes every /kit:* command found in the scaffold) \u2026");
1304
1741
  copyScaffoldDirAlways(scaffoldsRoot, marketplaceRoot, ".claude/marketplace");
1305
1742
  wireLsSettings(targetDir);
@@ -1307,14 +1744,14 @@ function scaffoldLsMarketplace(targetDir) {
1307
1744
  return { status: "ok", summary: "marketplace tree + settings refreshed" };
1308
1745
  }
1309
1746
  function wireLsSettings(targetDir) {
1310
- const p = path4.join(targetDir, ".claude", "settings.json");
1311
- const hadExisting = fs4.existsSync(p);
1747
+ const p = path5.join(targetDir, ".claude", "settings.json");
1748
+ const hadExisting = fs5.existsSync(p);
1312
1749
  let existing = {};
1313
1750
  if (hadExisting) {
1314
1751
  try {
1315
- existing = JSON.parse(fs4.readFileSync(p, "utf-8"));
1752
+ existing = JSON.parse(fs5.readFileSync(p, "utf-8"));
1316
1753
  } catch (err) {
1317
- fail(`Could not parse existing .claude/settings.json: ${err instanceof Error ? err.message : String(err)}`);
1754
+ fail2(`Could not parse existing .claude/settings.json: ${err instanceof Error ? err.message : String(err)}`);
1318
1755
  }
1319
1756
  }
1320
1757
  const merged = { ...existing };
@@ -1342,15 +1779,15 @@ function wireLsSettings(targetDir) {
1342
1779
  dryNote(`would ${hadExisting ? "merge into" : "write"} .claude/settings.json (set extraKnownMarketplaces.${MARKETPLACE_ID} + enabledPlugins.${PLUGIN_ID}@${MARKETPLACE_ID}; preserves every other key)`);
1343
1780
  return;
1344
1781
  }
1345
- fs4.mkdirSync(path4.dirname(p), { recursive: true });
1346
- fs4.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1782
+ fs5.mkdirSync(path5.dirname(p), { recursive: true });
1783
+ fs5.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1347
1784
  ok(`${hadExisting ? "merged into" : "wrote"} .claude/settings.json (extraKnownMarketplaces.${MARKETPLACE_ID} + enabledPlugins.${PLUGIN_ID}@${MARKETPLACE_ID})`);
1348
1785
  }
1349
1786
  var RECALL_HOOK_SIGNATURE = "ensure-recall.sh";
1350
1787
  var RECALL_HOOK_COMMAND = 'bash "${CLAUDE_PROJECT_DIR:-$PWD}/scripts/ensure-recall.sh"';
1351
1788
  function scaffoldRecallHook(targetDir) {
1352
- const scaffoldsRoot = path4.resolve(__dirname, "..", "..", "scaffolds", "recall-hook");
1353
- if (!fs4.existsSync(scaffoldsRoot)) {
1789
+ const scaffoldsRoot = path5.resolve(__dirname, "..", "..", "scaffolds", "recall-hook");
1790
+ if (!fs5.existsSync(scaffoldsRoot)) {
1354
1791
  warn(`recall-hook scaffolds missing at ${scaffoldsRoot} \u2014 packaging bug`);
1355
1792
  return { status: "warn", summary: "scaffolds missing (packaging bug)" };
1356
1793
  }
@@ -1361,14 +1798,14 @@ function scaffoldRecallHook(targetDir) {
1361
1798
  return wired ? { status: "ok", summary: "ensure-recall.sh refreshed + SessionStart hook appended" } : { status: "ok", summary: "ensure-recall.sh refreshed (hook already wired)" };
1362
1799
  }
1363
1800
  function wireRecallHook(targetDir) {
1364
- const p = path4.join(targetDir, ".claude", "settings.json");
1365
- const hadExisting = fs4.existsSync(p);
1801
+ const p = path5.join(targetDir, ".claude", "settings.json");
1802
+ const hadExisting = fs5.existsSync(p);
1366
1803
  let existing = {};
1367
1804
  if (hadExisting) {
1368
1805
  try {
1369
- existing = JSON.parse(fs4.readFileSync(p, "utf-8"));
1806
+ existing = JSON.parse(fs5.readFileSync(p, "utf-8"));
1370
1807
  } catch (err) {
1371
- fail(`Could not parse existing .claude/settings.json: ${err instanceof Error ? err.message : String(err)}`);
1808
+ fail2(`Could not parse existing .claude/settings.json: ${err instanceof Error ? err.message : String(err)}`);
1372
1809
  }
1373
1810
  }
1374
1811
  const hooks = existing.hooks ?? {};
@@ -1394,8 +1831,8 @@ function wireRecallHook(targetDir) {
1394
1831
  dryNote(`would append SessionStart hook to .claude/settings.json (bash scripts/ensure-recall.sh; preserves every other key + existing hooks)`);
1395
1832
  return true;
1396
1833
  }
1397
- fs4.mkdirSync(path4.dirname(p), { recursive: true });
1398
- fs4.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1834
+ fs5.mkdirSync(path5.dirname(p), { recursive: true });
1835
+ fs5.writeFileSync(p, JSON.stringify(merged, null, 2) + "\n", "utf-8");
1399
1836
  ok(`appended SessionStart hook to .claude/settings.json (bash scripts/ensure-recall.sh)`);
1400
1837
  return true;
1401
1838
  }
@@ -1415,8 +1852,35 @@ function tryActivateStatusline() {
1415
1852
  }
1416
1853
  return null;
1417
1854
  }
1418
- async function main() {
1855
+ async function main2() {
1419
1856
  const subcommand = process.argv[2];
1857
+ if (subcommand === "radar-docker-init") {
1858
+ await Promise.resolve().then(() => (init_radar_docker_init_entry(), radar_docker_init_entry_exports));
1859
+ return;
1860
+ }
1861
+ if (subcommand === "setup-git") {
1862
+ if (process.argv[3] === "--help" || process.argv[3] === "-h") {
1863
+ console.log('usage: launch-kit setup-git --identity="Name <email>"');
1864
+ console.log("");
1865
+ console.log(" Runs `gh auth setup-git` when GH_TOKEN is set, then sets the");
1866
+ console.log(" global git user.name, user.email, init.defaultBranch=main, and");
1867
+ console.log(" pull.rebase=false. Idempotent; safe to re-run.");
1868
+ return;
1869
+ }
1870
+ let identityVal = null;
1871
+ for (const a of process.argv.slice(3)) {
1872
+ if (a.startsWith("--identity=")) identityVal = a.slice("--identity=".length);
1873
+ else fail2(`Unknown setup-git flag: "${a}". Supported: --identity="Name <email>".`);
1874
+ }
1875
+ if (!identityVal) fail2(`launch-kit setup-git requires --identity="Name <email>".`);
1876
+ try {
1877
+ const identity = parseGitIdentityFlag(identityVal, "--identity");
1878
+ configureGitForBot(identity);
1879
+ } catch (err) {
1880
+ fail2(err instanceof Error ? err.message : String(err));
1881
+ }
1882
+ return;
1883
+ }
1420
1884
  if (subcommand === "statusline") {
1421
1885
  const action = process.argv[3];
1422
1886
  if (!action || action === "--help" || action === "-h") {
@@ -1433,17 +1897,50 @@ async function main() {
1433
1897
  for (const a of process.argv.slice(4)) {
1434
1898
  if (a.startsWith("--show=")) showArg = a.slice("--show=".length);
1435
1899
  else if (a === "--compact") compactArg = true;
1436
- else fail(`Unknown statusline flag: "${a}". Supported: --show=<csv>, --compact.`);
1900
+ else fail2(`Unknown statusline flag: "${a}". Supported: --show=<csv>, --compact.`);
1437
1901
  }
1438
1902
  const { activateStatusline: activateStatusline2, deactivateStatusline: deactivateStatusline2 } = await Promise.resolve().then(() => (init_statusline_install(), statusline_install_exports));
1439
1903
  let res;
1440
1904
  if (action === "activate") res = activateStatusline2({ show: showArg, compact: compactArg });
1441
1905
  else if (action === "deactivate") res = deactivateStatusline2();
1442
- else fail(`Unknown statusline action: "${action}". Supported: activate, deactivate.`);
1906
+ else fail2(`Unknown statusline action: "${action}". Supported: activate, deactivate.`);
1443
1907
  if (res.ok) ok(`statusline ${res.outcome} \u2014 ${res.message}`);
1444
1908
  else info(`statusline ${res.outcome} \u2014 ${res.message}`);
1445
1909
  return;
1446
1910
  }
1911
+ if (subcommand === "secrets") {
1912
+ const action = process.argv[3];
1913
+ if (!action || action === "--help" || action === "-h") {
1914
+ console.log("usage: launch-kit secrets pull [--env=<slug>] [--dir=<path>] [--file=<name>]");
1915
+ console.log("");
1916
+ console.log(" --env=<slug> env to pull from. Overrides $LS_ENV and the project-level default.");
1917
+ console.log(" --dir=<path> project root (default: cwd). Must contain a launch-kit cred file.");
1918
+ console.log(" --file=<name> output file relative to --dir (default: .env.local).");
1919
+ console.log("");
1920
+ console.log(" Resolution order: --env \u2192 $LS_ENV \u2192 server-side default \u2192 single-env auto-pick.");
1921
+ return;
1922
+ }
1923
+ if (action !== "pull") {
1924
+ fail2(`Unknown secrets action: "${action}". Supported: pull.`);
1925
+ }
1926
+ let envOverride = null;
1927
+ let dirArg = null;
1928
+ let fileArg = ".env.local";
1929
+ for (const a of process.argv.slice(4)) {
1930
+ if (a.startsWith("--env=")) envOverride = a.slice("--env=".length);
1931
+ else if (a.startsWith("--dir=")) dirArg = a.slice("--dir=".length);
1932
+ else if (a.startsWith("--file=")) fileArg = a.slice("--file=".length);
1933
+ else fail2(`Unknown secrets pull flag: "${a}". Supported: --env, --dir, --file.`);
1934
+ }
1935
+ const targetDir = path5.resolve(dirArg ?? process.cwd());
1936
+ const { runSecretsPull: runSecretsPull2 } = await Promise.resolve().then(() => (init_secrets_pull(), secrets_pull_exports));
1937
+ try {
1938
+ await runSecretsPull2({ targetDir, envOverride, fileName: fileArg });
1939
+ } catch (err) {
1940
+ fail2(err instanceof Error ? err.message : String(err));
1941
+ }
1942
+ return;
1943
+ }
1447
1944
  const args = parseArgs(process.argv.slice(2));
1448
1945
  if (args.help) {
1449
1946
  if (subcommand === "refresh") printRefreshHelp();
@@ -1451,10 +1948,10 @@ async function main() {
1451
1948
  return;
1452
1949
  }
1453
1950
  if (!subcommand || subcommand.startsWith("--")) {
1454
- fail(`missing subcommand. Usage: launch-kit <init|refresh|statusline> [options]. Run with --help.`);
1951
+ fail2(`missing subcommand. Usage: launch-kit <init|refresh|statusline|secrets> [options]. Run with --help.`);
1455
1952
  }
1456
1953
  if (subcommand !== "init" && subcommand !== "refresh") {
1457
- fail(`Unknown subcommand "${subcommand}". Supported: init, refresh, statusline. Run with --help for usage.`);
1954
+ fail2(`Unknown subcommand "${subcommand}". Supported: init, refresh, statusline, secrets. Run with --help for usage.`);
1458
1955
  }
1459
1956
  DRY_RUN = args.dryRun;
1460
1957
  VERBOSE = args.verbose || DRY_RUN;
@@ -1469,8 +1966,8 @@ async function main() {
1469
1966
  }
1470
1967
  async function mainRefresh(args) {
1471
1968
  const cwd = process.cwd();
1472
- const targetDir = path4.resolve(args.targetDir ?? cwd);
1473
- if (!fs4.existsSync(targetDir)) fail(`target dir does not exist: ${targetDir}`);
1969
+ const targetDir = path5.resolve(args.targetDir ?? cwd);
1970
+ if (!fs5.existsSync(targetDir)) fail2(`target dir does not exist: ${targetDir}`);
1474
1971
  let cred;
1475
1972
  let source;
1476
1973
  try {
@@ -1478,7 +1975,7 @@ async function mainRefresh(args) {
1478
1975
  cred = recovery.cred;
1479
1976
  source = recovery.source;
1480
1977
  } catch (err) {
1481
- fail(err instanceof Error ? err.message : String(err));
1978
+ fail2(err instanceof Error ? err.message : String(err));
1482
1979
  }
1483
1980
  if (cred && source === "mcp") {
1484
1981
  info(`recovered cred from .mcp.json launch-secure headers (PAT + org + project + url)`);
@@ -1487,24 +1984,24 @@ async function mainRefresh(args) {
1487
1984
  if (DRY_RUN) {
1488
1985
  dryNote(`would write ${CONFIG_FILENAME} from recovered .mcp.json cred (course: ${courseName})`);
1489
1986
  } else {
1490
- writeJsonAtomic(path4.join(targetDir, CONFIG_FILENAME), nested2, 384);
1987
+ writeJsonAtomic(path5.join(targetDir, CONFIG_FILENAME), nested2, 384);
1491
1988
  ok(`wrote ${CONFIG_FILENAME} (course: ${courseName})`);
1492
1989
  }
1493
1990
  }
1494
1991
  if (!cred) {
1495
- fail(
1496
- `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.`
1992
+ fail2(
1993
+ `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=${path5.relative(cwd, targetDir) || "."}\` first.`
1497
1994
  );
1498
1995
  }
1499
1996
  const nested = toNested(cred);
1500
- if (!nested) fail(`${CONFIG_FILENAME} is malformed or missing required fields (pat/orgSlug/projectSlug/serverUrl).`);
1997
+ if (!nested) fail2(`${CONFIG_FILENAME} is malformed or missing required fields (pat/orgSlug/projectSlug/serverUrl).`);
1501
1998
  const active = nested.profiles[nested.active];
1502
- if (!active) fail(`${CONFIG_FILENAME} active profile "${nested.active}" is not present in profiles.`);
1999
+ if (!active) fail2(`${CONFIG_FILENAME} active profile "${nested.active}" is not present in profiles.`);
1503
2000
  info(`refreshing launch-kit in ${targetDir} (course: ${nested.active}, project: ${active.orgSlug}/${active.projectSlug}) \u2026`);
1504
2001
  header("launch-kit refresh", [
1505
2002
  ["course", nested.active],
1506
2003
  ["project", `${active.orgSlug}/${active.projectSlug}`],
1507
- ["dir", path4.relative(cwd, targetDir) || "."]
2004
+ ["dir", path5.relative(cwd, targetDir) || "."]
1508
2005
  ]);
1509
2006
  const cfg = { pat: active.pat, orgSlug: active.orgSlug, projectSlug: active.projectSlug, serverUrl: active.serverUrl };
1510
2007
  phase(".mcp.json", mergeMcpFile(targetDir, buildLaunchKitMcpEntries(cfg)));
@@ -1532,8 +2029,8 @@ async function mainRefresh(args) {
1532
2029
  }
1533
2030
  }
1534
2031
  async function mainInit(args) {
1535
- const probeDir = path4.resolve(args.targetDir ?? process.cwd());
1536
- if (!args.force && fs4.existsSync(probeDir)) {
2032
+ const probeDir = path5.resolve(args.targetDir ?? process.cwd());
2033
+ if (!args.force && fs5.existsSync(probeDir)) {
1537
2034
  const detection = detectExistingBootstrap(probeDir);
1538
2035
  if (detection.bootstrapped) {
1539
2036
  info(`detected existing bootstrap at ${probeDir} (${detection.reason})`);
@@ -1542,8 +2039,8 @@ async function mainInit(args) {
1542
2039
  }
1543
2040
  }
1544
2041
  if (!args.token || !args.orgSlug || !args.projectSlug) {
1545
- const recoveryDir = path4.resolve(args.targetDir ?? process.cwd());
1546
- if (fs4.existsSync(recoveryDir)) {
2042
+ const recoveryDir = path5.resolve(args.targetDir ?? process.cwd());
2043
+ if (fs5.existsSync(recoveryDir)) {
1547
2044
  const { cred } = recoverCred(recoveryDir, getRecoveryOptions());
1548
2045
  const nested = cred ? toNested(cred) : null;
1549
2046
  const recovered = nested ? nested.profiles[nested.active] : cred;
@@ -1570,10 +2067,10 @@ async function mainInit(args) {
1570
2067
  const t = await prompt("LaunchSecure PAT (ls_pat_\u2026): ");
1571
2068
  args.token = t || null;
1572
2069
  }
1573
- if (!args.token) fail("--token (or LS_PAT env) is required.");
1574
- if (!/^ls_pat_/.test(args.token)) fail("Token does not look like a LaunchSecure PAT (expected prefix ls_pat_).");
1575
- if (!args.orgSlug) fail("--org=<orgSlug> is required.");
1576
- if (!args.projectSlug) fail("--project=<projectSlug> is required.");
2070
+ if (!args.token) fail2("--token (or LS_PAT env) is required.");
2071
+ if (!/^ls_pat_/.test(args.token)) fail2("Token does not look like a LaunchSecure PAT (expected prefix ls_pat_).");
2072
+ if (!args.orgSlug) fail2("--org=<orgSlug> is required.");
2073
+ if (!args.projectSlug) fail2("--project=<projectSlug> is required.");
1577
2074
  header("launch-kit init", [
1578
2075
  ["org", args.orgSlug],
1579
2076
  ["project", args.projectSlug],
@@ -1581,39 +2078,43 @@ async function mainInit(args) {
1581
2078
  ]);
1582
2079
  const { hasGh } = preflight();
1583
2080
  phase("preflight", { status: "ok", summary: `node ${process.versions.node}${hasGh ? " \xB7 git \xB7 gh" : " \xB7 git (gh not found \u2014 will use git for clone)"}` });
2081
+ if (args.gitIdentity) {
2082
+ configureGitForBot(args.gitIdentity);
2083
+ phase("git identity", { status: "ok", summary: `${args.gitIdentity.name} <${args.gitIdentity.email}>${process.env.GH_TOKEN ? " \xB7 gh credential helper wired" : ""}` });
2084
+ }
1584
2085
  info(`resolving project ${args.orgSlug}/${args.projectSlug} on ${args.serverUrl} \u2026`);
1585
2086
  let resolved;
1586
2087
  try {
1587
2088
  resolved = await callProjectInfo(args);
1588
2089
  } catch (err) {
1589
- fail(err instanceof Error ? err.message : String(err));
2090
+ fail2(err instanceof Error ? err.message : String(err));
1590
2091
  }
1591
2092
  ok(`resolved "${resolved.projectName}"`);
1592
2093
  phase("project_info", { status: "ok", summary: `"${resolved.projectName}"` });
1593
2094
  if (!resolved.repositoryUrl) {
1594
- fail(
2095
+ fail2(
1595
2096
  `Project "${resolved.projectSlug}" has no GitHub repository configured. Connect GitHub at ${args.serverUrl}/${resolved.orgSlug}/projects/${resolved.projectSlug}/settings/integrations, then re-run init.`
1596
2097
  );
1597
2098
  }
1598
2099
  const repoUrl = resolved.repositoryUrl;
1599
2100
  const cwd = process.cwd();
1600
- const targetDir = path4.resolve(args.targetDir ?? path4.join(cwd, resolved.projectSlug));
2101
+ const targetDir = path5.resolve(args.targetDir ?? path5.join(cwd, resolved.projectSlug));
1601
2102
  const normalizedRemote = normalizeRepoUrl(repoUrl);
1602
2103
  let skipClone = false;
1603
- if (fs4.existsSync(targetDir)) {
2104
+ if (fs5.existsSync(targetDir)) {
1604
2105
  if (isGitRepo(targetDir)) {
1605
2106
  const existingRemote = gitRemoteUrl(targetDir);
1606
2107
  if (existingRemote && normalizeRepoUrl(existingRemote) === normalizedRemote) {
1607
2108
  ok(`${targetDir} is already a clone of ${repoUrl} \u2014 skipping clone, refreshing configs only`);
1608
2109
  skipClone = true;
1609
2110
  } else {
1610
- fail(`${targetDir} is a git repo but its remote (${existingRemote ?? "unknown"}) does not match ${repoUrl}. Refusing to overwrite. Pass --dir=<other-path>.`);
2111
+ fail2(`${targetDir} is a git repo but its remote (${existingRemote ?? "unknown"}) does not match ${repoUrl}. Refusing to overwrite. Pass --dir=<other-path>.`);
1611
2112
  }
1612
2113
  } else if (!dirIsEmpty(targetDir)) {
1613
- fail(`${targetDir} exists and is not empty (and not a matching git repo). Refusing to clone into it. Pass --dir=<other-path>.`);
2114
+ fail2(`${targetDir} exists and is not empty (and not a matching git repo). Refusing to clone into it. Pass --dir=<other-path>.`);
1614
2115
  }
1615
2116
  }
1616
- const relTarget = path4.relative(cwd, targetDir) || ".";
2117
+ const relTarget = path5.relative(cwd, targetDir) || ".";
1617
2118
  if (!skipClone) {
1618
2119
  section(`Cloning ${repoUrl}`);
1619
2120
  cloneRepo(repoUrl, targetDir, hasGh);
@@ -1643,10 +2144,28 @@ async function mainInit(args) {
1643
2144
  section(`Installing dependencies (${detected.pm.name})`);
1644
2145
  runInstall(targetDir, detected);
1645
2146
  phase("install", { status: "ok", summary: `${detected.pm.binary} ${detected.pm.installArgs.join(" ")}` });
1646
- if (!args.noOnboard && hasOnboardScript(targetDir)) {
1647
- section(`Running ${detected.pm.binary} run ${ONBOARD_SCRIPT_NAME}`);
1648
- runOnboard(targetDir, detected.pm);
1649
- phase("onboard", { status: "ok", summary: `${detected.pm.binary} run ${ONBOARD_SCRIPT_NAME}` });
2147
+ if (!args.noOnboard) {
2148
+ if (hasOnboardScript(targetDir)) {
2149
+ section(`Running ${detected.pm.binary} run ${ONBOARD_SCRIPT_NAME}`);
2150
+ runOnboard(targetDir, detected.pm);
2151
+ phase("onboard", { status: "ok", summary: `${detected.pm.binary} run ${ONBOARD_SCRIPT_NAME}` });
2152
+ } else {
2153
+ section("Pulling environment secrets");
2154
+ info("running launch-kit secrets pull \u2026");
2155
+ if (DRY_RUN) {
2156
+ dryNote(`would run: launch-kit secrets pull --dir=${path5.relative(cwd, targetDir) || "."}`);
2157
+ phase("secrets pull", { status: "skipped", summary: "(dry-run)" });
2158
+ } else {
2159
+ try {
2160
+ await runSecretsPull({ targetDir, envOverride: null, fileName: ".env.local" });
2161
+ phase("secrets pull", { status: "ok", summary: ".env.local from cloud LS" });
2162
+ } catch (err) {
2163
+ const msg = err instanceof Error ? err.message : String(err);
2164
+ warn(`secrets pull skipped \u2014 ${msg}`);
2165
+ phase("secrets pull", { status: "warn", summary: "pull manually with `launch-kit secrets pull`" });
2166
+ }
2167
+ }
2168
+ }
1650
2169
  }
1651
2170
  }
1652
2171
  const hasOnboard = hasOnboardScript(targetDir);
@@ -1686,7 +2205,7 @@ async function mainInit(args) {
1686
2205
  ]);
1687
2206
  if (showGuide) console.log(getLaunchKitToolsGuide());
1688
2207
  }
1689
- main().catch((err) => {
2208
+ main2().catch((err) => {
1690
2209
  console.error(`[launch-kit] unexpected error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
1691
2210
  process.exit(1);
1692
2211
  });