@leadbay/mcp 0.23.4 → 0.23.6

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.
@@ -378,7 +378,7 @@ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
378
378
 
379
379
  // installer/install-shared.ts
380
380
  import { spawn as spawn2 } from "child_process";
381
- import { existsSync, readFileSync } from "fs";
381
+ import { existsSync, readFileSync, readdirSync } from "fs";
382
382
  import { join } from "path";
383
383
  import { homedir } from "os";
384
384
  var HOSTED_MCP_URL = "https://leadbay-mcp-prod.fly.dev/mcp";
@@ -435,6 +435,15 @@ async function windowsStoreAppInstalled(packageName, appName) {
435
435
  child.on("error", () => resolve(false));
436
436
  });
437
437
  }
438
+ function isClaudeStorePackagePresent(localAppData) {
439
+ const packagesDir = join(localAppData, "Packages");
440
+ if (!existsSync(packagesDir)) return false;
441
+ try {
442
+ return readdirSync(packagesDir).some((name) => /^Claude_/i.test(name));
443
+ } catch {
444
+ return false;
445
+ }
446
+ }
438
447
  async function isClaudeDesktopInstalled(home) {
439
448
  if (process.platform === "darwin") {
440
449
  return existsSync("/Applications/Claude.app") || existsSync(home + "/Applications/Claude.app");
@@ -443,12 +452,15 @@ async function isClaudeDesktopInstalled(home) {
443
452
  const local = process.env.LOCALAPPDATA ?? home + "/AppData/Local";
444
453
  const programFiles = process.env.ProgramFiles;
445
454
  const programFilesX86 = process.env["ProgramFiles(x86)"];
446
- return [
455
+ const exeInstalled = [
447
456
  local + "/Programs/Claude/Claude.exe",
448
457
  local + "/Claude/Claude.exe",
449
458
  programFiles ? programFiles + "/Claude/Claude.exe" : null,
450
459
  programFilesX86 ? programFilesX86 + "/Claude/Claude.exe" : null
451
460
  ].some((candidate) => candidate !== null && existsSync(candidate));
461
+ if (exeInstalled) return true;
462
+ if (isClaudeStorePackagePresent(local)) return true;
463
+ return await windowsStoreAppInstalled("AnthropicPBC.Claude", "Claude");
452
464
  }
453
465
  const desktopBin = await findOnPath("claude-desktop");
454
466
  if (desktopBin) return true;
@@ -523,7 +535,7 @@ import { createHash, randomBytes } from "crypto";
523
535
  import { createServer } from "http";
524
536
  import { request as httpsRequestRaw } from "https";
525
537
  import { spawn as spawn3 } from "child_process";
526
- import { readdirSync, existsSync as existsSync2 } from "fs";
538
+ import { readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
527
539
  var LEADBAY_LOOPBACK_PORTS = [51789, 51790, 51791, 51792];
528
540
  var BrowserOpenFailedError = class extends Error {
529
541
  authorizeUrl;
@@ -859,7 +871,7 @@ function browserLaunchEnv(debug) {
859
871
  }
860
872
  if (!env.WAYLAND_DISPLAY && runtimeDir) {
861
873
  try {
862
- const sock = readdirSync(runtimeDir).find((f) => /^wayland-\d+$/.test(f));
874
+ const sock = readdirSync2(runtimeDir).find((f) => /^wayland-\d+$/.test(f));
863
875
  if (sock) {
864
876
  env.WAYLAND_DISPLAY = sock;
865
877
  debug?.(`browserLaunchEnv: injected WAYLAND_DISPLAY=${sock}`);
@@ -876,7 +888,7 @@ function browserLaunchEnv(debug) {
876
888
  }
877
889
  if (!env.DISPLAY) {
878
890
  try {
879
- const x = readdirSync("/tmp/.X11-unix").map((f) => f.match(/^X(\d+)$/)?.[1]).filter((n) => !!n).sort((a, b) => Number(a) - Number(b))[0];
891
+ const x = readdirSync2("/tmp/.X11-unix").map((f) => f.match(/^X(\d+)$/)?.[1]).filter((n) => !!n).sort((a, b) => Number(a) - Number(b))[0];
880
892
  env.DISPLAY = x !== void 0 ? `:${x}` : ":0";
881
893
  } catch {
882
894
  env.DISPLAY = ":0";
@@ -887,7 +899,7 @@ function browserLaunchEnv(debug) {
887
899
  try {
888
900
  let xauth;
889
901
  if (runtimeDir) {
890
- const cookie = readdirSync(runtimeDir).find(
902
+ const cookie = readdirSync2(runtimeDir).find(
891
903
  (f) => /^\.mutter-Xwaylandauth\./.test(f)
892
904
  );
893
905
  if (cookie) xauth = `${runtimeDir}/${cookie}`;
@@ -1015,7 +1027,157 @@ async function oauthLogin(opts) {
1015
1027
  }
1016
1028
 
1017
1029
  // installer/installer-gui.ts
1018
- var VERSION = true ? "0.23.4" : "0.0.0-dev";
1030
+ var VERSION = true ? "0.23.6" : "0.0.0-dev";
1031
+ var MESSAGES = {
1032
+ en: {
1033
+ installer: {
1034
+ docTitle: "Leadbay MCP installer",
1035
+ steps: {
1036
+ 1: { title: "Connect Leadbay", sub: "Sign in to install Leadbay across your AI agents." },
1037
+ 2: { title: "Choose your agents", sub: "Pick where to install Leadbay." },
1038
+ 3: { title: "Installing", sub: "Keep this window open until it's done." }
1039
+ },
1040
+ btnSignIn: "Sign in with Leadbay",
1041
+ btnInstall: "Install",
1042
+ btnBack: "Back",
1043
+ btnRefresh: "Refresh",
1044
+ noClientsDetected: "No supported MCP client detected on this machine.",
1045
+ noAgentsDetected: "No supported agents detected.",
1046
+ selectAtLeastOne: "Select at least one agent.",
1047
+ openingSignIn: "Opening Leadbay sign-in in your browser...",
1048
+ oauthFailed: "OAuth login failed.",
1049
+ successInstalled: "MCP successfully installed",
1050
+ noneInstalled: "No agents were installed.",
1051
+ streamDisconnected: "Install stream disconnected.",
1052
+ closeWindow: "You can close this window.",
1053
+ allSet: "All set",
1054
+ somethingWrong: "Something went wrong",
1055
+ badgeManual: "manual",
1056
+ badgeUpdate: "update",
1057
+ badgeInstall: "install"
1058
+ },
1059
+ uninstaller: {
1060
+ docTitle: "Leadbay MCP uninstaller",
1061
+ steps: {
1062
+ 1: { title: "Remove Leadbay MCP", sub: "Select the agents to remove Leadbay MCP from." },
1063
+ 2: { title: "Removing", sub: "Keep this window open until it's done." }
1064
+ },
1065
+ btnRemove: "Remove selected",
1066
+ btnBack: "Back",
1067
+ btnRefresh: "Refresh",
1068
+ noInstallDetected: "No Leadbay MCP installation detected on this machine.",
1069
+ selectAtLeastOne: "Select at least one agent.",
1070
+ successRemoved: "MCP successfully removed",
1071
+ noneRemoved: "No agents were removed.",
1072
+ streamDisconnected: "Uninstall stream disconnected.",
1073
+ closeWindow: "You can close this window.",
1074
+ allSet: "All set",
1075
+ somethingWrong: "Something went wrong"
1076
+ },
1077
+ server: {
1078
+ loginExpired: "Login expired. Go back and sign in again.",
1079
+ selectAtLeastOne: "Select at least one agent.",
1080
+ noSelectedDetected: "No selected agents were detected on this machine.",
1081
+ installStopped: "Install stopped.",
1082
+ uninstallStopped: "Uninstall stopped.",
1083
+ connectedTo: (label) => `Connected to ${label}.`,
1084
+ toolFlags: (write, telemetry) => `Write tools ${write ? "enabled" : "disabled"}; telemetry ${telemetry ? "enabled" : "disabled"}.`,
1085
+ refreshingDetection: "Refreshing installed-agent detection...",
1086
+ installing: (label) => `Installing ${label}...`,
1087
+ preparingManual: (label) => `Preparing ${label} manual setup...`,
1088
+ removing: (label) => `Removing from ${label}...`,
1089
+ installedSummary: (ok, total) => `${ok}/${total} agent(s) installed, updated, or prepared.`,
1090
+ manualReady: "Manual ChatGPT setup instructions ready.",
1091
+ removedSummary: (ok, total) => `${ok}/${total} agent(s) removed.`,
1092
+ restartClients: "Restart your MCP client(s) to pick up the new server.",
1093
+ followManual: "Follow the manual setup instructions shown above.",
1094
+ completeRemoval: "Restart your MCP client(s) to complete the removal.",
1095
+ loggedInBackend: (region) => `Logged in to ${region.toUpperCase()} backend.`,
1096
+ settingsLine: (write, telemetry) => `Settings: write tools ${write ? "on" : "off"}, telemetry ${telemetry ? "on" : "off"}.`,
1097
+ installSummaryHeader: "Install summary:",
1098
+ noAgentsRemoved: "No agents were removed."
1099
+ }
1100
+ },
1101
+ fr: {
1102
+ installer: {
1103
+ docTitle: "Installateur Leadbay MCP",
1104
+ steps: {
1105
+ 1: { title: "Connectez Leadbay", sub: "Connectez-vous pour installer Leadbay sur vos agents IA." },
1106
+ 2: { title: "Choisissez vos agents", sub: "Choisissez o\xF9 installer Leadbay." },
1107
+ 3: { title: "Installation en cours", sub: "Gardez cette fen\xEAtre ouverte jusqu'\xE0 la fin." }
1108
+ },
1109
+ btnSignIn: "Se connecter avec Leadbay",
1110
+ btnInstall: "Installer",
1111
+ btnBack: "Retour",
1112
+ btnRefresh: "Actualiser",
1113
+ noClientsDetected: "Aucun client MCP compatible d\xE9tect\xE9 sur cette machine.",
1114
+ noAgentsDetected: "Aucun agent compatible d\xE9tect\xE9.",
1115
+ selectAtLeastOne: "S\xE9lectionnez au moins un agent.",
1116
+ openingSignIn: "Ouverture de la connexion Leadbay dans votre navigateur...",
1117
+ oauthFailed: "\xC9chec de la connexion OAuth.",
1118
+ successInstalled: "MCP install\xE9 avec succ\xE8s",
1119
+ noneInstalled: "Aucun agent n'a \xE9t\xE9 install\xE9.",
1120
+ streamDisconnected: "Flux d'installation interrompu.",
1121
+ closeWindow: "Vous pouvez fermer cette fen\xEAtre.",
1122
+ allSet: "Termin\xE9",
1123
+ somethingWrong: "Une erreur est survenue",
1124
+ badgeManual: "manuel",
1125
+ badgeUpdate: "mettre \xE0 jour",
1126
+ badgeInstall: "installer"
1127
+ },
1128
+ uninstaller: {
1129
+ docTitle: "D\xE9sinstallateur Leadbay MCP",
1130
+ steps: {
1131
+ 1: { title: "Supprimer Leadbay MCP", sub: "S\xE9lectionnez les agents desquels supprimer Leadbay MCP." },
1132
+ 2: { title: "Suppression en cours", sub: "Gardez cette fen\xEAtre ouverte jusqu'\xE0 la fin." }
1133
+ },
1134
+ btnRemove: "Supprimer la s\xE9lection",
1135
+ btnBack: "Retour",
1136
+ btnRefresh: "Actualiser",
1137
+ noInstallDetected: "Aucune installation Leadbay MCP d\xE9tect\xE9e sur cette machine.",
1138
+ selectAtLeastOne: "S\xE9lectionnez au moins un agent.",
1139
+ successRemoved: "MCP supprim\xE9 avec succ\xE8s",
1140
+ noneRemoved: "Aucun agent n'a \xE9t\xE9 supprim\xE9.",
1141
+ streamDisconnected: "Flux de d\xE9sinstallation interrompu.",
1142
+ closeWindow: "Vous pouvez fermer cette fen\xEAtre.",
1143
+ allSet: "Termin\xE9",
1144
+ somethingWrong: "Une erreur est survenue"
1145
+ },
1146
+ server: {
1147
+ loginExpired: "Session expir\xE9e. Revenez en arri\xE8re et reconnectez-vous.",
1148
+ selectAtLeastOne: "S\xE9lectionnez au moins un agent.",
1149
+ noSelectedDetected: "Aucun des agents s\xE9lectionn\xE9s n'a \xE9t\xE9 d\xE9tect\xE9 sur cette machine.",
1150
+ installStopped: "Installation arr\xEAt\xE9e.",
1151
+ uninstallStopped: "D\xE9sinstallation arr\xEAt\xE9e.",
1152
+ connectedTo: (label) => `Connect\xE9 \xE0 ${label}.`,
1153
+ toolFlags: (write, telemetry) => `Outils d'\xE9criture ${write ? "activ\xE9s" : "d\xE9sactiv\xE9s"} ; t\xE9l\xE9m\xE9trie ${telemetry ? "activ\xE9e" : "d\xE9sactiv\xE9e"}.`,
1154
+ refreshingDetection: "Actualisation de la d\xE9tection des agents install\xE9s...",
1155
+ installing: (label) => `Installation de ${label}...`,
1156
+ preparingManual: (label) => `Pr\xE9paration de la configuration manuelle de ${label}...`,
1157
+ removing: (label) => `Suppression de ${label}...`,
1158
+ installedSummary: (ok, total) => `${ok}/${total} agent(s) install\xE9(s), mis \xE0 jour ou pr\xE9par\xE9(s).`,
1159
+ manualReady: "Instructions de configuration manuelle de ChatGPT pr\xEAtes.",
1160
+ removedSummary: (ok, total) => `${ok}/${total} agent(s) supprim\xE9(s).`,
1161
+ restartClients: "Red\xE9marrez votre/vos client(s) MCP pour charger le nouveau serveur.",
1162
+ followManual: "Suivez les instructions de configuration manuelle ci-dessus.",
1163
+ completeRemoval: "Red\xE9marrez votre/vos client(s) MCP pour terminer la suppression.",
1164
+ loggedInBackend: (region) => `Connect\xE9 au backend ${region.toUpperCase()}.`,
1165
+ settingsLine: (write, telemetry) => `Param\xE8tres : outils d'\xE9criture ${write ? "activ\xE9s" : "d\xE9sactiv\xE9s"}, t\xE9l\xE9m\xE9trie ${telemetry ? "activ\xE9e" : "d\xE9sactiv\xE9e"}.`,
1166
+ installSummaryHeader: "R\xE9capitulatif de l'installation :",
1167
+ noAgentsRemoved: "Aucun agent n'a \xE9t\xE9 supprim\xE9."
1168
+ }
1169
+ }
1170
+ };
1171
+ async function detectLocale() {
1172
+ try {
1173
+ return await inferRegionViaStargate({ staging: false }) === "fr" ? "fr" : "en";
1174
+ } catch {
1175
+ return "en";
1176
+ }
1177
+ }
1178
+ function parseLocale(raw) {
1179
+ return raw === "fr" ? "fr" : "en";
1180
+ }
1019
1181
  var PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
1020
1182
  var sessions = /* @__PURE__ */ new Map();
1021
1183
  var OAUTH_BASE_URLS = {
@@ -1164,23 +1326,25 @@ async function install(body) {
1164
1326
  cleanupSessions();
1165
1327
  const session = body.sessionId ? sessions.get(body.sessionId) : void 0;
1166
1328
  const clientIds = body.clientIds ?? [];
1167
- if (!session) return { ok: false, output: "Login expired. Go back and sign in again." };
1168
- if (!clientIds.length) return { ok: false, output: "Select at least one agent." };
1329
+ if (!session) return { ok: false, output: MESSAGES.en.server.loginExpired };
1330
+ if (!clientIds.length) return { ok: false, output: MESSAGES.en.server.selectAtLeastOne };
1331
+ const locale = session.region === "fr" ? "fr" : "en";
1332
+ const s = MESSAGES[locale].server;
1169
1333
  const detected = await detectClients();
1170
1334
  const selected = detected.filter((client) => clientIds.includes(client.id));
1171
- if (!selected.length) return { ok: false, output: "No selected agents were detected on this machine." };
1335
+ if (!selected.length) return { ok: false, output: s.noSelectedDetected };
1172
1336
  const includeWrite = body.includeWrite !== false;
1173
1337
  const telemetryEnabled = body.telemetryEnabled !== false;
1174
1338
  const results = [];
1175
1339
  for (const client of selected) results.push(await installInto(client, session, includeWrite, telemetryEnabled));
1176
1340
  const output = [
1177
- `Logged in to ${session.region.toUpperCase()} backend.`,
1178
- `Settings: write tools ${includeWrite ? "on" : "off"}, telemetry ${telemetryEnabled ? "on" : "off"}.`,
1341
+ s.loggedInBackend(session.region),
1342
+ s.settingsLine(includeWrite, telemetryEnabled),
1179
1343
  "",
1180
- "Install summary:",
1344
+ s.installSummaryHeader,
1181
1345
  ...results.map((result) => `${result.ok ? "OK" : "ERROR"} ${result.label}: ${result.message}`),
1182
1346
  "",
1183
- "Restart your MCP client(s) to pick up the new server."
1347
+ s.restartClients
1184
1348
  ].join("\n");
1185
1349
  return { ok: results.some((result) => result.ok), output: sanitizeOutput(output), results };
1186
1350
  }
@@ -1195,6 +1359,8 @@ async function streamInstall(url, res, onDone) {
1195
1359
  const clientIds = (url.searchParams.get("clients") ?? "").split(",").filter(Boolean);
1196
1360
  const includeWrite = url.searchParams.get("write") !== "0";
1197
1361
  const telemetryEnabled = url.searchParams.get("telemetry") !== "0";
1362
+ const locale = parseLocale(url.searchParams.get("locale"));
1363
+ const s = MESSAGES[locale].server;
1198
1364
  const emit = (level, message) => sendSse(res, { level, message: sanitizeOutput(message) });
1199
1365
  const abort = (msg) => {
1200
1366
  emit("done", msg);
@@ -1206,29 +1372,29 @@ async function streamInstall(url, res, onDone) {
1206
1372
  onDone?.();
1207
1373
  };
1208
1374
  if (!session) {
1209
- emit("error", "Login expired. Go back and sign in again.");
1210
- abort("Install stopped.");
1375
+ emit("error", s.loginExpired);
1376
+ abort(s.installStopped);
1211
1377
  return;
1212
1378
  }
1213
1379
  if (!clientIds.length) {
1214
- emit("error", "Select at least one agent.");
1215
- abort("Install stopped.");
1380
+ emit("error", s.selectAtLeastOne);
1381
+ abort(s.installStopped);
1216
1382
  return;
1217
1383
  }
1218
- emit("info", `Connected to ${session.accountLabel}.`);
1219
- emit("info", `Write tools ${includeWrite ? "enabled" : "disabled"}; telemetry ${telemetryEnabled ? "enabled" : "disabled"}.`);
1220
- emit("info", "Refreshing installed-agent detection...");
1384
+ emit("info", s.connectedTo(session.accountLabel));
1385
+ emit("info", s.toolFlags(includeWrite, telemetryEnabled));
1386
+ emit("info", s.refreshingDetection);
1221
1387
  const detected = await detectClients();
1222
1388
  const selected = detected.filter((client) => clientIds.includes(client.id));
1223
1389
  const selectedHasOnlyManualSetup = selected.length > 0 && selected.every(isManualSetupClient);
1224
1390
  if (!selected.length) {
1225
- emit("error", "No selected agents were detected on this machine.");
1226
- abort("Install stopped.");
1391
+ emit("error", s.noSelectedDetected);
1392
+ abort(s.installStopped);
1227
1393
  return;
1228
1394
  }
1229
1395
  let okCount = 0;
1230
1396
  for (const client of selected) {
1231
- emit("active", isManualSetupClient(client) ? `Preparing ${client.label} manual setup...` : `Installing ${client.label}...`);
1397
+ emit("active", isManualSetupClient(client) ? s.preparingManual(client.label) : s.installing(client.label));
1232
1398
  const result = await installInto(client, session, includeWrite, telemetryEnabled);
1233
1399
  if (result.ok) {
1234
1400
  okCount += 1;
@@ -1237,8 +1403,8 @@ async function streamInstall(url, res, onDone) {
1237
1403
  emit("error", `${result.label}: ${result.message}`);
1238
1404
  }
1239
1405
  }
1240
- const summary = selectedHasOnlyManualSetup ? "Manual ChatGPT setup instructions ready." : `${okCount}/${selected.length} agent(s) installed, updated, or prepared.`;
1241
- const closing = selectedHasOnlyManualSetup ? "Follow the manual setup instructions shown above." : "Restart your MCP client(s) to pick up the new server.";
1406
+ const summary = selectedHasOnlyManualSetup ? s.manualReady : s.installedSummary(okCount, selected.length);
1407
+ const closing = selectedHasOnlyManualSetup ? s.followManual : s.restartClients;
1242
1408
  emit(okCount > 0 ? "success" : "error", summary);
1243
1409
  if (okCount > 0) {
1244
1410
  finish(closing);
@@ -1253,6 +1419,8 @@ async function streamUninstall(url, res, onDone) {
1253
1419
  connection: "keep-alive"
1254
1420
  });
1255
1421
  const clientIds = (url.searchParams.get("clients") ?? "").split(",").filter(Boolean);
1422
+ const locale = parseLocale(url.searchParams.get("locale"));
1423
+ const s = MESSAGES[locale].server;
1256
1424
  const emit = (level, message) => sendSse(res, { level, message });
1257
1425
  const abort = (msg) => {
1258
1426
  emit("done", msg);
@@ -1264,20 +1432,20 @@ async function streamUninstall(url, res, onDone) {
1264
1432
  onDone?.();
1265
1433
  };
1266
1434
  if (!clientIds.length) {
1267
- emit("error", "Select at least one agent.");
1268
- abort("Uninstall stopped.");
1435
+ emit("error", s.selectAtLeastOne);
1436
+ abort(s.uninstallStopped);
1269
1437
  return;
1270
1438
  }
1271
1439
  const detected = await detectClients();
1272
1440
  const selected = detected.filter((c) => clientIds.includes(c.id));
1273
1441
  if (!selected.length) {
1274
- emit("error", "No selected agents were detected on this machine.");
1275
- abort("Uninstall stopped.");
1442
+ emit("error", s.noSelectedDetected);
1443
+ abort(s.uninstallStopped);
1276
1444
  return;
1277
1445
  }
1278
1446
  let okCount = 0;
1279
1447
  for (const client of selected) {
1280
- emit("active", `Removing from ${client.label}...`);
1448
+ emit("active", s.removing(client.label));
1281
1449
  let res2;
1282
1450
  if (client.id === "claude-code") {
1283
1451
  res2 = await uninstallFromClaudeCode();
@@ -1295,16 +1463,17 @@ async function streamUninstall(url, res, onDone) {
1295
1463
  emit("error", `${client.label}: ${res2.message}`);
1296
1464
  }
1297
1465
  }
1298
- emit(okCount > 0 ? "success" : "error", `${okCount}/${selected.length} agent(s) removed.`);
1299
- finish("Restart your MCP client(s) to complete the removal.");
1466
+ emit(okCount > 0 ? "success" : "error", s.removedSummary(okCount, selected.length));
1467
+ finish(s.completeRemoval);
1300
1468
  }
1301
- function pageUninstallHtml() {
1469
+ function pageUninstallHtml(locale = "en") {
1470
+ const ui = MESSAGES[locale].uninstaller;
1302
1471
  return `<!doctype html>
1303
- <html lang="en">
1472
+ <html lang="${locale}">
1304
1473
  <head>
1305
1474
  <meta charset="utf-8" />
1306
1475
  <meta name="viewport" content="width=device-width, initial-scale=1" />
1307
- <title>Leadbay MCP uninstaller</title>
1476
+ <title>${ui.docTitle}</title>
1308
1477
  <style>
1309
1478
  :root { color-scheme: light; --bg:#fff; --card:#fff; --strong:#1d2228; --muted:#9aa0ab; --line:#e7e9ee; --accent:#0d0f0e; --danger:#d14343; --ok:#16a34a; --warn:#b06a00; }
1310
1479
  * { box-sizing:border-box; }
@@ -1356,8 +1525,8 @@ function pageUninstallHtml() {
1356
1525
  <main>
1357
1526
  <div class="steps"><div class="dot active" id="dot-1"></div><div class="dot" id="dot-2"></div></div>
1358
1527
  <div class="card">
1359
- <h1 id="title">Remove Leadbay MCP</h1>
1360
- <p class="sub" id="sub">Select the agents to remove Leadbay MCP from.</p>
1528
+ <h1 id="title">${ui.steps[1].title}</h1>
1529
+ <p class="sub" id="sub">${ui.steps[1].sub}</p>
1361
1530
 
1362
1531
  <section id="step-1">
1363
1532
  <div class="spinner" id="spinner"></div>
@@ -1375,19 +1544,18 @@ function pageUninstallHtml() {
1375
1544
  </section>
1376
1545
 
1377
1546
  <div class="actions">
1378
- <button id="back" class="ghost hidden">Back</button>
1379
- <button id="refresh">Refresh</button>
1380
- <button class="danger" id="next">Remove selected</button>
1547
+ <button id="back" class="ghost hidden">${ui.btnBack}</button>
1548
+ <button id="refresh">${ui.btnRefresh}</button>
1549
+ <button class="danger" id="next">${ui.btnRemove}</button>
1381
1550
  </div>
1382
1551
  </div>
1383
1552
  <p class="version">v${VERSION}</p>
1384
1553
  </main>
1385
1554
  <script>
1555
+ const LOCALE = "${locale}";
1556
+ const T = ${JSON.stringify(ui)};
1386
1557
  const $ = (id) => document.getElementById(id);
1387
- const STEPS = {
1388
- 1: { title: "Remove Leadbay MCP", sub: "Select the agents to remove Leadbay MCP from." },
1389
- 2: { title: "Removing", sub: "Keep this window open until it's done." },
1390
- };
1558
+ const STEPS = T.steps;
1391
1559
  const CHECK = "M20 33 l8 8 l16 -18";
1392
1560
  const CROSS = "M22 22 l20 20 M42 22 l-20 20";
1393
1561
  let step = 1;
@@ -1399,18 +1567,18 @@ function pageUninstallHtml() {
1399
1567
  function showResult(ok, msg) {
1400
1568
  $("sub").classList.add("hidden");
1401
1569
  $("result-msg").textContent = msg;
1402
- $("result-note").textContent = ok ? "You can close this window." : "";
1570
+ $("result-note").textContent = ok ? T.closeWindow : "";
1403
1571
  $("ring-mark").setAttribute("d", ok ? CHECK : CROSS);
1404
1572
  const ring = $("ring"); ring.classList.remove("ok", "err"); void ring.getBoundingClientRect();
1405
1573
  ring.classList.add(ok ? "ok" : "err");
1406
1574
  $("result").classList.toggle("err", !ok);
1407
1575
  $("result").classList.remove("hidden");
1408
- $("title").textContent = ok ? "All set" : "Something went wrong";
1576
+ $("title").textContent = ok ? T.allSet : T.somethingWrong;
1409
1577
  ["next", "back", "refresh"].forEach((id) => $(id).classList.add("hidden"));
1410
1578
  }
1411
- function renderAgents() { $("spinner").classList.add("hidden"); const root = $("agents"); if (!clients.length) { root.innerHTML = '<div class="sub">No Leadbay MCP installation detected on this machine.</div>'; return; } root.innerHTML = clients.map((c) => '<label class="agent"><input type="checkbox" data-client="' + esc(c.id) + '" checked /><span><strong>' + esc(c.label) + '</strong><span class="detail">' + esc(c.detail) + '</span></span></label>').join(""); }
1412
- async function refresh() { $("spinner").classList.remove("hidden"); $("agents").innerHTML = ""; const res = await fetch("/api/status"); const data = await res.json(); clients = (data.clients || []).filter((c) => c.configured); renderAgents(); if (!clients.length) say("No Leadbay MCP installation detected on this machine."); }
1413
- async function doUninstall() { const selected = [...document.querySelectorAll("[data-client]:checked")].map((el) => el.dataset.client); if (!selected.length) return say("Select at least one agent.", true); setStep(2); let okCount = 0, lastError = ""; const params = new URLSearchParams({ clients: selected.join(",") }); const events = new EventSource("/api/uninstall-stream?" + params.toString()); events.onmessage = (event) => { const data = JSON.parse(event.data); if (data.level === "error") lastError = data.message; if (data.level === "success") okCount += 1; if (data.level === "done") { events.close(); const ok = okCount > 0 && !lastError; showResult(ok, ok ? "MCP successfully removed" : (lastError || "No agents were removed.")); } else { say(data.message, data.level === "error"); } }; events.onerror = () => { events.close(); showResult(false, "Uninstall stream disconnected."); }; }
1579
+ function renderAgents() { $("spinner").classList.add("hidden"); const root = $("agents"); if (!clients.length) { root.innerHTML = '<div class="sub">' + esc(T.noInstallDetected) + '</div>'; return; } root.innerHTML = clients.map((c) => '<label class="agent"><input type="checkbox" data-client="' + esc(c.id) + '" checked /><span><strong>' + esc(c.label) + '</strong><span class="detail">' + esc(c.detail) + '</span></span></label>').join(""); }
1580
+ async function refresh() { $("spinner").classList.remove("hidden"); $("agents").innerHTML = ""; const res = await fetch("/api/status"); const data = await res.json(); clients = (data.clients || []).filter((c) => c.configured); renderAgents(); if (!clients.length) say(T.noInstallDetected); }
1581
+ async function doUninstall() { const selected = [...document.querySelectorAll("[data-client]:checked")].map((el) => el.dataset.client); if (!selected.length) return say(T.selectAtLeastOne, true); setStep(2); let okCount = 0, lastError = ""; const params = new URLSearchParams({ clients: selected.join(","), locale: LOCALE }); const events = new EventSource("/api/uninstall-stream?" + params.toString()); events.onmessage = (event) => { const data = JSON.parse(event.data); if (data.level === "error") lastError = data.message; if (data.level === "success") okCount += 1; if (data.level === "done") { events.close(); const ok = okCount > 0 && !lastError; showResult(ok, ok ? T.successRemoved : (lastError || T.noneRemoved)); } else { say(data.message, data.level === "error"); } }; events.onerror = () => { events.close(); showResult(false, T.streamDisconnected); }; }
1414
1582
  $("back").addEventListener("click", () => setStep(1));
1415
1583
  $("refresh").addEventListener("click", refresh);
1416
1584
  $("next").addEventListener("click", doUninstall);
@@ -1419,13 +1587,14 @@ function pageUninstallHtml() {
1419
1587
  </body>
1420
1588
  </html>`;
1421
1589
  }
1422
- function pageHtml() {
1590
+ function pageHtml(locale = "en") {
1591
+ const ui = MESSAGES[locale].installer;
1423
1592
  return `<!doctype html>
1424
- <html lang="en">
1593
+ <html lang="${locale}">
1425
1594
  <head>
1426
1595
  <meta charset="utf-8" />
1427
1596
  <meta name="viewport" content="width=device-width, initial-scale=1" />
1428
- <title>Leadbay MCP installer</title>
1597
+ <title>${ui.docTitle}</title>
1429
1598
  <style>
1430
1599
  :root { color-scheme: light; --bg:#fff; --card:#fff; --strong:#1d2228; --muted:#9aa0ab; --line:#e7e9ee; --accent:#0d0f0e; --cancel-line:#f0c8b8; --danger:#d14343; --ok:#16a34a; --warn:#b06a00; }
1431
1600
  * { box-sizing:border-box; }
@@ -1481,8 +1650,8 @@ function pageHtml() {
1481
1650
  <main>
1482
1651
  <div class="steps"><div class="dot active" id="dot-1"></div><div class="dot" id="dot-2"></div><div class="dot" id="dot-3"></div></div>
1483
1652
  <div class="card">
1484
- <h1 id="title">Connect Leadbay</h1>
1485
- <p class="sub" id="sub">Sign in to install Leadbay across your AI agents.</p>
1653
+ <h1 id="title">${ui.steps[1].title}</h1>
1654
+ <p class="sub" id="sub">${ui.steps[1].sub}</p>
1486
1655
 
1487
1656
  <section id="step-2" class="hidden">
1488
1657
  <div class="spinner" id="spinner"></div>
@@ -1500,20 +1669,18 @@ function pageHtml() {
1500
1669
  </section>
1501
1670
 
1502
1671
  <div class="actions">
1503
- <button id="back" class="cancel hidden">Back</button>
1504
- <button id="refresh" class="ghost hidden">Refresh</button>
1505
- <button class="primary" id="next">Sign in with Leadbay</button>
1672
+ <button id="back" class="cancel hidden">${ui.btnBack}</button>
1673
+ <button id="refresh" class="ghost hidden">${ui.btnRefresh}</button>
1674
+ <button class="primary" id="next">${ui.btnSignIn}</button>
1506
1675
  </div>
1507
1676
  </div>
1508
1677
  <p class="version">v${VERSION}</p>
1509
1678
  </main>
1510
1679
  <script>
1680
+ const LOCALE = "${locale}";
1681
+ const T = ${JSON.stringify(ui)};
1511
1682
  const $ = (id) => document.getElementById(id);
1512
- const STEPS = {
1513
- 1: { title: "Connect Leadbay", sub: "Sign in to install Leadbay across your AI agents." },
1514
- 2: { title: "Choose your agents", sub: "Pick where to install Leadbay." },
1515
- 3: { title: "Installing", sub: "Keep this window open until it's done." },
1516
- };
1683
+ const STEPS = T.steps;
1517
1684
  const CHECK = "M20 33 l8 8 l16 -18";
1518
1685
  const CROSS = "M22 22 l20 20 M42 22 l-20 20";
1519
1686
  let step = 1;
@@ -1531,30 +1698,30 @@ function pageHtml() {
1531
1698
  $("back").classList.toggle("hidden", step !== 2);
1532
1699
  $("refresh").classList.toggle("hidden", step !== 2);
1533
1700
  $("next").classList.toggle("hidden", step === 3);
1534
- $("next").textContent = step === 2 ? "Install" : "Sign in with Leadbay";
1701
+ $("next").textContent = step === 2 ? T.btnInstall : T.btnSignIn;
1535
1702
  }
1536
1703
  // Final completion state: animated green check / red cross + message.
1537
1704
  function showResult(ok, msg) {
1538
1705
  $("sub").classList.add("hidden");
1539
1706
  $("result-msg").textContent = msg;
1540
- $("result-note").textContent = ok ? "You can close this window." : "";
1707
+ $("result-note").textContent = ok ? T.closeWindow : "";
1541
1708
  $("ring-mark").setAttribute("d", ok ? CHECK : CROSS);
1542
1709
  const ring = $("ring"); ring.classList.remove("ok", "err"); void ring.getBoundingClientRect();
1543
1710
  ring.classList.add(ok ? "ok" : "err");
1544
1711
  $("result").classList.toggle("err", !ok);
1545
1712
  $("result").classList.remove("hidden");
1546
- $("title").textContent = ok ? "All set" : "Something went wrong";
1713
+ $("title").textContent = ok ? T.allSet : T.somethingWrong;
1547
1714
  ["next", "back", "refresh"].forEach((id) => $(id).classList.add("hidden"));
1548
1715
  }
1549
- function renderAgents() { $("spinner").classList.add("hidden"); const root = $("agents"); if (!clients.length) { root.innerHTML = '<div class="sub">No supported MCP client detected on this machine.</div>'; return; } root.innerHTML = clients.map((client) => { const manual = client.id === "chatgpt-desktop"; const badgeText = manual ? "manual" : client.configured ? "update" : "install"; const badgeClass = manual ? "badge-update" : client.configured ? "badge-update" : "badge-install"; return '<label class="agent"><input type="checkbox" data-client="' + esc(client.id) + '" checked /><span><strong>' + esc(client.label) + ' <span class="badge-pill ' + badgeClass + '">' + badgeText + '</span></strong><span class="detail">' + esc(client.detail) + '</span></span></label>'; }).join(""); }
1550
- async function refresh() { $("spinner").classList.remove("hidden"); $("agents").innerHTML = ""; const res = await fetch("/api/status"); const data = await res.json(); clients = data.clients || []; renderAgents(); if (!clients.length) say("No supported agents detected."); }
1551
- async function doLogin() { $("next").disabled = true; say("Opening Leadbay sign-in in your browser..."); try { const res = await fetch("/api/oauth-login", { method:"POST" }); const data = await res.json(); if (!data.ok) return say(data.error || "OAuth login failed.", true); sessionId = data.sessionId; setStep(2); await refresh(); } finally { $("next").disabled = false; } }
1716
+ function renderAgents() { $("spinner").classList.add("hidden"); const root = $("agents"); if (!clients.length) { root.innerHTML = '<div class="sub">' + esc(T.noClientsDetected) + '</div>'; return; } root.innerHTML = clients.map((client) => { const manual = client.id === "chatgpt-desktop"; const badgeText = manual ? T.badgeManual : client.configured ? T.badgeUpdate : T.badgeInstall; const badgeClass = manual ? "badge-update" : client.configured ? "badge-update" : "badge-install"; return '<label class="agent"><input type="checkbox" data-client="' + esc(client.id) + '" checked /><span><strong>' + esc(client.label) + ' <span class="badge-pill ' + badgeClass + '">' + esc(badgeText) + '</span></strong><span class="detail">' + esc(client.detail) + '</span></span></label>'; }).join(""); }
1717
+ async function refresh() { $("spinner").classList.remove("hidden"); $("agents").innerHTML = ""; const res = await fetch("/api/status"); const data = await res.json(); clients = data.clients || []; renderAgents(); if (!clients.length) say(T.noAgentsDetected); }
1718
+ async function doLogin() { $("next").disabled = true; say(T.openingSignIn); try { const res = await fetch("/api/oauth-login", { method:"POST" }); const data = await res.json(); if (!data.ok) return say(data.error || T.oauthFailed, true); sessionId = data.sessionId; setStep(2); await refresh(); } finally { $("next").disabled = false; } }
1552
1719
  async function install() {
1553
1720
  const selected = [...document.querySelectorAll("[data-client]:checked")].map((el) => el.dataset.client);
1554
- if (!selected.length) return say("Select at least one agent.", true);
1721
+ if (!selected.length) return say(T.selectAtLeastOne, true);
1555
1722
  setStep(3);
1556
1723
  let okCount = 0, lastError = "";
1557
- const params = new URLSearchParams({ sessionId, clients: selected.join(","), write: "1", telemetry: "1" });
1724
+ const params = new URLSearchParams({ sessionId, clients: selected.join(","), write: "1", telemetry: "1", locale: LOCALE });
1558
1725
  const events = new EventSource("/api/install-stream?" + params.toString());
1559
1726
  events.onmessage = (event) => {
1560
1727
  const data = JSON.parse(event.data);
@@ -1563,12 +1730,12 @@ function pageHtml() {
1563
1730
  if (data.level === "done") {
1564
1731
  events.close();
1565
1732
  const ok = okCount > 0 && !lastError;
1566
- showResult(ok, ok ? "MCP successfully installed" : (lastError || "No agents were installed."));
1733
+ showResult(ok, ok ? T.successInstalled : (lastError || T.noneInstalled));
1567
1734
  } else {
1568
1735
  say(data.message, data.level === "error");
1569
1736
  }
1570
1737
  };
1571
- events.onerror = () => { events.close(); showResult(false, "Install stream disconnected."); };
1738
+ events.onerror = () => { events.close(); showResult(false, T.streamDisconnected); };
1572
1739
  }
1573
1740
  $("back").addEventListener("click", () => setStep(1));
1574
1741
  $("refresh").addEventListener("click", refresh);
@@ -1623,7 +1790,7 @@ function makeGuiServer(options, pageContent, extraRoutes, logLabel) {
1623
1790
  }
1624
1791
  try {
1625
1792
  if (req.method === "GET" && req.url === "/") {
1626
- const raw = pageContent();
1793
+ const raw = await pageContent();
1627
1794
  res.writeHead(200, { "content-type": "text/html; charset=utf-8", "content-length": Buffer.byteLength(raw) });
1628
1795
  res.end(raw);
1629
1796
  return;
@@ -1650,7 +1817,7 @@ function makeGuiServer(options, pageContent, extraRoutes, logLabel) {
1650
1817
  });
1651
1818
  }
1652
1819
  function startInstallerGui(options = {}) {
1653
- return makeGuiServer(options, pageHtml, async (req, res, onDone) => {
1820
+ return makeGuiServer(options, async () => pageHtml(await detectLocale()), async (req, res, onDone) => {
1654
1821
  if (req.method === "GET" && req.url === "/api/status") {
1655
1822
  sendJson(res, 200, { os: formatInstallOsLabel(), hostedMcpUrl: HOSTED_MCP_URL, clients: await clientsWithConfiguredStatus() });
1656
1823
  return true;
@@ -1671,7 +1838,7 @@ function startInstallerGui(options = {}) {
1671
1838
  }, "installer");
1672
1839
  }
1673
1840
  function startUninstallerGui(options = {}) {
1674
- return makeGuiServer(options, pageUninstallHtml, async (req, res, onDone) => {
1841
+ return makeGuiServer(options, async () => pageUninstallHtml(await detectLocale()), async (req, res, onDone) => {
1675
1842
  if (req.method === "GET" && req.url === "/api/status") {
1676
1843
  sendJson(res, 200, { os: formatInstallOsLabel(), clients: await clientsWithConfiguredStatus() });
1677
1844
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.23.4",
3
+ "version": "0.23.6",
4
4
  "mcpName": "io.github.leadbay/leadbay-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
6
6
  "type": "module",