@leadbay/mcp 0.17.2 → 0.18.0
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 +24 -0
- package/README.md +38 -1
- package/dist/bin.js +1524 -99
- package/dist/http-server.js +1185 -113
- package/dist/installer-electron.js +145 -200
- package/dist/installer-gui.js +77 -109
- package/package.json +3 -4
|
@@ -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 = {
|
|
@@ -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,15 +1351,15 @@ function pageHtml() {
|
|
|
1335
1351
|
</html>`;
|
|
1336
1352
|
}
|
|
1337
1353
|
async function openBrowser(url) {
|
|
1338
|
-
const { spawn:
|
|
1339
|
-
const trySpawn = (command, args) => new Promise((
|
|
1354
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
1355
|
+
const trySpawn = (command, args) => new Promise((resolve) => {
|
|
1340
1356
|
try {
|
|
1341
|
-
const child =
|
|
1357
|
+
const child = spawn4(command, args, { stdio: "ignore", detached: true });
|
|
1342
1358
|
child.unref();
|
|
1343
|
-
child.on("error", () =>
|
|
1344
|
-
child.on("close", (code) =>
|
|
1359
|
+
child.on("error", () => resolve(false));
|
|
1360
|
+
child.on("close", (code) => resolve(code === 0));
|
|
1345
1361
|
} catch {
|
|
1346
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
|
1418
|
+
process.stderr.write(`Leadbay MCP ${logLabel} GUI: ${url}
|
|
1415
1419
|
`);
|
|
1416
1420
|
if (options.openBrowser !== false) await openBrowser(url).catch(() => void 0);
|
|
1417
|
-
|
|
1421
|
+
resolve({ url, done, close: () => new Promise((res, rej) => server.close((e) => e ? rej(e) : res())) });
|
|
1418
1422
|
});
|
|
1419
1423
|
});
|
|
1420
1424
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
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
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
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
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
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
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
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
|
|
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.
|
|
1469
|
+
VERSION = "0.18.0";
|
|
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 {
|
|
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
|
|
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
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
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
|
|
1512
|
+
var isEntrypoint = (() => {
|
|
1568
1513
|
try {
|
|
1569
1514
|
const entry = process.argv[1];
|
|
1570
1515
|
if (!entry) return false;
|
|
1571
|
-
return
|
|
1516
|
+
return realpathSync(fileURLToPath2(import.meta.url)) === realpathSync(entry);
|
|
1572
1517
|
} catch {
|
|
1573
1518
|
return false;
|
|
1574
1519
|
}
|
|
1575
1520
|
})();
|
|
1576
|
-
if (
|
|
1577
|
-
|
|
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);
|