@leadbay/mcp 0.17.2 → 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.
@@ -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 = {
@@ -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,15 +1351,15 @@ function pageHtml() {
1335
1351
  </html>`;
1336
1352
  }
1337
1353
  async function openBrowser(url) {
1338
- const { spawn: spawn5 } = await import("child_process");
1339
- const trySpawn = (command, args) => new Promise((resolve2) => {
1354
+ const { spawn: spawn4 } = await import("child_process");
1355
+ const trySpawn = (command, args) => new Promise((resolve) => {
1340
1356
  try {
1341
- const child = spawn5(command, args, { stdio: "ignore", detached: true });
1357
+ const child = spawn4(command, args, { stdio: "ignore", detached: true });
1342
1358
  child.unref();
1343
- child.on("error", () => resolve2(false));
1344
- child.on("close", (code) => resolve2(code === 0));
1359
+ child.on("error", () => resolve(false));
1360
+ child.on("close", (code) => resolve(code === 0));
1345
1361
  } catch {
1346
- resolve2(false);
1362
+ resolve(false);
1347
1363
  }
1348
1364
  });
1349
1365
  if (process.platform === "darwin") {
@@ -1364,8 +1380,15 @@ async function openBrowser(url) {
1364
1380
 
1365
1381
  `);
1366
1382
  }
