morphix-env 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -831,8 +831,8 @@ var require_react_development = __commonJS({
831
831
  function lazyInitializer(payload) {
832
832
  if (-1 === payload._status) {
833
833
  var resolveDebugValue = null, rejectDebugValue = null, ioInfo = payload._ioInfo;
834
- null != ioInfo && (ioInfo.start = ioInfo.end = performance.now(), ioInfo.value = new Promise(function(resolve6, reject) {
835
- resolveDebugValue = resolve6;
834
+ null != ioInfo && (ioInfo.start = ioInfo.end = performance.now(), ioInfo.value = new Promise(function(resolve7, reject) {
835
+ resolveDebugValue = resolve7;
836
836
  rejectDebugValue = reject;
837
837
  }));
838
838
  ioInfo = payload._result;
@@ -948,21 +948,21 @@ var require_react_development = __commonJS({
948
948
  );
949
949
  actScopeDepth = prevActScopeDepth;
950
950
  }
951
- function recursivelyFlushAsyncActWork(returnValue, resolve6, reject) {
951
+ function recursivelyFlushAsyncActWork(returnValue, resolve7, reject) {
952
952
  var queue = ReactSharedInternals.actQueue;
953
953
  if (null !== queue)
954
954
  if (0 !== queue.length)
955
955
  try {
956
956
  flushActQueue(queue);
957
957
  enqueueTask(function() {
958
- return recursivelyFlushAsyncActWork(returnValue, resolve6, reject);
958
+ return recursivelyFlushAsyncActWork(returnValue, resolve7, reject);
959
959
  });
960
960
  return;
961
961
  } catch (error) {
962
962
  ReactSharedInternals.thrownErrors.push(error);
963
963
  }
964
964
  else ReactSharedInternals.actQueue = null;
965
- 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve6(returnValue);
965
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve7(returnValue);
966
966
  }
967
967
  function flushActQueue(queue) {
968
968
  if (!isFlushing) {
@@ -1150,7 +1150,7 @@ var require_react_development = __commonJS({
1150
1150
  ));
1151
1151
  });
1152
1152
  return {
1153
- then: function(resolve6, reject) {
1153
+ then: function(resolve7, reject) {
1154
1154
  didAwaitActCall = true;
1155
1155
  thenable.then(
1156
1156
  function(returnValue) {
@@ -1160,7 +1160,7 @@ var require_react_development = __commonJS({
1160
1160
  flushActQueue(queue), enqueueTask(function() {
1161
1161
  return recursivelyFlushAsyncActWork(
1162
1162
  returnValue,
1163
- resolve6,
1163
+ resolve7,
1164
1164
  reject
1165
1165
  );
1166
1166
  });
@@ -1174,7 +1174,7 @@ var require_react_development = __commonJS({
1174
1174
  ReactSharedInternals.thrownErrors.length = 0;
1175
1175
  reject(_thrownError);
1176
1176
  }
1177
- } else resolve6(returnValue);
1177
+ } else resolve7(returnValue);
1178
1178
  },
1179
1179
  function(error) {
1180
1180
  popActScope(prevActQueue, prevActScopeDepth);
@@ -1196,15 +1196,15 @@ var require_react_development = __commonJS({
1196
1196
  if (0 < ReactSharedInternals.thrownErrors.length)
1197
1197
  throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
1198
1198
  return {
1199
- then: function(resolve6, reject) {
1199
+ then: function(resolve7, reject) {
1200
1200
  didAwaitActCall = true;
1201
1201
  0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
1202
1202
  return recursivelyFlushAsyncActWork(
1203
1203
  returnValue$jscomp$0,
1204
- resolve6,
1204
+ resolve7,
1205
1205
  reject
1206
1206
  );
1207
- })) : resolve6(returnValue$jscomp$0);
1207
+ })) : resolve7(returnValue$jscomp$0);
1208
1208
  }
1209
1209
  };
1210
1210
  };
@@ -12991,7 +12991,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
12991
12991
  };
12992
12992
  }
12993
12993
  exports2.prerender = function(children, options) {
12994
- return new Promise(function(resolve6, reject) {
12994
+ return new Promise(function(resolve7, reject) {
12995
12995
  var onHeaders = options ? options.onHeaders : void 0, onHeadersImpl;
12996
12996
  onHeaders && (onHeadersImpl = function(headersDescriptor) {
12997
12997
  onHeaders(new Headers(headersDescriptor));
@@ -13035,7 +13035,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13035
13035
  { highWaterMark: 0 }
13036
13036
  );
13037
13037
  stream2 = { postponed: getPostponedState(request), prelude: stream2 };
13038
- resolve6(stream2);
13038
+ resolve7(stream2);
13039
13039
  },
13040
13040
  void 0,
13041
13041
  void 0,
@@ -13046,7 +13046,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13046
13046
  });
13047
13047
  };
13048
13048
  exports2.prerenderToNodeStream = function(children, options) {
13049
- return new Promise(function(resolve6, reject) {
13049
+ return new Promise(function(resolve7, reject) {
13050
13050
  var resumableState = createResumableState(
13051
13051
  options ? options.identifierPrefix : void 0,
13052
13052
  options ? options.unstable_externalRuntimeSrc : void 0,
@@ -13078,7 +13078,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13078
13078
  postponed: getPostponedState(request),
13079
13079
  prelude: readable
13080
13080
  };
13081
- resolve6(readable);
13081
+ resolve7(readable);
13082
13082
  },
13083
13083
  void 0,
13084
13084
  void 0,
@@ -13123,7 +13123,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13123
13123
  };
13124
13124
  };
13125
13125
  exports2.renderToReadableStream = function(children, options) {
13126
- return new Promise(function(resolve6, reject) {
13126
+ return new Promise(function(resolve7, reject) {
13127
13127
  var onFatalError, onAllReady, allReady = new Promise(function(res, rej) {
13128
13128
  onAllReady = res;
13129
13129
  onFatalError = rej;
@@ -13173,7 +13173,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13173
13173
  { highWaterMark: 0 }
13174
13174
  );
13175
13175
  stream2.allReady = allReady;
13176
- resolve6(stream2);
13176
+ resolve7(stream2);
13177
13177
  },
13178
13178
  function(error) {
13179
13179
  allReady.catch(function() {
@@ -13188,7 +13188,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13188
13188
  });
13189
13189
  };
13190
13190
  exports2.resume = function(children, postponedState, options) {
13191
- return new Promise(function(resolve6, reject) {
13191
+ return new Promise(function(resolve7, reject) {
13192
13192
  var onFatalError, onAllReady, allReady = new Promise(function(res, rej) {
13193
13193
  onAllReady = res;
13194
13194
  onFatalError = rej;
@@ -13226,7 +13226,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13226
13226
  { highWaterMark: 0 }
13227
13227
  );
13228
13228
  stream2.allReady = allReady;
13229
- resolve6(stream2);
13229
+ resolve7(stream2);
13230
13230
  },
13231
13231
  function(error) {
13232
13232
  allReady.catch(function() {
@@ -13240,7 +13240,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13240
13240
  });
13241
13241
  };
13242
13242
  exports2.resumeAndPrerender = function(children, postponedState, options) {
13243
- return new Promise(function(resolve6, reject) {
13243
+ return new Promise(function(resolve7, reject) {
13244
13244
  var request = resumeAndPrerenderRequest(
13245
13245
  children,
13246
13246
  postponedState,
@@ -13272,7 +13272,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13272
13272
  { highWaterMark: 0 }
13273
13273
  );
13274
13274
  stream2 = { postponed: getPostponedState(request), prelude: stream2 };
13275
- resolve6(stream2);
13275
+ resolve7(stream2);
13276
13276
  },
13277
13277
  void 0,
13278
13278
  void 0,
@@ -13283,7 +13283,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13283
13283
  });
13284
13284
  };
13285
13285
  exports2.resumeAndPrerenderToNodeStream = function(children, postponedState, options) {
13286
- return new Promise(function(resolve6, reject) {
13286
+ return new Promise(function(resolve7, reject) {
13287
13287
  var request = resumeAndPrerenderRequest(
13288
13288
  children,
13289
13289
  postponedState,
@@ -13304,7 +13304,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
13304
13304
  }
13305
13305
  }), writable = createFakeWritableFromReadable(readable);
13306
13306
  readable = { postponed: getPostponedState(request), prelude: readable };
13307
- resolve6(readable);
13307
+ resolve7(readable);
13308
13308
  },
13309
13309
  void 0,
13310
13310
  void 0,
@@ -28776,7 +28776,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
28776
28776
  ensureCorrectIsomorphicReactVersion();
28777
28777
  ensureCorrectIsomorphicReactVersion();
28778
28778
  exports2.prerender = function(children, options) {
28779
- return new Promise(function(resolve6, reject) {
28779
+ return new Promise(function(resolve7, reject) {
28780
28780
  var onHeaders = options ? options.onHeaders : void 0, onHeadersImpl;
28781
28781
  onHeaders && (onHeadersImpl = function(headersDescriptor) {
28782
28782
  onHeaders(new Headers(headersDescriptor));
@@ -28825,7 +28825,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
28825
28825
  postponed: getPostponedState(request),
28826
28826
  prelude: stream2
28827
28827
  };
28828
- resolve6(stream2);
28828
+ resolve7(stream2);
28829
28829
  },
28830
28830
  void 0,
28831
28831
  void 0,
@@ -28836,7 +28836,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
28836
28836
  });
28837
28837
  };
28838
28838
  exports2.prerenderToNodeStream = function(children, options) {
28839
- return new Promise(function(resolve6, reject) {
28839
+ return new Promise(function(resolve7, reject) {
28840
28840
  var resumableState = createResumableState(
28841
28841
  options ? options.identifierPrefix : void 0,
28842
28842
  options ? options.unstable_externalRuntimeSrc : void 0,
@@ -28868,7 +28868,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
28868
28868
  postponed: getPostponedState(request),
28869
28869
  prelude: readable
28870
28870
  };
28871
- resolve6(readable);
28871
+ resolve7(readable);
28872
28872
  },
28873
28873
  void 0,
28874
28874
  void 0,
@@ -28913,7 +28913,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
28913
28913
  };
28914
28914
  };
28915
28915
  exports2.renderToReadableStream = function(children, options) {
28916
- return new Promise(function(resolve6, reject) {
28916
+ return new Promise(function(resolve7, reject) {
28917
28917
  var onFatalError, onAllReady, allReady = new Promise(function(res, rej) {
28918
28918
  onAllReady = res;
28919
28919
  onFatalError = rej;
@@ -28963,7 +28963,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
28963
28963
  { highWaterMark: 0 }
28964
28964
  );
28965
28965
  stream2.allReady = allReady;
28966
- resolve6(stream2);
28966
+ resolve7(stream2);
28967
28967
  },
28968
28968
  function(error) {
28969
28969
  allReady.catch(function() {
@@ -28978,7 +28978,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
28978
28978
  });
28979
28979
  };
28980
28980
  exports2.resume = function(children, postponedState, options) {
28981
- return new Promise(function(resolve6, reject) {
28981
+ return new Promise(function(resolve7, reject) {
28982
28982
  var onFatalError, onAllReady, allReady = new Promise(function(res, rej) {
28983
28983
  onAllReady = res;
28984
28984
  onFatalError = rej;
@@ -29016,7 +29016,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
29016
29016
  { highWaterMark: 0 }
29017
29017
  );
29018
29018
  stream2.allReady = allReady;
29019
- resolve6(stream2);
29019
+ resolve7(stream2);
29020
29020
  },
29021
29021
  function(error) {
29022
29022
  allReady.catch(function() {
@@ -29030,7 +29030,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
29030
29030
  });
29031
29031
  };
29032
29032
  exports2.resumeAndPrerender = function(children, postponedState, options) {
29033
- return new Promise(function(resolve6, reject) {
29033
+ return new Promise(function(resolve7, reject) {
29034
29034
  var request = resumeAndPrerenderRequest(
29035
29035
  children,
29036
29036
  postponedState,
@@ -29064,7 +29064,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
29064
29064
  { highWaterMark: 0 }
29065
29065
  );
29066
29066
  stream2 = { postponed: getPostponedState(request), prelude: stream2 };
29067
- resolve6(stream2);
29067
+ resolve7(stream2);
29068
29068
  },
29069
29069
  void 0,
29070
29070
  void 0,
@@ -29075,7 +29075,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
29075
29075
  });
29076
29076
  };
29077
29077
  exports2.resumeAndPrerenderToNodeStream = function(children, postponedState, options) {
29078
- return new Promise(function(resolve6, reject) {
29078
+ return new Promise(function(resolve7, reject) {
29079
29079
  var request = resumeAndPrerenderRequest(
29080
29080
  children,
29081
29081
  postponedState,
@@ -29099,7 +29099,7 @@ A.finished.finally(function(){document.__reactViewTransition===A&&(document.__re
29099
29099
  postponed: getPostponedState(request),
29100
29100
  prelude: readable
29101
29101
  };
29102
- resolve6(readable);
29102
+ resolve7(readable);
29103
29103
  },
29104
29104
  void 0,
29105
29105
  void 0,
@@ -29482,9 +29482,9 @@ var require_jsx_runtime = __commonJS({
29482
29482
 
29483
29483
  // src/cli.ts
29484
29484
  var import_cross_spawn = __toESM(require("cross-spawn"));
29485
- var import_fs8 = require("fs");
29486
- var import_os5 = require("os");
29487
- var import_path8 = require("path");
29485
+ var import_fs10 = require("fs");
29486
+ var import_os7 = require("os");
29487
+ var import_path10 = require("path");
29488
29488
 
29489
29489
  // src/audit.ts
29490
29490
  var import_fs = require("fs");
@@ -29502,11 +29502,12 @@ function writeAudit(event, homeDir = (0, import_os.homedir)()) {
29502
29502
  }
29503
29503
 
29504
29504
  // src/composition.ts
29505
- var import_os2 = require("os");
29505
+ var import_os3 = require("os");
29506
29506
 
29507
29507
  // src/config.ts
29508
29508
  var import_fs2 = require("fs");
29509
29509
  var import_path2 = require("path");
29510
+ var import_os2 = require("os");
29510
29511
  var CONFIG_FILE = "mx-env.config.json";
29511
29512
  var GLOBAL_CONFIG_DIR = ".mx-env";
29512
29513
  var GLOBAL_CONFIG_FILE = "config.json";
@@ -29534,7 +29535,7 @@ function loadConfigAt(path) {
29534
29535
  }
29535
29536
  function loadConfigLayers(options = {}) {
29536
29537
  const cwd = options.cwd || process.cwd();
29537
- const homeDir = options.homeDir || (0, import_path2.homedir)();
29538
+ const homeDir = options.homeDir || (0, import_os2.homedir)();
29538
29539
  const globalDir = (0, import_path2.resolve)(homeDir, GLOBAL_CONFIG_DIR);
29539
29540
  return {
29540
29541
  global: loadConfigAt((0, import_path2.resolve)(globalDir, GLOBAL_CONFIG_FILE)),
@@ -29549,7 +29550,7 @@ function normalizeProfile(input) {
29549
29550
  ...input?.extends ? { extends: input.extends } : {}
29550
29551
  };
29551
29552
  }
29552
- function expandHomePath(path, homeDir = (0, import_path2.homedir)()) {
29553
+ function expandHomePath(path, homeDir = (0, import_os2.homedir)()) {
29553
29554
  if (path === "~") return homeDir;
29554
29555
  if (path.startsWith("~/")) return (0, import_path2.resolve)(homeDir, path.slice(2));
29555
29556
  return path;
@@ -29580,6 +29581,14 @@ function validateConfig(config, label = CONFIG_FILE) {
29580
29581
  if (!source.project || !source.config) errors.push(`${label}: Doppler source "${name}" needs project and config`);
29581
29582
  if (source.optional) errors.push(`${label}: remote source "${name}" cannot be optional`);
29582
29583
  break;
29584
+ case "plugin":
29585
+ if (typeof source.plugin !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(source.plugin)) {
29586
+ errors.push(`${label}: plugin source "${name}" needs a plugin name (letters, digits, ".", "_", "-")`);
29587
+ }
29588
+ if (!Array.isArray(source.keys) || !source.keys.length || source.keys.some((key) => typeof key !== "string" || !key)) {
29589
+ errors.push(`${label}: plugin source "${name}" needs at least one key`);
29590
+ }
29591
+ break;
29583
29592
  default:
29584
29593
  errors.push(`${label}: source "${name}" has an unsupported provider`);
29585
29594
  }
@@ -29910,6 +29919,31 @@ function resolveDoppler(entry, source, context) {
29910
29919
  function parseDotenvText(text) {
29911
29920
  return (0, import_dotenv3.parse)(text);
29912
29921
  }
29922
+ var PLUGIN_TIMEOUT_MS = 3e4;
29923
+ function resolvePlugin(entry, source, context) {
29924
+ const command = `mx-env-provider-${source.plugin}`;
29925
+ const values = {};
29926
+ for (const key of source.keys) {
29927
+ let output;
29928
+ try {
29929
+ output = (0, import_child_process2.execFileSync)(command, [key], {
29930
+ encoding: "utf8",
29931
+ stdio: ["ignore", "pipe", "pipe"],
29932
+ env: { ...process.env, ...context.environment },
29933
+ timeout: PLUGIN_TIMEOUT_MS
29934
+ });
29935
+ } catch (error) {
29936
+ if (error?.code === "ENOENT") {
29937
+ if (source.optional) return { name: entry.name, provider: source.provider, values: {}, availability: "missing-optional" };
29938
+ throw new SourceError(entry.name, source.provider, `[morphix-env] plugin source "${entry.name}" needs ${command} on PATH.`);
29939
+ }
29940
+ const reason = typeof error?.code === "string" ? error.code : `exit ${error?.status ?? "unknown"}`;
29941
+ throw new SourceError(entry.name, source.provider, `[morphix-env] plugin source "${entry.name}" failed for key "${key}" (${reason}).`);
29942
+ }
29943
+ values[key] = output.endsWith("\r\n") ? output.slice(0, -2) : output.endsWith("\n") ? output.slice(0, -1) : output;
29944
+ }
29945
+ return { name: entry.name, provider: source.provider, values, availability: "loaded" };
29946
+ }
29913
29947
  async function loadSource(entry, context) {
29914
29948
  switch (entry.source.provider) {
29915
29949
  case "local":
@@ -29922,6 +29956,8 @@ async function loadSource(entry, context) {
29922
29956
  return resolveKeychain(entry, entry.source);
29923
29957
  case "doppler":
29924
29958
  return resolveDoppler(entry, entry.source, context);
29959
+ case "plugin":
29960
+ return resolvePlugin(entry, entry.source, context);
29925
29961
  }
29926
29962
  }
29927
29963
 
@@ -29936,6 +29972,9 @@ var CompositionError = class extends Error {
29936
29972
  this.name = "CompositionError";
29937
29973
  }
29938
29974
  };
29975
+ function collectRedactionValues(environment, provenance) {
29976
+ return [...new Set(Object.entries(provenance).filter(([key]) => !isPublicKey(key) && Boolean(environment[key])).map(([key]) => environment[key]).filter(Boolean))];
29977
+ }
29939
29978
  function declaredEntries(loaded) {
29940
29979
  return new Map(Object.entries(loaded.config.sources || {}).map(([name, source]) => [name, {
29941
29980
  name,
@@ -30045,7 +30084,7 @@ async function composeEnvironment(layers, options = {}) {
30045
30084
  const environment = { ...options.baseEnvironment || process.env };
30046
30085
  const owner = /* @__PURE__ */ new Map();
30047
30086
  const sourceStatuses = [];
30048
- const homeDir = options.homeDir || (0, import_os2.homedir)();
30087
+ const homeDir = options.homeDir || (0, import_os3.homedir)();
30049
30088
  for (let precedence = 0; precedence < entries.length; precedence++) {
30050
30089
  const entry = entries[precedence];
30051
30090
  const loaded = await loadSource(entry, {
@@ -30078,13 +30117,14 @@ async function composeEnvironment(layers, options = {}) {
30078
30117
  });
30079
30118
  }
30080
30119
  }
30120
+ const provenance = Object.fromEntries(owner);
30081
30121
  return {
30082
30122
  environment,
30083
30123
  profileName,
30084
30124
  sourceStatuses,
30085
- provenance: Object.fromEntries(owner),
30125
+ provenance,
30086
30126
  sourceEntries: entries,
30087
- redactionValues: [...new Set([...owner.entries()].filter(([key]) => !isPublicKey(key) && Boolean(environment[key])).map(([key]) => environment[key]).filter(Boolean))]
30127
+ redactionValues: collectRedactionValues(environment, provenance)
30088
30128
  };
30089
30129
  }
30090
30130
 
@@ -30092,7 +30132,7 @@ async function composeEnvironment(layers, options = {}) {
30092
30132
  var import_child_process3 = require("child_process");
30093
30133
  var import_http = require("http");
30094
30134
  var import_fs6 = require("fs");
30095
- var import_os3 = require("os");
30135
+ var import_os4 = require("os");
30096
30136
  var import_path6 = require("path");
30097
30137
  var import_crypto = require("crypto");
30098
30138
 
@@ -36909,7 +36949,7 @@ function localFile(entry, fileIndex = 0) {
36909
36949
  const source = entry.source;
36910
36950
  if (source.provider !== "local" && source.provider !== "dotenvx") throw new Error("Source does not use files");
36911
36951
  const raw = source.files[fileIndex];
36912
- const expanded = expandHomePath(raw, (0, import_os3.homedir)());
36952
+ const expanded = expandHomePath(raw, (0, import_os4.homedir)());
36913
36953
  return (0, import_path6.isAbsolute)(expanded) ? expanded : (0, import_path6.resolve)(entry.baseDir, expanded);
36914
36954
  }
36915
36955
  function sourceUrl(entry) {
@@ -37061,14 +37101,14 @@ async function startEditorServer(encodedState) {
37061
37101
  });
37062
37102
  const closeTimer = setTimeout(() => server.close(), EDITOR_LIFETIME_MS);
37063
37103
  closeTimer.unref();
37064
- await new Promise((resolve6, reject) => {
37104
+ await new Promise((resolve7, reject) => {
37065
37105
  server.once("error", reject);
37066
37106
  server.listen(0, "127.0.0.1", () => {
37067
37107
  const address = server.address();
37068
37108
  if (!address || typeof address === "string") return reject(new Error("Editor did not receive a loopback port"));
37069
37109
  process.stdout.write(`http://127.0.0.1:${address.port}${editorPath(state)}
37070
37110
  `);
37071
- resolve6();
37111
+ resolve7();
37072
37112
  });
37073
37113
  });
37074
37114
  }
@@ -37089,7 +37129,7 @@ async function launchEditor(entryScript, composition, selectedSource) {
37089
37129
  detached: true,
37090
37130
  stdio: ["ignore", "pipe", "ignore"]
37091
37131
  });
37092
- const url = await new Promise((resolve6, reject) => {
37132
+ const url = await new Promise((resolve7, reject) => {
37093
37133
  const timer = setTimeout(() => reject(new Error("[morphix-env] Timed out starting local editor.")), 5e3);
37094
37134
  child.once("error", () => {
37095
37135
  clearTimeout(timer);
@@ -37099,7 +37139,7 @@ async function launchEditor(entryScript, composition, selectedSource) {
37099
37139
  clearTimeout(timer);
37100
37140
  const url2 = chunk.toString("utf8").trim();
37101
37141
  child.stdout?.destroy();
37102
- resolve6(url2);
37142
+ resolve7(url2);
37103
37143
  });
37104
37144
  });
37105
37145
  child.unref();
@@ -37182,15 +37222,86 @@ var StreamingRedactor = class {
37182
37222
  }
37183
37223
  };
37184
37224
 
37225
+ // src/refs.ts
37226
+ var MX_ENV_REFERENCE_SOURCE = "mx-env:\\/\\/([A-Za-z0-9][A-Za-z0-9_-]*)\\/([A-Za-z0-9_][A-Za-z0-9_.-]*)";
37227
+ function referencePattern() {
37228
+ return new RegExp(MX_ENV_REFERENCE_SOURCE, "g");
37229
+ }
37230
+ function findMxEnvReferences(text) {
37231
+ const found = /* @__PURE__ */ new Map();
37232
+ for (const match of text.matchAll(referencePattern())) {
37233
+ const reference = { profile: match[1], key: match[2] };
37234
+ found.set(`${reference.profile}/${reference.key}`, reference);
37235
+ }
37236
+ return [...found.values()];
37237
+ }
37238
+ function hasMxEnvReference(text) {
37239
+ return referencePattern().test(text);
37240
+ }
37241
+ var ReferenceResolver = class {
37242
+ constructor(layers, composeOptions2) {
37243
+ this.layers = layers;
37244
+ this.composeOptions = composeOptions2;
37245
+ this.compositions = /* @__PURE__ */ new Map();
37246
+ }
37247
+ compose(profile) {
37248
+ let pending = this.compositions.get(profile);
37249
+ if (!pending) {
37250
+ pending = composeEnvironment(this.layers, { ...this.composeOptions, profile });
37251
+ this.compositions.set(profile, pending);
37252
+ }
37253
+ return pending;
37254
+ }
37255
+ /**
37256
+ * The value of KEY as `profile` composes it, resolving nested references.
37257
+ * The chain carries `profile/key` tokens of the current resolution path so
37258
+ * only a genuine loop (the same key re-entered) is a cycle — a nested lookup
37259
+ * of a different key in the same profile stays legal.
37260
+ */
37261
+ async valueOf(profile, key, chain = []) {
37262
+ const token = `${profile}/${key}`;
37263
+ if (chain.includes(token)) return { missing: { profile, key, reason: "cycle" } };
37264
+ let composition;
37265
+ try {
37266
+ composition = await this.compose(profile);
37267
+ } catch {
37268
+ return { missing: { profile, key, reason: "missing" } };
37269
+ }
37270
+ const raw = composition.environment[key];
37271
+ if (raw === void 0 || raw === null) return { missing: { profile, key, reason: "missing" } };
37272
+ if (!hasMxEnvReference(raw)) return { value: raw };
37273
+ const resolved = await this.resolveText(raw, [...chain, token]);
37274
+ if (resolved.missing.length) return { missing: resolved.missing[0] };
37275
+ return { value: resolved.text };
37276
+ }
37277
+ /** Substitutes every resolvable reference; unresolvable ones stay literal. */
37278
+ async resolveText(text, chain = []) {
37279
+ const references = findMxEnvReferences(text);
37280
+ if (!references.length) return { text, missing: [] };
37281
+ const resolved = await Promise.all(references.map(async (reference) => ({
37282
+ reference,
37283
+ result: await this.valueOf(reference.profile, reference.key, chain)
37284
+ })));
37285
+ const values = /* @__PURE__ */ new Map();
37286
+ const missing = [];
37287
+ for (const { reference, result } of resolved) {
37288
+ if (result.value !== void 0) values.set(`${reference.profile}/${reference.key}`, result.value);
37289
+ else if (result.missing) missing.push(result.missing);
37290
+ }
37291
+ const output = text.replace(referencePattern(), (match, profile, key) => values.get(`${profile}/${key}`) ?? match);
37292
+ return { text: output, missing };
37293
+ }
37294
+ };
37295
+
37185
37296
  // src/skill.ts
37186
37297
  var import_fs7 = require("fs");
37187
- var import_os4 = require("os");
37298
+ var import_os5 = require("os");
37188
37299
  var import_path7 = require("path");
37189
37300
  var SKILL_TARGETS = ["global", "project", "trellis"];
37190
37301
  function resolveSkillPath(target) {
37191
37302
  if (target === "project") return (0, import_path7.join)(process.cwd(), ".claude", "skills", "morphix-env", "SKILL.md");
37192
- if (target === "trellis") return (0, import_path7.join)((0, import_os4.homedir)(), ".trellis", "skills", "morphix-env", "SKILL.md");
37193
- return (0, import_path7.join)((0, import_os4.homedir)(), ".claude", "skills", "morphix-env", "SKILL.md");
37303
+ if (target === "trellis") return (0, import_path7.join)((0, import_os5.homedir)(), ".trellis", "skills", "morphix-env", "SKILL.md");
37304
+ return (0, import_path7.join)((0, import_os5.homedir)(), ".claude", "skills", "morphix-env", "SKILL.md");
37194
37305
  }
37195
37306
  function targetHint(target) {
37196
37307
  if (target === "project") {
@@ -37252,10 +37363,418 @@ function cmdSkill(subcommand, options) {
37252
37363
  return 0;
37253
37364
  }
37254
37365
 
37366
+ // src/serve.ts
37367
+ var import_http2 = require("http");
37368
+ var import_crypto2 = require("crypto");
37369
+ function authorized(req, token) {
37370
+ const header = req.headers.authorization;
37371
+ const presented = typeof header === "string" && header.startsWith("Bearer ") ? Buffer.from(header.slice("Bearer ".length)) : Buffer.alloc(0);
37372
+ return presented.length === token.length && (0, import_crypto2.timingSafeEqual)(presented, token);
37373
+ }
37374
+ async function startSecretServer(values) {
37375
+ const token = (0, import_crypto2.randomBytes)(24).toString("base64url");
37376
+ const tokenBuffer = Buffer.from(token);
37377
+ const server = (0, import_http2.createServer)((req, res) => {
37378
+ if (!authorized(req, tokenBuffer)) {
37379
+ res.writeHead(401, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
37380
+ res.end("unauthorized");
37381
+ return;
37382
+ }
37383
+ const path = (req.url || "").split("?")[0];
37384
+ if (req.method !== "GET") {
37385
+ res.writeHead(405, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
37386
+ res.end("method not allowed");
37387
+ return;
37388
+ }
37389
+ const match = /^\/key\/(.+)$/.exec(path);
37390
+ if (!match) {
37391
+ res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
37392
+ res.end("not found");
37393
+ return;
37394
+ }
37395
+ let key;
37396
+ try {
37397
+ key = decodeURIComponent(match[1]);
37398
+ } catch {
37399
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
37400
+ res.end("bad request");
37401
+ return;
37402
+ }
37403
+ if (!Object.prototype.hasOwnProperty.call(values, key)) {
37404
+ res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
37405
+ res.end("unknown key");
37406
+ return;
37407
+ }
37408
+ res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-store" });
37409
+ res.end(values[key]);
37410
+ });
37411
+ await new Promise((resolvePromise, reject) => {
37412
+ server.once("error", reject);
37413
+ server.listen(0, "127.0.0.1", () => {
37414
+ server.removeListener("error", reject);
37415
+ server.on("error", (error) => {
37416
+ console.error(`[morphix-env] Secret server error: ${error instanceof Error ? error.message : "unknown"}`);
37417
+ });
37418
+ resolvePromise();
37419
+ });
37420
+ });
37421
+ const address = server.address();
37422
+ if (!address || typeof address === "string") {
37423
+ server.close();
37424
+ throw new Error("[morphix-env] Secret server did not receive a loopback port.");
37425
+ }
37426
+ let closed = false;
37427
+ return {
37428
+ endpoint: `http://127.0.0.1:${address.port}`,
37429
+ token,
37430
+ close: () => {
37431
+ if (closed) return;
37432
+ closed = true;
37433
+ server.close();
37434
+ const withConnections = server;
37435
+ if (typeof withConnections.closeAllConnections === "function") withConnections.closeAllConnections();
37436
+ }
37437
+ };
37438
+ }
37439
+
37440
+ // src/shims.ts
37441
+ var import_fs8 = require("fs");
37442
+ var import_os6 = require("os");
37443
+ var import_path8 = require("path");
37444
+ var SHIM_MARKER = "# mx-env shim \u2014 regenerated by `mx-env shell-init`; do not edit by hand.";
37445
+ var RESERVED_SHIM_NAMES = [
37446
+ "run",
37447
+ "fetch",
37448
+ "export",
37449
+ "render",
37450
+ "inject",
37451
+ "generate",
37452
+ "gen",
37453
+ "inspect",
37454
+ "audit",
37455
+ "edit",
37456
+ "doc",
37457
+ "skill",
37458
+ "shell-init",
37459
+ "help",
37460
+ "mx-env",
37461
+ "morphix-env",
37462
+ "__mx_editor"
37463
+ ];
37464
+ var SHIM_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
37465
+ function shimsDir(homeDir = (0, import_os6.homedir)()) {
37466
+ return (0, import_path8.join)(homeDir, ".mx-env", "shims");
37467
+ }
37468
+ function validateShimList(list, label) {
37469
+ if (list === void 0 || list === null) return [];
37470
+ if (!Array.isArray(list)) return [`${label}: "shims" must be an array of command names`];
37471
+ const errors = [];
37472
+ const seen = /* @__PURE__ */ new Set();
37473
+ for (const entry of list) {
37474
+ if (typeof entry !== "string" || !entry) {
37475
+ errors.push(`${label}: shim entries must be non-empty command names`);
37476
+ continue;
37477
+ }
37478
+ if (!SHIM_NAME_PATTERN.test(entry)) {
37479
+ errors.push(`${label}: shim entry "${entry}" must be a bare command name (letters, digits, ".", "_", "-"; no path separators or whitespace)`);
37480
+ continue;
37481
+ }
37482
+ const lower = entry.toLowerCase();
37483
+ if (RESERVED_SHIM_NAMES.includes(lower)) {
37484
+ errors.push(`${label}: shim entry "${entry}" conflicts with a reserved mx-env command name`);
37485
+ continue;
37486
+ }
37487
+ if (seen.has(lower)) {
37488
+ errors.push(`${label}: duplicate shim entry "${entry}" (comparison is case-insensitive)`);
37489
+ continue;
37490
+ }
37491
+ seen.add(lower);
37492
+ }
37493
+ return errors;
37494
+ }
37495
+ function resolveShimCommands(layers, includeGlobal = true) {
37496
+ const seen = /* @__PURE__ */ new Set();
37497
+ const commands = [];
37498
+ const push = (list) => {
37499
+ if (!Array.isArray(list)) return;
37500
+ for (const entry of list) {
37501
+ if (typeof entry !== "string") continue;
37502
+ const lower = entry.toLowerCase();
37503
+ if (seen.has(lower)) continue;
37504
+ seen.add(lower);
37505
+ commands.push(entry);
37506
+ }
37507
+ };
37508
+ if (includeGlobal) push(layers.global.config.shims);
37509
+ push(layers.project.config.shims);
37510
+ return commands;
37511
+ }
37512
+ function renderShimScript(command) {
37513
+ return `#!/bin/sh
37514
+ ${SHIM_MARKER}
37515
+ command -v mx-env >/dev/null 2>&1 || { echo "[mx-env] mx-env not on PATH; reinstall morphix-env or run 'mx-env shell-init --remove' to clean up this shim." >&2; exit 127; }
37516
+ MXENV_SHIM=1 exec mx-env run -- ${command} "$@"
37517
+ `;
37518
+ }
37519
+ function readIfExists(path) {
37520
+ try {
37521
+ return (0, import_fs8.readFileSync)(path, "utf8");
37522
+ } catch {
37523
+ return null;
37524
+ }
37525
+ }
37526
+ function writeShims(commands, homeDir = (0, import_os6.homedir)()) {
37527
+ const dir = shimsDir(homeDir);
37528
+ (0, import_fs8.mkdirSync)(dir, { recursive: true, mode: 448 });
37529
+ const wanted = new Set(commands.map((command) => command.toLowerCase()));
37530
+ const written = [];
37531
+ const removed = [];
37532
+ const skipped = [];
37533
+ for (const file of (0, import_fs8.readdirSync)(dir)) {
37534
+ const path = (0, import_path8.join)(dir, file);
37535
+ const content = readIfExists(path);
37536
+ if (content === null || !content.includes(SHIM_MARKER)) continue;
37537
+ if (!wanted.has(file.toLowerCase())) {
37538
+ (0, import_fs8.rmSync)(path);
37539
+ removed.push(file);
37540
+ }
37541
+ }
37542
+ for (const command of commands) {
37543
+ const path = (0, import_path8.join)(dir, command);
37544
+ const content = renderShimScript(command);
37545
+ let isSymlink = false;
37546
+ try {
37547
+ isSymlink = (0, import_fs8.lstatSync)(path).isSymbolicLink();
37548
+ } catch {
37549
+ }
37550
+ const existing = isSymlink ? null : readIfExists(path);
37551
+ if (!isSymlink && existing !== null && !existing.includes(SHIM_MARKER)) {
37552
+ skipped.push(command);
37553
+ continue;
37554
+ }
37555
+ if (isSymlink) (0, import_fs8.rmSync)(path);
37556
+ else if (existing === content) continue;
37557
+ (0, import_fs8.writeFileSync)(path, content, { mode: 493 });
37558
+ (0, import_fs8.chmodSync)(path, 493);
37559
+ written.push(command);
37560
+ }
37561
+ return { written, removed, skipped };
37562
+ }
37563
+ function removeAllShims(homeDir = (0, import_os6.homedir)()) {
37564
+ const dir = shimsDir(homeDir);
37565
+ if (!(0, import_fs8.existsSync)(dir)) return [];
37566
+ const removed = [];
37567
+ for (const file of (0, import_fs8.readdirSync)(dir)) {
37568
+ const path = (0, import_path8.join)(dir, file);
37569
+ const content = readIfExists(path);
37570
+ if (content !== null && content.includes(SHIM_MARKER)) {
37571
+ (0, import_fs8.rmSync)(path);
37572
+ removed.push(file);
37573
+ }
37574
+ }
37575
+ return removed;
37576
+ }
37577
+ function detectHandmadeReservedShims(homeDir = (0, import_os6.homedir)()) {
37578
+ const dir = shimsDir(homeDir);
37579
+ if (!(0, import_fs8.existsSync)(dir)) return [];
37580
+ const found = [];
37581
+ for (const file of (0, import_fs8.readdirSync)(dir)) {
37582
+ if (!RESERVED_SHIM_NAMES.includes(file.toLowerCase())) continue;
37583
+ const content = readIfExists((0, import_path8.join)(dir, file));
37584
+ if (content === null || !content.includes(SHIM_MARKER)) found.push(file);
37585
+ }
37586
+ return found;
37587
+ }
37588
+ var RC_BLOCK_BEGIN = "# >>> mx-env shims >>>";
37589
+ var RC_BLOCK_END = "# <<< mx-env shims <<<";
37590
+ function rcBlock(homeDir = (0, import_os6.homedir)()) {
37591
+ const dir = shimsDir(homeDir);
37592
+ if (/["$`\\]/.test(dir)) {
37593
+ throw new Error("[morphix-env] Shims directory path contains characters that are unsafe in a shell rc line.");
37594
+ }
37595
+ return `${RC_BLOCK_BEGIN}
37596
+ export PATH="${dir}:$PATH"
37597
+ ${RC_BLOCK_END}`;
37598
+ }
37599
+ function defaultRcPath(shell, homeDir = (0, import_os6.homedir)()) {
37600
+ const name = shell ? shell.split("/").pop() : "";
37601
+ if (name === "zsh") return (0, import_path8.join)(homeDir, ".zshrc");
37602
+ if (name === "bash") return (0, import_path8.join)(homeDir, ".bashrc");
37603
+ return null;
37604
+ }
37605
+ function writeFileAtomic(path, content) {
37606
+ const tmp = `${path}.mx-env-${process.pid}.tmp`;
37607
+ (0, import_fs8.writeFileSync)(tmp, content);
37608
+ (0, import_fs8.renameSync)(tmp, path);
37609
+ }
37610
+ function upsertRcBlock(rcPath, homeDir = (0, import_os6.homedir)()) {
37611
+ const existing = readIfExists(rcPath) ?? "";
37612
+ const block = rcBlock(homeDir);
37613
+ const pattern = new RegExp(`${escapeRegExp(RC_BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(RC_BLOCK_END)}`, "g");
37614
+ if (pattern.test(existing)) {
37615
+ const replaced = existing.replace(pattern, block);
37616
+ if (replaced === existing) return "unchanged";
37617
+ writeFileAtomic(rcPath, replaced.endsWith("\n") ? replaced : `${replaced}
37618
+ `);
37619
+ return "updated";
37620
+ }
37621
+ const separator = existing.length && !existing.endsWith("\n") ? "\n" : "";
37622
+ writeFileAtomic(rcPath, `${existing}${separator}${block}
37623
+ `);
37624
+ return "added";
37625
+ }
37626
+ function removeRcBlock(rcPath) {
37627
+ const existing = readIfExists(rcPath);
37628
+ if (existing === null) return "absent";
37629
+ const pattern = new RegExp(`\\n?${escapeRegExp(RC_BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(RC_BLOCK_END)}\\n?`, "g");
37630
+ if (!pattern.test(existing)) return "absent";
37631
+ writeFileAtomic(rcPath, existing.replace(pattern, "\n"));
37632
+ return "removed";
37633
+ }
37634
+ function escapeRegExp(text) {
37635
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37636
+ }
37637
+ function stripShimsFromPath(pathValue, homeDir = (0, import_os6.homedir)()) {
37638
+ if (!pathValue) return pathValue;
37639
+ const target = shimsDir(homeDir);
37640
+ let targetReal = target;
37641
+ try {
37642
+ targetReal = (0, import_fs8.realpathSync)(target);
37643
+ } catch {
37644
+ }
37645
+ const equal = (a, b) => process.platform === "darwin" ? a.toLowerCase() === b.toLowerCase() : a === b;
37646
+ const entries = pathValue.split(import_path8.delimiter).filter((entry) => {
37647
+ if (!entry) return true;
37648
+ const literal = entry.replace(/\/+$/, "");
37649
+ let resolved = literal;
37650
+ try {
37651
+ resolved = (0, import_fs8.realpathSync)(literal);
37652
+ } catch {
37653
+ }
37654
+ return !equal(literal, target) && !equal(resolved, targetReal);
37655
+ });
37656
+ return entries.join(import_path8.delimiter);
37657
+ }
37658
+ function sanitizeBootEnv(env = process.env, homeDir) {
37659
+ const originalPath = env.PATH;
37660
+ const viaShim = env.MXENV_SHIM === "1";
37661
+ try {
37662
+ const home = homeDir ?? (0, import_os6.homedir)();
37663
+ const stripped = stripShimsFromPath(originalPath, home);
37664
+ if (stripped !== originalPath) {
37665
+ if (stripped) env.PATH = stripped;
37666
+ else delete env.PATH;
37667
+ }
37668
+ } catch {
37669
+ }
37670
+ delete env.MXENV_SHIM;
37671
+ return { viaShim, originalPath };
37672
+ }
37673
+ function findShadowedCommands(commands, originalPath, homeDir = (0, import_os6.homedir)()) {
37674
+ if (!originalPath) return { absent: [...commands], shadowed: [] };
37675
+ const dirs = originalPath.split(import_path8.delimiter).filter(Boolean);
37676
+ const absent = [];
37677
+ const shadowed = [];
37678
+ for (const command of commands) {
37679
+ const shimPath = (0, import_path8.join)(shimsDir(homeDir), command);
37680
+ let resolvedTo = null;
37681
+ for (const dir of dirs) {
37682
+ const candidate = (0, import_path8.join)(dir, command);
37683
+ try {
37684
+ (0, import_fs8.accessSync)(candidate, import_fs8.constants.X_OK);
37685
+ resolvedTo = candidate;
37686
+ break;
37687
+ } catch {
37688
+ }
37689
+ }
37690
+ const samePath = (a, b) => process.platform === "darwin" ? a.toLowerCase() === b.toLowerCase() : a === b;
37691
+ if (resolvedTo === null) absent.push(command);
37692
+ else if (!samePath(resolvedTo, shimPath)) shadowed.push(command);
37693
+ }
37694
+ return { absent, shadowed };
37695
+ }
37696
+
37697
+ // src/template.ts
37698
+ var import_child_process4 = require("child_process");
37699
+ var import_fs9 = require("fs");
37700
+ var import_path9 = require("path");
37701
+ function shellQuote(value) {
37702
+ return `'${value.replace(/'/g, "'\\''")}'`;
37703
+ }
37704
+ function isShellIdentifier(key) {
37705
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
37706
+ }
37707
+ function findTemplateAnomalies(text) {
37708
+ const anomalies = [];
37709
+ const dollarDollar = text.indexOf("$$");
37710
+ if (dollarDollar >= 0) {
37711
+ anomalies.push(`Escaped dollar $$ at offset ${dollarDollar} is not interpolated; left literal.`);
37712
+ }
37713
+ let index2 = text.indexOf("${");
37714
+ while (index2 >= 0) {
37715
+ const rest = text.slice(index2);
37716
+ if (/^\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(rest)) {
37717
+ } else if (/^\$\{[A-Za-z_][A-Za-z0-9_]*[:+=-]/.test(rest)) {
37718
+ anomalies.push(`Unsupported \${...} modifier or default at offset ${index2}; only \${NAME} is substituted \u2014 left literal.`);
37719
+ } else {
37720
+ anomalies.push(`Unclosed or invalid \${ at offset ${index2}; left literal.`);
37721
+ }
37722
+ index2 = text.indexOf("${", index2 + 2);
37723
+ }
37724
+ return anomalies;
37725
+ }
37726
+ function templateVariablePattern() {
37727
+ return /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
37728
+ }
37729
+ function findTemplateVariables(text) {
37730
+ const names = /* @__PURE__ */ new Set();
37731
+ for (const match of text.matchAll(templateVariablePattern())) names.add(match[1]);
37732
+ return [...names];
37733
+ }
37734
+ function renderTemplate(text, environment) {
37735
+ const missing = /* @__PURE__ */ new Set();
37736
+ const output = text.replace(templateVariablePattern(), (match, name) => {
37737
+ const value = environment[name];
37738
+ if (value === void 0 || value === null) {
37739
+ missing.add(name);
37740
+ return match;
37741
+ }
37742
+ return value;
37743
+ });
37744
+ return { output, missing: [...missing] };
37745
+ }
37746
+ function writePrivateFile(path, content) {
37747
+ const absolute = (0, import_path9.resolve)(path);
37748
+ (0, import_fs9.mkdirSync)((0, import_path9.dirname)(absolute), { recursive: true, mode: 448 });
37749
+ (0, import_fs9.writeFileSync)(absolute, content, { mode: 384 });
37750
+ (0, import_fs9.chmodSync)(absolute, 384);
37751
+ }
37752
+ function gitOutputStatus(path) {
37753
+ const absolute = (0, import_path9.resolve)(path);
37754
+ const cwd = (0, import_path9.dirname)(absolute);
37755
+ const name = (0, import_path9.basename)(absolute);
37756
+ const inside = (0, import_child_process4.spawnSync)("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8" });
37757
+ if (inside.status !== 0) return "outside-repo";
37758
+ const tracked = (0, import_child_process4.spawnSync)("git", ["ls-files", "--error-unmatch", "--", name], { cwd, encoding: "utf8" });
37759
+ if (tracked.status === 0) return "tracked";
37760
+ const ignored = (0, import_child_process4.spawnSync)("git", ["check-ignore", "-q", "--", name], { cwd, encoding: "utf8" });
37761
+ if (ignored.status === 0) return "ignored";
37762
+ if (ignored.status === 1) return "untracked";
37763
+ return "outside-repo";
37764
+ }
37765
+ function warnOutputGitRisk(path) {
37766
+ const status = gitOutputStatus(path);
37767
+ if (status === "tracked") {
37768
+ console.warn(`[morphix-env] Warning: ${path} is tracked by git; a rendered value could be committed. Add it to .gitignore and untrack it.`);
37769
+ } else if (status === "untracked") {
37770
+ console.warn(`[morphix-env] Warning: ${path} is not git-ignored; add it to .gitignore before committing.`);
37771
+ }
37772
+ }
37773
+
37255
37774
  // package.json
37256
37775
  var package_default = {
37257
37776
  name: "morphix-env",
37258
- version: "0.7.1",
37777
+ version: "0.9.0",
37259
37778
  description: "Secret composition CLI \u2014 Infisical, Doppler, macOS Keychain, dotenvx, and local files stay value owners; composes them into one value-safe child environment and ships a built-in Agent Skill (mx-env skill install).",
37260
37779
  author: "MorphixAI <dev@morphixai.com>",
37261
37780
  homepage: "https://github.com/Morphicai/morphix-env#readme",
@@ -37270,9 +37789,22 @@ var package_default = {
37270
37789
  "morphix-env": "./dist/cli.js",
37271
37790
  "mx-env": "./dist/cli.js"
37272
37791
  },
37792
+ main: "./dist/index.js",
37793
+ types: "./dist/index.d.ts",
37794
+ exports: {
37795
+ ".": {
37796
+ types: "./dist/index.d.ts",
37797
+ default: "./dist/index.js"
37798
+ },
37799
+ "./cli": {
37800
+ types: "./dist/cli.d.ts",
37801
+ default: "./dist/cli.js"
37802
+ },
37803
+ "./package.json": "./package.json"
37804
+ },
37273
37805
  scripts: {
37274
- build: "node scripts/build-editor-ui.mjs && tsup src/cli.ts --format cjs --dts --clean --external keytar && node scripts/copy-skill.mjs",
37275
- dev: "node scripts/build-editor-ui.mjs && tsup src/cli.ts --format cjs --watch",
37806
+ build: "node scripts/build-editor-ui.mjs && tsup src/cli.ts src/index.ts --format cjs --dts --clean --external keytar && node scripts/copy-skill.mjs",
37807
+ dev: "node scripts/build-editor-ui.mjs && tsup src/cli.ts src/index.ts --format cjs --watch",
37276
37808
  pretest: "pnpm build",
37277
37809
  test: "vitest run",
37278
37810
  "test:watch": "vitest",
@@ -37334,6 +37866,7 @@ function parseArgs(argv) {
37334
37866
  const args = {
37335
37867
  command: "",
37336
37868
  subArgs: [],
37869
+ positionals: [],
37337
37870
  envFiles: [],
37338
37871
  outFile: null,
37339
37872
  verbose: false,
@@ -37346,7 +37879,12 @@ function parseArgs(argv) {
37346
37879
  printEditorUrl: false,
37347
37880
  source: null,
37348
37881
  target: null,
37349
- force: false
37882
+ force: false,
37883
+ dryRun: false,
37884
+ serve: false,
37885
+ write: false,
37886
+ remove: false,
37887
+ unknownFlags: []
37350
37888
  };
37351
37889
  let i = 2;
37352
37890
  const command = argv[i];
@@ -37385,28 +37923,60 @@ function parseArgs(argv) {
37385
37923
  if (argv[++i]) args.target = argv[i];
37386
37924
  } else if (arg === "--force") {
37387
37925
  args.force = true;
37926
+ } else if (arg === "--dry-run") {
37927
+ args.dryRun = true;
37928
+ } else if (arg === "--write") {
37929
+ args.write = true;
37930
+ } else if (arg === "--remove") {
37931
+ args.remove = true;
37932
+ } else if (arg === "--serve") {
37933
+ args.serve = true;
37388
37934
  } else if (args.command === "run") {
37389
37935
  args.subArgs = argv.slice(i);
37390
37936
  break;
37937
+ } else if (!arg.startsWith("-")) {
37938
+ args.positionals.push(arg);
37939
+ } else {
37940
+ args.unknownFlags.push(arg);
37391
37941
  }
37392
37942
  i++;
37393
37943
  }
37394
37944
  return args;
37395
37945
  }
37396
- function resolveComposition(args) {
37397
- const layers = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os5.homedir)() });
37398
- return composeEnvironment(layers, {
37946
+ function configLayers() {
37947
+ return loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os7.homedir)() });
37948
+ }
37949
+ function composeOptions(args) {
37950
+ return {
37399
37951
  profile: args.profile,
37400
37952
  noGlobal: args.noGlobal,
37401
37953
  noInfisical: args.noInfisical,
37402
37954
  envName: args.env,
37403
37955
  allowInsecureGlobal: args.allowInsecureGlobal,
37404
37956
  envFiles: args.envFiles,
37405
- homeDir: (0, import_os5.homedir)()
37406
- });
37957
+ homeDir: (0, import_os7.homedir)()
37958
+ };
37959
+ }
37960
+ var bootInfo = { viaShim: false };
37961
+ var shimWarned = false;
37962
+ function warnInvalidShims(layers, includeGlobal) {
37963
+ if (shimWarned) return;
37964
+ shimWarned = true;
37965
+ const problems = [
37966
+ ...includeGlobal ? validateShimList(layers.global.config.shims, layers.global.path) : [],
37967
+ ...validateShimList(layers.project.config.shims, layers.project.path)
37968
+ ];
37969
+ if (problems.length) {
37970
+ console.warn(`[morphix-env] Ignoring invalid "shims" entries (${problems.length} problem(s)); run \`mx-env shell-init\` for the details.`);
37971
+ }
37972
+ }
37973
+ function resolveComposition(args) {
37974
+ const layers = configLayers();
37975
+ warnInvalidShims(layers, !args.noGlobal);
37976
+ return composeEnvironment(layers, composeOptions(args));
37407
37977
  }
37408
37978
  function generatedOptions(args) {
37409
- const config = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os5.homedir)() }).project.config;
37979
+ const config = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os7.homedir)() }).project.config;
37410
37980
  return {
37411
37981
  outFile: args.outFile || config.generate?.out || "public/__env.js",
37412
37982
  filter: args.filter || config.generate?.filter || null
@@ -37415,12 +37985,15 @@ function generatedOptions(args) {
37415
37985
  function generateClientEnv(outFile, filter, environment) {
37416
37986
  let vars = extractPublicVars(environment);
37417
37987
  if (filter) vars = Object.fromEntries(Object.entries(vars).filter(([key]) => key.startsWith(filter)));
37418
- (0, import_fs8.mkdirSync)((0, import_path8.dirname)(outFile), { recursive: true });
37419
- (0, import_fs8.writeFileSync)(outFile, `window.__ENV=${JSON.stringify(vars)};`);
37988
+ (0, import_fs10.mkdirSync)((0, import_path10.dirname)(outFile), { recursive: true });
37989
+ (0, import_fs10.writeFileSync)(outFile, `window.__ENV=${JSON.stringify(vars)};`);
37420
37990
  console.log(`[morphix-env] Generated ${outFile} (${Object.keys(vars).length} client vars)`);
37421
37991
  }
37422
37992
  async function runChild(command, commandArgs, composition) {
37423
- return new Promise((resolve6) => {
37993
+ return new Promise((resolve7) => {
37994
+ if (composition.environment.PATH) {
37995
+ composition.environment.PATH = stripShimsFromPath(composition.environment.PATH) ?? composition.environment.PATH;
37996
+ }
37424
37997
  const child = (0, import_cross_spawn.default)(command, commandArgs, { env: composition.environment, stdio: ["inherit", "pipe", "pipe"] });
37425
37998
  const stdout = new StreamingRedactor(composition.redactionValues);
37426
37999
  const stderr = new StreamingRedactor(composition.redactionValues);
@@ -37428,14 +38001,14 @@ async function runChild(command, commandArgs, composition) {
37428
38001
  child.stderr?.on("data", (chunk) => process.stderr.write(stderr.write(chunk)));
37429
38002
  child.on("error", () => {
37430
38003
  console.error(`[morphix-env] Failed to execute: ${command}`);
37431
- resolve6(1);
38004
+ resolve7(1);
37432
38005
  });
37433
38006
  child.on("close", (code) => {
37434
38007
  const stdoutFinal = stdout.flush();
37435
38008
  const stderrFinal = stderr.flush();
37436
38009
  if (stdoutFinal) process.stdout.write(stdoutFinal);
37437
38010
  if (stderrFinal) process.stderr.write(stderrFinal);
37438
- resolve6(code ?? 1);
38011
+ resolve7(code ?? 1);
37439
38012
  });
37440
38013
  });
37441
38014
  }
@@ -37447,9 +38020,53 @@ function writeRunAudit(composition, startedAt, outcome, exitCode) {
37447
38020
  keys: Object.keys(composition.provenance).sort(),
37448
38021
  outcome,
37449
38022
  exitCode,
37450
- durationMs: Date.now() - startedAt
38023
+ durationMs: Date.now() - startedAt,
38024
+ via: bootInfo.viaShim ? "shim" : "cli"
37451
38025
  });
37452
38026
  }
38027
+ async function startServeFor(composition, verbose) {
38028
+ const values = {};
38029
+ for (const key of Object.keys(composition.provenance)) {
38030
+ const value = composition.environment[key];
38031
+ if (value !== void 0) values[key] = value;
38032
+ }
38033
+ const server = await startSecretServer(values);
38034
+ for (const key of Object.keys(values)) {
38035
+ if (!isPublicKey(key)) delete composition.environment[key];
38036
+ }
38037
+ composition.environment.MXENV_ENDPOINT = server.endpoint;
38038
+ composition.environment.MXENV_TOKEN = server.token;
38039
+ composition.redactionValues = [.../* @__PURE__ */ new Set([...composition.redactionValues, server.token])];
38040
+ if (verbose) {
38041
+ console.error(`[morphix-env] Secret server listening on ${server.endpoint} (${Object.keys(values).length} keys; token is visible to the child only).`);
38042
+ }
38043
+ return server;
38044
+ }
38045
+ function warnUnresolvedReference(reference, context) {
38046
+ console.warn(`[morphix-env] Unresolved mx-env://${reference.profile}/${reference.key} reference ${context} (${reference.reason}); left as-is.`);
38047
+ }
38048
+ async function resolveEnvironmentReferences(composition, args) {
38049
+ const targets = Object.entries(composition.environment).filter((entry) => typeof entry[1] === "string" && hasMxEnvReference(entry[1]));
38050
+ if (!targets.length) return;
38051
+ const resolver = new ReferenceResolver(configLayers(), composeOptions(args));
38052
+ const changed = [];
38053
+ for (const [key, value] of targets) {
38054
+ const resolved = await resolver.resolveText(value);
38055
+ if (resolved.missing.length) {
38056
+ for (const reference of resolved.missing) warnUnresolvedReference(reference, `in key "${key}"`);
38057
+ continue;
38058
+ }
38059
+ composition.environment[key] = resolved.text;
38060
+ changed.push(key);
38061
+ }
38062
+ if (changed.length) {
38063
+ const extra = changed.map((key) => composition.environment[key]).filter((value) => Boolean(value));
38064
+ composition.redactionValues = [.../* @__PURE__ */ new Set([
38065
+ ...collectRedactionValues(composition.environment, composition.provenance),
38066
+ ...extra
38067
+ ])];
38068
+ }
38069
+ }
37453
38070
  async function cmdRun(args) {
37454
38071
  if (!args.subArgs.length) {
37455
38072
  console.error("[morphix-env] No command specified. Usage: mx-env run -- <command>");
@@ -37462,18 +38079,72 @@ async function cmdRun(args) {
37462
38079
  console.log(`[morphix-env] ${source.name}: ${source.availability} (${source.keyCount} keys)`);
37463
38080
  }
37464
38081
  }
38082
+ if (args.dryRun) {
38083
+ console.log(`profile: ${composition.profileName}`);
38084
+ for (const key of Object.keys(composition.provenance).sort()) console.log(` ${key}`);
38085
+ return 0;
38086
+ }
38087
+ await resolveEnvironmentReferences(composition, args);
37465
38088
  if (args.outFile || generatedOptions(args).outFile) {
37466
38089
  const { outFile, filter } = generatedOptions(args);
37467
- const configured = Boolean(args.outFile || loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os5.homedir)() }).project.config.generate);
38090
+ const configured = Boolean(args.outFile || loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os7.homedir)() }).project.config.generate);
37468
38091
  if (configured) generateClientEnv(outFile, filter, composition.environment);
37469
38092
  }
37470
38093
  const [command, ...commandArgs] = args.subArgs;
37471
- const exitCode = await runChild(command, commandArgs, composition);
37472
- writeRunAudit(composition, startedAt, exitCode === 0 ? "success" : "failure", exitCode);
37473
- return exitCode;
38094
+ const secretServer = args.serve ? await startServeFor(composition, args.verbose) : null;
38095
+ try {
38096
+ const exitCode = await runChild(command, commandArgs, composition);
38097
+ writeRunAudit(composition, startedAt, exitCode === 0 ? "success" : "failure", exitCode);
38098
+ return exitCode;
38099
+ } finally {
38100
+ secretServer?.close();
38101
+ }
38102
+ }
38103
+ async function cmdFetch(args) {
38104
+ const key = args.positionals[0];
38105
+ if (!key) {
38106
+ console.error("[morphix-env] Usage: mx-env fetch <key-name>");
38107
+ return 1;
38108
+ }
38109
+ const endpoint = process.env.MXENV_ENDPOINT;
38110
+ const token = process.env.MXENV_TOKEN;
38111
+ if (!endpoint || !token) {
38112
+ console.error('[morphix-env] mx-env fetch needs MXENV_ENDPOINT and MXENV_TOKEN; run it inside "mx-env run --serve -- <command>".');
38113
+ return 1;
38114
+ }
38115
+ let endpointUrl;
38116
+ try {
38117
+ endpointUrl = new URL(endpoint);
38118
+ } catch {
38119
+ console.error("[morphix-env] MXENV_ENDPOINT is not a valid URL.");
38120
+ return 1;
38121
+ }
38122
+ if (!["127.0.0.1", "localhost", "[::1]"].includes(endpointUrl.hostname)) {
38123
+ console.error("[morphix-env] MXENV_ENDPOINT is not a loopback address; refusing to send the fetch token off-machine.");
38124
+ return 1;
38125
+ }
38126
+ try {
38127
+ const response = await fetch(`${endpoint}/key/${encodeURIComponent(key)}`, {
38128
+ headers: { Authorization: `Bearer ${token}` }
38129
+ });
38130
+ if (response.ok) {
38131
+ process.stdout.write(await response.text());
38132
+ return 0;
38133
+ }
38134
+ if (response.status === 404) {
38135
+ console.error(`[morphix-env] Unknown key on the secret server: ${key}`);
38136
+ return 1;
38137
+ }
38138
+ console.error(`[morphix-env] Secret server rejected the request (HTTP ${response.status}).`);
38139
+ return 1;
38140
+ } catch {
38141
+ console.error("[morphix-env] Secret server is unreachable; the parent process may have exited.");
38142
+ return 1;
38143
+ }
37474
38144
  }
37475
38145
  async function cmdGenerate(args) {
37476
38146
  const composition = await resolveComposition(args);
38147
+ await resolveEnvironmentReferences(composition, args);
37477
38148
  const { outFile, filter } = generatedOptions(args);
37478
38149
  generateClientEnv(outFile, filter, composition.environment);
37479
38150
  if (args.verbose) {
@@ -37495,6 +38166,94 @@ async function cmdInspect(args) {
37495
38166
  }
37496
38167
  return 0;
37497
38168
  }
38169
+ async function cmdExport(args) {
38170
+ const startedAt = Date.now();
38171
+ const composition = await resolveComposition(args);
38172
+ const keys = Object.keys(composition.provenance).sort();
38173
+ if (args.dryRun) {
38174
+ for (const key of keys) console.log(key);
38175
+ return 0;
38176
+ }
38177
+ await resolveEnvironmentReferences(composition, args);
38178
+ for (const key of keys) {
38179
+ if (!isShellIdentifier(key)) {
38180
+ console.warn(`[morphix-env] Warning: key "${key}" is not a valid shell identifier; skipped in export output.`);
38181
+ continue;
38182
+ }
38183
+ console.log(`export ${key}=${shellQuote(composition.environment[key] ?? "")}`);
38184
+ }
38185
+ writeRunAudit(composition, startedAt, "success", 0);
38186
+ return 0;
38187
+ }
38188
+ async function cmdRender(args) {
38189
+ const startedAt = Date.now();
38190
+ const [templatePath, outPath] = args.positionals;
38191
+ if (!templatePath || !outPath) {
38192
+ console.error("[morphix-env] Usage: mx-env render <template-file> <output-file> [options]");
38193
+ return 1;
38194
+ }
38195
+ if (!(0, import_fs10.existsSync)(templatePath)) {
38196
+ console.error(`[morphix-env] Template not found: ${templatePath}`);
38197
+ return 1;
38198
+ }
38199
+ const template = (0, import_fs10.readFileSync)(templatePath, "utf8");
38200
+ const composition = await resolveComposition(args);
38201
+ const names = findTemplateVariables(template);
38202
+ const missing = names.filter((name) => composition.environment[name] === void 0 || composition.environment[name] === null);
38203
+ if (missing.length) console.warn(`[morphix-env] Missing variables: ${missing.join(", ")} (left unresolved)`);
38204
+ for (const anomaly of findTemplateAnomalies(template)) console.warn(`[morphix-env] ${anomaly}`);
38205
+ if (args.dryRun) {
38206
+ for (const name of names) console.log(`${name}: ${missing.includes(name) ? "missing" : "resolved"}`);
38207
+ return 0;
38208
+ }
38209
+ await resolveEnvironmentReferences(composition, args);
38210
+ const { output } = renderTemplate(template, composition.environment);
38211
+ warnOutputGitRisk(outPath);
38212
+ writePrivateFile(outPath, output);
38213
+ writeRunAudit(composition, startedAt, "success", 0);
38214
+ console.log(`[morphix-env] Rendered ${templatePath} -> ${outPath} (${names.length} variables, ${missing.length} unresolved)`);
38215
+ return 0;
38216
+ }
38217
+ async function cmdInject(args) {
38218
+ const startedAt = Date.now();
38219
+ const [templatePath, outPath] = args.positionals;
38220
+ if (!templatePath || !outPath) {
38221
+ console.error("[morphix-env] Usage: mx-env inject <template-file> <output-file> [options]");
38222
+ return 1;
38223
+ }
38224
+ if (!(0, import_fs10.existsSync)(templatePath)) {
38225
+ console.error(`[morphix-env] Template not found: ${templatePath}`);
38226
+ return 1;
38227
+ }
38228
+ const template = (0, import_fs10.readFileSync)(templatePath, "utf8");
38229
+ const layers = configLayers();
38230
+ warnInvalidShims(layers, !args.noGlobal);
38231
+ const resolver = new ReferenceResolver(layers, composeOptions(args));
38232
+ const references = findMxEnvReferences(template);
38233
+ if (args.dryRun) {
38234
+ for (const reference of references) {
38235
+ const result = await resolver.valueOf(reference.profile, reference.key);
38236
+ const status = result.value !== void 0 ? "resolved" : result.missing?.reason ?? "missing";
38237
+ console.log(`${reference.profile}/${reference.key}: ${status}`);
38238
+ }
38239
+ return 0;
38240
+ }
38241
+ const resolved = await resolver.resolveText(template);
38242
+ for (const reference of resolved.missing) warnUnresolvedReference(reference, `in ${templatePath}`);
38243
+ warnOutputGitRisk(outPath);
38244
+ writePrivateFile(outPath, resolved.text);
38245
+ writeAudit({
38246
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
38247
+ profile: [...new Set(references.map((reference) => reference.profile))].join(",") || "none",
38248
+ sources: [],
38249
+ keys: [...new Set(references.map((reference) => reference.key))].sort(),
38250
+ outcome: "success",
38251
+ exitCode: 0,
38252
+ durationMs: Date.now() - startedAt
38253
+ });
38254
+ console.log(`[morphix-env] Injected ${templatePath} -> ${outPath} (${references.length} references, ${resolved.missing.length} unresolved)`);
38255
+ return 0;
38256
+ }
37498
38257
  async function cmdEdit(args) {
37499
38258
  if (!args.profile && !args.source) {
37500
38259
  console.error("[morphix-env] Specify --profile <name> or --source <name> to open the editor.");
@@ -37502,7 +38261,7 @@ async function cmdEdit(args) {
37502
38261
  }
37503
38262
  const composition = await resolveComposition(args);
37504
38263
  if (args.source && !composition.sourceEntries.some((entry) => entry.name === args.source)) {
37505
- const layers = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os5.homedir)() });
38264
+ const layers = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os7.homedir)() });
37506
38265
  const declared = layers.project.config.sources?.[args.source] ? { source: layers.project.config.sources[args.source], baseDir: layers.project.baseDir } : !args.noGlobal && layers.global.config.sources?.[args.source] ? { source: layers.global.config.sources[args.source], baseDir: layers.global.baseDir } : null;
37507
38266
  if (!declared) {
37508
38267
  console.error(`[morphix-env] Unknown source "${args.source}".`);
@@ -37520,7 +38279,7 @@ async function cmdEdit(args) {
37520
38279
  }
37521
38280
  async function cmdDoc(args) {
37522
38281
  const composition = await resolveComposition(args);
37523
- const outFile = args.outFile || (0, import_path8.join)(process.cwd(), ".agents", "skills", "morphix-env", "SKILL.md");
38282
+ const outFile = args.outFile || (0, import_path10.join)(process.cwd(), ".agents", "skills", "morphix-env", "SKILL.md");
37524
38283
  const keyRows = Object.entries(composition.provenance).sort(([a], [b]) => a.localeCompare(b)).map(([key, provenance]) => `| \`${key}\` | ${provenance.source} (${provenance.provider}) |`).join("\n") || "| _No resolved keys_ | \u2014 |";
37525
38284
  const skill = `---
37526
38285
  name: morphix-env
@@ -37554,18 +38313,90 @@ ${keyRows}
37554
38313
 
37555
38314
  Secret values and value prefixes do not belong in this Skill, command output, audit records, or conversation context.
37556
38315
  `;
37557
- (0, import_fs8.mkdirSync)((0, import_path8.dirname)(outFile), { recursive: true });
37558
- (0, import_fs8.writeFileSync)(outFile, skill, { mode: 384 });
38316
+ (0, import_fs10.mkdirSync)((0, import_path10.dirname)(outFile), { recursive: true });
38317
+ (0, import_fs10.writeFileSync)(outFile, skill, { mode: 384 });
37559
38318
  console.log(`[morphix-env] Generated value-free workflow Skill: ${outFile}`);
37560
38319
  return 0;
37561
38320
  }
38321
+ function cmdShellInit(args) {
38322
+ if (process.platform === "win32") {
38323
+ console.error("[morphix-env] shell-init is supported on macOS/Linux only; on Windows keep using explicit `mx-env run -- <command>`.");
38324
+ return 1;
38325
+ }
38326
+ if (args.unknownFlags.length) {
38327
+ console.error(`[morphix-env] shell-init: unrecognized flag(s): ${args.unknownFlags.join(", ")}`);
38328
+ return 1;
38329
+ }
38330
+ if (args.write && args.remove) {
38331
+ console.error("[morphix-env] shell-init: --write and --remove are mutually exclusive.");
38332
+ return 1;
38333
+ }
38334
+ try {
38335
+ return runShellInit(args);
38336
+ } catch (error) {
38337
+ const code = error?.code;
38338
+ console.error(`[morphix-env] shell-init failed${code ? ` (${code})` : ""}: check that your home directory and rc file are writable, then retry.`);
38339
+ return 1;
38340
+ }
38341
+ }
38342
+ function runShellInit(args) {
38343
+ const layers = configLayers();
38344
+ const home = (0, import_os7.homedir)();
38345
+ const rcPath = defaultRcPath(process.env.SHELL, home);
38346
+ if (args.remove) {
38347
+ const removed2 = removeAllShims(home);
38348
+ const rcOutcome = rcPath ? removeRcBlock(rcPath) : "absent";
38349
+ console.log(`[morphix-env] Removed ${removed2.length} shim(s)${removed2.length ? ` (${removed2.join(", ")})` : ""}; rc block ${rcOutcome}${rcPath ? ` in ${rcPath}` : ""}.`);
38350
+ return 0;
38351
+ }
38352
+ const problems = [
38353
+ ...validateShimList(layers.global.config.shims, layers.global.path),
38354
+ ...validateShimList(layers.project.config.shims, layers.project.path)
38355
+ ];
38356
+ if (problems.length) {
38357
+ for (const problem of problems) console.error(`[morphix-env] ${problem}`);
38358
+ return 1;
38359
+ }
38360
+ const commands = resolveShimCommands(layers, !args.noGlobal);
38361
+ const { written, removed, skipped } = writeShims(commands, home);
38362
+ console.log(`[morphix-env] Shims ready in ${shimsDir(home)} (${commands.length} command(s): ${commands.join(", ") || "none"}; ${written.length} written, ${removed.length} removed).`);
38363
+ for (const command of skipped) {
38364
+ console.warn(`[morphix-env] Warning: "${command}" exists in the shims directory as a handmade file (no mx-env marker); refusing to overwrite. Remove or rename it by hand, then re-run shell-init.`);
38365
+ }
38366
+ for (const file of detectHandmadeReservedShims(home)) {
38367
+ console.warn(`[morphix-env] Warning: "${file}" in the shims directory was not created by mx-env and shadows a reserved command; delete it by hand. Do not place files in ${shimsDir(home)} manually.`);
38368
+ }
38369
+ const { absent, shadowed } = findShadowedCommands(commands, bootInfo.originalPath, home);
38370
+ if (absent.length) {
38371
+ console.warn(`[morphix-env] Note: ${absent.join(", ")} cannot be resolved on the current PATH yet \u2014 add the shims directory (below) and open a new shell.`);
38372
+ }
38373
+ if (shadowed.length) {
38374
+ console.warn(`[morphix-env] Warning: these commands resolve to a different executable ahead of their shim: ${shadowed.join(", ")} \u2014 move ${shimsDir(home)} ahead of the entries that provide them.`);
38375
+ }
38376
+ if (args.write) {
38377
+ if (!rcPath) {
38378
+ console.error("[morphix-env] shell-init: cannot infer an rc file from $SHELL (zsh/bash supported); add the PATH line manually.");
38379
+ return 1;
38380
+ }
38381
+ const outcome = upsertRcBlock(rcPath, home);
38382
+ console.log(`[morphix-env] rc block ${outcome} in ${rcPath}. Open a new shell to activate.`);
38383
+ return 0;
38384
+ }
38385
+ console.log(`
38386
+ Add the shims directory to PATH${rcPath ? ` (e.g. in ${rcPath})` : ""}:
38387
+
38388
+ export PATH="${shimsDir(home)}:$PATH"
38389
+
38390
+ Or re-run with --write to let mx-env maintain a marked block in your rc file.`);
38391
+ return 0;
38392
+ }
37562
38393
  function cmdAudit(args) {
37563
- const file = (0, import_path8.join)((0, import_os5.homedir)(), ".mx-env", "audit", `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`);
37564
- if (!(0, import_fs8.existsSync)(file)) {
38394
+ const file = (0, import_path10.join)((0, import_os7.homedir)(), ".mx-env", "audit", `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`);
38395
+ if (!(0, import_fs10.existsSync)(file)) {
37565
38396
  console.log("[morphix-env] No audit events for today.");
37566
38397
  return 0;
37567
38398
  }
37568
- const lines = (0, import_fs8.readFileSync)(file, "utf8").trim().split("\n").filter(Boolean);
38399
+ const lines = (0, import_fs10.readFileSync)(file, "utf8").trim().split("\n").filter(Boolean);
37569
38400
  const selected = args.subArgs[0] === "tail" ? lines.slice(-20) : lines;
37570
38401
  for (const line of selected) console.log(line);
37571
38402
  return 0;
@@ -37576,6 +38407,10 @@ morphix-env v${VERSION} \u2014 secret composition and environment delivery
37576
38407
 
37577
38408
  Usage:
37578
38409
  mx-env run [options] -- <command> Compose env + execute command
38410
+ mx-env fetch <key-name> Print one value from a run --serve secret server
38411
+ mx-env export [options] Print eval-able export KEY='value' lines
38412
+ mx-env render <template> <output> Substitute \${VAR} into a file (mode 0600)
38413
+ mx-env inject <template> <output> Resolve mx-env:// references into a file (mode 0600)
37579
38414
  mx-env generate [options] Generate public __env.js
37580
38415
  mx-env inspect [options] Print key names, sources, and status only
37581
38416
  mx-env doc [options] Generate a value-free project Skill
@@ -37583,6 +38418,7 @@ Usage:
37583
38418
  mx-env edit --profile <name> Open the one-shot visual editor
37584
38419
  mx-env skill install [--target <t>] Install the Agent Skill (global|project|trellis)
37585
38420
  mx-env skill show Show skill install status per target
38421
+ mx-env shell-init [--write|--remove] Generate PATH shims for declared commands
37586
38422
 
37587
38423
  Both mx-env and morphix-env are supported.
37588
38424
 
@@ -37592,6 +38428,8 @@ Options:
37592
38428
  -e, --env <name> Infisical environment override
37593
38429
  -o, --out <path> Client __env.js output
37594
38430
  --filter <prefix> Include public keys with this prefix
38431
+ --serve Keep composed values out of env; serve them to the child over loopback
38432
+ --dry-run Report key/variable names only, never values
37595
38433
  --no-infisical Do not load Infisical sources
37596
38434
  --no-global Ignore ~/.mx-env/.env and ~/.mx-env/config.json
37597
38435
  --allow-insecure-global Allow a global .env with permissive file mode
@@ -37604,10 +38442,19 @@ Options:
37604
38442
  `);
37605
38443
  }
37606
38444
  async function main() {
38445
+ Object.assign(bootInfo, sanitizeBootEnv());
37607
38446
  const args = parseArgs(process.argv);
37608
38447
  switch (args.command) {
37609
38448
  case "run":
37610
38449
  return cmdRun(args);
38450
+ case "fetch":
38451
+ return cmdFetch(args);
38452
+ case "export":
38453
+ return cmdExport(args);
38454
+ case "render":
38455
+ return cmdRender(args);
38456
+ case "inject":
38457
+ return cmdInject(args);
37611
38458
  case "generate":
37612
38459
  case "gen":
37613
38460
  return cmdGenerate(args);
@@ -37619,6 +38466,8 @@ async function main() {
37619
38466
  return cmdEdit(args);
37620
38467
  case "doc":
37621
38468
  return cmdDoc(args);
38469
+ case "shell-init":
38470
+ return cmdShellInit(args);
37622
38471
  case "skill": {
37623
38472
  const positional = process.argv.slice(3).find((arg) => !arg.startsWith("-")) ?? "install";
37624
38473
  return cmdSkill(positional, { target: args.target, force: args.force });