@maintainer-pro/ai-bridge 0.1.21 → 0.1.23

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maintainer-pro/ai-bridge",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Local bridge daemon that pairs a machine to Maintainer Pro and configures multiple client sandboxes.",
5
5
  "keywords": [
6
6
  "maintainer-pro",
@@ -28,7 +28,7 @@
28
28
  "node": ">=22"
29
29
  },
30
30
  "dependencies": {
31
- "@maintainer-pro/ai-cli": "^0.1.12",
31
+ "@maintainer-pro/ai-cli": "^0.1.13",
32
32
  "@maintainer-pro/ai-server": "^0.1.7"
33
33
  }
34
34
  }
package/src/daemon.mjs CHANGED
@@ -16,6 +16,7 @@ import os from "node:os";
16
16
  import path from "node:path";
17
17
  import readline from "node:readline";
18
18
  import { fileURLToPath, pathToFileURL } from "node:url";
19
+ import { createRequire } from "node:module";
19
20
  import { createLogger, ensureProjectDataDir } from "@maintainer-pro/ai-cli";
20
21
  import { findIife, startAiServer } from "@maintainer-pro/ai-server";
21
22
  import {
@@ -39,7 +40,9 @@ import {
39
40
  } from "./discarded-tunnels.mjs";
40
41
 
41
42
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
43
+ const requireFromHere = createRequire(import.meta.url);
42
44
  const PACKAGE_VERSION = readPackageVersion();
45
+ const PACKAGE_VERSIONS = readPackageVersions();
43
46
  const HEARTBEAT_MS = 15_000;
44
47
  const WS_PING_MS = 10_000;
45
48
  const WS_RECONNECT_MIN_MS = 1_000;
@@ -157,6 +160,53 @@ function readPackageVersion() {
157
160
  }
158
161
  }
159
162
 
163
+ function readDepPackageVersion(name) {
164
+ const files = [];
165
+ try {
166
+ files.push(requireFromHere.resolve(`${name}/package.json`));
167
+ } catch {
168
+ /* try nested next */
169
+ }
170
+ try {
171
+ const serverDir = path.dirname(
172
+ requireFromHere.resolve("@maintainer-pro/ai-server/package.json")
173
+ );
174
+ files.push(path.join(serverDir, "node_modules", name, "package.json"));
175
+ files.push(path.join(serverDir, "..", name.replace("@maintainer-pro/", ""), "package.json"));
176
+ } catch {
177
+ /* ignore */
178
+ }
179
+ for (const file of files) {
180
+ try {
181
+ const pkg = JSON.parse(fs.readFileSync(file, "utf8"));
182
+ if (typeof pkg.version === "string" && pkg.version) return pkg.version;
183
+ } catch {
184
+ /* try next */
185
+ }
186
+ }
187
+ return null;
188
+ }
189
+
190
+ function readPackageVersions() {
191
+ return {
192
+ bridge: PACKAGE_VERSION,
193
+ cli: readDepPackageVersion("@maintainer-pro/ai-cli"),
194
+ server: readDepPackageVersion("@maintainer-pro/ai-server"),
195
+ ui: readDepPackageVersion("@maintainer-pro/ai-ui"),
196
+ };
197
+ }
198
+
199
+ function formatPackageVersions(versions = PACKAGE_VERSIONS) {
200
+ return [
201
+ versions.bridge && `bridge ${versions.bridge}`,
202
+ versions.cli && `cli ${versions.cli}`,
203
+ versions.server && `server ${versions.server}`,
204
+ versions.ui && `ui ${versions.ui}`,
205
+ ]
206
+ .filter(Boolean)
207
+ .join(" · ");
208
+ }
209
+
160
210
  async function warnIfBridgeOutdated() {
161
211
  const fromWorkspace = path
162
212
  .normalize(__dirname)
@@ -2325,6 +2375,7 @@ function bridgeEmbedConfigJs(ws) {
2325
2375
  logLevel: "debug",
2326
2376
  maintainerProUrl: bridgeCfg?.adminUrl || "",
2327
2377
  maintainerProApiKey: String(store.clientKey || "").trim(),
2378
+ versions: PACKAGE_VERSIONS,
2328
2379
  };
2329
2380
  return `window.__MAINTAINER_PRO__=${JSON.stringify(payload)};`;
2330
2381
  }
