@leadbay/mcp 0.17.1 → 0.17.3

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.
@@ -29,7 +29,7 @@ function buildClaudeCodeAddArgs(token, region, includeWrite, telemetryEnabled, l
29
29
  if (localBinPath) {
30
30
  args.push("--", "node", localBinPath);
31
31
  } else {
32
- args.push("--", "npx", "-y", "@leadbay/mcp@latest");
32
+ args.push("--", "npx", "-y", "-p", "@leadbay/mcp@latest", "leadbay-mcp");
33
33
  }
34
34
  return args;
35
35
  }
@@ -37,14 +37,14 @@ function buildClaudeCodeRemoveArgs() {
37
37
  return ["mcp", "remove", "leadbay", "--scope", "user"];
38
38
  }
39
39
  async function runClaudeMcp(args) {
40
- return await new Promise((resolve2) => {
40
+ return await new Promise((resolve) => {
41
41
  const child = spawn("claude", args, { stdio: ["ignore", "pipe", "pipe"] });
42
42
  let stdout = "";
43
43
  let stderr = "";
44
44
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
45
45
  child.stderr.on("data", (chunk) => stderr += chunk.toString());
46
- child.on("close", (code) => resolve2({ code, stdout, stderr }));
47
- child.on("error", (err) => resolve2({ code: null, stdout, stderr, spawnError: err.message }));
46
+ child.on("close", (code) => resolve({ code, stdout, stderr }));
47
+ child.on("error", (err) => resolve({ code: null, stdout, stderr, spawnError: err.message }));
48
48
  });
49
49
  }
50
50
  async function isLeadbayConfiguredInClaudeCode() {
@@ -90,11 +90,11 @@ var init_install_claude_code = __esm({
90
90
  // installer/install-json-config.ts
91
91
  async function installInJsonConfig(configPath, token, region, includeWrite, telemetryEnabled, localBinPath) {
92
92
  try {
93
- const { readFileSync: readFileSync3, writeFileSync, existsSync: existsSync4, mkdirSync, statSync } = await import("fs");
94
- const { dirname: dirname2 } = await import("path");
93
+ const { readFileSync: readFileSync3, writeFileSync, existsSync: existsSync3, mkdirSync, statSync } = await import("fs");
94
+ const { dirname } = await import("path");
95
95
  let parsed = {};
96
96
  let preserved = {};
97
- const existed = existsSync4(configPath);
97
+ const existed = existsSync3(configPath);
98
98
  if (existed) {
99
99
  const raw = readFileSync3(configPath, "utf8");
100
100
  try {
@@ -104,7 +104,7 @@ async function installInJsonConfig(configPath, token, region, includeWrite, tele
104
104
  return { ok: false, message: `existing ${configPath} is not valid JSON; refusing to overwrite` };
105
105
  }
106
106
  } else {
107
- mkdirSync(dirname2(configPath), { recursive: true });
107
+ mkdirSync(dirname(configPath), { recursive: true });
108
108
  }
109
109
  parsed.mcpServers = parsed.mcpServers ?? {};
110
110
  const env = {
@@ -114,7 +114,7 @@ async function installInJsonConfig(configPath, token, region, includeWrite, tele
114
114
  LEADBAY_TELEMETRY_ENABLED: telemetryEnabled ? "true" : "false"
115
115
  };
116
116
  if (!includeWrite) env.LEADBAY_MCP_WRITE = "0";
117
- parsed.mcpServers.leadbay = localBinPath ? { command: "node", args: [localBinPath], env } : { command: "npx", args: ["-y", "@leadbay/mcp@latest"], env };
117
+ parsed.mcpServers.leadbay = localBinPath ? { command: "node", args: [localBinPath], env } : { command: "npx", args: ["-y", "-p", "@leadbay/mcp@latest", "leadbay-mcp"], env };
118
118
  const tmp = configPath + ".tmp";
119
119
  writeFileSync(tmp, JSON.stringify(parsed, null, 2) + "\n", "utf8");
120
120
  const { renameSync, chmodSync } = await import("fs");
@@ -144,8 +144,8 @@ function stripJsonMcpEntry(existing) {
144
144
  }
145
145
  async function uninstallFromJsonConfig(configPath) {
146
146
  try {
147
- const { existsSync: existsSync4, readFileSync: readFileSync3, writeFileSync } = await import("fs");
148
- if (!existsSync4(configPath)) return { ok: true, message: "config not found \u2014 nothing to do" };
147
+ const { existsSync: existsSync3, readFileSync: readFileSync3, writeFileSync } = await import("fs");
148
+ if (!existsSync3(configPath)) return { ok: true, message: "config not found \u2014 nothing to do" };
149
149
  const existing = readFileSync3(configPath, "utf8");
150
150
  const { content, changed } = stripJsonMcpEntry(existing);
151
151
  if (!changed) return { ok: true, message: "leadbay entry not present" };
@@ -233,14 +233,14 @@ function stripShellExportBlock(existing) {
233
233
  }
234
234
  async function installInCodexConfig(configPath, includeWrite, telemetryEnabled, localBinPath) {
235
235
  try {
236
- const { readFileSync: readFileSync3, writeFileSync, existsSync: existsSync4, mkdirSync, statSync, renameSync, chmodSync } = await import("fs");
237
- const { dirname: dirname2 } = await import("path");
236
+ const { readFileSync: readFileSync3, writeFileSync, existsSync: existsSync3, mkdirSync, statSync, renameSync, chmodSync } = await import("fs");
237
+ const { dirname } = await import("path");
238
238
  let existing = "";
239
- const existed = existsSync4(configPath);
239
+ const existed = existsSync3(configPath);
240
240
  if (existed) {
241
241
  existing = readFileSync3(configPath, "utf8");
242
242
  } else {
243
- mkdirSync(dirname2(configPath), { recursive: true });
243
+ mkdirSync(dirname(configPath), { recursive: true });
244
244
  }
245
245
  const hadLeadbayConfig = /(^|\r?\n)\[mcp_servers\.leadbay\]\r?\n/.test(existing);
246
246
  const next = mergeCodexConfig(
@@ -273,24 +273,24 @@ async function appendShellExports(token, region, includeWrite, telemetryEnabled)
273
273
  };
274
274
  if (!includeWrite) values.LEADBAY_MCP_WRITE = "0";
275
275
  for (const [key, value] of Object.entries(values)) {
276
- const ok = await new Promise((resolve2) => {
276
+ const ok = await new Promise((resolve) => {
277
277
  const child = cp.spawn("setx", [key, value], { stdio: "ignore" });
278
- child.on("close", (code) => resolve2(code === 0));
279
- child.on("error", () => resolve2(false));
278
+ child.on("close", (code) => resolve(code === 0));
279
+ child.on("error", () => resolve(false));
280
280
  });
281
281
  if (!ok) return { ok: false, message: `failed to set ${key} with setx` };
282
282
  }
283
283
  return { ok: true, message: "env exported with setx; restart Codex/terminal" };
284
284
  }
285
- const { existsSync: existsSync4, readFileSync: readFileSync3, writeFileSync, renameSync } = await import("fs");
285
+ const { existsSync: existsSync3, readFileSync: readFileSync3, writeFileSync, renameSync } = await import("fs");
286
286
  const os = await import("os");
287
287
  const home = os.homedir();
288
- const preferred = [`${home}/.zshrc`, `${home}/.bashrc`].filter((path) => existsSync4(path));
288
+ const preferred = [`${home}/.zshrc`, `${home}/.bashrc`].filter((path) => existsSync3(path));
289
289
  const paths = preferred.length ? preferred : [`${home}/.profile`];
290
290
  const block = buildShellExportBlock(token, region, includeWrite, telemetryEnabled);
291
291
  const updated = [];
292
292
  for (const path of paths) {
293
- const existing = existsSync4(path) ? readFileSync3(path, "utf8") : "";
293
+ const existing = existsSync3(path) ? readFileSync3(path, "utf8") : "";
294
294
  const merged = mergeShellExportBlock(existing, block);
295
295
  if (!merged.changed) continue;
296
296
  const tmp = `${path}.leadbay.tmp`;
@@ -308,8 +308,8 @@ async function appendShellExports(token, region, includeWrite, telemetryEnabled)
308
308
  }
309
309
  async function uninstallFromCodexConfig(configPath) {
310
310
  try {
311
- const { existsSync: existsSync4, readFileSync: readFileSync3, writeFileSync } = await import("fs");
312
- if (!existsSync4(configPath)) return { ok: true, message: "config not found \u2014 nothing to do" };
311
+ const { existsSync: existsSync3, readFileSync: readFileSync3, writeFileSync } = await import("fs");
312
+ if (!existsSync3(configPath)) return { ok: true, message: "config not found \u2014 nothing to do" };
313
313
  const existing = readFileSync3(configPath, "utf8");
314
314
  const { content, changed } = stripCodexBlock(existing);
315
315
  if (!changed) return { ok: true, message: "leadbay block not present" };
@@ -321,11 +321,11 @@ async function uninstallFromCodexConfig(configPath) {
321
321
  }
322
322
  async function uninstallShellExports() {
323
323
  try {
324
- const { existsSync: existsSync4, readFileSync: readFileSync3, writeFileSync, renameSync } = await import("fs");
324
+ const { existsSync: existsSync3, readFileSync: readFileSync3, writeFileSync, renameSync } = await import("fs");
325
325
  const os = await import("os");
326
326
  const home = os.homedir();
327
327
  const candidates = [`${home}/.zshrc`, `${home}/.bashrc`, `${home}/.profile`].filter(
328
- (p) => existsSync4(p)
328
+ (p) => existsSync3(p)
329
329
  );
330
330
  const updated = [];
331
331
  for (const p of candidates) {
@@ -354,17 +354,17 @@ var init_install_codex = __esm({
354
354
  // installer/install-dxt.ts
355
355
  async function removeDxtExtension(claudeSupportDir) {
356
356
  try {
357
- const { existsSync: existsSync4, readFileSync: readFileSync3, writeFileSync, rmSync } = await import("fs");
357
+ const { existsSync: existsSync3, readFileSync: readFileSync3, writeFileSync, rmSync } = await import("fs");
358
358
  const { join: join2 } = await import("path");
359
359
  const extensionDir = join2(claudeSupportDir, "Claude Extensions", DXT_EXTENSION_ID);
360
360
  const registryPath = join2(claudeSupportDir, "extensions-installations.json");
361
361
  let removedDir = false;
362
362
  let removedEntry = false;
363
- if (existsSync4(extensionDir)) {
363
+ if (existsSync3(extensionDir)) {
364
364
  rmSync(extensionDir, { recursive: true, force: true });
365
365
  removedDir = true;
366
366
  }
367
- if (existsSync4(registryPath)) {
367
+ if (existsSync3(registryPath)) {
368
368
  try {
369
369
  const raw = readFileSync3(registryPath, "utf8");
370
370
  const parsed = JSON.parse(raw);
@@ -430,18 +430,18 @@ function detectClaudeDesktopMode(claudeSupportDir) {
430
430
  return { legacy, dxt: markers.length > 0, markers };
431
431
  }
432
432
  async function findOnPath(bin) {
433
- return await new Promise((resolve2) => {
433
+ return await new Promise((resolve) => {
434
434
  const cmd = process.platform === "win32" ? "where" : "which";
435
435
  const child = spawn2(cmd, [bin], { stdio: ["ignore", "pipe", "ignore"] });
436
436
  let buf = "";
437
437
  child.stdout.on("data", (chunk) => buf += chunk.toString());
438
- child.on("close", (code) => resolve2(code === 0 ? buf.split(/\r?\n/)[0] : null));
439
- child.on("error", () => resolve2(null));
438
+ child.on("close", (code) => resolve(code === 0 ? buf.split(/\r?\n/)[0] : null));
439
+ child.on("error", () => resolve(null));
440
440
  });
441
441
  }
442
442
  async function windowsStoreAppInstalled(packageName, appName) {
443
443
  if (process.platform !== "win32") return false;
444
- return await new Promise((resolve2) => {
444
+ return await new Promise((resolve) => {
445
445
  const script = [
446
446
  `$pkg = Get-AppxPackage -Name '${packageName}' -ErrorAction SilentlyContinue`,
447
447
  `$app = Get-StartApps | Where-Object { $_.AppID -like '${packageName}_*!${appName}' } | Select-Object -First 1`,
@@ -451,8 +451,8 @@ async function windowsStoreAppInstalled(packageName, appName) {
451
451
  stdio: "ignore",
452
452
  windowsHide: true
453
453
  });
454
- child.on("close", (code) => resolve2(code === 0));
455
- child.on("error", () => resolve2(false));
454
+ child.on("close", (code) => resolve(code === 0));
455
+ child.on("error", () => resolve(false));
456
456
  });
457
457
  }
458
458
  async function isClaudeDesktopInstalled(home) {
@@ -585,7 +585,7 @@ function base64UrlEncode(buf) {
585
585
  return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
586
586
  }
587
587
  function httpsCall(method, url, headers, body) {
588
- return new Promise((resolve2, reject) => {
588
+ return new Promise((resolve, reject) => {
589
589
  const u = new URL(url);
590
590
  const reqHeaders = { ...headers };
591
591
  if (body !== void 0) reqHeaders["Content-Length"] = Buffer.byteLength(body);
@@ -602,7 +602,7 @@ function httpsCall(method, url, headers, body) {
602
602
  res.on("data", (c) => chunks.push(c));
603
603
  res.on(
604
604
  "end",
605
- () => resolve2({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") })
605
+ () => resolve({ status: res.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") })
606
606
  );
607
607
  }
608
608
  );
@@ -720,11 +720,11 @@ async function startLoopbackListener(opts) {
720
720
  res.end(renderHtml("You're signed in", "You can close this tab and return to the terminal."));
721
721
  resolveCallback({ code, state });
722
722
  });
723
- await new Promise((resolve2, reject) => {
723
+ await new Promise((resolve, reject) => {
724
724
  server.once("error", reject);
725
725
  server.listen(0, "127.0.0.1", () => {
726
726
  server.off("error", reject);
727
- resolve2();
727
+ resolve();
728
728
  });
729
729
  });
730
730
  const addr = server.address();
@@ -813,12 +813,12 @@ async function openInBrowser(url) {
813
813
  cmd = "xdg-open";
814
814
  args = [url];
815
815
  }
816
- await new Promise((resolve2, reject) => {
816
+ await new Promise((resolve, reject) => {
817
817
  const child = spawn3(cmd, args, { stdio: "ignore", detached: true });
818
818
  child.on("error", reject);
819
819
  child.on("spawn", () => {
820
820
  child.unref();
821
- resolve2();
821
+ resolve();
822
822
  });
823
823
  });
824
824
  }
@@ -915,7 +915,6 @@ __export(installer_gui_exports, {
915
915
  });
916
916
  import { createServer as createServer2 } from "http";
917
917
  import { randomUUID } from "crypto";
918
- import { realpathSync } from "fs";
919
918
  import { resolve as resolvePath } from "path";
920
919
  import { fileURLToPath } from "url";
921
920
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
@@ -965,12 +964,12 @@ function sendSse(res, event) {
965
964
  `);
966
965
  }
967
966
  function readJson(req) {
968
- return new Promise((resolve2, reject) => {
967
+ return new Promise((resolve, reject) => {
969
968
  const chunks = [];
970
969
  req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
971
970
  req.on("end", () => {
972
971
  try {
973
- resolve2(JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"));
972
+ resolve(JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"));
974
973
  } catch (err) {
975
974
  reject(err);
976
975
  }
@@ -1069,7 +1068,7 @@ async function install(body) {
1069
1068
  ].join("\n");
1070
1069
  return { ok: results.some((result) => result.ok), output: sanitizeOutput(output), results };
1071
1070
  }
1072
- async function streamInstall(url, res) {
1071
+ async function streamInstall(url, res, onDone) {
1073
1072
  cleanupSessions();
1074
1073
  res.writeHead(200, {
1075
1074
  "content-type": "text/event-stream; charset=utf-8",
@@ -1081,16 +1080,23 @@ async function streamInstall(url, res) {
1081
1080
  const includeWrite = url.searchParams.get("write") !== "0";
1082
1081
  const telemetryEnabled = url.searchParams.get("telemetry") !== "0";
1083
1082
  const emit = (level, message) => sendSse(res, { level, message: sanitizeOutput(message) });
1083
+ const abort = (msg) => {
1084
+ emit("done", msg);
1085
+ res.end();
1086
+ };
1087
+ const finish = (msg) => {
1088
+ emit("done", msg);
1089
+ res.end();
1090
+ onDone?.();
1091
+ };
1084
1092
  if (!session) {
1085
1093
  emit("error", "Login expired. Go back and sign in again.");
1086
- emit("done", "Install stopped.");
1087
- res.end();
1094
+ abort("Install stopped.");
1088
1095
  return;
1089
1096
  }
1090
1097
  if (!clientIds.length) {
1091
1098
  emit("error", "Select at least one agent.");
1092
- emit("done", "Install stopped.");
1093
- res.end();
1099
+ abort("Install stopped.");
1094
1100
  return;
1095
1101
  }
1096
1102
  emit("info", `Connected to ${session.accountLabel}.`);
@@ -1101,8 +1107,7 @@ async function streamInstall(url, res) {
1101
1107
  const selectedHasOnlyManualSetup = selected.length > 0 && selected.every(isManualSetupClient);
1102
1108
  if (!selected.length) {
1103
1109
  emit("error", "No selected agents were detected on this machine.");
1104
- emit("done", "Install stopped.");
1105
- res.end();
1110
+ abort("Install stopped.");
1106
1111
  return;
1107
1112
  }
1108
1113
  let okCount = 0;
@@ -1116,11 +1121,16 @@ async function streamInstall(url, res) {
1116
1121
  emit("error", `${result.label}: ${result.message}`);
1117
1122
  }
1118
1123
  }
1119
- emit(okCount > 0 ? "success" : "error", selectedHasOnlyManualSetup ? "Manual ChatGPT setup instructions ready." : `${okCount}/${selected.length} agent(s) installed, updated, or prepared.`);
1120
- emit("done", selectedHasOnlyManualSetup ? "Follow the manual setup instructions shown above." : "Restart your MCP client(s) to pick up the new server.");
1121
- res.end();
1124
+ const summary = selectedHasOnlyManualSetup ? "Manual ChatGPT setup instructions ready." : `${okCount}/${selected.length} agent(s) installed, updated, or prepared.`;
1125
+ const closing = selectedHasOnlyManualSetup ? "Follow the manual setup instructions shown above." : "Restart your MCP client(s) to pick up the new server.";
1126
+ emit(okCount > 0 ? "success" : "error", summary);
1127
+ if (okCount > 0) {
1128
+ finish(closing);
1129
+ } else {
1130
+ abort(closing);
1131
+ }
1122
1132
  }
1123
- async function streamUninstall(url, res) {
1133
+ async function streamUninstall(url, res, onDone) {
1124
1134
  res.writeHead(200, {
1125
1135
  "content-type": "text/event-stream; charset=utf-8",
1126
1136
  "cache-control": "no-cache, no-transform",
@@ -1128,18 +1138,25 @@ async function streamUninstall(url, res) {
1128
1138
  });
1129
1139
  const clientIds = (url.searchParams.get("clients") ?? "").split(",").filter(Boolean);
1130
1140
  const emit = (level, message) => sendSse(res, { level, message });
1141
+ const abort = (msg) => {
1142
+ emit("done", msg);
1143
+ res.end();
1144
+ };
1145
+ const finish = (msg) => {
1146
+ emit("done", msg);
1147
+ res.end();
1148
+ onDone?.();
1149
+ };
1131
1150
  if (!clientIds.length) {
1132
1151
  emit("error", "Select at least one agent.");
1133
- emit("done", "Uninstall stopped.");
1134
- res.end();
1152
+ abort("Uninstall stopped.");
1135
1153
  return;
1136
1154
  }
1137
1155
  const detected = await detectClients();
1138
1156
  const selected = detected.filter((c) => clientIds.includes(c.id));
1139
1157
  if (!selected.length) {
1140
1158
  emit("error", "No selected agents were detected on this machine.");
1141
- emit("done", "Uninstall stopped.");
1142
- res.end();
1159
+ abort("Uninstall stopped.");
1143
1160
  return;
1144
1161
  }
1145
1162
  let okCount = 0;
@@ -1163,8 +1180,7 @@ async function streamUninstall(url, res) {
1163
1180
  }
1164
1181
  }
1165
1182
  emit(okCount > 0 ? "success" : "error", `${okCount}/${selected.length} agent(s) removed.`);
1166
- emit("done", "Restart your MCP client(s) to complete the removal.");
1167
- res.end();
1183
+ finish("Restart your MCP client(s) to complete the removal.");
1168
1184
  }
1169
1185
  function pageUninstallHtml() {
1170
1186
  return `<!doctype html>
@@ -1335,68 +1351,44 @@ function pageHtml() {
1335
1351
  </html>`;
1336
1352
  }
1337
1353
  async function openBrowser(url) {
1338
- const { spawn: spawn5 } = await import("child_process");
1339
- const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
1340
- const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
1341
- const child = spawn5(command, args, { stdio: "ignore", detached: true });
1342
- child.unref();
1343
- }
1344
- async function startInstallerGui(options = {}) {
1345
- let expectedHost = `127.0.0.1:${(options.port ?? PORT) || 0}`;
1346
- const server = createServer2(async (req, res) => {
1347
- if (!isAllowedOrigin(req, expectedHost)) {
1348
- sendJson(res, 403, { ok: false, error: "forbidden" });
1349
- return;
1350
- }
1354
+ const { spawn: spawn4 } = await import("child_process");
1355
+ const trySpawn = (command, args) => new Promise((resolve) => {
1351
1356
  try {
1352
- if (req.method === "GET" && req.url === "/") {
1353
- const raw = pageHtml();
1354
- res.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": Buffer.byteLength(raw) });
1355
- res.end(raw);
1356
- return;
1357
- }
1358
- if (req.method === "GET" && req.url === "/api/status") {
1359
- sendJson(res, 200, {
1360
- os: formatInstallOsLabel(),
1361
- hostedMcpUrl: HOSTED_MCP_URL,
1362
- clients: await clientsWithConfiguredStatus()
1363
- });
1364
- return;
1365
- }
1366
- if (req.method === "POST" && req.url === "/api/oauth-login") {
1367
- sendJson(res, 200, await loginWithOAuth());
1368
- return;
1369
- }
1370
- if (req.method === "POST" && req.url === "/api/install") {
1371
- sendJson(res, 200, await install(await readJson(req)));
1372
- return;
1373
- }
1374
- if (req.method === "GET" && req.url?.startsWith("/api/install-stream")) {
1375
- await streamInstall(new URL(req.url, "http://127.0.0.1"), res);
1376
- return;
1377
- }
1378
- sendJson(res, 404, { ok: false, error: "not found" });
1379
- } catch (err) {
1380
- sendJson(res, 500, { ok: false, error: err?.message ?? String(err) });
1357
+ const child = spawn4(command, args, { stdio: "ignore", detached: true });
1358
+ child.unref();
1359
+ child.on("error", () => resolve(false));
1360
+ child.on("close", (code) => resolve(code === 0));
1361
+ } catch {
1362
+ resolve(false);
1381
1363
  }
1382
1364
  });
1383
- return await new Promise((resolve2, reject) => {
1384
- server.once("error", reject);
1385
- server.listen(options.port ?? PORT, "127.0.0.1", async () => {
1386
- server.off("error", reject);
1387
- const address = server.address();
1388
- const port = typeof address === "object" && address ? address.port : options.port ?? PORT;
1389
- expectedHost = `127.0.0.1:${port}`;
1390
- const url = `http://127.0.0.1:${port}/`;
1391
- process.stderr.write(`Leadbay MCP installer GUI: ${url}
1365
+ if (process.platform === "darwin") {
1366
+ await trySpawn("open", [url]);
1367
+ return;
1368
+ }
1369
+ if (process.platform === "win32") {
1370
+ await trySpawn("cmd", ["/c", "start", "", url]);
1371
+ return;
1372
+ }
1373
+ const candidates = ["xdg-open", "sensible-browser", "google-chrome", "chromium-browser", "firefox"];
1374
+ for (const cmd of candidates) {
1375
+ if (await trySpawn(cmd, [url])) return;
1376
+ }
1377
+ process.stderr.write(`
1378
+ Open this URL in your browser to continue:
1379
+ ${url}
1380
+
1392
1381
  `);
1393
- if (options.openBrowser !== false) await openBrowser(url).catch(() => void 0);
1394
- resolve2({ url, close: () => new Promise((closeResolve, closeReject) => server.close((err) => err ? closeReject(err) : closeResolve())) });
1395
- });
1396
- });
1397
1382
  }
1398
- async function startUninstallerGui(options = {}) {
1383
+ function makeGuiServer(options, pageContent, extraRoutes, logLabel) {
1399
1384
  let expectedHost = `127.0.0.1:${(options.port ?? PORT) || 0}`;
1385
+ let resolveDone;
1386
+ const done = new Promise((r) => {
1387
+ resolveDone = r;
1388
+ });
1389
+ const onDone = () => setTimeout(() => {
1390
+ resolveDone();
1391
+ }, 1500);
1400
1392
  const server = createServer2(async (req, res) => {
1401
1393
  if (!isAllowedOrigin(req, expectedHost)) {
1402
1394
  sendJson(res, 403, { ok: false, error: "forbidden" });
@@ -1404,28 +1396,18 @@ async function startUninstallerGui(options = {}) {
1404
1396
  }
1405
1397
  try {
1406
1398
  if (req.method === "GET" && req.url === "/") {
1407
- const raw = pageUninstallHtml();
1399
+ const raw = pageContent();
1408
1400
  res.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": Buffer.byteLength(raw) });
1409
1401
  res.end(raw);
1410
1402
  return;
1411
1403
  }
1412
- if (req.method === "GET" && req.url === "/api/status") {
1413
- sendJson(res, 200, {
1414
- os: formatInstallOsLabel(),
1415
- clients: await clientsWithConfiguredStatus()
1416
- });
1417
- return;
1418
- }
1419
- if (req.method === "GET" && req.url?.startsWith("/api/uninstall-stream")) {
1420
- await streamUninstall(new URL(req.url, "http://127.0.0.1"), res);
1421
- return;
1422
- }
1404
+ if (await extraRoutes(req, res, onDone)) return;
1423
1405
  sendJson(res, 404, { ok: false, error: "not found" });
1424
1406
  } catch (err) {
1425
1407
  sendJson(res, 500, { ok: false, error: err?.message ?? String(err) });
1426
1408
  }
1427
1409
  });
1428
- return await new Promise((resolve2, reject) => {
1410
+ return new Promise((resolve, reject) => {
1429
1411
  server.once("error", reject);
1430
1412
  server.listen(options.port ?? PORT, "127.0.0.1", async () => {
1431
1413
  server.off("error", reject);
@@ -1433,23 +1415,48 @@ async function startUninstallerGui(options = {}) {
1433
1415
  const port = typeof address === "object" && address ? address.port : options.port ?? PORT;
1434
1416
  expectedHost = `127.0.0.1:${port}`;
1435
1417
  const url = `http://127.0.0.1:${port}/`;
1436
- process.stderr.write(`Leadbay MCP uninstaller GUI: ${url}
1418
+ process.stderr.write(`Leadbay MCP ${logLabel} GUI: ${url}
1437
1419
  `);
1438
1420
  if (options.openBrowser !== false) await openBrowser(url).catch(() => void 0);
1439
- resolve2({ url, close: () => new Promise((closeResolve, closeReject) => server.close((err) => err ? closeReject(err) : closeResolve())) });
1421
+ resolve({ url, done, close: () => new Promise((res, rej) => server.close((e) => e ? rej(e) : res())) });
1440
1422
  });
1441
1423
  });
1442
1424
  }
1443
- async function main() {
1444
- const uninstall = process.argv.includes("--uninstall");
1445
- const handle = uninstall ? await startUninstallerGui({ openBrowser: !process.argv.includes("--no-open") }) : await startInstallerGui({ openBrowser: !process.argv.includes("--no-open") });
1446
- await new Promise((resolve2) => {
1447
- process.once("SIGINT", () => resolve2());
1448
- process.once("SIGTERM", () => resolve2());
1449
- });
1450
- await handle.close().catch(() => void 0);
1425
+ function startInstallerGui(options = {}) {
1426
+ return makeGuiServer(options, pageHtml, async (req, res, onDone) => {
1427
+ if (req.method === "GET" && req.url === "/api/status") {
1428
+ sendJson(res, 200, { os: formatInstallOsLabel(), hostedMcpUrl: HOSTED_MCP_URL, clients: await clientsWithConfiguredStatus() });
1429
+ return true;
1430
+ }
1431
+ if (req.method === "POST" && req.url === "/api/oauth-login") {
1432
+ sendJson(res, 200, await loginWithOAuth());
1433
+ return true;
1434
+ }
1435
+ if (req.method === "POST" && req.url === "/api/install") {
1436
+ sendJson(res, 200, await install(await readJson(req)));
1437
+ return true;
1438
+ }
1439
+ if (req.method === "GET" && req.url?.startsWith("/api/install-stream")) {
1440
+ await streamInstall(new URL(req.url, "http://127.0.0.1"), res, onDone);
1441
+ return true;
1442
+ }
1443
+ return false;
1444
+ }, "installer");
1451
1445
  }
1452
- var VERSION, PORT, sessions, OAUTH_BASE_URLS, LOCAL_BIN_PATH, isEntrypoint;
1446
+ function startUninstallerGui(options = {}) {
1447
+ return makeGuiServer(options, pageUninstallHtml, async (req, res, onDone) => {
1448
+ if (req.method === "GET" && req.url === "/api/status") {
1449
+ sendJson(res, 200, { os: formatInstallOsLabel(), clients: await clientsWithConfiguredStatus() });
1450
+ return true;
1451
+ }
1452
+ if (req.method === "GET" && req.url?.startsWith("/api/uninstall-stream")) {
1453
+ await streamUninstall(new URL(req.url, "http://127.0.0.1"), res, onDone);
1454
+ return true;
1455
+ }
1456
+ return false;
1457
+ }, "uninstaller");
1458
+ }
1459
+ var VERSION, PORT, sessions, OAUTH_BASE_URLS, LOCAL_BIN_PATH;
1453
1460
  var init_installer_gui = __esm({
1454
1461
  "installer/installer-gui.ts"() {
1455
1462
  "use strict";
@@ -1459,7 +1466,7 @@ var init_installer_gui = __esm({
1459
1466
  init_install_dxt();
1460
1467
  init_install_shared();
1461
1468
  init_oauth();
1462
- VERSION = "0.17.1";
1469
+ VERSION = "0.17.3";
1463
1470
  PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
1464
1471
  sessions = /* @__PURE__ */ new Map();
1465
1472
  OAUTH_BASE_URLS = {
@@ -1478,76 +1485,41 @@ var init_installer_gui = __esm({
1478
1485
  const here = typeof __dirname !== "undefined" ? __dirname : resolvePath(fileURLToPath(import.meta.url), "..");
1479
1486
  return resolvePath(here, "..", "dist", "bin.js");
1480
1487
  })();
1481
- isEntrypoint = (() => {
1482
- try {
1483
- const entry = process.argv[1];
1484
- if (!entry) return false;
1485
- return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);
1486
- } catch {
1487
- return false;
1488
- }
1489
- })();
1490
- if (isEntrypoint) {
1491
- main().catch((err) => {
1492
- process.stderr.write(`leadbay-mcp-installer: ${err?.message ?? err}
1493
- `);
1494
- process.exit(1);
1495
- });
1496
- }
1497
1488
  }
1498
1489
  });
1499
1490
 
1500
1491
  // installer/installer-electron.ts
1501
- import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
1502
- import { spawn as spawn4 } from "child_process";
1503
- import { createRequire } from "module";
1504
- import { dirname, resolve } from "path";
1492
+ import { realpathSync } from "fs";
1505
1493
  import { fileURLToPath as fileURLToPath2 } from "url";
1506
- async function runBrowserFallback(args) {
1494
+ async function main() {
1495
+ const args = process.argv.slice(2);
1507
1496
  const { startInstallerGui: startInstallerGui2, startUninstallerGui: startUninstallerGui2 } = await Promise.resolve().then(() => (init_installer_gui(), installer_gui_exports));
1508
1497
  const opts = { openBrowser: !args.includes("--no-open") };
1509
- if (args.includes("--uninstall")) {
1510
- await startUninstallerGui2(opts);
1511
- } else {
1512
- await startInstallerGui2(opts);
1513
- }
1514
- }
1515
- async function main2() {
1516
- const args = process.argv.slice(2);
1517
- const mainPath = resolve(dirname(fileURLToPath2(import.meta.url)), "../installer/electron-main.cjs");
1518
- if (!existsSync3(mainPath) || args.includes("--browser")) {
1519
- await runBrowserFallback(args);
1520
- return;
1521
- }
1522
- let electronPath;
1523
- try {
1524
- const require2 = createRequire(import.meta.url);
1525
- electronPath = require2("electron");
1526
- } catch {
1527
- await runBrowserFallback(args);
1528
- return;
1529
- }
1530
- await new Promise((resolve2, reject) => {
1531
- const electronArgs = process.platform === "linux" && process.env.LEADBAY_INSTALLER_ELECTRON_SANDBOX !== "1" ? ["--no-sandbox", mainPath, ...args] : [mainPath, ...args];
1532
- const child = spawn4(electronPath, electronArgs, { stdio: "inherit", env: process.env });
1533
- child.on("error", reject);
1534
- child.on("exit", (code, signal) => {
1535
- if (signal) process.exit(128 + (signal === "SIGINT" ? 2 : signal === "SIGTERM" ? 15 : 0));
1536
- process.exit(code ?? 0);
1537
- });
1538
- });
1498
+ const handle = args.includes("--uninstall") ? await startUninstallerGui2(opts) : await startInstallerGui2(opts);
1499
+ let completed = false;
1500
+ await Promise.race([
1501
+ handle.done.then(() => {
1502
+ completed = true;
1503
+ }),
1504
+ new Promise((resolve) => {
1505
+ process.once("SIGINT", () => resolve());
1506
+ process.once("SIGTERM", () => resolve());
1507
+ })
1508
+ ]);
1509
+ await handle.close().catch(() => void 0);
1510
+ process.stderr.write(completed ? "\nInstallation complete. Exiting.\n" : "\nExiting.\n");
1539
1511
  }
1540
- var isEntrypoint2 = (() => {
1512
+ var isEntrypoint = (() => {
1541
1513
  try {
1542
1514
  const entry = process.argv[1];
1543
1515
  if (!entry) return false;
1544
- return realpathSync2(fileURLToPath2(import.meta.url)) === realpathSync2(entry);
1516
+ return realpathSync(fileURLToPath2(import.meta.url)) === realpathSync(entry);
1545
1517
  } catch {
1546
1518
  return false;
1547
1519
  }
1548
1520
  })();
1549
- if (isEntrypoint2) {
1550
- main2().catch((err) => {
1521
+ if (isEntrypoint) {
1522
+ main().catch((err) => {
1551
1523
  process.stderr.write(`leadbay-mcp-installer: ${err?.message ?? err}
1552
1524
  `);
1553
1525
  process.exit(1);