@mrrlin-dev/mcp 0.4.87 → 0.4.88

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 +494 -62
  2. package/package.json +1 -1
package/dist/bin.cjs CHANGED
@@ -56831,7 +56831,7 @@ Do NOT derail the current task - if the operator has an in-flight question, answ
56831
56831
  }
56832
56832
 
56833
56833
  // src/_generated/version.ts
56834
- var PKG_VERSION = "0.4.87";
56834
+ var PKG_VERSION = "0.4.88";
56835
56835
 
56836
56836
  // src/api-base-url-migrate.ts
56837
56837
  var import_node_fs13 = __toESM(require("node:fs"), 1);
@@ -56856,28 +56856,45 @@ function resolveServiceCwd(opts) {
56856
56856
  }
56857
56857
  var BRIDGE_APP_NAME = "mrrlin-bridge";
56858
56858
  function renderEcosystemConfig(opts) {
56859
- const env = {
56860
- HOME: opts.home,
56861
- PATH: opts.pathEnv,
56862
- MRRLIN_DIRECTOR_BRIDGE_LOG: "1"
56863
- };
56864
- if (opts.user) {
56865
- env.USER = opts.user;
56866
- if (process.platform === "win32") {
56867
- env.USERNAME = opts.user;
56859
+ let envLines;
56860
+ if ("env" in opts && opts.env !== void 0) {
56861
+ const safeEnv = /* @__PURE__ */ Object.create(null);
56862
+ for (const key of Object.keys(opts.env)) {
56863
+ const val = opts.env[key];
56864
+ if (val !== void 0) {
56865
+ safeEnv[key] = val;
56866
+ }
56867
+ }
56868
+ const keys = Object.keys(safeEnv).sort();
56869
+ envLines = keys.map((k) => {
56870
+ const keyProp = k === "__proto__" ? '["__proto__"]' : k;
56871
+ return ` ${keyProp}: ${JSON.stringify(safeEnv[k])},`;
56872
+ }).join("\n");
56873
+ } else {
56874
+ const legacyOpts = opts;
56875
+ const env = {
56876
+ HOME: legacyOpts.home,
56877
+ PATH: legacyOpts.pathEnv,
56878
+ MRRLIN_DIRECTOR_BRIDGE_LOG: "1"
56879
+ };
56880
+ if (legacyOpts.user) {
56881
+ env.USER = legacyOpts.user;
56882
+ if (process.platform === "win32") {
56883
+ env.USERNAME = legacyOpts.user;
56884
+ }
56868
56885
  }
56869
- }
56870
- if (opts.codexHome) env.CODEX_HOME = opts.codexHome;
56871
- const claudeConfigDir = opts.claudeConfigDir?.trim();
56872
- if (claudeConfigDir) env.CLAUDE_CONFIG_DIR = claudeConfigDir;
56873
- const apiBaseUrl = opts.apiBaseUrl ? retargetRetiredApiBaseUrl(opts.apiBaseUrl).baseUrl : void 0;
56874
- if (apiBaseUrl && !isProductionApiBaseUrl(apiBaseUrl)) {
56875
- env.MRRLIN_API_BASE_URL = apiBaseUrl;
56876
- if (opts.apiAllowRemote && !isStagingApiBaseUrl(apiBaseUrl) && !isLoopbackUrl(apiBaseUrl)) {
56877
- env.MRRLIN_API_ALLOW_REMOTE = "1";
56886
+ if (legacyOpts.codexHome) env.CODEX_HOME = legacyOpts.codexHome;
56887
+ const claudeConfigDir = legacyOpts.claudeConfigDir?.trim();
56888
+ if (claudeConfigDir) env.CLAUDE_CONFIG_DIR = claudeConfigDir;
56889
+ const apiBaseUrl = legacyOpts.apiBaseUrl ? retargetRetiredApiBaseUrl(legacyOpts.apiBaseUrl).baseUrl : void 0;
56890
+ if (apiBaseUrl && !isProductionApiBaseUrl(apiBaseUrl)) {
56891
+ env.MRRLIN_API_BASE_URL = apiBaseUrl;
56892
+ if (legacyOpts.apiAllowRemote && !isStagingApiBaseUrl(apiBaseUrl) && !isLoopbackUrl(apiBaseUrl)) {
56893
+ env.MRRLIN_API_ALLOW_REMOTE = "1";
56894
+ }
56878
56895
  }
56896
+ envLines = Object.entries(env).map(([k, v]) => ` ${k}: ${JSON.stringify(v)},`).join("\n");
56879
56897
  }
56880
- const envLines = Object.entries(env).map(([k, v]) => ` ${k}: ${JSON.stringify(v)},`).join("\n");
56881
56898
  return `module.exports = {
56882
56899
  apps: [{
56883
56900
  name: ${JSON.stringify(BRIDGE_APP_NAME)},
@@ -71029,49 +71046,440 @@ function runSetCredential(args) {
71029
71046
  var import_node_os18 = __toESM(require("node:os"), 1);
71030
71047
  var import_node_fs24 = require("node:fs");
71031
71048
  var import_node_path38 = __toESM(require("node:path"), 1);
71032
- function isOwnedEcosystemConfig(text) {
71033
- return /name:\s*"mrrlin-bridge"/.test(text) && /script:\s*"mrrlin-mcp"/.test(text) && /args:\s*"director-bridge"/.test(text);
71034
- }
71035
- function readEnvString(text, key) {
71036
- const match = new RegExp(`\\b${key}:\\s*("(?:[^"\\\\]|\\\\.)*")`).exec(text);
71037
- if (!match?.[1]) return void 0;
71049
+ function parseEnvLine(line, env) {
71050
+ const trimmed = line.trim();
71051
+ if (!trimmed || trimmed === "}" || trimmed === "},") return;
71052
+ const match = /^\s*(?:([a-zA-Z_$][a-zA-Z0-9_$]*)|"((?:[^"\\]|\\.)*)"|(\["__proto__"\]))\s*:\s*("(?:[^"\\]|\\.)*")\s*,?\s*$/.exec(line);
71053
+ if (!match) return;
71054
+ const identKey = match[1];
71055
+ const quotedKey = match[2];
71056
+ const computedProto = match[3];
71057
+ const rawValue = match[4];
71058
+ if (!rawValue) return;
71059
+ let key = null;
71060
+ if (computedProto) {
71061
+ key = "__proto__";
71062
+ } else if (identKey) {
71063
+ if (identKey === "__proto__") {
71064
+ return;
71065
+ }
71066
+ key = identKey;
71067
+ } else if (quotedKey !== void 0) {
71068
+ try {
71069
+ const parsedKey = JSON.parse(`"${quotedKey}"`);
71070
+ if (typeof parsedKey !== "string" || parsedKey === "__proto__") {
71071
+ return;
71072
+ }
71073
+ key = parsedKey;
71074
+ } catch {
71075
+ return;
71076
+ }
71077
+ }
71078
+ if (key === null) return;
71038
71079
  try {
71039
- const value = JSON.parse(match[1]);
71040
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
71080
+ const parsedValue = JSON.parse(rawValue);
71081
+ if (typeof parsedValue === "string") {
71082
+ env[key] = parsedValue;
71083
+ }
71041
71084
  } catch {
71042
- return void 0;
71085
+ return;
71043
71086
  }
71044
71087
  }
71045
- function normalizeServiceApiTarget(apiBaseUrl, allowRemote) {
71046
- const normalized = retargetRetiredApiBaseUrl(apiBaseUrl).baseUrl;
71047
- if (isProductionApiBaseUrl(normalized)) return {};
71048
- const target = { apiBaseUrl: normalized };
71049
- if (allowRemote && !isStagingApiBaseUrl(normalized) && !isLoopbackUrl(normalized)) target.apiAllowRemote = true;
71050
- return target;
71088
+ function parseExistingEcosystemConfig(text) {
71089
+ if (text === null || text === void 0) {
71090
+ return { kind: "missing" };
71091
+ }
71092
+ let mode = "code";
71093
+ const templateCurlyStack = [];
71094
+ let curlyDepth = 0;
71095
+ let squareDepth = 0;
71096
+ let parenDepth = 0;
71097
+ let moduleExportsOpened = false;
71098
+ let moduleExportsClosed = false;
71099
+ let appsArrayOpened = false;
71100
+ let appsArrayClosed = false;
71101
+ let appCandidateActive = false;
71102
+ let candidateHasName = false;
71103
+ let candidateHasScript = false;
71104
+ let candidateHasArgs = false;
71105
+ let candidateHasEnv = false;
71106
+ let candidateEnvOpenedOnLine = false;
71107
+ let candidateEnvClosed = false;
71108
+ let candidateEnv = null;
71109
+ let matchedAppEnv = null;
71110
+ let matchedAppCount = 0;
71111
+ let recentCode = "";
71112
+ let lineCode = "";
71113
+ const len = text.length;
71114
+ let i = 0;
71115
+ while (i < len) {
71116
+ const ch = text[i] ?? "";
71117
+ if (mode === "line_comment") {
71118
+ if (ch === "\n") {
71119
+ mode = "code";
71120
+ lineCode = "";
71121
+ candidateEnvOpenedOnLine = false;
71122
+ }
71123
+ i++;
71124
+ continue;
71125
+ }
71126
+ if (mode === "block_comment") {
71127
+ if (ch === "*" && i + 1 < len && text[i + 1] === "/") {
71128
+ mode = "code";
71129
+ i += 2;
71130
+ continue;
71131
+ }
71132
+ if (ch === "\n") {
71133
+ lineCode = "";
71134
+ candidateEnvOpenedOnLine = false;
71135
+ }
71136
+ i++;
71137
+ continue;
71138
+ }
71139
+ if (mode === "single_quote") {
71140
+ if (ch === "\\") {
71141
+ i += 2;
71142
+ continue;
71143
+ }
71144
+ if (ch === "'") {
71145
+ mode = "code";
71146
+ i++;
71147
+ continue;
71148
+ }
71149
+ if (ch === "\n" || ch === "\r") {
71150
+ return { kind: "rejected", text };
71151
+ }
71152
+ i++;
71153
+ continue;
71154
+ }
71155
+ if (mode === "double_quote") {
71156
+ if (ch === "\\") {
71157
+ lineCode += ch + (text[i + 1] ?? "");
71158
+ i += 2;
71159
+ continue;
71160
+ }
71161
+ lineCode += ch;
71162
+ if (ch === '"') {
71163
+ mode = "code";
71164
+ if (appCandidateActive && curlyDepth === 2 && squareDepth === 1) {
71165
+ if (/name\s*:\s*"mrrlin-bridge"/.test(lineCode)) candidateHasName = true;
71166
+ if (/script\s*:\s*"mrrlin-mcp"/.test(lineCode)) candidateHasScript = true;
71167
+ if (/args\s*:\s*"director-bridge"/.test(lineCode)) candidateHasArgs = true;
71168
+ }
71169
+ i++;
71170
+ continue;
71171
+ }
71172
+ if (ch === "\n" || ch === "\r") {
71173
+ return { kind: "rejected", text };
71174
+ }
71175
+ i++;
71176
+ continue;
71177
+ }
71178
+ if (mode === "template") {
71179
+ if (ch === "\\") {
71180
+ i += 2;
71181
+ continue;
71182
+ }
71183
+ if (ch === "`") {
71184
+ mode = "code";
71185
+ i++;
71186
+ continue;
71187
+ }
71188
+ if (ch === "$" && i + 1 < len && text[i + 1] === "{") {
71189
+ mode = "code";
71190
+ templateCurlyStack.push(curlyDepth);
71191
+ curlyDepth++;
71192
+ i += 2;
71193
+ continue;
71194
+ }
71195
+ i++;
71196
+ continue;
71197
+ }
71198
+ if (ch === "/" && i + 1 < len) {
71199
+ if (text[i + 1] === "/") {
71200
+ mode = "line_comment";
71201
+ i += 2;
71202
+ continue;
71203
+ }
71204
+ if (text[i + 1] === "*") {
71205
+ mode = "block_comment";
71206
+ i += 2;
71207
+ continue;
71208
+ }
71209
+ return { kind: "rejected", text };
71210
+ }
71211
+ if (ch === "/") {
71212
+ return { kind: "rejected", text };
71213
+ }
71214
+ if (ch === "'") {
71215
+ mode = "single_quote";
71216
+ i++;
71217
+ continue;
71218
+ }
71219
+ if (ch === '"') {
71220
+ mode = "double_quote";
71221
+ lineCode += ch;
71222
+ i++;
71223
+ continue;
71224
+ }
71225
+ if (ch === "`") {
71226
+ mode = "template";
71227
+ i++;
71228
+ continue;
71229
+ }
71230
+ if (ch === "\n") {
71231
+ if (appCandidateActive && candidateHasEnv && !candidateEnvClosed && curlyDepth === 3 && candidateEnv) {
71232
+ parseEnvLine(lineCode, candidateEnv);
71233
+ }
71234
+ lineCode = "";
71235
+ candidateEnvOpenedOnLine = false;
71236
+ recentCode += " ";
71237
+ i++;
71238
+ continue;
71239
+ }
71240
+ lineCode += ch;
71241
+ recentCode += ch;
71242
+ if (ch === "{") {
71243
+ if (curlyDepth === 0 && squareDepth === 0) {
71244
+ if (/module\.exports\s*=\s*\{$/.test(recentCode.trim())) {
71245
+ if (moduleExportsOpened) return { kind: "rejected", text };
71246
+ moduleExportsOpened = true;
71247
+ }
71248
+ } else if (moduleExportsOpened && !moduleExportsClosed && appsArrayOpened && !appsArrayClosed && squareDepth === 1 && curlyDepth === 1) {
71249
+ appCandidateActive = true;
71250
+ candidateHasName = false;
71251
+ candidateHasScript = false;
71252
+ candidateHasArgs = false;
71253
+ candidateHasEnv = false;
71254
+ candidateEnvOpenedOnLine = false;
71255
+ candidateEnvClosed = false;
71256
+ candidateEnv = /* @__PURE__ */ Object.create(null);
71257
+ } else if (appCandidateActive && squareDepth === 1 && curlyDepth === 2) {
71258
+ if (/\benv\s*:\s*\{$/.test(recentCode.trim())) {
71259
+ candidateHasEnv = true;
71260
+ candidateEnvOpenedOnLine = true;
71261
+ }
71262
+ }
71263
+ curlyDepth++;
71264
+ recentCode = "";
71265
+ i++;
71266
+ continue;
71267
+ }
71268
+ if (ch === "}") {
71269
+ if (curlyDepth === 0) return { kind: "rejected", text };
71270
+ if (templateCurlyStack.length > 0 && templateCurlyStack[templateCurlyStack.length - 1] === curlyDepth - 1) {
71271
+ templateCurlyStack.pop();
71272
+ mode = "template";
71273
+ curlyDepth--;
71274
+ recentCode = "";
71275
+ i++;
71276
+ continue;
71277
+ }
71278
+ if (appCandidateActive && candidateHasEnv && !candidateEnvClosed && curlyDepth === 3) {
71279
+ if (candidateEnvOpenedOnLine) {
71280
+ candidateHasEnv = false;
71281
+ }
71282
+ const beforeBrace = lineCode.slice(0, -1);
71283
+ if (beforeBrace.trim() && candidateEnv) {
71284
+ parseEnvLine(beforeBrace, candidateEnv);
71285
+ }
71286
+ candidateEnvClosed = true;
71287
+ } else if (appCandidateActive && curlyDepth === 2 && squareDepth === 1) {
71288
+ if (candidateHasName && candidateHasScript && candidateHasArgs && candidateHasEnv && candidateEnvClosed) {
71289
+ matchedAppCount++;
71290
+ if (matchedAppCount > 1) return { kind: "rejected", text };
71291
+ matchedAppEnv = candidateEnv;
71292
+ }
71293
+ appCandidateActive = false;
71294
+ candidateEnv = null;
71295
+ } else if (moduleExportsOpened && !moduleExportsClosed && curlyDepth === 1 && squareDepth === 0) {
71296
+ moduleExportsClosed = true;
71297
+ }
71298
+ curlyDepth--;
71299
+ recentCode = "";
71300
+ i++;
71301
+ continue;
71302
+ }
71303
+ if (ch === "[") {
71304
+ if (moduleExportsOpened && !moduleExportsClosed && curlyDepth === 1 && squareDepth === 0) {
71305
+ if (/\bapps\s*:\s*\[$/.test(recentCode.trim())) {
71306
+ if (appsArrayOpened) return { kind: "rejected", text };
71307
+ appsArrayOpened = true;
71308
+ }
71309
+ }
71310
+ squareDepth++;
71311
+ recentCode = "";
71312
+ i++;
71313
+ continue;
71314
+ }
71315
+ if (ch === "]") {
71316
+ if (squareDepth === 0) return { kind: "rejected", text };
71317
+ if (moduleExportsOpened && !moduleExportsClosed && appsArrayOpened && squareDepth === 1 && curlyDepth === 1) {
71318
+ appsArrayClosed = true;
71319
+ }
71320
+ squareDepth--;
71321
+ recentCode = "";
71322
+ i++;
71323
+ continue;
71324
+ }
71325
+ if (ch === "(") {
71326
+ parenDepth++;
71327
+ i++;
71328
+ continue;
71329
+ }
71330
+ if (ch === ")") {
71331
+ if (parenDepth === 0) return { kind: "rejected", text };
71332
+ parenDepth--;
71333
+ i++;
71334
+ continue;
71335
+ }
71336
+ i++;
71337
+ }
71338
+ if (mode !== "code") return { kind: "rejected", text };
71339
+ if (curlyDepth !== 0 || squareDepth !== 0 || parenDepth !== 0) return { kind: "rejected", text };
71340
+ if (!moduleExportsOpened || !moduleExportsClosed) return { kind: "rejected", text };
71341
+ if (!appsArrayOpened || !appsArrayClosed) return { kind: "rejected", text };
71342
+ if (matchedAppCount !== 1 || !matchedAppEnv) return { kind: "rejected", text };
71343
+ return { kind: "accepted", env: matchedAppEnv, text };
71344
+ }
71345
+ var CURATED_KEYS = [
71346
+ "CODEX_HOME",
71347
+ "CLAUDE_CONFIG_DIR",
71348
+ "MRRLIN_DIRECTOR_BRIDGE_LOG",
71349
+ "MRRLIN_API_BASE_URL",
71350
+ "MRRLIN_API_ALLOW_REMOTE",
71351
+ "MRRLIN_STAGING"
71352
+ ];
71353
+ function mergeConfigEnv(depsEnv, carriedEnv) {
71354
+ const isWin = process.platform === "win32";
71355
+ const DENIED_CARRIED_KEYS = new Set(isWin ? ["HOME", "PATH", "USER", "USERNAME"] : ["HOME", "PATH", "USER"]);
71356
+ const mergedEnv = /* @__PURE__ */ Object.create(null);
71357
+ const preservedNames = [];
71358
+ const droppedNames = [];
71359
+ const carriedKeys = Object.keys(carriedEnv);
71360
+ for (const key of carriedKeys) {
71361
+ if (DENIED_CARRIED_KEYS.has(key)) {
71362
+ droppedNames.push(key);
71363
+ continue;
71364
+ }
71365
+ const envVal = depsEnv[key];
71366
+ if (Object.hasOwn(depsEnv, key) && envVal !== void 0) {
71367
+ let installerVal = envVal;
71368
+ if (key === "CODEX_HOME" || key === "CLAUDE_CONFIG_DIR" || key === "MRRLIN_API_BASE_URL") {
71369
+ installerVal = installerVal.trim();
71370
+ }
71371
+ if (installerVal === "") {
71372
+ droppedNames.push(key);
71373
+ } else {
71374
+ preservedNames.push(key);
71375
+ mergedEnv[key] = installerVal;
71376
+ }
71377
+ } else {
71378
+ const carriedVal = carriedEnv[key];
71379
+ if (carriedVal !== void 0) {
71380
+ preservedNames.push(key);
71381
+ mergedEnv[key] = carriedVal;
71382
+ }
71383
+ }
71384
+ }
71385
+ for (const key of CURATED_KEYS) {
71386
+ if (Object.hasOwn(carriedEnv, key)) continue;
71387
+ const envVal = depsEnv[key];
71388
+ if (Object.hasOwn(depsEnv, key) && envVal !== void 0) {
71389
+ let val = envVal;
71390
+ if (key === "CODEX_HOME" || key === "CLAUDE_CONFIG_DIR" || key === "MRRLIN_API_BASE_URL") {
71391
+ val = val.trim();
71392
+ }
71393
+ if (val !== "") {
71394
+ mergedEnv[key] = val;
71395
+ }
71396
+ } else {
71397
+ if (key === "MRRLIN_DIRECTOR_BRIDGE_LOG") {
71398
+ mergedEnv[key] = "1";
71399
+ }
71400
+ }
71401
+ }
71402
+ preservedNames.sort();
71403
+ droppedNames.sort();
71404
+ return { mergedEnv, preservedNames, droppedNames };
71051
71405
  }
71052
- function readExistingServiceApiTarget(deps, ecoPath) {
71053
- try {
71054
- const existing = deps.readFile?.(ecoPath);
71055
- if (!existing || !isOwnedEcosystemConfig(existing)) return void 0;
71056
- const apiBaseUrl = readEnvString(existing, "MRRLIN_API_BASE_URL");
71057
- if (apiBaseUrl) {
71058
- return normalizeServiceApiTarget(apiBaseUrl, readEnvString(existing, "MRRLIN_API_ALLOW_REMOTE") === "1");
71406
+ function deriveMergedApiRouting(mergedEnv, invokingBridgeUrl) {
71407
+ const result = /* @__PURE__ */ Object.create(null);
71408
+ for (const k of Object.keys(mergedEnv)) {
71409
+ const v = mergedEnv[k];
71410
+ if (v !== void 0) {
71411
+ result[k] = v;
71059
71412
  }
71060
- if (readEnvString(existing, "MRRLIN_STAGING") === "1") {
71061
- return { apiBaseUrl: STAGING_API_BASE_URL };
71413
+ }
71414
+ if (Object.hasOwn(result, "MRRLIN_API_BASE_URL")) {
71415
+ const rawApi = result["MRRLIN_API_BASE_URL"];
71416
+ if (rawApi === "") {
71417
+ result["MRRLIN_API_BASE_URL"] = "";
71418
+ } else if (rawApi !== void 0) {
71419
+ const retargeted = retargetRetiredApiBaseUrl(rawApi).baseUrl;
71420
+ result["MRRLIN_API_BASE_URL"] = retargeted;
71421
+ }
71422
+ } else {
71423
+ if (result["MRRLIN_STAGING"] === "1") {
71424
+ result["MRRLIN_API_BASE_URL"] = STAGING_API_BASE_URL;
71425
+ } else if (bridgeUrlImpliesStaging(invokingBridgeUrl)) {
71426
+ result["MRRLIN_API_BASE_URL"] = STAGING_API_BASE_URL;
71062
71427
  }
71063
- return void 0;
71064
- } catch {
71065
- return void 0;
71066
71428
  }
71429
+ return result;
71430
+ }
71431
+ function escapeRegex2(s) {
71432
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
71067
71433
  }
71068
- function deriveServiceApiTarget(deps, ecoPath) {
71069
- const apiBaseUrl = deps.env.MRRLIN_API_BASE_URL?.trim();
71070
- if (apiBaseUrl) return normalizeServiceApiTarget(apiBaseUrl, deps.env.MRRLIN_API_ALLOW_REMOTE === "1");
71071
- if (deps.env.MRRLIN_STAGING === "1" || bridgeUrlImpliesStaging(deps.env.MRRLIN_BRIDGE_URL)) {
71072
- return { apiBaseUrl: STAGING_API_BASE_URL };
71434
+ function performConfigBackup(deps, ecoPath, existingText) {
71435
+ try {
71436
+ if (!deps.listDir || !deps.writeExclusiveFile) {
71437
+ deps.log("Service config backup failed.");
71438
+ return;
71439
+ }
71440
+ const dir = import_node_path38.default.dirname(ecoPath);
71441
+ let files;
71442
+ try {
71443
+ files = deps.listDir(dir);
71444
+ } catch {
71445
+ deps.log("Service config backup failed.");
71446
+ return;
71447
+ }
71448
+ if (!files || !Array.isArray(files)) {
71449
+ deps.log("Service config backup failed.");
71450
+ return;
71451
+ }
71452
+ const base = import_node_path38.default.basename(ecoPath);
71453
+ const backupRegex = new RegExp(`^${escapeRegex2(base)}\\.bak-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}Z$`);
71454
+ const existingBackups = files.filter((f) => backupRegex.test(f));
71455
+ if (existingBackups.length >= 10) {
71456
+ deps.log("Service config backup failed.");
71457
+ return;
71458
+ }
71459
+ let now;
71460
+ try {
71461
+ now = deps.clock ? deps.clock() : /* @__PURE__ */ new Date();
71462
+ } catch {
71463
+ deps.log("Service config backup failed.");
71464
+ return;
71465
+ }
71466
+ let iso;
71467
+ try {
71468
+ iso = now.toISOString();
71469
+ } catch {
71470
+ deps.log("Service config backup failed.");
71471
+ return;
71472
+ }
71473
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(iso)) {
71474
+ deps.log("Service config backup failed.");
71475
+ return;
71476
+ }
71477
+ const ts = iso.replace(/:/g, "-").replace(/\./g, "-");
71478
+ const backupPath = `${ecoPath}.bak-${ts}`;
71479
+ deps.writeExclusiveFile(backupPath, existingText);
71480
+ } catch {
71481
+ deps.log("Service config backup failed.");
71073
71482
  }
71074
- return readExistingServiceApiTarget(deps, ecoPath) ?? {};
71075
71483
  }
71076
71484
  var SYSTEM_PATH_DIRS = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"];
71077
71485
  function defaultHomeOwnerUid(home) {
@@ -71147,19 +71555,34 @@ function installService(deps) {
71147
71555
  /* @__PURE__ */ new Set([...resolvedBins.map((b) => import_node_path38.default.dirname(b)), ...systemDirs])
71148
71556
  ).join(import_node_path38.default.delimiter);
71149
71557
  const ecoPath = import_node_path38.default.join(home, ".mrrlin", "ecosystem.config.cjs");
71150
- const apiTarget = deriveServiceApiTarget(deps, ecoPath);
71558
+ const existingText = deps.readFile ? deps.readFile(ecoPath) : null;
71559
+ const parseResult = parseExistingEcosystemConfig(existingText);
71560
+ if (parseResult.kind === "rejected") {
71561
+ deps.log("Service config env carry skipped.");
71562
+ }
71563
+ if (existingText !== null && existingText !== void 0) {
71564
+ performConfigBackup(deps, ecoPath, existingText);
71565
+ }
71566
+ const carriedEnv = parseResult.kind === "accepted" ? parseResult.env : /* @__PURE__ */ Object.create(null);
71567
+ const { mergedEnv, preservedNames, droppedNames } = mergeConfigEnv(deps.env, carriedEnv);
71568
+ const routedEnv = deriveMergedApiRouting(mergedEnv, deps.env.MRRLIN_BRIDGE_URL);
71569
+ routedEnv.HOME = home;
71570
+ routedEnv.PATH = pathEnv;
71571
+ if (user !== void 0) {
71572
+ routedEnv.USER = user;
71573
+ }
71574
+ if (preservedNames.length > 0) {
71575
+ deps.log(`Service env preserved: ${preservedNames.join(", ")}.`);
71576
+ }
71577
+ if (droppedNames.length > 0) {
71578
+ deps.log(`Service env dropped: ${droppedNames.join(", ")}.`);
71579
+ }
71151
71580
  const text = renderEcosystemConfig({
71152
71581
  cwd: resolved.cwd,
71153
- home,
71154
- user,
71155
- codexHome: deps.env.CODEX_HOME?.trim() || void 0,
71156
- claudeConfigDir: deps.env.CLAUDE_CONFIG_DIR?.trim() || void 0,
71157
- pathEnv,
71158
- apiBaseUrl: apiTarget.apiBaseUrl,
71159
- apiAllowRemote: apiTarget.apiAllowRemote
71582
+ env: routedEnv
71160
71583
  });
71161
71584
  deps.writeFile(ecoPath, text);
71162
- const start = deps.runPm2(["startOrReload", ecoPath, "--reset-env"]);
71585
+ const start = deps.runPm2(["startOrReload", ecoPath, "--update-env"]);
71163
71586
  if (start.code !== 0) {
71164
71587
  deps.log(`pm2 startOrReload failed (exit ${start.code}).`);
71165
71588
  return { ok: false };
@@ -71782,6 +72205,15 @@ async function main() {
71782
72205
  (0, import_node_fs25.mkdirSync)(import_node_path42.default.dirname(p), { recursive: true, mode: 448 });
71783
72206
  (0, import_node_fs25.writeFileSync)(p, c, { mode: 384 });
71784
72207
  },
72208
+ writeExclusiveFile: (p, c) => (0, import_node_fs25.writeFileSync)(p, c, { flag: "wx", mode: 384 }),
72209
+ listDir: (p) => {
72210
+ try {
72211
+ return (0, import_node_fs25.readdirSync)(p);
72212
+ } catch {
72213
+ return null;
72214
+ }
72215
+ },
72216
+ clock: () => /* @__PURE__ */ new Date(),
71785
72217
  // Swallows every error on purpose — the only caller uses this to recover settings from the
71786
72218
  // config it is about to overwrite, and a missing or unreadable file means "no previous
71787
72219
  // value", never a failed install. See readExistingTelemetryDefaultProject.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrrlin-dev/mcp",
3
- "version": "0.4.87",
3
+ "version": "0.4.88",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mrrlin-mcp": "dist/bin.cjs"