@@ -4834,6 +4885,7 @@ async function sendHeartbeat(cfg, folders, localStates) {
4834
4885
  hostname: os.hostname(),
4835
4886
  platform: `${os.platform()}-${os.arch()}`,
4836
4887
  bridgeVersion: PACKAGE_VERSION,
4888
+ packageVersions: PACKAGE_VERSIONS,
4837
4889
  folders,
4838
4890
  issues: await buildIssues(cfg, localStates),
4839
4891
  workspaces: localStates.map((st) => ({
@@ -4866,6 +4918,7 @@ function buildLightHeartbeatPayload(cfg, folders) {
4866
4918
  hostname: os.hostname(),
4867
4919
  platform: `${os.platform()}-${os.arch()}`,
4868
4920
  bridgeVersion: PACKAGE_VERSION,
4921
+ packageVersions: PACKAGE_VERSIONS,
4869
4922
  folders,
4870
4923
  };
4871
4924
  }
@@ -4887,6 +4940,7 @@ async function buildHeartbeatPayload(cfg, folders, localStates) {
4887
4940
  hostname: os.hostname(),
4888
4941
  platform: `${os.platform()}-${os.arch()}`,
4889
4942
  bridgeVersion: PACKAGE_VERSION,
4943
+ packageVersions: PACKAGE_VERSIONS,
4890
4944
  folders,
4891
4945
  issues: await buildIssues(cfg, localStates),
4892
4946
  workspaces: localStates.map((st) => ({
@@ -5021,6 +5075,7 @@ async function pairFlow(args) {
5021
5075
  hostname: os.hostname(),
5022
5076
  platform: `${os.platform()}-${os.arch()}`,
5023
5077
  bridgeVersion: PACKAGE_VERSION,
5078
+ packageVersions: PACKAGE_VERSIONS,
5024
5079
  name: os.hostname(),
5025
5080
  });
5026
5081
 
@@ -5048,9 +5103,9 @@ async function main() {
5048
5103
  process.exit(0);
5049
5104
  }
5050
5105
 
5051
- logger.debug(
5052
- { logLevel: logger.level, nodeEnv: process.env.NODE_ENV },
5053
- "bridge starting"
5106
+ logger.info(
5107
+ { versions: PACKAGE_VERSIONS },
5108
+ `bridge v${PACKAGE_VERSION} starting (${formatPackageVersions()})`
5054
5109
  );
5055
5110
  void warnIfBridgeOutdated();
5056
5111
 
@@ -274,6 +274,33 @@ function mappedUrlForPort(portUrls, port) {
274
274
  return dest ? String(dest).replace(/\/$/, "") : "";
275
275
  }
276
276
 
277
+ function backendShareBase(portUrls) {
278
+ if (!portUrls || typeof portUrls !== "object") return "";
279
+ for (const dest of Object.values(portUrls)) {
280
+ const url = String(dest || "").replace(/\/$/, "");
281
+ if (!url) continue;
282
+ try {
283
+ const slug = new URL(url).pathname.split("/").filter(Boolean)[2] || "";
284
+ if (slug === "backend" || slug.startsWith("backend-")) return url;
285
+ } catch {
286
+ if (/\/backend(?:-[^/]+)?$/i.test(url)) return url;
287
+ }
288
+ }
289
+ return "";
290
+ }
291
+
292
+ function shareBaseForPath(path, publicBase, portUrls) {
293
+ const pathname = String(path || "").split("?")[0] || "/";
294
+ if (
295
+ isShareAssetPath(pathname) ||
296
+ pathname.startsWith("/_next/") ||
297
+ pathname.startsWith("/@")
298
+ ) {
299
+ return publicBase;
300
+ }
301
+ return backendShareBase(portUrls) || publicBase;
302
+ }
303
+
277
304
  function isProbablyUtf8Text(buf) {
278
305
  if (!Buffer.isBuffer(buf) || !buf.length || buf.length > 8 * 1024 * 1024) {
279
306
  return false;
@@ -410,7 +437,7 @@ function rewriteHeaderUrls(headers, publicBase, port, portUrls) {
410
437
  if (lower === "set-cookie" || lower === "content-length" || lower === "content-encoding") {
411
438
  continue;
412
439
  }
413
- headers[key] = rewriteShareOriginPaths(headers[key], publicBase);
440
+ headers[key] = rewriteShareOriginPaths(headers[key], publicBase, portUrls);
414
441
  }
415
442
  }
416
443
  }
@@ -448,7 +475,7 @@ export function prefixShareOriginUrl(value, publicBase) {
448
475
  }
449
476
  }
450
477
 
451
- export function rewriteShareOriginPaths(text, publicBase) {
478
+ export function rewriteShareOriginPaths(text, publicBase, portUrls) {
452
479
  if (!text || !publicBase) return text;
453
480
  let origin = "";
454
481
  try {
@@ -461,15 +488,24 @@ export function rewriteShareOriginPaths(text, publicBase) {
461
488
  const originSlashEsc = origin
462
489
  .replace(/\//g, "\\/")
463
490
  .replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
491
+ const baseFor = function (matched) {
492
+ let path = "/";
493
+ try {
494
+ path = new URL(matched).pathname || "/";
495
+ } catch {
496
+ /* keep */
497
+ }
498
+ return shareBaseForPath(path, publicBase, portUrls);
499
+ };
464
500
  let out = String(text);
465
501
  out = out.replace(new RegExp(originEsc + "/(?!p/)[^\\s'\"<>\\\\]+", "gi"), function (matched) {
466
- return prefixShareOriginUrl(matched, publicBase);
502
+ return prefixShareOriginUrl(matched, baseFor(matched));
467
503
  });
468
504
  out = out.replace(
469
505
  new RegExp(originSlashEsc + "\\\\/(?!p/)[^\\s'\"<>]+", "gi"),
470
506
  function (matched) {
471
507
  const unescaped = matched.replace(/\\\//g, "/");
472
- return prefixShareOriginUrl(unescaped, publicBase).replace(/\//g, "\\/");
508
+ return prefixShareOriginUrl(unescaped, baseFor(unescaped)).replace(/\//g, "\\/");
473
509
  }
474
510
  );
475
511
  return out;
@@ -495,7 +531,18 @@ function rewriteLocation(value, publicBase, port, portUrls) {
495
531
  }
496
532
  const fromPorts = rewriteMappedLocalUrls(String(value), portUrls);
497
533
  if (fromPorts !== String(value)) return fromPorts;
498
- if (publicBase) return prefixShareOriginUrl(String(value), publicBase);
534
+ if (publicBase) {
535
+ let path = "/";
536
+ try {
537
+ path = new URL(String(value)).pathname || "/";
538
+ } catch {
539
+ /* keep */
540
+ }
541
+ return prefixShareOriginUrl(
542
+ String(value),
543
+ shareBaseForPath(path, publicBase, portUrls)
544
+ );
545
+ }
499
546
  } catch {
500
547
  /* keep */
501
548
  }
@@ -651,6 +698,19 @@ function isShareAssetPath(path) {
651
698
  );
652
699
  }
653
700
 
701
+ function rewriteShareAsLocalEnv(body) {
702
+ let out = String(body);
703
+ out = out.replace(
704
+ /(\/\.\*localhost\.\*\/\.test\()([^)]+)(\))/g,
705
+ "($1$2$3||location.pathname.indexOf(\"/p/\")===0)"
706
+ );
707
+ out = out.replace(
708
+ /((?:window\.)?location\.hostname)\s*===\s*(['"])localhost\2/g,
709
+ "($1===$2localhost$2||location.pathname.indexOf(\"/p/\")===0)"
710
+ );
711
+ return out;
712
+ }
713
+
654
714
  function prefixQuotedAssetPaths(body, pathPrefix) {
655
715
  if (!pathPrefix) return body;
656
716
  return String(body).replace(/(["'`])(\/(?!\/)[^"'`]*)/g, (full, q, path) => {
@@ -797,6 +857,7 @@ function prefixRootPaths(body, publicBase, contentType = "") {
797
857
  const css = mime === "text/css" || (!isCode && !html && /css/.test(mime));
798
858
  if (!html && !css) {
799
859
  let code = rewriteViteBaseLiterals(body, pathPrefix);
860
+ code = rewriteShareAsLocalEnv(code);
800
861
  code = prefixQuotedAssetPaths(code, pathPrefix);
801
862
  code = code.replace(
802
863
  /(?<![A-Za-z0-9])\/__nextjs_/g,
@@ -866,7 +927,7 @@ function isHtmlDocument(headers, body) {
866
927
 
867
928
  function isScriptRequestPath(path) {
868
929
  const p = String(path || "").split("?")[0]?.toLowerCase() || "";
869
- return /\.(?:m?js|cjs)$/.test(p);
930
+ return /\.(?:m?[jt]sx?|cjs)$/.test(p);
870
931
  }
871
932
 
872
933
  function isScriptOrJsonBody(headers, path) {
@@ -1017,6 +1078,21 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
1017
1078
  } catch (e) {}
1018
1079
  return v;
1019
1080
  }
1081
+ function backendBase() {
1082
+ var map = portMap || {};
1083
+ var keys = Object.keys(map);
1084
+ for (var i = 0; i < keys.length; i++) {
1085
+ var url = String(map[keys[i]] || "").replace(/\/$/, "");
1086
+ if (!url) continue;
1087
+ try {
1088
+ var slug = new URL(url).pathname.split("/").filter(Boolean)[2] || "";
1089
+ if (slug === "backend" || slug.indexOf("backend-") === 0) return url;
1090
+ } catch (e) {
1091
+ if (/\/backend(?:-[^/]+)?$/i.test(url)) return url;
1092
+ }
1093
+ }
1094
+ return "";
1095
+ }
1020
1096
  function addNav(v) {
1021
1097
  if (typeof v !== "string" || !v) return v;
1022
1098
  var mapped = mapLocal(v);
@@ -1038,10 +1114,27 @@ function shareProxyShim(p, portMap, rewriteMappedLocalUrls) {
1038
1114
  if (mapped !== v) return mapped;
1039
1115
  if (/^(https?:|wss?:)\/\//i.test(v)) {
1040
1116
  try {
1041
- var u = new URL(v);
1042
- if (!loopback(u.hostname)) return v;
1117
+ var abs = new URL(v);
1118
+ if (!loopback(abs.hostname)) return v;
1043
1119
  } catch (e) {}
1044
1120
  }
1121
+ if (v.charAt(0) === "/" && v.charAt(1) !== "/") {
1122
+ var backend = backendBase();
1123
+ var path = v.split("?")[0];
1124
+ if (path === "/api/v1" || path.indexOf("/api/v1/") === 0 || path.indexOf("/p/") === 0) {
1125
+ return v;
1126
+ }
1127
+ if (
1128
+ backend &&
1129
+ !isAsset(path) &&
1130
+ (/^\/sreo(?:\/|$)/i.test(path) || /^\/api(?:\/|$)/i.test(path))
1131
+ ) {
1132
+ try {
1133
+ var rel = new URL(v, location.href);
1134
+ return backend + rel.pathname + rel.search + rel.hash;
1135
+ } catch (e) {}
1136
+ }
1137
+ }
1045
1138
  return addNav(v);
1046
1139
  }
1047
1140
  function mapSel(sel) {
@@ -1244,6 +1337,21 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
1244
1337
  h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0"
1245
1338
  );
1246
1339
  }
1340
+ function backendBase() {
1341
+ var map = portMap || {};
1342
+ var keys = Object.keys(map);
1343
+ for (var i = 0; i < keys.length; i++) {
1344
+ var url = String(map[keys[i]] || "").replace(/\/$/, "");
1345
+ if (!url) continue;
1346
+ try {
1347
+ var slug = new URL(url).pathname.split("/").filter(Boolean)[2] || "";
1348
+ if (slug === "backend" || slug.indexOf("backend-") === 0) return url;
1349
+ } catch (e) {
1350
+ if (/\/backend(?:-[^/]+)?$/i.test(url)) return url;
1351
+ }
1352
+ }
1353
+ return "";
1354
+ }
1247
1355
  function prefixWith(url, prefix) {
1248
1356
  if (!prefix) return url;
1249
1357
  try {
@@ -1252,9 +1360,19 @@ function shareServiceWorkerMain(portMap, tokenRoot, rewriteMappedLocalUrls) {
1252
1360
  if (!loopback(abs.hostname)) return url;
1253
1361
  }
1254
1362
  var u = new URL(url, self.location.href);
1363
+ var path = u.pathname || "/";
1364
+ var backend = backendBase();
1365
+ if (path === "/api/v1" || path.indexOf("/api/v1/") === 0) return url;
1366
+ if (path.indexOf("/p/") === 0) return url;
1367
+ if (
1368
+ backend &&
1369
+ !/^(https?:|wss?:)\/\//i.test(url) &&
1370
+ (/^\/sreo(?:\/|$)/i.test(path) || /^\/api(?:\/|$)/i.test(path))
1371
+ ) {
1372
+ return backend + path + (u.search || "") + (u.hash || "");
1373
+ }
1255
1374
  if (u.origin !== self.location.origin) return url;
1256
1375
  if (u.pathname === "/" || u.pathname === "") return url;
1257
- if (u.pathname.indexOf("/p/") === 0) return url;
1258
1376
  if (u.pathname === prefix || u.pathname.indexOf(prefix + "/") === 0) return url;
1259
1377
  u.pathname = u.pathname === "/" ? prefix : prefix + u.pathname;
1260
1378
  return u.toString();
@@ -1420,7 +1538,7 @@ export function processShareHttpResponse(opts) {
1420
1538
  text = prefixRootPaths(text, publicBase, headers["content-type"] || "");
1421
1539
  mutated = true;
1422
1540
  }
1423
- if (Object.keys(portUrls).length && rewriteTextBody) {
1541
+ if (Object.keys(portUrls).length && rewriteTextBody && !isScriptOrJsonBody(headers, path)) {
1424
1542
  const mapped = rewriteMappedLocalUrls(text, portUrls);
1425
1543
  if (mapped !== text) {
1426
1544
  text = mapped;
@@ -1428,7 +1546,7 @@ export function processShareHttpResponse(opts) {
1428
1546
  }
1429
1547
  }
1430
1548
  if (publicBase && rewriteTextBody && !isScriptOrJsonBody(headers, path)) {
1431
- const prefixed = rewriteShareOriginPaths(text, publicBase);
1549
+ const prefixed = rewriteShareOriginPaths(text, publicBase, portUrls);
1432
1550
  if (prefixed !== text) {
1433
1551
  text = prefixed;
1434
1552
  mutated = true;