1367
- async function startInstallerGui(options = {}) {
1383
+ function makeGuiServer(options, pageContent, extraRoutes, logLabel) {
1368
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);
1369
1392
  const server = createServer2(async (req, res) => {
1370
1393
  if (!isAllowedOrigin(req, expectedHost)) {
1371
1394
  sendJson(res, 403, { ok: false, error: "forbidden" });
@@ -1373,37 +1396,18 @@ async function startInstallerGui(options = {}) {
1373
1396
  }
1374
1397
  try {
1375
1398
  if (req.method === "GET" && req.url === "/") {
1376
- const raw = pageHtml();
1399
+ const raw = pageContent();
1377
1400
  res.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": Buffer.byteLength(raw) });
1378
1401
  res.end(raw);
1379
1402
  return;
1380
1403
  }
1381
- if (req.method === "GET" && req.url === "/api/status") {
1382
- sendJson(res, 200, {
1383
- os: formatInstallOsLabel(),
1384
- hostedMcpUrl: HOSTED_MCP_URL,
1385
- clients: await clientsWithConfiguredStatus()
1386
- });
1387
- return;
1388
- }
1389
- if (req.method === "POST" && req.url === "/api/oauth-login") {
1390
- sendJson(res, 200, await loginWithOAuth());
1391
- return;
1392
- }
1393
- if (req.method === "POST" && req.url === "/api/install") {
1394
- sendJson(res, 200, await install(await readJson(req)));
1395
- return;
1396
- }
1397
- if (req.method === "GET" && req.url?.startsWith("/api/install-stream")) {
1398
- await streamInstall(new URL(req.url, "http://127.0.0.1"), res);
1399
- return;
1400
- }
1404
+ if (await extraRoutes(req, res, onDone)) return;
1401
1405
  sendJson(res, 404, { ok: false, error: "not found" });
1402
1406
  } catch (err) {
1403
1407
  sendJson(res, 500, { ok: false, error: err?.message ?? String(err) });
1404
1408
  }
1405
1409
  });
1406
- return await new Promise((resolve2, reject) => {
1410
+ return new Promise((resolve, reject) => {
1407
1411
  server.once("error", reject);
1408
1412
  server.listen(options.port ?? PORT, "127.0.0.1", async () => {
1409
1413
  server.off("error", reject);
@@ -1411,68 +1415,48 @@ async function startInstallerGui(options = {}) {
1411
1415
  const port = typeof address === "object" && address ? address.port : options.port ?? PORT;
1412
1416
  expectedHost = `127.0.0.1:${port}`;
1413
1417
  const url = `http://127.0.0.1:${port}/`;
1414
- process.stderr.write(`Leadbay MCP installer GUI: ${url}
1418
+ process.stderr.write(`Leadbay MCP ${logLabel} GUI: ${url}
1415
1419
  `);
1416
1420
  if (options.openBrowser !== false) await openBrowser(url).catch(() => void 0);
1417
- 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())) });
1418
1422
  });
1419
1423
  });
1420
1424
  }
1421
- async function startUninstallerGui(options = {}) {
1422
- let expectedHost = `127.0.0.1:${(options.port ?? PORT) || 0}`;
1423
- const server = createServer2(async (req, res) => {
1424
- if (!isAllowedOrigin(req, expectedHost)) {
1425
- sendJson(res, 403, { ok: false, error: "forbidden" });
1426
- return;
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;
1427
1430
  }
1428
- try {
1429
- if (req.method === "GET" && req.url === "/") {
1430
- const raw = pageUninstallHtml();
1431
- res.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": Buffer.byteLength(raw) });
1432
- res.end(raw);
1433
- return;
1434
- }
1435
- if (req.method === "GET" && req.url === "/api/status") {
1436
- sendJson(res, 200, {
1437
- os: formatInstallOsLabel(),
1438
- clients: await clientsWithConfiguredStatus()
1439
- });
1440
- return;
1441
- }
1442
- if (req.method === "GET" && req.url?.startsWith("/api/uninstall-stream")) {
1443
- await streamUninstall(new URL(req.url, "http://127.0.0.1"), res);
1444
- return;
1445
- }
1446
- sendJson(res, 404, { ok: false, error: "not found" });
1447
- } catch (err) {
1448
- sendJson(res, 500, { ok: false, error: err?.message ?? String(err) });
1431
+ if (req.method === "POST" && req.url === "/api/oauth-login") {
1432
+ sendJson(res, 200, await loginWithOAuth());
1433
+ return true;
1449
1434
  }
1450
- });
1451
- return await new Promise((resolve2, reject) => {
1452
- server.once("error", reject);
1453
- server.listen(options.port ?? PORT, "127.0.0.1", async () => {
1454
- server.off("error", reject);
1455
- const address = server.address();
1456
- const port = typeof address === "object" && address ? address.port : options.port ?? PORT;
1457
- expectedHost = `127.0.0.1:${port}`;
1458
- const url = `http://127.0.0.1:${port}/`;
1459
- process.stderr.write(`Leadbay MCP uninstaller GUI: ${url}
1460
- `);
1461
- if (options.openBrowser !== false) await openBrowser(url).catch(() => void 0);
1462
- resolve2({ url, close: () => new Promise((closeResolve, closeReject) => server.close((err) => err ? closeReject(err) : closeResolve())) });
1463
- });
1464
- });
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");
1465
1445
  }
1466
- async function main() {
1467
- const uninstall = process.argv.includes("--uninstall");
1468
- const handle = uninstall ? await startUninstallerGui({ openBrowser: !process.argv.includes("--no-open") }) : await startInstallerGui({ openBrowser: !process.argv.includes("--no-open") });
1469
- await new Promise((resolve2) => {
1470
- process.once("SIGINT", () => resolve2());
1471
- process.once("SIGTERM", () => resolve2());
1472
- });
1473
- await handle.close().catch(() => void 0);
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");
1474
1458
  }
1475
- var VERSION, PORT, sessions, OAUTH_BASE_URLS, LOCAL_BIN_PATH, isEntrypoint;
1459
+ var VERSION, PORT, sessions, OAUTH_BASE_URLS, LOCAL_BIN_PATH;
1476
1460
  var init_installer_gui = __esm({
1477
1461
  "installer/installer-gui.ts"() {
1478
1462
  "use strict";
@@ -1482,7 +1466,7 @@ var init_installer_gui = __esm({
1482
1466
  init_install_dxt();
1483
1467
  init_install_shared();
1484
1468
  init_oauth();
1485
- VERSION = "0.17.2";
1469
+ VERSION = "0.17.3";
1486
1470
  PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
1487
1471
  sessions = /* @__PURE__ */ new Map();
1488
1472
  OAUTH_BASE_URLS = {
@@ -1501,80 +1485,41 @@ var init_installer_gui = __esm({
1501
1485
  const here = typeof __dirname !== "undefined" ? __dirname : resolvePath(fileURLToPath(import.meta.url), "..");
1502
1486
  return resolvePath(here, "..", "dist", "bin.js");
1503
1487
  })();
1504
- isEntrypoint = (() => {
1505
- try {
1506
- const entry = process.argv[1];
1507
- if (!entry) return false;
1508
- return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);
1509
- } catch {
1510
- return false;
1511
- }
1512
- })();
1513
- if (isEntrypoint) {
1514
- main().catch((err) => {
1515
- process.stderr.write(`leadbay-mcp-installer: ${err?.message ?? err}
1516
- `);
1517
- process.exit(1);
1518
- });
1519
- }
1520
1488
  }
1521
1489
  });
1522
1490
 
1523
1491
  // installer/installer-electron.ts
1524
- import { existsSync as existsSync3, realpathSync as realpathSync2 } from "fs";
1525
- import { spawn as spawn4 } from "child_process";
1526
- import { createRequire } from "module";
1527
- import { dirname, resolve } from "path";
1492
+ import { realpathSync } from "fs";
1528
1493
  import { fileURLToPath as fileURLToPath2 } from "url";
1529
- async function runBrowserFallback(args) {
1494
+ async function main() {
1495
+ const args = process.argv.slice(2);
1530
1496
  const { startInstallerGui: startInstallerGui2, startUninstallerGui: startUninstallerGui2 } = await Promise.resolve().then(() => (init_installer_gui(), installer_gui_exports));
1531
1497
  const opts = { openBrowser: !args.includes("--no-open") };
1532
- if (args.includes("--uninstall")) {
1533
- await startUninstallerGui2(opts);
1534
- } else {
1535
- await startInstallerGui2(opts);
1536
- }
1537
- }
1538
- function hasDisplay() {
1539
- if (process.platform !== "linux") return true;
1540
- return !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
1541
- }
1542
- async function main2() {
1543
- const args = process.argv.slice(2);
1544
- const mainPath = resolve(dirname(fileURLToPath2(import.meta.url)), "../installer/electron-main.cjs");
1545
- if (!existsSync3(mainPath) || args.includes("--browser") || !hasDisplay()) {
1546
- await runBrowserFallback(args);
1547
- return;
1548
- }
1549
- let electronPath;
1550
- try {
1551
- const require2 = createRequire(import.meta.url);
1552
- electronPath = require2("electron");
1553
- } catch {
1554
- await runBrowserFallback(args);
1555
- return;
1556
- }
1557
- await new Promise((resolve2, reject) => {
1558
- const electronArgs = process.platform === "linux" && process.env.LEADBAY_INSTALLER_ELECTRON_SANDBOX !== "1" ? ["--no-sandbox", mainPath, ...args] : [mainPath, ...args];
1559
- const child = spawn4(electronPath, electronArgs, { stdio: "inherit", env: process.env });
1560
- child.on("error", reject);
1561
- child.on("exit", (code, signal) => {
1562
- if (signal) process.exit(128 + (signal === "SIGINT" ? 2 : signal === "SIGTERM" ? 15 : 0));
1563
- process.exit(code ?? 0);
1564
- });
1565
- });
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");
1566
1511
  }
1567
- var isEntrypoint2 = (() => {
1512
+ var isEntrypoint = (() => {
1568
1513
  try {
1569
1514
  const entry = process.argv[1];
1570
1515
  if (!entry) return false;
1571
- return realpathSync2(fileURLToPath2(import.meta.url)) === realpathSync2(entry);
1516
+ return realpathSync(fileURLToPath2(import.meta.url)) === realpathSync(entry);
1572
1517
  } catch {
1573
1518
  return false;
1574
1519
  }
1575
1520
  })();
1576
- if (isEntrypoint2) {
1577
- main2().catch((err) => {
1521
+ if (isEntrypoint) {
1522
+ main().catch((err) => {
1578
1523
  process.stderr.write(`leadbay-mcp-installer: ${err?.message ?? err}
1579
1524
  `);
1580
1525
  process.exit(1);