@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.
- package/CHANGELOG.md +16 -0
- package/README.md +38 -1
- package/dist/bin.js +844 -35
- package/dist/http-server.js +822 -30
- package/dist/installer-electron.js +168 -196
- package/dist/installer-gui.js +104 -113
- package/package.json +3 -4
|
@@ -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((
|
|
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) =>
|
|
47
|
-
child.on("error", (err) =>
|
|
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:
|
|
94
|
-
const { dirname
|
|
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 =
|
|
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(
|
|
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:
|
|
148
|
-
if (!
|
|
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:
|
|
237
|
-
const { dirname
|
|
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 =
|
|
239
|
+
const existed = existsSync3(configPath);
|
|
240
240
|
if (existed) {
|
|
241
241
|
existing = readFileSync3(configPath, "utf8");
|
|
242
242
|
} else {
|
|
243
|
-
mkdirSync(
|
|
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((
|
|
276
|
+
const ok = await new Promise((resolve) => {
|
|
277
277
|
const child = cp.spawn("setx", [key, value], { stdio: "ignore" });
|
|
278
|
-
child.on("close", (code) =>
|
|
279
|
-
child.on("error", () =>
|
|
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:
|
|
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) =>
|
|
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 =
|
|
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:
|
|
312
|
-
if (!
|
|
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:
|
|
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) =>
|
|
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:
|
|
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 (
|
|
363
|
+
if (existsSync3(extensionDir)) {
|
|
364
364
|
rmSync(extensionDir, { recursive: true, force: true });
|
|
365
365
|
removedDir = true;
|
|
366
366
|
}
|
|
367
|
-
if (
|
|
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((
|
|
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) =>
|
|
439
|
-
child.on("error", () =>
|
|
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((
|
|
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) =>
|
|
455
|
-
child.on("error", () =>
|
|
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((
|
|
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
|
-
() =>
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1339
|
-
const
|
|
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
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
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
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
|
1418
|
+
process.stderr.write(`Leadbay MCP ${logLabel} GUI: ${url}
|
|
1437
1419
|
`);
|
|
1438
1420
|
if (options.openBrowser !== false) await openBrowser(url).catch(() => void 0);
|
|
1439
|
-
|
|
1421
|
+
resolve({ url, done, close: () => new Promise((res, rej) => server.close((e) => e ? rej(e) : res())) });
|
|
1440
1422
|
});
|
|
1441
1423
|
});
|
|
1442
1424
|
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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
|
|
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
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
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
|
|
1512
|
+
var isEntrypoint = (() => {
|
|
1541
1513
|
try {
|
|
1542
1514
|
const entry = process.argv[1];
|
|
1543
1515
|
if (!entry) return false;
|
|
1544
|
-
return
|
|
1516
|
+
return realpathSync(fileURLToPath2(import.meta.url)) === realpathSync(entry);
|
|
1545
1517
|
} catch {
|
|
1546
1518
|
return false;
|
|
1547
1519
|
}
|
|
1548
1520
|
})();
|
|
1549
|
-
if (
|
|
1550
|
-
|
|
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);
|