@mrrlin-dev/mcp 0.4.54 → 0.4.55

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.
Files changed (2) hide show
  1. package/dist/bin.cjs +336 -85
  2. package/package.json +3 -3
package/dist/bin.cjs CHANGED
@@ -20659,6 +20659,7 @@ var toml = __toESM(require_toml(), 1);
20659
20659
  // src/api-base-url.ts
20660
20660
  var DEFAULT_API_BASE_URL = "http://127.0.0.1:8787";
20661
20661
  var STAGING_API_BASE_URL = "https://api-staging.mrrlin.com";
20662
+ var PRODUCTION_API_BASE_URL = "https://api.mrrlin.com";
20662
20663
  function normalizeUrl(value) {
20663
20664
  return value.replace(/\/$/, "");
20664
20665
  }
@@ -20668,7 +20669,9 @@ var RETIRED_API_BASE_URLS = {
20668
20669
  "mrrlin-api-fnnzzz-fnnzzzs-projects.vercel.app": STAGING_API_BASE_URL,
20669
20670
  // Retired 2026-08-14 when staging decoupled onto the stable custom domain api-staging.mrrlin.com.
20670
20671
  // A persisted operator configuration may still pin the mrrlin3 Vercel alias; retarget it in one hop.
20671
- "mrrlin-api-mrrlin3.vercel.app": STAGING_API_BASE_URL
20672
+ "mrrlin-api-mrrlin3.vercel.app": STAGING_API_BASE_URL,
20673
+ // Retired 2026-09-07 when production moved from the transitional api-prod host to api.mrrlin.com.
20674
+ "api-prod.mrrlin.com": PRODUCTION_API_BASE_URL
20672
20675
  };
20673
20676
  function isSameApiHost(a, b) {
20674
20677
  const origin = (value) => {
@@ -20685,6 +20688,9 @@ function isSameApiHost(a, b) {
20685
20688
  function isStagingApiBaseUrl(input) {
20686
20689
  return isSameApiHost(input, STAGING_API_BASE_URL);
20687
20690
  }
20691
+ function isProductionApiBaseUrl(input) {
20692
+ return isSameApiHost(input, PRODUCTION_API_BASE_URL);
20693
+ }
20688
20694
  function retargetRetiredApiBaseUrl(input) {
20689
20695
  const normalized = normalizeUrl(input.trim());
20690
20696
  let hostname3;
@@ -20703,7 +20709,6 @@ function retiredApiHostnames() {
20703
20709
  function bridgeUrlImpliesStaging(input) {
20704
20710
  const raw = input?.trim();
20705
20711
  if (!raw) return false;
20706
- if (isLoopbackUrl(raw)) return true;
20707
20712
  let hostname3;
20708
20713
  try {
20709
20714
  hostname3 = new URL(raw).hostname.toLowerCase();
@@ -20728,8 +20733,14 @@ function readConfiguredApiBaseUrl(explicit) {
20728
20733
  if (process.env.MRRLIN_STAGING === "1") return STAGING_API_BASE_URL;
20729
20734
  return DEFAULT_API_BASE_URL;
20730
20735
  }
20736
+ function readDirectorBridgeApiBaseUrl(explicit) {
20737
+ const direct = explicit ?? process.env.MRRLIN_API_BASE_URL;
20738
+ if (direct?.trim()) return retargetRetiredApiBaseUrl(direct.trim()).baseUrl;
20739
+ if (process.env.MRRLIN_STAGING === "1") return STAGING_API_BASE_URL;
20740
+ return PRODUCTION_API_BASE_URL;
20741
+ }
20731
20742
  function isRemoteApiAllowed(apiBaseUrl) {
20732
- return isLoopbackUrl(apiBaseUrl) || isStagingApiBaseUrl(apiBaseUrl) || process.env.MRRLIN_API_ALLOW_REMOTE === "1" || process.env.MRRLIN_STAGING === "1";
20743
+ return isLoopbackUrl(apiBaseUrl) || isStagingApiBaseUrl(apiBaseUrl) || isProductionApiBaseUrl(apiBaseUrl) || process.env.MRRLIN_API_ALLOW_REMOTE === "1";
20733
20744
  }
20734
20745
  function readProbeTimeoutMs(env) {
20735
20746
  const raw = Number((env.MRRLIN_API_PROBE_TIMEOUT_MS ?? "").trim());
@@ -20770,7 +20781,7 @@ async function resolveApiBaseUrlDynamic(opts = {}) {
20770
20781
  return { baseUrl: STAGING_API_BASE_URL, source: "staging-autodetect" };
20771
20782
  }
20772
20783
  throw new Error(
20773
- `Local Mrrlin API not reachable at ${DEFAULT_API_BASE_URL}. Start it, or set MRRLIN_STAGING=1 to target staging (or MRRLIN_API_AUTODETECT=1 to auto-fall back to staging when local is down).`
20784
+ `Local Mrrlin API not reachable at ${DEFAULT_API_BASE_URL}. Start it, or set MRRLIN_API_BASE_URL=${STAGING_API_BASE_URL} to target staging (or MRRLIN_API_AUTODETECT=1 to auto-fall back to staging when local is down).`
20774
20785
  );
20775
20786
  }
20776
20787
 
@@ -20994,6 +21005,24 @@ function blockEnvHasRetiredApiUrl(actual) {
20994
21005
  }
20995
21006
  return retiredApiHostnames().includes(hostname3);
20996
21007
  }
21008
+ function isFirstPartyOrLoopbackApiUrl(value) {
21009
+ return isStagingApiBaseUrl(value) || isProductionApiBaseUrl(value) || isLoopbackUrl(value);
21010
+ }
21011
+ function normalizedApiUrlFromEnv(env) {
21012
+ const pinned = env.MRRLIN_API_BASE_URL;
21013
+ if (typeof pinned === "string" && pinned.trim()) return retargetRetiredApiBaseUrl(pinned.trim()).baseUrl;
21014
+ return env.MRRLIN_STAGING === "1" ? STAGING_API_BASE_URL : void 0;
21015
+ }
21016
+ function blockEnvNeedsApiRoutingMigration(actual) {
21017
+ if (typeof actual !== "object" || actual === null) return false;
21018
+ const env = actual.env;
21019
+ if (typeof env !== "object" || env === null) return false;
21020
+ const vars = env;
21021
+ if (vars.MRRLIN_STAGING === "1") return true;
21022
+ if (blockEnvHasRetiredApiUrl(actual)) return true;
21023
+ const apiUrl = normalizedApiUrlFromEnv(vars);
21024
+ return apiUrl !== void 0 && (isProductionApiBaseUrl(apiUrl) || vars.MRRLIN_API_ALLOW_REMOTE === "1" && isFirstPartyOrLoopbackApiUrl(apiUrl));
21025
+ }
20997
21026
  function renderEnvPreservedNotice(blockId, envKeys) {
20998
21027
  const keys = [...envKeys].sort().join(", ");
20999
21028
  return `[mrrlin-mcp configure bridge] kept your existing [mcp_servers.${blockId}] env keys (${keys}) so your API target is preserved. Re-run with --force to replace or remove it.`;
@@ -21001,16 +21030,30 @@ function renderEnvPreservedNotice(blockId, envKeys) {
21001
21030
  function reconcileMcpServerBlock(existing, desired) {
21002
21031
  if (existing === void 0) return { block: desired, preservedEnvKeys: [] };
21003
21032
  if (tablesEqual(existing, desired)) return { block: existing, preservedEnvKeys: [] };
21004
- if (blockEnvHasRetiredApiUrl(existing)) {
21033
+ if (blockEnvNeedsApiRoutingMigration(existing)) {
21005
21034
  const existingEnv = existing.env ?? {};
21035
+ const apiUrl = normalizedApiUrlFromEnv(existingEnv);
21006
21036
  const healedEnv = {};
21007
21037
  for (const [key, value] of Object.entries(existingEnv)) {
21008
- if (key === "MRRLIN_API_BASE_URL") continue;
21038
+ if (key === "MRRLIN_API_BASE_URL" || key === "MRRLIN_STAGING" || key === "MRRLIN_API_ALLOW_REMOTE") {
21039
+ continue;
21040
+ }
21009
21041
  if (typeof value === "string") healedEnv[key] = value;
21010
21042
  }
21011
- healedEnv.MRRLIN_STAGING = "1";
21012
- delete healedEnv.MRRLIN_API_ALLOW_REMOTE;
21013
- return { block: { ...desired, env: healedEnv }, preservedEnvKeys: [] };
21043
+ if (apiUrl && !isProductionApiBaseUrl(apiUrl)) {
21044
+ healedEnv.MRRLIN_API_BASE_URL = apiUrl;
21045
+ if (existingEnv.MRRLIN_API_ALLOW_REMOTE === "1" && !isFirstPartyOrLoopbackApiUrl(apiUrl)) {
21046
+ healedEnv.MRRLIN_API_ALLOW_REMOTE = "1";
21047
+ }
21048
+ }
21049
+ const finalEnv = { ...desired.env, ...healedEnv };
21050
+ const block = { ...desired };
21051
+ if (Object.keys(finalEnv).length > 0) {
21052
+ block.env = finalEnv;
21053
+ } else {
21054
+ delete block.env;
21055
+ }
21056
+ return { block, preservedEnvKeys: [] };
21014
21057
  }
21015
21058
  if (classifyExistingBlock(existing, desired) === "preserve-env-noop") {
21016
21059
  const env = existing.env ?? {};
@@ -21057,11 +21100,15 @@ function deriveServeEnvFromProcessEnv(env) {
21057
21100
  const rawApiUrl = env.MRRLIN_API_BASE_URL?.trim();
21058
21101
  const bridgeWantsStaging = bridgeUrlImpliesStaging(env.MRRLIN_BRIDGE_URL);
21059
21102
  const apiUrl = rawApiUrl ? retargetRetiredApiBaseUrl(rawApiUrl).baseUrl : void 0;
21060
- if (apiUrl && !isStagingApiBaseUrl(apiUrl)) {
21061
- out.MRRLIN_API_BASE_URL = apiUrl;
21062
- if (env.MRRLIN_API_ALLOW_REMOTE === "1") out.MRRLIN_API_ALLOW_REMOTE = "1";
21063
- } else if (apiUrl || env.MRRLIN_STAGING === "1" || bridgeWantsStaging) {
21064
- out.MRRLIN_STAGING = "1";
21103
+ if (apiUrl) {
21104
+ if (!isProductionApiBaseUrl(apiUrl)) {
21105
+ out.MRRLIN_API_BASE_URL = apiUrl;
21106
+ if (env.MRRLIN_API_ALLOW_REMOTE === "1" && !isStagingApiBaseUrl(apiUrl) && !isLoopbackUrl(apiUrl)) {
21107
+ out.MRRLIN_API_ALLOW_REMOTE = "1";
21108
+ }
21109
+ }
21110
+ } else if (env.MRRLIN_STAGING === "1" || bridgeWantsStaging) {
21111
+ out.MRRLIN_API_BASE_URL = STAGING_API_BASE_URL;
21065
21112
  }
21066
21113
  return Object.keys(out).length > 0 ? out : void 0;
21067
21114
  }
@@ -21204,7 +21251,7 @@ async function installCodex(options) {
21204
21251
  }
21205
21252
  const classification = classifyExistingBlock(existingTable, block.expected);
21206
21253
  if (classification === "equal") continue;
21207
- if (blockEnvHasRetiredApiUrl(existingTable)) {
21254
+ if (blockEnvNeedsApiRoutingMigration(existingTable)) {
21208
21255
  const { block: healedBlock } = reconcileMcpServerBlock(existingTable, block.expected);
21209
21256
  content = replaceBlock(content, block.blockRe, renderBlock(block.id, healedBlock));
21210
21257
  action = "replaced";
@@ -21377,7 +21424,7 @@ async function installClaude(options) {
21377
21424
  }
21378
21425
  const classification = classifyExistingBlock(existingEntry, entry.expected);
21379
21426
  if (classification === "equal") continue;
21380
- if (blockEnvHasRetiredApiUrl(existingEntry)) {
21427
+ if (blockEnvNeedsApiRoutingMigration(existingEntry)) {
21381
21428
  const { block: healedBlock } = reconcileMcpServerBlock(existingEntry, entry.expected);
21382
21429
  mcpServers[entry.id] = healedBlock;
21383
21430
  action = "replaced";
@@ -42441,7 +42488,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
42441
42488
  }
42442
42489
 
42443
42490
  // src/_generated/version.ts
42444
- var PKG_VERSION = "0.4.54";
42491
+ var PKG_VERSION = "0.4.55";
42445
42492
 
42446
42493
  // src/api-base-url-migrate.ts
42447
42494
  var import_node_fs4 = __toESM(require("node:fs"), 1);
@@ -42475,9 +42522,13 @@ function renderEcosystemConfig(opts) {
42475
42522
  if (telemetryProject) env.MRRLIN_TELEMETRY_DEFAULT_PROJECT = telemetryProject;
42476
42523
  if (opts.user) env.USER = opts.user;
42477
42524
  if (opts.codexHome) env.CODEX_HOME = opts.codexHome;
42478
- const retargeted = opts.apiBaseUrl ? retargetRetiredApiBaseUrl(opts.apiBaseUrl).baseUrl : void 0;
42479
- if (opts.staging || retargeted && isStagingApiBaseUrl(retargeted)) env.MRRLIN_STAGING = "1";
42480
- else if (retargeted) env.MRRLIN_API_BASE_URL = retargeted;
42525
+ const apiBaseUrl = opts.apiBaseUrl ? retargetRetiredApiBaseUrl(opts.apiBaseUrl).baseUrl : void 0;
42526
+ if (apiBaseUrl && !isProductionApiBaseUrl(apiBaseUrl)) {
42527
+ env.MRRLIN_API_BASE_URL = apiBaseUrl;
42528
+ if (opts.apiAllowRemote && !isStagingApiBaseUrl(apiBaseUrl) && !isLoopbackUrl(apiBaseUrl)) {
42529
+ env.MRRLIN_API_ALLOW_REMOTE = "1";
42530
+ }
42531
+ }
42481
42532
  const envLines = Object.entries(env).map(([k, v]) => ` ${k}: ${JSON.stringify(v)},`).join("\n");
42482
42533
  return `module.exports = {
42483
42534
  apps: [{
@@ -42521,6 +42572,10 @@ function defaultDeps(deps) {
42521
42572
  function findRetiredHost(text) {
42522
42573
  return retiredApiHostnames().find((host) => text.toLowerCase().includes(host));
42523
42574
  }
42575
+ function hasLiveLegacyStagingFlag(text, syntax) {
42576
+ const lineRe = syntax === "js" ? /^[ \t]*MRRLIN_STAGING:\s*"1",?[ \t]*\r?$/gm : /^[ \t]*(?!#).*MRRLIN_STAGING\s*=\s*"1"/gm;
42577
+ return lineRe.test(text);
42578
+ }
42524
42579
  function isRetiredApiUrl(value) {
42525
42580
  if (!value?.trim()) return false;
42526
42581
  try {
@@ -42529,6 +42584,19 @@ function isRetiredApiUrl(value) {
42529
42584
  return false;
42530
42585
  }
42531
42586
  }
42587
+ function migratedApiUrl(value) {
42588
+ const { baseUrl, retargeted } = retargetRetiredApiBaseUrl(value);
42589
+ return retargeted ? baseUrl : void 0;
42590
+ }
42591
+ function isFirstPartyOrLoopbackUrl(value) {
42592
+ return isStagingApiBaseUrl(value) || isProductionApiBaseUrl(value) || isLoopbackUrl(value);
42593
+ }
42594
+ function isRecord(value) {
42595
+ return typeof value === "object" && value !== null && !Array.isArray(value);
42596
+ }
42597
+ function removeStandaloneAllowRemoteLine(text) {
42598
+ return text.replace(/^[ \t]*MRRLIN_API_ALLOW_REMOTE:\s*"1",?[ \t]*\r?\n/gm, "");
42599
+ }
42532
42600
  function backupOnce(file2, text, deps) {
42533
42601
  const bak = `${file2}.bak-retired-api-url`;
42534
42602
  if (!deps.exists(bak)) deps.writeFile(bak, text);
@@ -42538,7 +42606,8 @@ function migrateEcosystemConfig(deps = {}) {
42538
42606
  const file2 = import_node_path8.default.join(d.home, ".mrrlin", "ecosystem.config.cjs");
42539
42607
  if (!d.exists(file2)) return { file: file2, status: "absent" };
42540
42608
  const text = d.readFile(file2);
42541
- if (!findRetiredHost(text)) return { file: file2, status: "clean" };
42609
+ if (!findRetiredHost(text) && !hasLiveLegacyStagingFlag(text, "js"))
42610
+ return { file: file2, status: "clean" };
42542
42611
  const fields = {
42543
42612
  cwd: /cwd:\s*"((?:[^"\\]|\\.)*)"/.exec(text)?.[1],
42544
42613
  home: /HOME:\s*"((?:[^"\\]|\\.)*)"/.exec(text)?.[1],
@@ -42547,14 +42616,48 @@ function migrateEcosystemConfig(deps = {}) {
42547
42616
  };
42548
42617
  if (fields.cwd && fields.home && fields.pathEnv) {
42549
42618
  const retiredUrl = /MRRLIN_API_BASE_URL:\s*"((?:[^"\\]|\\.)*)"/.exec(text)?.[1];
42550
- const legacyShape = retiredUrl && isRetiredApiUrl(retiredUrl) ? renderEcosystemConfigLegacy({
42619
+ const legacyUrlShape = retiredUrl && isRetiredApiUrl(retiredUrl) ? renderEcosystemConfigLegacy({
42551
42620
  cwd: fields.cwd,
42552
42621
  home: fields.home,
42553
42622
  pathEnv: fields.pathEnv,
42554
42623
  codexHome: fields.codexHome,
42555
42624
  apiBaseUrl: retiredUrl
42556
42625
  }) : void 0;
42557
- if (legacyShape && legacyShape === text) {
42626
+ const legacyStagingShape = renderEcosystemConfigWithEnv(
42627
+ { cwd: fields.cwd, home: fields.home, pathEnv: fields.pathEnv, codexHome: fields.codexHome },
42628
+ { MRRLIN_STAGING: "1" }
42629
+ );
42630
+ if (legacyUrlShape && legacyUrlShape === text) {
42631
+ const nextUrl = migratedApiUrl(retiredUrl ?? "");
42632
+ if (!nextUrl) {
42633
+ return {
42634
+ file: file2,
42635
+ status: "skipped-drifted",
42636
+ detail: "the pinned API URL did not resolve to a known successor"
42637
+ };
42638
+ }
42639
+ backupOnce(file2, text, d);
42640
+ d.writeFile(
42641
+ file2,
42642
+ isProductionApiBaseUrl(nextUrl) ? renderEcosystemConfigWithEnv(
42643
+ {
42644
+ cwd: fields.cwd,
42645
+ home: fields.home,
42646
+ pathEnv: fields.pathEnv,
42647
+ codexHome: fields.codexHome
42648
+ },
42649
+ { MRRLIN_API_BASE_URL: nextUrl }
42650
+ ) : renderEcosystemConfig({
42651
+ cwd: fields.cwd,
42652
+ home: fields.home,
42653
+ pathEnv: fields.pathEnv,
42654
+ codexHome: fields.codexHome,
42655
+ apiBaseUrl: STAGING_API_BASE_URL
42656
+ })
42657
+ );
42658
+ return { file: file2, status: "migrated", detail: "regenerated from the canonical template" };
42659
+ }
42660
+ if (legacyStagingShape === text) {
42558
42661
  backupOnce(file2, text, d);
42559
42662
  d.writeFile(
42560
42663
  file2,
@@ -42563,8 +42666,7 @@ function migrateEcosystemConfig(deps = {}) {
42563
42666
  home: fields.home,
42564
42667
  pathEnv: fields.pathEnv,
42565
42668
  codexHome: fields.codexHome,
42566
- staging: true,
42567
- apiBaseUrl: void 0
42669
+ apiBaseUrl: STAGING_API_BASE_URL
42568
42670
  })
42569
42671
  );
42570
42672
  return { file: file2, status: "migrated", detail: "regenerated from the canonical template" };
@@ -42574,16 +42676,49 @@ function migrateEcosystemConfig(deps = {}) {
42574
42676
  const matches = (text.match(lineRe) ?? []).filter((line) => !line.trimStart().startsWith("//"));
42575
42677
  const target = matches.length === 1 ? matches[0] : void 0;
42576
42678
  if (target !== void 0 && isRetiredApiUrl(/"((?:[^"\\]|\\.)*)"/.exec(target)?.[1])) {
42679
+ const nextUrl = migratedApiUrl(/"((?:[^"\\]|\\.)*)"/.exec(target)?.[1] ?? "");
42680
+ if (!nextUrl) {
42681
+ return {
42682
+ file: file2,
42683
+ status: "skipped-drifted",
42684
+ detail: "the pinned API URL did not resolve to a known successor"
42685
+ };
42686
+ }
42577
42687
  backupOnce(file2, text, d);
42578
42688
  const indent = /^[ \t]*/.exec(target)?.[0] ?? " ";
42579
- d.writeFile(file2, text.replace(target, () => `${indent}MRRLIN_STAGING: "1",
42580
- `));
42581
- return { file: file2, status: "migrated", detail: "replaced the pinned URL line with MRRLIN_STAGING" };
42689
+ let out = text.replace(
42690
+ target,
42691
+ () => `${indent}MRRLIN_API_BASE_URL: ${JSON.stringify(nextUrl)},
42692
+ `
42693
+ );
42694
+ if (isFirstPartyOrLoopbackUrl(nextUrl)) out = removeStandaloneAllowRemoteLine(out);
42695
+ return d.writeFile(file2, out), { file: file2, status: "migrated", detail: "replaced the pinned URL line" };
42696
+ }
42697
+ const stagingLineRe = /^[ \t]*MRRLIN_STAGING:\s*"1",?[ \t]*\r?\n/gm;
42698
+ const stagingMatches = (text.match(stagingLineRe) ?? []).filter(
42699
+ (line) => !line.trimStart().startsWith("//")
42700
+ );
42701
+ const stagingTarget = stagingMatches.length === 1 ? stagingMatches[0] : void 0;
42702
+ if (stagingTarget !== void 0) {
42703
+ backupOnce(file2, text, d);
42704
+ const indent = /^[ \t]*/.exec(stagingTarget)?.[0] ?? " ";
42705
+ let out = text.replace(
42706
+ stagingTarget,
42707
+ () => `${indent}MRRLIN_API_BASE_URL: ${JSON.stringify(STAGING_API_BASE_URL)},
42708
+ `
42709
+ );
42710
+ out = removeStandaloneAllowRemoteLine(out);
42711
+ d.writeFile(file2, out);
42712
+ return {
42713
+ file: file2,
42714
+ status: "migrated",
42715
+ detail: "replaced the legacy staging flag with the explicit API URL"
42716
+ };
42582
42717
  }
42583
42718
  return {
42584
42719
  file: file2,
42585
42720
  status: "skipped-drifted",
42586
- detail: matches.length === 0 ? "a retired host appears in the file but no MRRLIN_API_BASE_URL line was found \u2014 fix it by hand or re-run install-service" : `found ${matches.length} MRRLIN_API_BASE_URL lines; refusing to guess \u2014 fix by hand or re-run install-service`
42721
+ detail: matches.length === 0 ? "a retired host or legacy staging flag appears in the file but no live routing env line was found \u2014 fix it by hand or re-run install-service" : `found ${matches.length} MRRLIN_API_BASE_URL lines; refusing to guess \u2014 fix by hand or re-run install-service`
42587
42722
  };
42588
42723
  }
42589
42724
  function renderEcosystemConfigLegacy(opts) {
@@ -42622,7 +42757,7 @@ function migrateCodexConfig(deps = {}) {
42622
42757
  if (!d.exists(file2)) return { file: file2, status: "absent" };
42623
42758
  const text = d.readFile(file2);
42624
42759
  const host = findRetiredHost(text);
42625
- if (!host) return { file: file2, status: "clean" };
42760
+ if (!host && !hasLiveLegacyStagingFlag(text, "toml")) return { file: file2, status: "clean" };
42626
42761
  const managedBlockRe = /^\[mcp_servers\.mrrlin\][^\n]*\n(?:(?!\[)[^\n]*\n?)*/gm;
42627
42762
  const blocks = text.match(managedBlockRe) ?? [];
42628
42763
  if (blocks.length === 0) {
@@ -42633,21 +42768,39 @@ function migrateCodexConfig(deps = {}) {
42633
42768
  };
42634
42769
  }
42635
42770
  const assignRe = /MRRLIN_API_BASE_URL\s*=\s*"([^"]*)"/g;
42771
+ const stagingAssignRe = /MRRLIN_STAGING\s*=\s*"1"/g;
42772
+ const allowRemoteAssignRe = /,?\s*MRRLIN_API_ALLOW_REMOTE\s*=\s*"1"\s*,?/g;
42636
42773
  let out = text;
42637
42774
  let changed = 0;
42638
42775
  for (const block of blocks) {
42639
42776
  let blockChanged = false;
42640
42777
  const healedBlock = block.split("\n").map((line) => {
42641
42778
  if (line.trimStart().startsWith("#")) return line;
42642
- const healedLine = line.replace(
42643
- assignRe,
42644
- (match, url2) => isRetiredApiUrl(url2) ? `MRRLIN_STAGING = "1"` : match
42645
- );
42779
+ let nextApiUrl;
42780
+ let healedLine = line.replace(assignRe, (match, url2) => {
42781
+ const successor = migratedApiUrl(url2);
42782
+ if (!successor) return match;
42783
+ nextApiUrl = successor;
42784
+ return `MRRLIN_API_BASE_URL = ${JSON.stringify(successor)}`;
42785
+ });
42786
+ healedLine = healedLine.replace(stagingAssignRe, () => {
42787
+ nextApiUrl = STAGING_API_BASE_URL;
42788
+ return `MRRLIN_API_BASE_URL = ${JSON.stringify(STAGING_API_BASE_URL)}`;
42789
+ });
42790
+ if (nextApiUrl && isFirstPartyOrLoopbackUrl(nextApiUrl)) {
42791
+ healedLine = healedLine.replace(
42792
+ allowRemoteAssignRe,
42793
+ (match) => match.startsWith(",") && match.endsWith(",") ? "," : ""
42794
+ ).replace(/\{\s*,\s*/, "{ ").replace(/,\s*\}/, " }").replace(/,\s*,/g, ",");
42795
+ }
42646
42796
  if (healedLine !== line) blockChanged = true;
42647
42797
  return healedLine;
42648
42798
  }).join("\n");
42649
42799
  if (!blockChanged) continue;
42650
- const deduped = healedBlock.replace(/MRRLIN_STAGING = "1"\s*,\s*MRRLIN_STAGING = "1"/g, `MRRLIN_STAGING = "1"`);
42800
+ const deduped = healedBlock.replace(
42801
+ /MRRLIN_API_BASE_URL = "https:\/\/api-staging\.mrrlin\.com"\s*,\s*MRRLIN_API_BASE_URL = "https:\/\/api-staging\.mrrlin\.com"/g,
42802
+ `MRRLIN_API_BASE_URL = "https://api-staging.mrrlin.com"`
42803
+ );
42651
42804
  out = out.replace(block, () => deduped);
42652
42805
  changed += 1;
42653
42806
  }
@@ -42660,14 +42813,67 @@ function migrateCodexConfig(deps = {}) {
42660
42813
  }
42661
42814
  backupOnce(file2, text, d);
42662
42815
  d.writeFile(file2, out);
42663
- return { file: file2, status: "migrated", detail: "swapped the pinned URL for MRRLIN_STAGING" };
42816
+ return {
42817
+ file: file2,
42818
+ status: "migrated",
42819
+ detail: "rewrote legacy API routing env to explicit URL form"
42820
+ };
42821
+ }
42822
+ function migrateClaudeConfig(deps = {}) {
42823
+ const d = defaultDeps(deps);
42824
+ const file2 = import_node_path8.default.join(d.home, ".claude.json");
42825
+ if (!d.exists(file2)) return { file: file2, status: "absent" };
42826
+ const text = d.readFile(file2);
42827
+ let parsed;
42828
+ try {
42829
+ parsed = text.trim() === "" ? {} : JSON.parse(text);
42830
+ } catch (error51) {
42831
+ const detail2 = error51 instanceof Error ? error51.message : String(error51);
42832
+ return { file: file2, status: "failed", detail: `malformed JSON: ${detail2}` };
42833
+ }
42834
+ if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) {
42835
+ return { file: file2, status: "clean" };
42836
+ }
42837
+ const entry = parsed.mcpServers.mrrlin;
42838
+ if (!isRecord(entry) || entry.command !== "mrrlin-mcp" || !Array.isArray(entry.args) || entry.args.length !== 1 || entry.args[0] !== "serve") {
42839
+ return { file: file2, status: "clean" };
42840
+ }
42841
+ const env = entry.env;
42842
+ if (!isRecord(env)) return { file: file2, status: "clean" };
42843
+ const envKeys = Object.keys(env);
42844
+ const routingKeys = /* @__PURE__ */ new Set(["MRRLIN_STAGING", "MRRLIN_API_BASE_URL", "MRRLIN_API_ALLOW_REMOTE"]);
42845
+ if (envKeys.length === 0 || !envKeys.every((key) => routingKeys.has(key)) || !envKeys.every((key) => typeof env[key] === "string")) {
42846
+ return { file: file2, status: "clean" };
42847
+ }
42848
+ let nextEnv;
42849
+ let detail;
42850
+ if (env.MRRLIN_STAGING === "1" && envKeys.length === 1) {
42851
+ nextEnv = { MRRLIN_API_BASE_URL: STAGING_API_BASE_URL };
42852
+ detail = "replaced the legacy staging flag with the explicit API URL";
42853
+ } else if (typeof env.MRRLIN_API_BASE_URL === "string" && (envKeys.length === 1 || envKeys.length === 2 && env.MRRLIN_API_ALLOW_REMOTE === "1")) {
42854
+ const nextUrl = migratedApiUrl(env.MRRLIN_API_BASE_URL);
42855
+ if (!nextUrl) return { file: file2, status: "clean" };
42856
+ nextEnv = isProductionApiBaseUrl(nextUrl) ? {} : { MRRLIN_API_BASE_URL: nextUrl };
42857
+ detail = isProductionApiBaseUrl(nextUrl) ? "removed the retired production routing override" : "retargeted the retired API URL";
42858
+ }
42859
+ if (!nextEnv) return { file: file2, status: "clean" };
42860
+ backupOnce(file2, text, d);
42861
+ const next = JSON.parse(JSON.stringify(parsed));
42862
+ const nextServers = next.mcpServers;
42863
+ const nextEntry = nextServers.mrrlin;
42864
+ if (Object.keys(nextEnv).length === 0) delete nextEntry.env;
42865
+ else nextEntry.env = nextEnv;
42866
+ d.writeFile(file2, `${JSON.stringify(next, null, 2)}
42867
+ `);
42868
+ return { file: file2, status: "migrated", detail };
42664
42869
  }
42665
42870
  function migrateRetiredApiBaseUrls(deps = {}) {
42666
42871
  const d = defaultDeps(deps);
42667
42872
  const results = [];
42668
42873
  const steps = [
42669
42874
  { run: migrateEcosystemConfig, file: import_node_path8.default.join(d.home, ".mrrlin", "ecosystem.config.cjs") },
42670
- { run: migrateCodexConfig, file: import_node_path8.default.join(d.codexHome, "config.toml") }
42875
+ { run: migrateCodexConfig, file: import_node_path8.default.join(d.codexHome, "config.toml") },
42876
+ { run: migrateClaudeConfig, file: import_node_path8.default.join(d.home, ".claude.json") }
42671
42877
  ];
42672
42878
  for (const step of steps) {
42673
42879
  try {
@@ -45062,7 +45268,7 @@ You revise a deliverable to address the accepted issues so it satisfies the acce
45062
45268
  }
45063
45269
 
45064
45270
  // src/telemetry/pii-gate.ts
45065
- function isRecord(v) {
45271
+ function isRecord2(v) {
45066
45272
  return v !== null && typeof v === "object";
45067
45273
  }
45068
45274
  function resolvePath(root, path24) {
@@ -45074,7 +45280,7 @@ function resolvePath(root, path24) {
45074
45280
  if (Array.isArray(cur) && Number.isInteger(idx)) {
45075
45281
  if (idx < 0 || idx >= cur.length) return null;
45076
45282
  cur = cur[idx];
45077
- } else if (isRecord(cur) && part in cur) {
45283
+ } else if (isRecord2(cur) && part in cur) {
45078
45284
  cur = cur[part];
45079
45285
  } else {
45080
45286
  return null;
@@ -45086,7 +45292,7 @@ function resolvePath(root, path24) {
45086
45292
  if (lastIdx < 0 || lastIdx >= cur.length) return null;
45087
45293
  return { parent: cur, key: lastIdx };
45088
45294
  }
45089
- if (isRecord(cur) && last in cur) {
45295
+ if (isRecord2(cur) && last in cur) {
45090
45296
  return { parent: cur, key: last };
45091
45297
  }
45092
45298
  return null;
@@ -45095,7 +45301,7 @@ function withheldExcerptClone(artifact) {
45095
45301
  const clone2 = { ...artifact };
45096
45302
  if (Array.isArray(clone2.steps)) {
45097
45303
  clone2.steps = clone2.steps.map((step) => {
45098
- if (!isRecord(step)) return step;
45304
+ if (!isRecord2(step)) return step;
45099
45305
  const copy = { ...step };
45100
45306
  delete copy.excerpt;
45101
45307
  return copy;
@@ -45126,9 +45332,9 @@ async function gate(artifact, prose, ctx) {
45126
45332
  } catch {
45127
45333
  return { ok: true, gated: true, artifact: withheldExcerptClone(artifact) };
45128
45334
  }
45129
- const fields = isRecord(parsed) && Array.isArray(parsed.fields) ? parsed.fields : null;
45335
+ const fields = isRecord2(parsed) && Array.isArray(parsed.fields) ? parsed.fields : null;
45130
45336
  const isValidShape = fields !== null && fields.length === prose.length && fields.every(
45131
- (entry, i) => isRecord(entry) && typeof entry.path === "string" && typeof entry.masked === "string" && entry.path === prose[i]?.path && resolvePath(artifact, entry.path) !== null
45337
+ (entry, i) => isRecord2(entry) && typeof entry.path === "string" && typeof entry.masked === "string" && entry.path === prose[i]?.path && resolvePath(artifact, entry.path) !== null
45132
45338
  );
45133
45339
  if (!isValidShape || fields === null) {
45134
45340
  return { ok: true, gated: true, artifact: withheldExcerptClone(artifact) };
@@ -49350,27 +49556,27 @@ function redact(input) {
49350
49556
 
49351
49557
  // src/telemetry/projector.ts
49352
49558
  var import_node_crypto8 = require("node:crypto");
49353
- function isRecord2(x) {
49559
+ function isRecord3(x) {
49354
49560
  return typeof x === "object" && x !== null;
49355
49561
  }
49356
49562
  function buildProjection(payload, type, ms, telemetry) {
49357
- if (!isRecord2(payload)) return null;
49563
+ if (!isRecord3(payload)) return null;
49358
49564
  const seqRaw = payload.seq;
49359
49565
  const turnIdRaw = payload.turnId;
49360
49566
  const maybeSeq = typeof seqRaw === "number" ? seqRaw : void 0;
49361
49567
  const maybeTurnId = typeof turnIdRaw === "string" ? turnIdRaw : void 0;
49362
49568
  if (type === "event") {
49363
49569
  const rawEvent = payload.event;
49364
- if (!isRecord2(rawEvent) || typeof rawEvent.type !== "string") return null;
49570
+ if (!isRecord3(rawEvent) || typeof rawEvent.type !== "string") return null;
49365
49571
  if (rawEvent.type === "tool_use") {
49366
49572
  const tool = rawEvent.tool;
49367
- if (!isRecord2(tool)) return null;
49573
+ if (!isRecord3(tool)) return null;
49368
49574
  const toolId = tool.id;
49369
49575
  const toolName = tool.name;
49370
49576
  if (typeof toolId !== "string" || typeof toolName !== "string") return null;
49371
49577
  const input = tool.input;
49372
- const argKeys = isRecord2(input) ? Object.keys(input) : [];
49373
- const projectSlug = isRecord2(input) && typeof input.projectSlug === "string" ? input.projectSlug : void 0;
49578
+ const argKeys = isRecord3(input) ? Object.keys(input) : [];
49579
+ const projectSlug = isRecord3(input) && typeof input.projectSlug === "string" ? input.projectSlug : void 0;
49374
49580
  const proj = { kind: "tool_use", toolId, toolName, argKeys, seq: maybeSeq ?? 0 };
49375
49581
  if (maybeTurnId !== void 0) proj.turnId = maybeTurnId;
49376
49582
  if (ms !== void 0) proj.ms = ms;
@@ -49379,7 +49585,7 @@ function buildProjection(payload, type, ms, telemetry) {
49379
49585
  }
49380
49586
  if (rawEvent.type === "tool_result") {
49381
49587
  const tool = rawEvent.tool;
49382
- if (!isRecord2(tool)) return null;
49588
+ if (!isRecord3(tool)) return null;
49383
49589
  const toolId = tool.id;
49384
49590
  if (typeof toolId !== "string") return null;
49385
49591
  const exitCode = tool.exitCode;
@@ -49427,7 +49633,7 @@ function buildProjection(payload, type, ms, telemetry) {
49427
49633
  const proj = { kind: "plumbing", plumbingKind: "rate_limit_status", seq: maybeSeq ?? 0 };
49428
49634
  if (maybeTurnId !== void 0) proj.turnId = maybeTurnId;
49429
49635
  if (ms !== void 0) proj.ms = ms;
49430
- if (isRecord2(rateLimits)) proj.rateLimitReached = rateLimits.rateLimitReachedType != null;
49636
+ if (isRecord3(rateLimits)) proj.rateLimitReached = rateLimits.rateLimitReachedType != null;
49431
49637
  return proj;
49432
49638
  }
49433
49639
  const bracketMatch = /^<([a-z-]+)>$/.exec(type);
@@ -54680,7 +54886,7 @@ function dayFilesInWindow(now, windowMs) {
54680
54886
  }
54681
54887
  return files;
54682
54888
  }
54683
- function isRecord3(value) {
54889
+ function isRecord4(value) {
54684
54890
  return typeof value === "object" && value !== null;
54685
54891
  }
54686
54892
  function extractItems(lines2, sessionIds) {
@@ -54698,7 +54904,7 @@ function extractItems(lines2, sessionIds) {
54698
54904
  skipped2++;
54699
54905
  continue;
54700
54906
  }
54701
- if (!isRecord3(parsed)) {
54907
+ if (!isRecord4(parsed)) {
54702
54908
  skipped2++;
54703
54909
  continue;
54704
54910
  }
@@ -54707,11 +54913,11 @@ function extractItems(lines2, sessionIds) {
54707
54913
  const ts = obj.ts;
54708
54914
  const type = obj.type;
54709
54915
  const payload = obj.payload;
54710
- if (type === "turn" && isRecord3(payload) && typeof payload.message === "string") {
54916
+ if (type === "turn" && isRecord4(payload) && typeof payload.message === "string") {
54711
54917
  items.push({ ts, kind: "user", text: payload.message });
54712
- } else if (type === "event" && isRecord3(payload) && isRecord3(payload.event) && payload.event.type === "assistant" && typeof payload.event.content === "string") {
54918
+ } else if (type === "event" && isRecord4(payload) && isRecord4(payload.event) && payload.event.type === "assistant" && typeof payload.event.content === "string") {
54713
54919
  items.push({ ts, kind: "assistant", text: payload.event.content });
54714
- } else if (type === "event" && isRecord3(payload) && isRecord3(payload.event) && payload.event.type === "tool_use" && isRecord3(payload.event.tool) && typeof payload.event.tool.name === "string") {
54920
+ } else if (type === "event" && isRecord4(payload) && isRecord4(payload.event) && payload.event.type === "tool_use" && isRecord4(payload.event.tool) && typeof payload.event.tool.name === "string") {
54715
54921
  items.push({ ts, kind: "tool", text: payload.event.tool.name });
54716
54922
  }
54717
54923
  }
@@ -54804,7 +55010,7 @@ async function readSessionRows(logsDir, files, sessionIds) {
54804
55010
  stream.destroy();
54805
55011
  }
54806
55012
  } catch (e) {
54807
- if (isRecord3(e) && e.code === "ENOENT") {
55013
+ if (isRecord4(e) && e.code === "ENOENT") {
54808
55014
  missingFiles++;
54809
55015
  continue;
54810
55016
  }
@@ -57402,10 +57608,10 @@ async function startDirectorBridge() {
57402
57608
  migrateRetiredApiBaseUrls();
57403
57609
  } catch {
57404
57610
  }
57405
- const apiBaseUrl = readConfiguredApiBaseUrl();
57611
+ const apiBaseUrl = readDirectorBridgeApiBaseUrl();
57406
57612
  if (!isRemoteApiAllowed(apiBaseUrl)) {
57407
57613
  throw new Error(
57408
- `Refusing to run the director-bridge execution consumer against non-loopback API URL ${apiBaseUrl}. Set MRRLIN_API_ALLOW_REMOTE=1 or MRRLIN_STAGING=1 if this is intentional.`
57614
+ `Refusing to run the director-bridge execution consumer against untrusted remote API URL ${apiBaseUrl}. Set MRRLIN_API_ALLOW_REMOTE=1 for a custom remote, or set MRRLIN_API_BASE_URL=https://api-staging.mrrlin.com for the official staging API.`
57409
57615
  );
57410
57616
  }
57411
57617
  const envToken = process.env.MRRLIN_TOKEN?.trim() || void 0;
@@ -64607,7 +64813,7 @@ async function invokeCapability(server, toolName, payload) {
64607
64813
  await client.connect(transport);
64608
64814
  result = await client.callTool({
64609
64815
  name: toolName,
64610
- arguments: isRecord4(payload) ? payload : void 0
64816
+ arguments: isRecord5(payload) ? payload : void 0
64611
64817
  });
64612
64818
  } catch (error51) {
64613
64819
  throw classifyInvokeError(error51, server.url, toolName);
@@ -64659,7 +64865,7 @@ function classifyInvokeError(error51, url2, toolName) {
64659
64865
  if (error51 instanceof TypeError) return new InvokeUnreachableError(url2, error51);
64660
64866
  return new InvokeMalformedResultError(error51 instanceof Error ? error51.message : String(error51), error51);
64661
64867
  }
64662
- function isRecord4(value) {
64868
+ function isRecord5(value) {
64663
64869
  return typeof value === "object" && value !== null && !Array.isArray(value);
64664
64870
  }
64665
64871
  function extractFirstUrl(content) {
@@ -64668,7 +64874,7 @@ function extractFirstUrl(content) {
64668
64874
  function extractFirstText(content) {
64669
64875
  if (Array.isArray(content)) {
64670
64876
  for (const block of content) {
64671
- if (isRecord4(block) && typeof block.text === "string") return block.text;
64877
+ if (isRecord5(block) && typeof block.text === "string") return block.text;
64672
64878
  }
64673
64879
  }
64674
64880
  return JSON.stringify(content);
@@ -64685,7 +64891,7 @@ function findFirstMatch(value, pattern) {
64685
64891
  }
64686
64892
  return null;
64687
64893
  }
64688
- if (isRecord4(value)) {
64894
+ if (isRecord5(value)) {
64689
64895
  for (const nested of Object.values(value)) {
64690
64896
  const found = findFirstMatch(nested, pattern);
64691
64897
  if (found) return found;
@@ -64906,7 +65112,7 @@ async function recordUse(client, route, input, outcome) {
64906
65112
  }
64907
65113
  }
64908
65114
  async function callDestination(client, projectSlug, route, payload, mcpConfigPath) {
64909
- const operation = isRecord5(payload) ? payload.operation : void 0;
65115
+ const operation = isRecord6(payload) ? payload.operation : void 0;
64910
65116
  if (route.capability === "wiki") {
64911
65117
  if (operation !== "create" && operation !== "update") {
64912
65118
  throw new Error(`callDestination: payload.operation "${String(operation)}" is not valid for wiki`);
@@ -64923,7 +65129,7 @@ async function callDestination(client, projectSlug, route, payload, mcpConfigPat
64923
65129
  }
64924
65130
  throw new Error(`callDestination: no destination wired for capability "${route.capability}"`);
64925
65131
  }
64926
- function isRecord5(value) {
65132
+ function isRecord6(value) {
64927
65133
  return typeof value === "object" && value !== null;
64928
65134
  }
64929
65135
 
@@ -65973,7 +66179,7 @@ var import_node_path31 = require("node:path");
65973
66179
  function evidenceDedupKey(sessionId, reportId) {
65974
66180
  return `${sessionId}:${reportId}`;
65975
66181
  }
65976
- function isRecord6(v) {
66182
+ function isRecord7(v) {
65977
66183
  return v !== null && typeof v === "object";
65978
66184
  }
65979
66185
  async function readDayLines(stateDir, day, sessionId) {
@@ -65992,7 +66198,7 @@ async function readDayLines(stateDir, day, sessionId) {
65992
66198
  } catch {
65993
66199
  continue;
65994
66200
  }
65995
- if (!isRecord6(rec) || !isRecord6(rec.payload)) continue;
66201
+ if (!isRecord7(rec) || !isRecord7(rec.payload)) continue;
65996
66202
  if (rec.payload.directorSessionId !== sessionId) continue;
65997
66203
  kept.push(rec);
65998
66204
  }
@@ -66062,7 +66268,7 @@ async function assembleEvidence(stateDir, sessionId, reportId, gateCtx) {
66062
66268
  } catch {
66063
66269
  continue;
66064
66270
  }
66065
- if (!isRecord6(rec) || !Array.isArray(rec.daysRemoved)) continue;
66271
+ if (!isRecord7(rec) || !Array.isArray(rec.daysRemoved)) continue;
66066
66272
  for (const removedDay of rec.daysRemoved) {
66067
66273
  if (daysRead.includes(removedDay)) {
66068
66274
  windowPruned = true;
@@ -68115,7 +68321,7 @@ function resolveServerOptions(input = {}) {
68115
68321
  const remoteAllowed = isRemoteApiAllowed(apiBaseUrl) || input.allowRemoteApi === true;
68116
68322
  if (!remoteAllowed) {
68117
68323
  throw new Error(
68118
- `Refusing to start MCP server against non-loopback URL ${apiBaseUrl}. Set MRRLIN_API_ALLOW_REMOTE=1 or MRRLIN_STAGING=1 if this is intentional.`
68324
+ `Refusing to start MCP server against non-loopback URL ${apiBaseUrl}. Set MRRLIN_API_ALLOW_REMOTE=1 for a custom remote, or set MRRLIN_API_BASE_URL=https://api-staging.mrrlin.com for the official staging API.`
68119
68325
  );
68120
68326
  }
68121
68327
  return {
@@ -68297,6 +68503,50 @@ function readExistingTelemetryDefaultProject(deps, ecoPath) {
68297
68503
  return void 0;
68298
68504
  }
68299
68505
  }
68506
+ function isOwnedEcosystemConfig(text) {
68507
+ return /name:\s*"mrrlin-bridge"/.test(text) && /script:\s*"mrrlin-mcp"/.test(text) && /args:\s*"director-bridge"/.test(text);
68508
+ }
68509
+ function readEnvString(text, key) {
68510
+ const match = new RegExp(`\\b${key}:\\s*("(?:[^"\\\\]|\\\\.)*")`).exec(text);
68511
+ if (!match?.[1]) return void 0;
68512
+ try {
68513
+ const value = JSON.parse(match[1]);
68514
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
68515
+ } catch {
68516
+ return void 0;
68517
+ }
68518
+ }
68519
+ function normalizeServiceApiTarget(apiBaseUrl, allowRemote) {
68520
+ const normalized = retargetRetiredApiBaseUrl(apiBaseUrl).baseUrl;
68521
+ if (isProductionApiBaseUrl(normalized)) return {};
68522
+ const target = { apiBaseUrl: normalized };
68523
+ if (allowRemote && !isStagingApiBaseUrl(normalized) && !isLoopbackUrl(normalized)) target.apiAllowRemote = true;
68524
+ return target;
68525
+ }
68526
+ function readExistingServiceApiTarget(deps, ecoPath) {
68527
+ try {
68528
+ const existing = deps.readFile?.(ecoPath);
68529
+ if (!existing || !isOwnedEcosystemConfig(existing)) return void 0;
68530
+ const apiBaseUrl = readEnvString(existing, "MRRLIN_API_BASE_URL");
68531
+ if (apiBaseUrl) {
68532
+ return normalizeServiceApiTarget(apiBaseUrl, readEnvString(existing, "MRRLIN_API_ALLOW_REMOTE") === "1");
68533
+ }
68534
+ if (readEnvString(existing, "MRRLIN_STAGING") === "1") {
68535
+ return { apiBaseUrl: STAGING_API_BASE_URL };
68536
+ }
68537
+ return void 0;
68538
+ } catch {
68539
+ return void 0;
68540
+ }
68541
+ }
68542
+ function deriveServiceApiTarget(deps, ecoPath) {
68543
+ const apiBaseUrl = deps.env.MRRLIN_API_BASE_URL?.trim();
68544
+ if (apiBaseUrl) return normalizeServiceApiTarget(apiBaseUrl, deps.env.MRRLIN_API_ALLOW_REMOTE === "1");
68545
+ if (deps.env.MRRLIN_STAGING === "1" || bridgeUrlImpliesStaging(deps.env.MRRLIN_BRIDGE_URL)) {
68546
+ return { apiBaseUrl: STAGING_API_BASE_URL };
68547
+ }
68548
+ return readExistingServiceApiTarget(deps, ecoPath) ?? {};
68549
+ }
68300
68550
  var SYSTEM_PATH_DIRS = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"];
68301
68551
  function defaultHomeOwnerUid(home) {
68302
68552
  try {
@@ -68363,9 +68613,8 @@ function installService(deps) {
68363
68613
  const pathEnv = Array.from(
68364
68614
  /* @__PURE__ */ new Set([...resolvedBins.map((b) => import_node_path33.default.dirname(b)), ...SYSTEM_PATH_DIRS])
68365
68615
  ).join(":");
68366
- const apiBaseUrl = deps.env.MRRLIN_API_BASE_URL?.trim() || void 0;
68367
- const staging = deps.env.MRRLIN_STAGING === "1" || !apiBaseUrl && bridgeUrlImpliesStaging(deps.env.MRRLIN_BRIDGE_URL);
68368
68616
  const ecoPath = import_node_path33.default.join(home, ".mrrlin", "ecosystem.config.cjs");
68617
+ const apiTarget = deriveServiceApiTarget(deps, ecoPath);
68369
68618
  const telemetryDefaultProject = "MRRLIN_TELEMETRY_DEFAULT_PROJECT" in deps.env ? deps.env.MRRLIN_TELEMETRY_DEFAULT_PROJECT?.trim() || void 0 : readExistingTelemetryDefaultProject(deps, ecoPath);
68370
68619
  const text = renderEcosystemConfig({
68371
68620
  cwd: resolved.cwd,
@@ -68373,8 +68622,8 @@ function installService(deps) {
68373
68622
  user,
68374
68623
  codexHome: deps.env.CODEX_HOME?.trim() || void 0,
68375
68624
  pathEnv,
68376
- staging,
68377
- apiBaseUrl,
68625
+ apiBaseUrl: apiTarget.apiBaseUrl,
68626
+ apiAllowRemote: apiTarget.apiAllowRemote,
68378
68627
  telemetryDefaultProject
68379
68628
  });
68380
68629
  deps.writeFile(ecoPath, text);
@@ -68512,7 +68761,7 @@ function parseArgs(args) {
68512
68761
  }
68513
68762
  return map2;
68514
68763
  }
68515
- function isRecord7(v) {
68764
+ function isRecord8(v) {
68516
68765
  return v !== null && typeof v === "object";
68517
68766
  }
68518
68767
  async function runExtract(args, deps) {
@@ -68549,8 +68798,8 @@ Missing required flag(s): ${missing.join(
68549
68798
  } catch {
68550
68799
  continue;
68551
68800
  }
68552
- const payload = isRecord7(parsed) ? parsed.payload : void 0;
68553
- if (isRecord7(payload) && payload.directorSessionId === sessionId) {
68801
+ const payload = isRecord8(parsed) ? parsed.payload : void 0;
68802
+ if (isRecord8(payload) && payload.directorSessionId === sessionId) {
68554
68803
  matchingLines.push(line);
68555
68804
  }
68556
68805
  }
@@ -68658,8 +68907,9 @@ Usage:
68658
68907
  mrrlin-mcp serve Run the stdio MCP server. Honors
68659
68908
  MRRLIN_API_BASE_URL (default http://127.0.0.1:8787)
68660
68909
  and MRRLIN_TOKEN. Non-loopback API URLs require
68661
- MRRLIN_API_ALLOW_REMOTE=1, or use MRRLIN_STAGING=1
68662
- to target the known staging API alias.
68910
+ MRRLIN_API_ALLOW_REMOTE=1 for a custom remote. Use
68911
+ MRRLIN_API_BASE_URL=https://api-staging.mrrlin.com
68912
+ to target the known staging API.
68663
68913
 
68664
68914
  mrrlin-mcp install-browser Idempotently install the dedicated, pinned
68665
68915
  Chromium into ~/.mrrlin/browser/browsers (its own
@@ -68695,16 +68945,16 @@ Usage:
68695
68945
  Bakes API-target env vars into the block as an
68696
68946
  inline env table so \`serve\` boots even when the
68697
68947
  local 127.0.0.1:8787 is not running. Setup guide:
68698
- MRRLIN_STAGING=1 mrrlin-mcp configure bridge
68699
- is the canonical path (staging alias + remote-
68700
- allow gate in one flag). For a non-staging remote,
68948
+ MRRLIN_API_BASE_URL=https://api-staging.mrrlin.com mrrlin-mcp configure bridge
68949
+ is the canonical staging path. For a custom remote,
68701
68950
  pass BOTH the URL AND the allow gate:
68702
68951
  MRRLIN_API_BASE_URL=https://... \\
68703
68952
  MRRLIN_API_ALLOW_REMOTE=1 mrrlin-mcp configure bridge
68704
68953
  Without an allow gate, \`serve\` still refuses any
68705
68954
  non-loopback URL (see \`mrrlin-mcp serve\` above).
68706
- URL and MRRLIN_STAGING are mutually exclusive at
68707
- bake time \u2014 URL wins, mirrors serve's resolver.
68955
+ An explicit URL is the only routing selector at bake
68956
+ time. Production needs no routing env; a custom
68957
+ remote also needs MRRLIN_API_ALLOW_REMOTE=1.
68708
68958
  Re-running BARE (no env vars) over a block you
68709
68959
  already pinned to staging/a remote target is safe:
68710
68960
  it keeps your env verbatim (reported as
@@ -68738,8 +68988,9 @@ Usage:
68738
68988
  pending runs, read-only). Consumer env:
68739
68989
  - MRRLIN_API_BASE_URL (default http://127.0.0.1:8787;
68740
68990
  non-loopback requires MRRLIN_API_ALLOW_REMOTE=1,
68741
- or MRRLIN_STAGING=1 for the known staging API alias)
68742
- - MRRLIN_STAGING=1 (shortcut for the public staging API)
68991
+ or an explicit staging API URL)
68992
+ - MRRLIN_API_BASE_URL=https://api-staging.mrrlin.com
68993
+ (known staging API)
68743
68994
  - MRRLIN_TOKEN (service-principal auth to the API;
68744
68995
  if unset, chat still works but claim-sweep /
68745
68996
  autonomous execution stays disabled)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.54",
3
+ "version": "0.4.55",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"
@@ -21,11 +21,11 @@
21
21
  "@types/ws": "^8.18.1",
22
22
  "esbuild": "^0.24.0",
23
23
  "tsx": "^4.22.3",
24
+ "@mrrlin/client": "0.0.0",
24
25
  "@mrrlin/agent-client": "0.0.0",
25
26
  "@mrrlin/director-e2e": "0.0.0",
26
- "@mrrlin/client": "0.0.0",
27
- "@mrrlin/schemas": "0.0.0",
28
27
  "@mrrlin/tsconfig": "0.0.0",
28
+ "@mrrlin/schemas": "0.0.0",
29
29
  "@mrrlin/wiki": "0.0.0"
30
30
  },
31
31
  "dependencies": {