morphix-env 0.8.0 → 0.10.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(resolve7, reject) {
835
- resolveDebugValue = resolve7;
834
+ null != ioInfo && (ioInfo.start = ioInfo.end = performance.now(), ioInfo.value = new Promise(function(resolve8, reject) {
835
+ resolveDebugValue = resolve8;
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, resolve7, reject) {
951
+ function recursivelyFlushAsyncActWork(returnValue, resolve8, 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, resolve7, reject);
958
+ return recursivelyFlushAsyncActWork(returnValue, resolve8, 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)) : resolve7(returnValue);
965
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve8(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(resolve7, reject) {
1153
+ then: function(resolve8, 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
- resolve7,
1163
+ resolve8,
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 resolve7(returnValue);
1177
+ } else resolve8(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(resolve7, reject) {
1199
+ then: function(resolve8, reject) {
1200
1200
  didAwaitActCall = true;
1201
1201
  0 === prevActScopeDepth ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
1202
1202
  return recursivelyFlushAsyncActWork(
1203
1203
  returnValue$jscomp$0,
1204
- resolve7,
1204
+ resolve8,
1205
1205
  reject
1206
1206
  );
1207
- })) : resolve7(returnValue$jscomp$0);
1207
+ })) : resolve8(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(resolve7, reject) {
12994
+ return new Promise(function(resolve8, 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
- resolve7(stream2);
13038
+ resolve8(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(resolve7, reject) {
13049
+ return new Promise(function(resolve8, 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
- resolve7(readable);
13081
+ resolve8(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(resolve7, reject) {
13126
+ return new Promise(function(resolve8, 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
- resolve7(stream2);
13176
+ resolve8(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(resolve7, reject) {
13191
+ return new Promise(function(resolve8, 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
- resolve7(stream2);
13229
+ resolve8(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(resolve7, reject) {
13243
+ return new Promise(function(resolve8, 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
- resolve7(stream2);
13275
+ resolve8(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(resolve7, reject) {
13286
+ return new Promise(function(resolve8, 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
- resolve7(readable);
13307
+ resolve8(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(resolve7, reject) {
28779
+ return new Promise(function(resolve8, 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
- resolve7(stream2);
28828
+ resolve8(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(resolve7, reject) {
28839
+ return new Promise(function(resolve8, 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
- resolve7(readable);
28871
+ resolve8(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(resolve7, reject) {
28916
+ return new Promise(function(resolve8, 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
- resolve7(stream2);
28966
+ resolve8(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(resolve7, reject) {
28981
+ return new Promise(function(resolve8, 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
- resolve7(stream2);
29019
+ resolve8(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(resolve7, reject) {
29033
+ return new Promise(function(resolve8, 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
- resolve7(stream2);
29067
+ resolve8(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(resolve7, reject) {
29078
+ return new Promise(function(resolve8, 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
- resolve7(readable);
29102
+ resolve8(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_fs9 = require("fs");
29486
- var import_os6 = require("os");
29487
- var import_path9 = require("path");
29485
+ var import_fs11 = require("fs");
29486
+ var import_os8 = require("os");
29487
+ var import_path11 = require("path");
29488
29488
 
29489
29489
  // src/audit.ts
29490
29490
  var import_fs = require("fs");
@@ -29603,12 +29603,6 @@ function validateConfig(config, label = CONFIG_FILE) {
29603
29603
  return errors;
29604
29604
  }
29605
29605
 
29606
- // src/sources.ts
29607
- var import_child_process2 = require("child_process");
29608
- var import_fs5 = require("fs");
29609
- var import_path5 = require("path");
29610
- var import_dotenv3 = require("dotenv");
29611
-
29612
29606
  // src/env.ts
29613
29607
  var import_fs3 = require("fs");
29614
29608
  var import_path3 = require("path");
@@ -29618,6 +29612,14 @@ function parseEnvFile(filePath) {
29618
29612
  if (!(0, import_fs3.existsSync)(absPath)) return {};
29619
29613
  return (0, import_dotenv.parse)((0, import_fs3.readFileSync)(absPath, "utf8"));
29620
29614
  }
29615
+ function isDotenvxEncrypted(filePath) {
29616
+ try {
29617
+ const content = (0, import_fs3.readFileSync)((0, import_path3.resolve)(filePath), "utf8");
29618
+ return content.includes("DOTENV_PUBLIC_KEY") && /^[A-Za-z_][A-Za-z0-9_]*="?encrypted:/m.test(content);
29619
+ } catch {
29620
+ return false;
29621
+ }
29622
+ }
29621
29623
  var PUBLIC_PREFIXES = ["NEXT_PUBLIC_", "VITE_", "EXPO_PUBLIC_"];
29622
29624
  function extractPublicVars(environment = process.env) {
29623
29625
  const vars = {};
@@ -29629,6 +29631,12 @@ function extractPublicVars(environment = process.env) {
29629
29631
  return vars;
29630
29632
  }
29631
29633
 
29634
+ // src/sources.ts
29635
+ var import_child_process2 = require("child_process");
29636
+ var import_fs5 = require("fs");
29637
+ var import_path5 = require("path");
29638
+ var import_dotenv3 = require("dotenv");
29639
+
29632
29640
  // src/infisical.ts
29633
29641
  var import_sdk = require("@infisical/sdk");
29634
29642
  var import_child_process = require("child_process");
@@ -29739,7 +29747,7 @@ function assertSafeGlobalFile(file, context, entry) {
29739
29747
  if ((mode & 63) !== 0) {
29740
29748
  throw new SourceError(
29741
29749
  entry.name,
29742
- "local",
29750
+ entry.source.provider,
29743
29751
  `[morphix-env] Global env file ${file} is readable by group or other users; set mode 0600 or pass --allow-insecure-global.`
29744
29752
  );
29745
29753
  }
@@ -29831,6 +29839,16 @@ function resolveDotenvx(entry, source, context) {
29831
29839
  if (existingFiles.length !== files.length) {
29832
29840
  throw new SourceError(entry.name, source.provider, `[morphix-env] dotenvx source "${entry.name}" is missing a declared env file.`);
29833
29841
  }
29842
+ if (entry.globalImplicit && !context.allowInsecureGlobal && process.platform !== "win32") {
29843
+ const keysFile = (0, import_path5.join)((0, import_path5.dirname)(existingFiles[0]), ".env.keys");
29844
+ if ((0, import_fs5.existsSync)(keysFile) && ((0, import_fs5.statSync)(keysFile).mode & 63) !== 0) {
29845
+ throw new SourceError(
29846
+ entry.name,
29847
+ source.provider,
29848
+ `[morphix-env] Global dotenvx keys file ${keysFile} is readable by group or other users; set mode 0600 or pass --allow-insecure-global.`
29849
+ );
29850
+ }
29851
+ }
29834
29852
  const command = ensureCommand("dotenvx", entry.name, source.provider);
29835
29853
  const keys = [...new Set(existingFiles.flatMap((file) => Object.keys(parseEnvFile(file))))].filter((key) => !key.startsWith("DOTENV_PUBLIC_KEY"));
29836
29854
  if (!keys.length) return { name: entry.name, provider: source.provider, values: {}, availability: "loaded" };
@@ -29838,8 +29856,10 @@ function resolveDotenvx(entry, source, context) {
29838
29856
  let stage = "run";
29839
29857
  try {
29840
29858
  const output = (0, import_child_process2.execFileSync)(
29859
+ // --overload aligns dotenvx with every other provider: composed file
29860
+ // values win over same-named variables already in the parent env.
29841
29861
  command,
29842
- ["run", "-f", existingFiles.join(","), "--", process.execPath, "-e", helper, JSON.stringify(keys)],
29862
+ ["run", "--overload", "-f", existingFiles.join(","), "--", process.execPath, "-e", helper, JSON.stringify(keys)],
29843
29863
  {
29844
29864
  encoding: "utf8",
29845
29865
  stdio: ["ignore", "pipe", "pipe"],
@@ -29862,7 +29882,8 @@ function resolveDotenvx(entry, source, context) {
29862
29882
  return { name: entry.name, provider: source.provider, values, availability: "loaded" };
29863
29883
  } catch (error) {
29864
29884
  const category = typeof error?.code === "string" ? error.code : "failed";
29865
- throw new SourceError(entry.name, source.provider, `[morphix-env] dotenvx source "${entry.name}" could not decrypt or load (${stage}:${category}).`);
29885
+ const sniffHint = entry.globalImplicit ? " The global file looks dotenvx-encrypted; if it is actually plaintext, remove the DOTENV_PUBLIC_KEY / encrypted: markers." : "";
29886
+ throw new SourceError(entry.name, source.provider, `[morphix-env] dotenvx source "${entry.name}" could not decrypt or load (${stage}:${category}).${sniffHint}`);
29866
29887
  }
29867
29888
  }
29868
29889
  var keytarModule;
@@ -30049,9 +30070,10 @@ function resolveEntries(layers, options) {
30049
30070
  ...includeGlobal ? Object.entries(layers.global.config.profiles || {}) : [],
30050
30071
  ...Object.entries(layers.project.config.profiles || {})
30051
30072
  ]);
30073
+ const globalEncrypted = isDotenvxEncrypted(layers.globalEnvFile);
30052
30074
  const globalDotenv = includeGlobal ? [{
30053
30075
  name: GLOBAL_DOTENV_SOURCE,
30054
- source: { provider: "local", files: [layers.globalEnvFile], optional: true },
30076
+ source: globalEncrypted ? { provider: "dotenvx", files: [layers.globalEnvFile], optional: true } : { provider: "local", files: [layers.globalEnvFile], optional: true },
30055
30077
  baseDir: layers.global.baseDir,
30056
30078
  globalImplicit: true
30057
30079
  }] : [];
@@ -37101,14 +37123,14 @@ async function startEditorServer(encodedState) {
37101
37123
  });
37102
37124
  const closeTimer = setTimeout(() => server.close(), EDITOR_LIFETIME_MS);
37103
37125
  closeTimer.unref();
37104
- await new Promise((resolve7, reject) => {
37126
+ await new Promise((resolve8, reject) => {
37105
37127
  server.once("error", reject);
37106
37128
  server.listen(0, "127.0.0.1", () => {
37107
37129
  const address = server.address();
37108
37130
  if (!address || typeof address === "string") return reject(new Error("Editor did not receive a loopback port"));
37109
37131
  process.stdout.write(`http://127.0.0.1:${address.port}${editorPath(state)}
37110
37132
  `);
37111
- resolve7();
37133
+ resolve8();
37112
37134
  });
37113
37135
  });
37114
37136
  }
@@ -37129,7 +37151,7 @@ async function launchEditor(entryScript, composition, selectedSource) {
37129
37151
  detached: true,
37130
37152
  stdio: ["ignore", "pipe", "ignore"]
37131
37153
  });
37132
- const url = await new Promise((resolve7, reject) => {
37154
+ const url = await new Promise((resolve8, reject) => {
37133
37155
  const timer = setTimeout(() => reject(new Error("[morphix-env] Timed out starting local editor.")), 5e3);
37134
37156
  child.once("error", () => {
37135
37157
  clearTimeout(timer);
@@ -37139,7 +37161,7 @@ async function launchEditor(entryScript, composition, selectedSource) {
37139
37161
  clearTimeout(timer);
37140
37162
  const url2 = chunk.toString("utf8").trim();
37141
37163
  child.stdout?.destroy();
37142
- resolve7(url2);
37164
+ resolve8(url2);
37143
37165
  });
37144
37166
  });
37145
37167
  child.unref();
@@ -37154,6 +37176,267 @@ async function launchEditor(entryScript, composition, selectedSource) {
37154
37176
  return { url, opened };
37155
37177
  }
37156
37178
 
37179
+ // src/migrate.ts
37180
+ var import_child_process4 = require("child_process");
37181
+ var import_fs7 = require("fs");
37182
+ var import_os5 = require("os");
37183
+ var import_path7 = require("path");
37184
+ var import_dotenv4 = require("dotenv");
37185
+ function resolveUnitFile(rawFile, baseDir, homeDir) {
37186
+ const expanded = expandHomePath(rawFile, homeDir);
37187
+ return (0, import_path7.isAbsolute)(expanded) ? expanded : (0, import_path7.resolve)(baseDir, expanded);
37188
+ }
37189
+ function snapshotValues(unit) {
37190
+ const values = {};
37191
+ for (const file of unit.files) Object.assign(values, parseEnvFile(file));
37192
+ return values;
37193
+ }
37194
+ function declaredUnits(loaded, scope, homeDir) {
37195
+ const units = [];
37196
+ for (const [name, source] of Object.entries(loaded.config.sources || {})) {
37197
+ if (source.provider !== "local") continue;
37198
+ const files = source.files.map((file) => resolveUnitFile(file, loaded.baseDir, homeDir)).filter(import_fs7.existsSync);
37199
+ if (!files.length) continue;
37200
+ const probe = {
37201
+ name,
37202
+ kind: "declared",
37203
+ scope,
37204
+ baseDir: loaded.baseDir,
37205
+ configPath: loaded.path,
37206
+ source,
37207
+ files,
37208
+ keyNames: []
37209
+ };
37210
+ units.push({ ...probe, keyNames: Object.keys(snapshotValues(probe)).sort() });
37211
+ }
37212
+ return units;
37213
+ }
37214
+ function discoverPlaintextUnits(layers, homeDir = (0, import_os5.homedir)()) {
37215
+ const units = [];
37216
+ if ((0, import_fs7.existsSync)(layers.globalEnvFile) && !isDotenvxEncrypted(layers.globalEnvFile)) {
37217
+ const source = { provider: "local", files: [layers.globalEnvFile], optional: true };
37218
+ const probe = {
37219
+ name: GLOBAL_DOTENV_SOURCE,
37220
+ kind: "global-implicit",
37221
+ scope: null,
37222
+ baseDir: (0, import_path7.dirname)(layers.globalEnvFile),
37223
+ configPath: null,
37224
+ source,
37225
+ files: [layers.globalEnvFile],
37226
+ keyNames: []
37227
+ };
37228
+ units.push({ ...probe, keyNames: Object.keys(snapshotValues(probe)).sort() });
37229
+ }
37230
+ units.push(...declaredUnits(layers.global, "global", homeDir));
37231
+ units.push(...declaredUnits(layers.project, "project", homeDir));
37232
+ return units;
37233
+ }
37234
+ function legacyPlaintextHints(layers, units, homeDir = (0, import_os5.homedir)()) {
37235
+ const covered = new Set(units.flatMap((unit) => unit.files));
37236
+ const legacyFiles = layers.project.config.envFiles || [".env.local"];
37237
+ return legacyFiles.map((file) => resolveUnitFile(file, layers.project.baseDir, homeDir)).filter((file) => (0, import_fs7.existsSync)(file) && !covered.has(file) && !isDotenvxEncrypted(file));
37238
+ }
37239
+ function backupFile(file, stamp) {
37240
+ const backup = `${file}.mx-env-backup-${stamp}`;
37241
+ (0, import_fs7.copyFileSync)(file, backup);
37242
+ (0, import_fs7.chmodSync)(backup, 384);
37243
+ return backup;
37244
+ }
37245
+ var BACKUP_MARK = ".mx-env-backup-";
37246
+ function restoreBackups(backups) {
37247
+ const failed = [];
37248
+ for (const backup of backups) {
37249
+ const original = backup.slice(0, backup.lastIndexOf(BACKUP_MARK));
37250
+ try {
37251
+ if (original && (0, import_fs7.existsSync)(backup)) (0, import_fs7.copyFileSync)(backup, original);
37252
+ } catch {
37253
+ if (original) failed.push(original);
37254
+ }
37255
+ }
37256
+ return failed;
37257
+ }
37258
+ function withRestoreOutcome(message, failed, backups) {
37259
+ if (failed.length) {
37260
+ return `${message} RESTORE FAILED for: ${failed.join(", ")} \u2014 restore manually from the .mx-env-backup-* copies${backups.length ? ` (${backups.join(", ")})` : ""}.`;
37261
+ }
37262
+ return `${message} Plaintext and configuration were restored from backup${backups.length ? ` (${backups.join(", ")})` : ""}.`;
37263
+ }
37264
+ function equivalent(expected, actual) {
37265
+ const expectedKeys = Object.keys(expected).sort();
37266
+ const actualKeys = Object.keys(actual).sort();
37267
+ if (expectedKeys.length !== actualKeys.length) return false;
37268
+ return expectedKeys.every((key, index2) => key === actualKeys[index2] && expected[key] === actual[key]);
37269
+ }
37270
+ function existingDeclaredFiles(unit, homeDir) {
37271
+ const existing = new Set(unit.files);
37272
+ return unit.source.files.filter((file) => existing.has(resolveUnitFile(file, unit.baseDir, homeDir)));
37273
+ }
37274
+ function droppedDeclaredFiles(unit, homeDir) {
37275
+ const existing = new Set(unit.files);
37276
+ return unit.source.files.filter((file) => !existing.has(resolveUnitFile(file, unit.baseDir, homeDir)));
37277
+ }
37278
+ function rewriteDeclaredSource(unit, newSource, stamp, backups) {
37279
+ const configPath = unit.configPath;
37280
+ backups.push(backupFile(configPath, stamp));
37281
+ const parsed = JSON.parse((0, import_fs7.readFileSync)(configPath, "utf8"));
37282
+ parsed.sources[unit.name] = newSource;
37283
+ (0, import_fs7.writeFileSync)(configPath, `${JSON.stringify(parsed, null, 2)}
37284
+ `);
37285
+ }
37286
+ function gitIgnoreWarnings(paths) {
37287
+ const warnings = [];
37288
+ for (const path of paths) {
37289
+ try {
37290
+ (0, import_child_process4.execFileSync)("git", ["rev-parse", "--is-inside-work-tree"], { cwd: (0, import_path7.dirname)(path), stdio: ["ignore", "ignore", "ignore"] });
37291
+ } catch {
37292
+ continue;
37293
+ }
37294
+ try {
37295
+ (0, import_child_process4.execFileSync)("git", ["check-ignore", "-q", path], { cwd: (0, import_path7.dirname)(path), stdio: ["ignore", "ignore", "ignore"] });
37296
+ } catch (error) {
37297
+ if (error?.status === 1) {
37298
+ warnings.push(`[morphix-env] WARNING: ${path} is inside a git repository and NOT git-ignored \u2014 this file holds the decryption key and must never be committed.`);
37299
+ }
37300
+ }
37301
+ }
37302
+ return warnings;
37303
+ }
37304
+ async function reloadViaKeychain(unit, newSource, homeDir) {
37305
+ const entry = {
37306
+ name: unit.name,
37307
+ source: newSource,
37308
+ baseDir: unit.baseDir,
37309
+ globalImplicit: unit.kind === "global-implicit"
37310
+ };
37311
+ const loaded = await loadSource(entry, { homeDir, environment: { ...process.env } });
37312
+ return loaded.values;
37313
+ }
37314
+ function decryptValues(command, files) {
37315
+ const values = {};
37316
+ for (const file of files) {
37317
+ const output = (0, import_child_process4.execFileSync)(command, ["decrypt", "-f", file, "--stdout", "--no-native"], {
37318
+ encoding: "utf8",
37319
+ cwd: (0, import_path7.dirname)(file),
37320
+ stdio: ["ignore", "pipe", "ignore"]
37321
+ });
37322
+ const markerKeys = new Set(Object.keys(parseEnvFile(file)).filter((key) => key.startsWith("DOTENV_PUBLIC_KEY")));
37323
+ for (const [key, value] of Object.entries((0, import_dotenv4.parse)(output))) {
37324
+ if (!markerKeys.has(key)) values[key] = value;
37325
+ }
37326
+ }
37327
+ return values;
37328
+ }
37329
+ async function migrateToDotenvx(unit, stamp, homeDir, backups) {
37330
+ const snapshot = snapshotValues(unit);
37331
+ for (const file of unit.files) backups.push(backupFile(file, stamp));
37332
+ const candidateKeysFiles = [...new Set(unit.files.map((file) => (0, import_path7.join)((0, import_path7.dirname)(file), ".env.keys")))];
37333
+ const preExistingKeys = new Set(candidateKeysFiles.filter(import_fs7.existsSync));
37334
+ for (const keysFile of preExistingKeys) backups.push(backupFile(keysFile, stamp));
37335
+ try {
37336
+ let command;
37337
+ try {
37338
+ command = ensureCommand("dotenvx", unit.name, "dotenvx");
37339
+ } catch {
37340
+ throw new Error("the dotenvx CLI is required; install it (e.g. `npm i -g @dotenvx/dotenvx`) or migrate to another target.");
37341
+ }
37342
+ for (const file of unit.files) {
37343
+ (0, import_child_process4.execFileSync)(command, ["encrypt", "-f", file, "--no-native"], { cwd: (0, import_path7.dirname)(file), stdio: ["ignore", "ignore", "ignore"] });
37344
+ const keysFile = (0, import_path7.join)((0, import_path7.dirname)(file), ".env.keys");
37345
+ if ((0, import_fs7.existsSync)(keysFile)) (0, import_fs7.chmodSync)(keysFile, 384);
37346
+ }
37347
+ const keysFiles = candidateKeysFiles.filter(import_fs7.existsSync);
37348
+ const values = decryptValues(command, unit.files);
37349
+ if (!equivalent(snapshot, values)) {
37350
+ throw new Error("post-migration verification failed: decrypted values differ from the plaintext snapshot.");
37351
+ }
37352
+ const warnings = [];
37353
+ if (unit.kind === "declared") {
37354
+ const newSource = {
37355
+ provider: "dotenvx",
37356
+ files: existingDeclaredFiles(unit, homeDir),
37357
+ ...unit.source.override ? { override: true } : {},
37358
+ ...unit.source.optional ? { optional: true } : {}
37359
+ };
37360
+ rewriteDeclaredSource(unit, newSource, stamp, backups);
37361
+ for (const dropped of droppedDeclaredFiles(unit, homeDir)) {
37362
+ warnings.push(`[morphix-env] Warning: declared file "${dropped}" did not exist at migration time and was dropped from the rewritten source; re-add it to the config if it returns.`);
37363
+ }
37364
+ }
37365
+ return { warnings: [...warnings, ...gitIgnoreWarnings(keysFiles)] };
37366
+ } catch (error) {
37367
+ const failed = restoreBackups(backups);
37368
+ for (const keysFile of candidateKeysFiles) {
37369
+ if (!preExistingKeys.has(keysFile) && (0, import_fs7.existsSync)(keysFile)) (0, import_fs7.rmSync)(keysFile);
37370
+ }
37371
+ const message = error instanceof Error ? error.message : "unknown failure";
37372
+ throw new Error(withRestoreOutcome(message, failed, backups));
37373
+ }
37374
+ }
37375
+ function snapshotCovered(expected, actual) {
37376
+ return Object.keys(expected).every((key) => actual[key] === expected[key]);
37377
+ }
37378
+ async function migrateToKeychain(unit, service, stamp, homeDir, backups) {
37379
+ if (process.platform !== "darwin") throw new Error("the os-keychain target is supported on macOS only; use --to dotenvx on this machine.");
37380
+ const keytar = await loadKeytar();
37381
+ if (!keytar) throw new Error('the os-keychain target needs the optional native module "keytar"; reinstall morphix-env or use --to dotenvx.');
37382
+ const snapshot = snapshotValues(unit);
37383
+ for (const file of unit.files) backups.push(backupFile(file, stamp));
37384
+ const preExisting = new Map((await keytar.findCredentials(service)).map(({ account, password }) => [account, password]));
37385
+ try {
37386
+ for (const [key, value] of Object.entries(snapshot)) await keytar.setPassword(service, key, value);
37387
+ const newSource = {
37388
+ provider: "os-keychain",
37389
+ service,
37390
+ ...unit.source.override ? { override: true } : {}
37391
+ };
37392
+ const values = await reloadViaKeychain(unit, newSource, homeDir);
37393
+ if (!snapshotCovered(snapshot, values)) {
37394
+ throw new Error("post-migration verification failed: keychain values differ from the plaintext snapshot.");
37395
+ }
37396
+ const foreign = Object.keys(values).filter((key) => !(key in snapshot));
37397
+ const warnings = foreign.length ? [`[morphix-env] Warning: keychain service "${service}" also holds ${foreign.length} unrelated account(s) (e.g. "${foreign[0]}"); verify the service is not shared unexpectedly.`] : [];
37398
+ rewriteDeclaredSource(unit, newSource, stamp, backups);
37399
+ for (const file of unit.files) (0, import_fs7.rmSync)(file);
37400
+ return { warnings };
37401
+ } catch (error) {
37402
+ const failed = restoreBackups(backups);
37403
+ for (const key of Object.keys(snapshot)) {
37404
+ if (preExisting.has(key)) {
37405
+ await keytar.setPassword(service, key, preExisting.get(key)).catch(() => {
37406
+ });
37407
+ } else {
37408
+ await keytar.deletePassword(service, key).catch(() => {
37409
+ });
37410
+ }
37411
+ }
37412
+ const message = error instanceof Error ? error.message : "unknown failure";
37413
+ throw new Error(withRestoreOutcome(message, failed, backups));
37414
+ }
37415
+ }
37416
+ async function executeUnit(unit, target, options = {}, homeDir = (0, import_os5.homedir)()) {
37417
+ if (unit.kind === "global-implicit" && target === "keychain") {
37418
+ return {
37419
+ unit,
37420
+ status: "skipped",
37421
+ backups: [],
37422
+ warnings: [],
37423
+ detail: "the global implicit file cannot migrate to os-keychain (its loading does not go through profiles); use --to dotenvx for it."
37424
+ };
37425
+ }
37426
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
37427
+ const backups = [];
37428
+ if (!unit.keyNames.length) {
37429
+ return { unit, status: "skipped", backups: [], warnings: [], detail: "no keys in the file(s); nothing to migrate." };
37430
+ }
37431
+ try {
37432
+ const { warnings } = target === "dotenvx" ? await migrateToDotenvx(unit, stamp, homeDir, backups) : await migrateToKeychain(unit, options.service || `mx-env:${unit.name}`, stamp, homeDir, backups);
37433
+ return { unit, status: "migrated", backups, warnings, detail: "" };
37434
+ } catch (error) {
37435
+ const message = error instanceof Error ? error.message : "unknown failure";
37436
+ return { unit, status: "failed", backups, warnings: [], detail: message };
37437
+ }
37438
+ }
37439
+
37157
37440
  // src/redaction.ts
37158
37441
  var StreamingRedactor = class {
37159
37442
  constructor(values) {
@@ -37294,14 +37577,14 @@ var ReferenceResolver = class {
37294
37577
  };
37295
37578
 
37296
37579
  // src/skill.ts
37297
- var import_fs7 = require("fs");
37298
- var import_os5 = require("os");
37299
- var import_path7 = require("path");
37580
+ var import_fs8 = require("fs");
37581
+ var import_os6 = require("os");
37582
+ var import_path8 = require("path");
37300
37583
  var SKILL_TARGETS = ["global", "project", "trellis"];
37301
37584
  function resolveSkillPath(target) {
37302
- if (target === "project") return (0, import_path7.join)(process.cwd(), ".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");
37585
+ if (target === "project") return (0, import_path8.join)(process.cwd(), ".claude", "skills", "morphix-env", "SKILL.md");
37586
+ if (target === "trellis") return (0, import_path8.join)((0, import_os6.homedir)(), ".trellis", "skills", "morphix-env", "SKILL.md");
37587
+ return (0, import_path8.join)((0, import_os6.homedir)(), ".claude", "skills", "morphix-env", "SKILL.md");
37305
37588
  }
37306
37589
  function targetHint(target) {
37307
37590
  if (target === "project") {
@@ -37320,17 +37603,17 @@ function printNextSteps() {
37320
37603
  4. mx-env doc generate this project's key-to-source workflow Skill`);
37321
37604
  }
37322
37605
  function cmdSkill(subcommand, options) {
37323
- const templatePath = (0, import_path7.join)(__dirname, "SKILL.md");
37324
- const template = (0, import_fs7.readFileSync)(templatePath, "utf8");
37606
+ const templatePath = (0, import_path8.join)(__dirname, "SKILL.md");
37607
+ const template = (0, import_fs8.readFileSync)(templatePath, "utf8");
37325
37608
  if (subcommand === "show") {
37326
37609
  console.log(`[morphix-env] Skill template: ${templatePath}`);
37327
37610
  for (const target2 of SKILL_TARGETS) {
37328
37611
  const path = resolveSkillPath(target2);
37329
- if (!(0, import_fs7.existsSync)(path)) {
37612
+ if (!(0, import_fs8.existsSync)(path)) {
37330
37613
  console.log(` ${target2}: not installed (${path})`);
37331
37614
  continue;
37332
37615
  }
37333
- const state = (0, import_fs7.readFileSync)(path, "utf8") === template ? "installed (matches this version)" : "installed (differs from this version)";
37616
+ const state = (0, import_fs8.readFileSync)(path, "utf8") === template ? "installed (matches this version)" : "installed (differs from this version)";
37334
37617
  console.log(` ${target2}: ${state} (${path})`);
37335
37618
  }
37336
37619
  return 0;
@@ -37345,8 +37628,8 @@ function cmdSkill(subcommand, options) {
37345
37628
  return 1;
37346
37629
  }
37347
37630
  const dest = resolveSkillPath(target);
37348
- if ((0, import_fs7.existsSync)(dest)) {
37349
- if ((0, import_fs7.readFileSync)(dest, "utf8") === template) {
37631
+ if ((0, import_fs8.existsSync)(dest)) {
37632
+ if ((0, import_fs8.readFileSync)(dest, "utf8") === template) {
37350
37633
  console.log(`[morphix-env] Skill already installed and up to date: ${dest}`);
37351
37634
  return 0;
37352
37635
  }
@@ -37355,8 +37638,8 @@ function cmdSkill(subcommand, options) {
37355
37638
  return 1;
37356
37639
  }
37357
37640
  }
37358
- (0, import_fs7.mkdirSync)((0, import_path7.dirname)(dest), { recursive: true });
37359
- (0, import_fs7.writeFileSync)(dest, template, { mode: 420 });
37641
+ (0, import_fs8.mkdirSync)((0, import_path8.dirname)(dest), { recursive: true });
37642
+ (0, import_fs8.writeFileSync)(dest, template, { mode: 420 });
37360
37643
  console.log(`[morphix-env] Installed skill: ${dest}`);
37361
37644
  console.log(`[morphix-env] ${targetHint(target)}`);
37362
37645
  printNextSteps();
@@ -37437,10 +37720,268 @@ async function startSecretServer(values) {
37437
37720
  };
37438
37721
  }
37439
37722
 
37723
+ // src/shims.ts
37724
+ var import_fs9 = require("fs");
37725
+ var import_os7 = require("os");
37726
+ var import_path9 = require("path");
37727
+ var SHIM_MARKER = "# mx-env shim \u2014 regenerated by `mx-env shell-init`; do not edit by hand.";
37728
+ var RESERVED_SHIM_NAMES = [
37729
+ "run",
37730
+ "fetch",
37731
+ "export",
37732
+ "render",
37733
+ "inject",
37734
+ "generate",
37735
+ "gen",
37736
+ "inspect",
37737
+ "migrate",
37738
+ "audit",
37739
+ "edit",
37740
+ "doc",
37741
+ "skill",
37742
+ "shell-init",
37743
+ "help",
37744
+ "mx-env",
37745
+ "morphix-env",
37746
+ "__mx_editor"
37747
+ ];
37748
+ var SHIM_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
37749
+ function shimsDir(homeDir = (0, import_os7.homedir)()) {
37750
+ return (0, import_path9.join)(homeDir, ".mx-env", "shims");
37751
+ }
37752
+ function validateShimList(list, label) {
37753
+ if (list === void 0 || list === null) return [];
37754
+ if (!Array.isArray(list)) return [`${label}: "shims" must be an array of command names`];
37755
+ const errors = [];
37756
+ const seen = /* @__PURE__ */ new Set();
37757
+ for (const entry of list) {
37758
+ if (typeof entry !== "string" || !entry) {
37759
+ errors.push(`${label}: shim entries must be non-empty command names`);
37760
+ continue;
37761
+ }
37762
+ if (!SHIM_NAME_PATTERN.test(entry)) {
37763
+ errors.push(`${label}: shim entry "${entry}" must be a bare command name (letters, digits, ".", "_", "-"; no path separators or whitespace)`);
37764
+ continue;
37765
+ }
37766
+ const lower = entry.toLowerCase();
37767
+ if (RESERVED_SHIM_NAMES.includes(lower)) {
37768
+ errors.push(`${label}: shim entry "${entry}" conflicts with a reserved mx-env command name`);
37769
+ continue;
37770
+ }
37771
+ if (seen.has(lower)) {
37772
+ errors.push(`${label}: duplicate shim entry "${entry}" (comparison is case-insensitive)`);
37773
+ continue;
37774
+ }
37775
+ seen.add(lower);
37776
+ }
37777
+ return errors;
37778
+ }
37779
+ function resolveShimCommands(layers, includeGlobal = true) {
37780
+ const seen = /* @__PURE__ */ new Set();
37781
+ const commands = [];
37782
+ const push = (list) => {
37783
+ if (!Array.isArray(list)) return;
37784
+ for (const entry of list) {
37785
+ if (typeof entry !== "string") continue;
37786
+ const lower = entry.toLowerCase();
37787
+ if (seen.has(lower)) continue;
37788
+ seen.add(lower);
37789
+ commands.push(entry);
37790
+ }
37791
+ };
37792
+ if (includeGlobal) push(layers.global.config.shims);
37793
+ push(layers.project.config.shims);
37794
+ return commands;
37795
+ }
37796
+ function renderShimScript(command) {
37797
+ return `#!/bin/sh
37798
+ ${SHIM_MARKER}
37799
+ 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; }
37800
+ MXENV_SHIM=1 exec mx-env run -- ${command} "$@"
37801
+ `;
37802
+ }
37803
+ function readIfExists(path) {
37804
+ try {
37805
+ return (0, import_fs9.readFileSync)(path, "utf8");
37806
+ } catch {
37807
+ return null;
37808
+ }
37809
+ }
37810
+ function writeShims(commands, homeDir = (0, import_os7.homedir)()) {
37811
+ const dir = shimsDir(homeDir);
37812
+ (0, import_fs9.mkdirSync)(dir, { recursive: true, mode: 448 });
37813
+ const wanted = new Set(commands.map((command) => command.toLowerCase()));
37814
+ const written = [];
37815
+ const removed = [];
37816
+ const skipped = [];
37817
+ for (const file of (0, import_fs9.readdirSync)(dir)) {
37818
+ const path = (0, import_path9.join)(dir, file);
37819
+ const content = readIfExists(path);
37820
+ if (content === null || !content.includes(SHIM_MARKER)) continue;
37821
+ if (!wanted.has(file.toLowerCase())) {
37822
+ (0, import_fs9.rmSync)(path);
37823
+ removed.push(file);
37824
+ }
37825
+ }
37826
+ for (const command of commands) {
37827
+ const path = (0, import_path9.join)(dir, command);
37828
+ const content = renderShimScript(command);
37829
+ let isSymlink = false;
37830
+ try {
37831
+ isSymlink = (0, import_fs9.lstatSync)(path).isSymbolicLink();
37832
+ } catch {
37833
+ }
37834
+ const existing = isSymlink ? null : readIfExists(path);
37835
+ if (!isSymlink && existing !== null && !existing.includes(SHIM_MARKER)) {
37836
+ skipped.push(command);
37837
+ continue;
37838
+ }
37839
+ if (isSymlink) (0, import_fs9.rmSync)(path);
37840
+ else if (existing === content) continue;
37841
+ (0, import_fs9.writeFileSync)(path, content, { mode: 493 });
37842
+ (0, import_fs9.chmodSync)(path, 493);
37843
+ written.push(command);
37844
+ }
37845
+ return { written, removed, skipped };
37846
+ }
37847
+ function removeAllShims(homeDir = (0, import_os7.homedir)()) {
37848
+ const dir = shimsDir(homeDir);
37849
+ if (!(0, import_fs9.existsSync)(dir)) return [];
37850
+ const removed = [];
37851
+ for (const file of (0, import_fs9.readdirSync)(dir)) {
37852
+ const path = (0, import_path9.join)(dir, file);
37853
+ const content = readIfExists(path);
37854
+ if (content !== null && content.includes(SHIM_MARKER)) {
37855
+ (0, import_fs9.rmSync)(path);
37856
+ removed.push(file);
37857
+ }
37858
+ }
37859
+ return removed;
37860
+ }
37861
+ function detectHandmadeReservedShims(homeDir = (0, import_os7.homedir)()) {
37862
+ const dir = shimsDir(homeDir);
37863
+ if (!(0, import_fs9.existsSync)(dir)) return [];
37864
+ const found = [];
37865
+ for (const file of (0, import_fs9.readdirSync)(dir)) {
37866
+ if (!RESERVED_SHIM_NAMES.includes(file.toLowerCase())) continue;
37867
+ const content = readIfExists((0, import_path9.join)(dir, file));
37868
+ if (content === null || !content.includes(SHIM_MARKER)) found.push(file);
37869
+ }
37870
+ return found;
37871
+ }
37872
+ var RC_BLOCK_BEGIN = "# >>> mx-env shims >>>";
37873
+ var RC_BLOCK_END = "# <<< mx-env shims <<<";
37874
+ function rcBlock(homeDir = (0, import_os7.homedir)()) {
37875
+ const dir = shimsDir(homeDir);
37876
+ if (/["$`\\]/.test(dir)) {
37877
+ throw new Error("[morphix-env] Shims directory path contains characters that are unsafe in a shell rc line.");
37878
+ }
37879
+ return `${RC_BLOCK_BEGIN}
37880
+ export PATH="${dir}:$PATH"
37881
+ ${RC_BLOCK_END}`;
37882
+ }
37883
+ function defaultRcPath(shell, homeDir = (0, import_os7.homedir)()) {
37884
+ const name = shell ? shell.split("/").pop() : "";
37885
+ if (name === "zsh") return (0, import_path9.join)(homeDir, ".zshrc");
37886
+ if (name === "bash") return (0, import_path9.join)(homeDir, ".bashrc");
37887
+ return null;
37888
+ }
37889
+ function writeFileAtomic(path, content) {
37890
+ const tmp = `${path}.mx-env-${process.pid}.tmp`;
37891
+ (0, import_fs9.writeFileSync)(tmp, content);
37892
+ (0, import_fs9.renameSync)(tmp, path);
37893
+ }
37894
+ function upsertRcBlock(rcPath, homeDir = (0, import_os7.homedir)()) {
37895
+ const existing = readIfExists(rcPath) ?? "";
37896
+ const block = rcBlock(homeDir);
37897
+ const pattern = new RegExp(`${escapeRegExp(RC_BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(RC_BLOCK_END)}`, "g");
37898
+ if (pattern.test(existing)) {
37899
+ const replaced = existing.replace(pattern, block);
37900
+ if (replaced === existing) return "unchanged";
37901
+ writeFileAtomic(rcPath, replaced.endsWith("\n") ? replaced : `${replaced}
37902
+ `);
37903
+ return "updated";
37904
+ }
37905
+ const separator = existing.length && !existing.endsWith("\n") ? "\n" : "";
37906
+ writeFileAtomic(rcPath, `${existing}${separator}${block}
37907
+ `);
37908
+ return "added";
37909
+ }
37910
+ function removeRcBlock(rcPath) {
37911
+ const existing = readIfExists(rcPath);
37912
+ if (existing === null) return "absent";
37913
+ const pattern = new RegExp(`\\n?${escapeRegExp(RC_BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(RC_BLOCK_END)}\\n?`, "g");
37914
+ if (!pattern.test(existing)) return "absent";
37915
+ writeFileAtomic(rcPath, existing.replace(pattern, "\n"));
37916
+ return "removed";
37917
+ }
37918
+ function escapeRegExp(text) {
37919
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37920
+ }
37921
+ function stripShimsFromPath(pathValue, homeDir = (0, import_os7.homedir)()) {
37922
+ if (!pathValue) return pathValue;
37923
+ const target = shimsDir(homeDir);
37924
+ let targetReal = target;
37925
+ try {
37926
+ targetReal = (0, import_fs9.realpathSync)(target);
37927
+ } catch {
37928
+ }
37929
+ const equal = (a, b) => process.platform === "darwin" ? a.toLowerCase() === b.toLowerCase() : a === b;
37930
+ const entries = pathValue.split(import_path9.delimiter).filter((entry) => {
37931
+ if (!entry) return true;
37932
+ const literal = entry.replace(/\/+$/, "");
37933
+ let resolved = literal;
37934
+ try {
37935
+ resolved = (0, import_fs9.realpathSync)(literal);
37936
+ } catch {
37937
+ }
37938
+ return !equal(literal, target) && !equal(resolved, targetReal);
37939
+ });
37940
+ return entries.join(import_path9.delimiter);
37941
+ }
37942
+ function sanitizeBootEnv(env = process.env, homeDir) {
37943
+ const originalPath = env.PATH;
37944
+ const viaShim = env.MXENV_SHIM === "1";
37945
+ try {
37946
+ const home = homeDir ?? (0, import_os7.homedir)();
37947
+ const stripped = stripShimsFromPath(originalPath, home);
37948
+ if (stripped !== originalPath) {
37949
+ if (stripped) env.PATH = stripped;
37950
+ else delete env.PATH;
37951
+ }
37952
+ } catch {
37953
+ }
37954
+ delete env.MXENV_SHIM;
37955
+ return { viaShim, originalPath };
37956
+ }
37957
+ function findShadowedCommands(commands, originalPath, homeDir = (0, import_os7.homedir)()) {
37958
+ if (!originalPath) return { absent: [...commands], shadowed: [] };
37959
+ const dirs = originalPath.split(import_path9.delimiter).filter(Boolean);
37960
+ const absent = [];
37961
+ const shadowed = [];
37962
+ for (const command of commands) {
37963
+ const shimPath = (0, import_path9.join)(shimsDir(homeDir), command);
37964
+ let resolvedTo = null;
37965
+ for (const dir of dirs) {
37966
+ const candidate = (0, import_path9.join)(dir, command);
37967
+ try {
37968
+ (0, import_fs9.accessSync)(candidate, import_fs9.constants.X_OK);
37969
+ resolvedTo = candidate;
37970
+ break;
37971
+ } catch {
37972
+ }
37973
+ }
37974
+ const samePath = (a, b) => process.platform === "darwin" ? a.toLowerCase() === b.toLowerCase() : a === b;
37975
+ if (resolvedTo === null) absent.push(command);
37976
+ else if (!samePath(resolvedTo, shimPath)) shadowed.push(command);
37977
+ }
37978
+ return { absent, shadowed };
37979
+ }
37980
+
37440
37981
  // src/template.ts
37441
- var import_child_process4 = require("child_process");
37442
- var import_fs8 = require("fs");
37443
- var import_path8 = require("path");
37982
+ var import_child_process5 = require("child_process");
37983
+ var import_fs10 = require("fs");
37984
+ var import_path10 = require("path");
37444
37985
  function shellQuote(value) {
37445
37986
  return `'${value.replace(/'/g, "'\\''")}'`;
37446
37987
  }
@@ -37487,20 +38028,20 @@ function renderTemplate(text, environment) {
37487
38028
  return { output, missing: [...missing] };
37488
38029
  }
37489
38030
  function writePrivateFile(path, content) {
37490
- const absolute = (0, import_path8.resolve)(path);
37491
- (0, import_fs8.mkdirSync)((0, import_path8.dirname)(absolute), { recursive: true, mode: 448 });
37492
- (0, import_fs8.writeFileSync)(absolute, content, { mode: 384 });
37493
- (0, import_fs8.chmodSync)(absolute, 384);
38031
+ const absolute = (0, import_path10.resolve)(path);
38032
+ (0, import_fs10.mkdirSync)((0, import_path10.dirname)(absolute), { recursive: true, mode: 448 });
38033
+ (0, import_fs10.writeFileSync)(absolute, content, { mode: 384 });
38034
+ (0, import_fs10.chmodSync)(absolute, 384);
37494
38035
  }
37495
38036
  function gitOutputStatus(path) {
37496
- const absolute = (0, import_path8.resolve)(path);
37497
- const cwd = (0, import_path8.dirname)(absolute);
37498
- const name = (0, import_path8.basename)(absolute);
37499
- const inside = (0, import_child_process4.spawnSync)("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8" });
38037
+ const absolute = (0, import_path10.resolve)(path);
38038
+ const cwd = (0, import_path10.dirname)(absolute);
38039
+ const name = (0, import_path10.basename)(absolute);
38040
+ const inside = (0, import_child_process5.spawnSync)("git", ["rev-parse", "--is-inside-work-tree"], { cwd, encoding: "utf8" });
37500
38041
  if (inside.status !== 0) return "outside-repo";
37501
- const tracked = (0, import_child_process4.spawnSync)("git", ["ls-files", "--error-unmatch", "--", name], { cwd, encoding: "utf8" });
38042
+ const tracked = (0, import_child_process5.spawnSync)("git", ["ls-files", "--error-unmatch", "--", name], { cwd, encoding: "utf8" });
37502
38043
  if (tracked.status === 0) return "tracked";
37503
- const ignored = (0, import_child_process4.spawnSync)("git", ["check-ignore", "-q", "--", name], { cwd, encoding: "utf8" });
38044
+ const ignored = (0, import_child_process5.spawnSync)("git", ["check-ignore", "-q", "--", name], { cwd, encoding: "utf8" });
37504
38045
  if (ignored.status === 0) return "ignored";
37505
38046
  if (ignored.status === 1) return "untracked";
37506
38047
  return "outside-repo";
@@ -37517,7 +38058,7 @@ function warnOutputGitRisk(path) {
37517
38058
  // package.json
37518
38059
  var package_default = {
37519
38060
  name: "morphix-env",
37520
- version: "0.8.0",
38061
+ version: "0.10.0",
37521
38062
  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).",
37522
38063
  author: "MorphixAI <dev@morphixai.com>",
37523
38064
  homepage: "https://github.com/Morphicai/morphix-env#readme",
@@ -37622,9 +38163,15 @@ function parseArgs(argv) {
37622
38163
  printEditorUrl: false,
37623
38164
  source: null,
37624
38165
  target: null,
38166
+ to: null,
38167
+ service: null,
37625
38168
  force: false,
37626
38169
  dryRun: false,
37627
- serve: false
38170
+ serve: false,
38171
+ write: false,
38172
+ remove: false,
38173
+ warnPlaintext: false,
38174
+ unknownFlags: []
37628
38175
  };
37629
38176
  let i = 2;
37630
38177
  const command = argv[i];
@@ -37661,24 +38208,36 @@ function parseArgs(argv) {
37661
38208
  args.printEditorUrl = true;
37662
38209
  } else if (arg === "--target") {
37663
38210
  if (argv[++i]) args.target = argv[i];
38211
+ } else if (arg === "--to") {
38212
+ if (argv[++i]) args.to = argv[i];
38213
+ } else if (arg === "--service") {
38214
+ if (argv[++i]) args.service = argv[i];
37664
38215
  } else if (arg === "--force") {
37665
38216
  args.force = true;
37666
38217
  } else if (arg === "--dry-run") {
37667
38218
  args.dryRun = true;
38219
+ } else if (arg === "--write") {
38220
+ args.write = true;
38221
+ } else if (arg === "--remove") {
38222
+ args.remove = true;
37668
38223
  } else if (arg === "--serve") {
37669
38224
  args.serve = true;
38225
+ } else if (arg === "--warn-plaintext") {
38226
+ args.warnPlaintext = true;
37670
38227
  } else if (args.command === "run") {
37671
38228
  args.subArgs = argv.slice(i);
37672
38229
  break;
37673
38230
  } else if (!arg.startsWith("-")) {
37674
38231
  args.positionals.push(arg);
38232
+ } else {
38233
+ args.unknownFlags.push(arg);
37675
38234
  }
37676
38235
  i++;
37677
38236
  }
37678
38237
  return args;
37679
38238
  }
37680
38239
  function configLayers() {
37681
- return loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os6.homedir)() });
38240
+ return loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os8.homedir)() });
37682
38241
  }
37683
38242
  function composeOptions(args) {
37684
38243
  return {
@@ -37688,14 +38247,40 @@ function composeOptions(args) {
37688
38247
  envName: args.env,
37689
38248
  allowInsecureGlobal: args.allowInsecureGlobal,
37690
38249
  envFiles: args.envFiles,
37691
- homeDir: (0, import_os6.homedir)()
38250
+ homeDir: (0, import_os8.homedir)()
37692
38251
  };
37693
38252
  }
38253
+ var bootInfo = { viaShim: false };
38254
+ var shimWarned = false;
38255
+ function warnInvalidShims(layers, includeGlobal) {
38256
+ if (shimWarned) return;
38257
+ shimWarned = true;
38258
+ const problems = [
38259
+ ...includeGlobal ? validateShimList(layers.global.config.shims, layers.global.path) : [],
38260
+ ...validateShimList(layers.project.config.shims, layers.project.path)
38261
+ ];
38262
+ if (problems.length) {
38263
+ console.warn(`[morphix-env] Ignoring invalid "shims" entries (${problems.length} problem(s)); run \`mx-env shell-init\` for the details.`);
38264
+ }
38265
+ }
37694
38266
  function resolveComposition(args) {
37695
- return composeEnvironment(configLayers(), composeOptions(args));
38267
+ const layers = configLayers();
38268
+ warnInvalidShims(layers, !args.noGlobal);
38269
+ return composeEnvironment(layers, composeOptions(args));
38270
+ }
38271
+ var plaintextWarned = false;
38272
+ function warnPlaintextSources(composition, args) {
38273
+ if (plaintextWarned) return;
38274
+ if (!args.warnPlaintext && process.env.MXENV_WARN_PLAINTEXT !== "1") return;
38275
+ plaintextWarned = true;
38276
+ const loaded = composition.sourceStatuses.filter(
38277
+ (source) => source.provider === "local" && source.availability === "loaded" && source.keyCount > 0
38278
+ );
38279
+ if (!loaded.length) return;
38280
+ console.warn(`[morphix-env] Plaintext local source(s) in use: ${loaded.map((source) => source.name).join(", ")} \u2014 local plaintext files are dev-only; run \`mx-env migrate\` to move them into encrypted storage.`);
37696
38281
  }
37697
38282
  function generatedOptions(args) {
37698
- const config = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os6.homedir)() }).project.config;
38283
+ const config = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os8.homedir)() }).project.config;
37699
38284
  return {
37700
38285
  outFile: args.outFile || config.generate?.out || "public/__env.js",
37701
38286
  filter: args.filter || config.generate?.filter || null
@@ -37704,12 +38289,15 @@ function generatedOptions(args) {
37704
38289
  function generateClientEnv(outFile, filter, environment) {
37705
38290
  let vars = extractPublicVars(environment);
37706
38291
  if (filter) vars = Object.fromEntries(Object.entries(vars).filter(([key]) => key.startsWith(filter)));
37707
- (0, import_fs9.mkdirSync)((0, import_path9.dirname)(outFile), { recursive: true });
37708
- (0, import_fs9.writeFileSync)(outFile, `window.__ENV=${JSON.stringify(vars)};`);
38292
+ (0, import_fs11.mkdirSync)((0, import_path11.dirname)(outFile), { recursive: true });
38293
+ (0, import_fs11.writeFileSync)(outFile, `window.__ENV=${JSON.stringify(vars)};`);
37709
38294
  console.log(`[morphix-env] Generated ${outFile} (${Object.keys(vars).length} client vars)`);
37710
38295
  }
37711
38296
  async function runChild(command, commandArgs, composition) {
37712
- return new Promise((resolve7) => {
38297
+ return new Promise((resolve8) => {
38298
+ if (composition.environment.PATH) {
38299
+ composition.environment.PATH = stripShimsFromPath(composition.environment.PATH) ?? composition.environment.PATH;
38300
+ }
37713
38301
  const child = (0, import_cross_spawn.default)(command, commandArgs, { env: composition.environment, stdio: ["inherit", "pipe", "pipe"] });
37714
38302
  const stdout = new StreamingRedactor(composition.redactionValues);
37715
38303
  const stderr = new StreamingRedactor(composition.redactionValues);
@@ -37717,14 +38305,14 @@ async function runChild(command, commandArgs, composition) {
37717
38305
  child.stderr?.on("data", (chunk) => process.stderr.write(stderr.write(chunk)));
37718
38306
  child.on("error", () => {
37719
38307
  console.error(`[morphix-env] Failed to execute: ${command}`);
37720
- resolve7(1);
38308
+ resolve8(1);
37721
38309
  });
37722
38310
  child.on("close", (code) => {
37723
38311
  const stdoutFinal = stdout.flush();
37724
38312
  const stderrFinal = stderr.flush();
37725
38313
  if (stdoutFinal) process.stdout.write(stdoutFinal);
37726
38314
  if (stderrFinal) process.stderr.write(stderrFinal);
37727
- resolve7(code ?? 1);
38315
+ resolve8(code ?? 1);
37728
38316
  });
37729
38317
  });
37730
38318
  }
@@ -37736,7 +38324,8 @@ function writeRunAudit(composition, startedAt, outcome, exitCode) {
37736
38324
  keys: Object.keys(composition.provenance).sort(),
37737
38325
  outcome,
37738
38326
  exitCode,
37739
- durationMs: Date.now() - startedAt
38327
+ durationMs: Date.now() - startedAt,
38328
+ via: bootInfo.viaShim ? "shim" : "cli"
37740
38329
  });
37741
38330
  }
37742
38331
  async function startServeFor(composition, verbose) {
@@ -37789,6 +38378,7 @@ async function cmdRun(args) {
37789
38378
  }
37790
38379
  const startedAt = Date.now();
37791
38380
  const composition = await resolveComposition(args);
38381
+ warnPlaintextSources(composition, args);
37792
38382
  if (args.verbose) {
37793
38383
  for (const source of composition.sourceStatuses) {
37794
38384
  console.log(`[morphix-env] ${source.name}: ${source.availability} (${source.keyCount} keys)`);
@@ -37802,7 +38392,7 @@ async function cmdRun(args) {
37802
38392
  await resolveEnvironmentReferences(composition, args);
37803
38393
  if (args.outFile || generatedOptions(args).outFile) {
37804
38394
  const { outFile, filter } = generatedOptions(args);
37805
- const configured = Boolean(args.outFile || loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os6.homedir)() }).project.config.generate);
38395
+ const configured = Boolean(args.outFile || loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os8.homedir)() }).project.config.generate);
37806
38396
  if (configured) generateClientEnv(outFile, filter, composition.environment);
37807
38397
  }
37808
38398
  const [command, ...commandArgs] = args.subArgs;
@@ -37859,6 +38449,7 @@ async function cmdFetch(args) {
37859
38449
  }
37860
38450
  async function cmdGenerate(args) {
37861
38451
  const composition = await resolveComposition(args);
38452
+ warnPlaintextSources(composition, args);
37862
38453
  await resolveEnvironmentReferences(composition, args);
37863
38454
  const { outFile, filter } = generatedOptions(args);
37864
38455
  generateClientEnv(outFile, filter, composition.environment);
@@ -37871,7 +38462,8 @@ async function cmdInspect(args) {
37871
38462
  const composition = await resolveComposition(args);
37872
38463
  console.log(`profile: ${composition.profileName}`);
37873
38464
  for (const source of composition.sourceStatuses) {
37874
- console.log(`source: ${source.name} (${source.provider}) \u2014 ${source.availability}, ${source.keyCount} keys`);
38465
+ const plaintextMark = source.provider === "local" ? " [plaintext, dev-only]" : "";
38466
+ console.log(`source: ${source.name} (${source.provider}) \u2014 ${source.availability}, ${source.keyCount} keys${plaintextMark}`);
37875
38467
  }
37876
38468
  for (const [key, provenance] of Object.entries(composition.provenance).sort(([a], [b]) => a.localeCompare(b))) {
37877
38469
  if (!args.filter || key.startsWith(args.filter)) {
@@ -37884,6 +38476,7 @@ async function cmdInspect(args) {
37884
38476
  async function cmdExport(args) {
37885
38477
  const startedAt = Date.now();
37886
38478
  const composition = await resolveComposition(args);
38479
+ warnPlaintextSources(composition, args);
37887
38480
  const keys = Object.keys(composition.provenance).sort();
37888
38481
  if (args.dryRun) {
37889
38482
  for (const key of keys) console.log(key);
@@ -37907,12 +38500,13 @@ async function cmdRender(args) {
37907
38500
  console.error("[morphix-env] Usage: mx-env render <template-file> <output-file> [options]");
37908
38501
  return 1;
37909
38502
  }
37910
- if (!(0, import_fs9.existsSync)(templatePath)) {
38503
+ if (!(0, import_fs11.existsSync)(templatePath)) {
37911
38504
  console.error(`[morphix-env] Template not found: ${templatePath}`);
37912
38505
  return 1;
37913
38506
  }
37914
- const template = (0, import_fs9.readFileSync)(templatePath, "utf8");
38507
+ const template = (0, import_fs11.readFileSync)(templatePath, "utf8");
37915
38508
  const composition = await resolveComposition(args);
38509
+ warnPlaintextSources(composition, args);
37916
38510
  const names = findTemplateVariables(template);
37917
38511
  const missing = names.filter((name) => composition.environment[name] === void 0 || composition.environment[name] === null);
37918
38512
  if (missing.length) console.warn(`[morphix-env] Missing variables: ${missing.join(", ")} (left unresolved)`);
@@ -37936,12 +38530,14 @@ async function cmdInject(args) {
37936
38530
  console.error("[morphix-env] Usage: mx-env inject <template-file> <output-file> [options]");
37937
38531
  return 1;
37938
38532
  }
37939
- if (!(0, import_fs9.existsSync)(templatePath)) {
38533
+ if (!(0, import_fs11.existsSync)(templatePath)) {
37940
38534
  console.error(`[morphix-env] Template not found: ${templatePath}`);
37941
38535
  return 1;
37942
38536
  }
37943
- const template = (0, import_fs9.readFileSync)(templatePath, "utf8");
37944
- const resolver = new ReferenceResolver(configLayers(), composeOptions(args));
38537
+ const template = (0, import_fs11.readFileSync)(templatePath, "utf8");
38538
+ const layers = configLayers();
38539
+ warnInvalidShims(layers, !args.noGlobal);
38540
+ const resolver = new ReferenceResolver(layers, composeOptions(args));
37945
38541
  const references = findMxEnvReferences(template);
37946
38542
  if (args.dryRun) {
37947
38543
  for (const reference of references) {
@@ -37974,7 +38570,7 @@ async function cmdEdit(args) {
37974
38570
  }
37975
38571
  const composition = await resolveComposition(args);
37976
38572
  if (args.source && !composition.sourceEntries.some((entry) => entry.name === args.source)) {
37977
- const layers = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os6.homedir)() });
38573
+ const layers = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os8.homedir)() });
37978
38574
  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;
37979
38575
  if (!declared) {
37980
38576
  console.error(`[morphix-env] Unknown source "${args.source}".`);
@@ -37992,7 +38588,7 @@ async function cmdEdit(args) {
37992
38588
  }
37993
38589
  async function cmdDoc(args) {
37994
38590
  const composition = await resolveComposition(args);
37995
- const outFile = args.outFile || (0, import_path9.join)(process.cwd(), ".agents", "skills", "morphix-env", "SKILL.md");
38591
+ const outFile = args.outFile || (0, import_path11.join)(process.cwd(), ".agents", "skills", "morphix-env", "SKILL.md");
37996
38592
  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 |";
37997
38593
  const skill = `---
37998
38594
  name: morphix-env
@@ -38026,18 +38622,194 @@ ${keyRows}
38026
38622
 
38027
38623
  Secret values and value prefixes do not belong in this Skill, command output, audit records, or conversation context.
38028
38624
  `;
38029
- (0, import_fs9.mkdirSync)((0, import_path9.dirname)(outFile), { recursive: true });
38030
- (0, import_fs9.writeFileSync)(outFile, skill, { mode: 384 });
38625
+ (0, import_fs11.mkdirSync)((0, import_path11.dirname)(outFile), { recursive: true });
38626
+ (0, import_fs11.writeFileSync)(outFile, skill, { mode: 384 });
38031
38627
  console.log(`[morphix-env] Generated value-free workflow Skill: ${outFile}`);
38032
38628
  return 0;
38033
38629
  }
38630
+ function cmdShellInit(args) {
38631
+ if (process.platform === "win32") {
38632
+ console.error("[morphix-env] shell-init is supported on macOS/Linux only; on Windows keep using explicit `mx-env run -- <command>`.");
38633
+ return 1;
38634
+ }
38635
+ if (args.unknownFlags.length) {
38636
+ console.error(`[morphix-env] shell-init: unrecognized flag(s): ${args.unknownFlags.join(", ")}`);
38637
+ return 1;
38638
+ }
38639
+ if (args.write && args.remove) {
38640
+ console.error("[morphix-env] shell-init: --write and --remove are mutually exclusive.");
38641
+ return 1;
38642
+ }
38643
+ try {
38644
+ return runShellInit(args);
38645
+ } catch (error) {
38646
+ const code = error?.code;
38647
+ console.error(`[morphix-env] shell-init failed${code ? ` (${code})` : ""}: check that your home directory and rc file are writable, then retry.`);
38648
+ return 1;
38649
+ }
38650
+ }
38651
+ function runShellInit(args) {
38652
+ const layers = configLayers();
38653
+ const home = (0, import_os8.homedir)();
38654
+ const rcPath = defaultRcPath(process.env.SHELL, home);
38655
+ if (args.remove) {
38656
+ const removed2 = removeAllShims(home);
38657
+ const rcOutcome = rcPath ? removeRcBlock(rcPath) : "absent";
38658
+ console.log(`[morphix-env] Removed ${removed2.length} shim(s)${removed2.length ? ` (${removed2.join(", ")})` : ""}; rc block ${rcOutcome}${rcPath ? ` in ${rcPath}` : ""}.`);
38659
+ return 0;
38660
+ }
38661
+ const problems = [
38662
+ ...validateShimList(layers.global.config.shims, layers.global.path),
38663
+ ...validateShimList(layers.project.config.shims, layers.project.path)
38664
+ ];
38665
+ if (problems.length) {
38666
+ for (const problem of problems) console.error(`[morphix-env] ${problem}`);
38667
+ return 1;
38668
+ }
38669
+ const commands = resolveShimCommands(layers, !args.noGlobal);
38670
+ const { written, removed, skipped } = writeShims(commands, home);
38671
+ console.log(`[morphix-env] Shims ready in ${shimsDir(home)} (${commands.length} command(s): ${commands.join(", ") || "none"}; ${written.length} written, ${removed.length} removed).`);
38672
+ for (const command of skipped) {
38673
+ 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.`);
38674
+ }
38675
+ for (const file of detectHandmadeReservedShims(home)) {
38676
+ 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.`);
38677
+ }
38678
+ const { absent, shadowed } = findShadowedCommands(commands, bootInfo.originalPath, home);
38679
+ if (absent.length) {
38680
+ 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.`);
38681
+ }
38682
+ if (shadowed.length) {
38683
+ 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.`);
38684
+ }
38685
+ if (args.write) {
38686
+ if (!rcPath) {
38687
+ console.error("[morphix-env] shell-init: cannot infer an rc file from $SHELL (zsh/bash supported); add the PATH line manually.");
38688
+ return 1;
38689
+ }
38690
+ const outcome = upsertRcBlock(rcPath, home);
38691
+ console.log(`[morphix-env] rc block ${outcome} in ${rcPath}. Open a new shell to activate.`);
38692
+ return 0;
38693
+ }
38694
+ console.log(`
38695
+ Add the shims directory to PATH${rcPath ? ` (e.g. in ${rcPath})` : ""}:
38696
+
38697
+ export PATH="${shimsDir(home)}:$PATH"
38698
+
38699
+ Or re-run with --write to let mx-env maintain a marked block in your rc file.`);
38700
+ return 0;
38701
+ }
38702
+ function printMigratePlan(units, hints, target) {
38703
+ if (!units.length) {
38704
+ console.log("[morphix-env] No plaintext units in scope; nothing to migrate.");
38705
+ }
38706
+ for (const unit of units) {
38707
+ const where = unit.kind === "global-implicit" ? "global implicit" : `${unit.scope}, declared in ${unit.configPath}`;
38708
+ console.log(`unit: ${unit.name} (${where})`);
38709
+ for (const file of unit.files) console.log(` file: ${file}`);
38710
+ console.log(` keys: ${unit.keyNames.join(", ") || "(none)"}`);
38711
+ console.log(` --to dotenvx: encrypt file(s) in place${unit.kind === "declared" ? " + rewrite provider in config" : " (no config change; loading auto-detects encryption)"}`);
38712
+ console.log(unit.kind === "global-implicit" ? " --to keychain: not supported for the global implicit file" : " --to keychain: move values to a keychain service, rewrite config, delete plaintext");
38713
+ }
38714
+ for (const hint of hints) {
38715
+ console.log(`legacy plaintext (not auto-migrated): ${hint} \u2014 declare it as a source in mx-env.config.json to migrate it.`);
38716
+ }
38717
+ if (units.length && !target) {
38718
+ console.log("\nPlan only. Re-run with --to dotenvx|keychain to execute; every unit gets a 0600 backup and a post-migration equivalence check before its config changes.");
38719
+ }
38720
+ }
38721
+ async function cmdMigrate(args) {
38722
+ if (args.unknownFlags.length) {
38723
+ console.error(`[morphix-env] migrate: unrecognized flag(s): ${args.unknownFlags.join(", ")}`);
38724
+ return 1;
38725
+ }
38726
+ if (args.to && args.to !== "dotenvx" && args.to !== "keychain") {
38727
+ console.error('[morphix-env] migrate: --to must be "dotenvx" or "keychain".');
38728
+ return 1;
38729
+ }
38730
+ if (args.service && args.to !== "keychain") {
38731
+ console.error("[morphix-env] migrate: --service is only valid with --to keychain.");
38732
+ return 1;
38733
+ }
38734
+ if (args.service && !args.source) {
38735
+ console.error("[morphix-env] migrate: --service requires --source (one service per migrated source).");
38736
+ return 1;
38737
+ }
38738
+ const layers = configLayers();
38739
+ const home = (0, import_os8.homedir)();
38740
+ const units = discoverPlaintextUnits(layers, home);
38741
+ const hints = legacyPlaintextHints(layers, units, home);
38742
+ const fileOwners = /* @__PURE__ */ new Map();
38743
+ const collisions = [];
38744
+ for (const unit of units) {
38745
+ for (const file of unit.files) {
38746
+ const owner = fileOwners.get(file);
38747
+ if (owner && owner !== unit.name) {
38748
+ collisions.push(`file ${file} is shared by units "${owner}" and "${unit.name}"`);
38749
+ }
38750
+ if (!owner) fileOwners.set(file, unit.name);
38751
+ }
38752
+ }
38753
+ for (const collision of collisions) {
38754
+ if (!args.to || args.dryRun) console.warn(`[morphix-env] Warning: ${collision}; split them into separate files (or sources) before migrating.`);
38755
+ else {
38756
+ console.error(`[morphix-env] migrate: ${collision}; split them into separate files (or sources) before migrating.`);
38757
+ return 1;
38758
+ }
38759
+ }
38760
+ const selected = args.source ? units.filter((unit) => unit.name === args.source) : units;
38761
+ if (args.source && !selected.length) {
38762
+ console.error(`[morphix-env] migrate: no plaintext unit named "${args.source}" in scope (already migrated or unknown).`);
38763
+ return 1;
38764
+ }
38765
+ if (!args.to || args.dryRun) {
38766
+ printMigratePlan(selected, hints, args.to);
38767
+ return 0;
38768
+ }
38769
+ if (!selected.length) {
38770
+ console.log("[morphix-env] No plaintext units in scope; nothing to migrate.");
38771
+ return 0;
38772
+ }
38773
+ const target = args.to;
38774
+ const startedAt = Date.now();
38775
+ const outcomes = [];
38776
+ for (const unit of selected) {
38777
+ const outcome = await executeUnit(unit, target, { service: args.service || void 0 }, home);
38778
+ outcomes.push(outcome);
38779
+ if (outcome.status === "migrated") {
38780
+ console.log(`[morphix-env] Migrated ${unit.name} -> ${target} (${unit.keyNames.length} keys).`);
38781
+ for (const warning of outcome.warnings) console.warn(warning);
38782
+ for (const backup of outcome.backups) console.log(`[morphix-env] Backup (0600): ${backup}`);
38783
+ } else if (outcome.status === "skipped") {
38784
+ console.warn(`[morphix-env] Skipped ${unit.name}: ${outcome.detail}`);
38785
+ } else {
38786
+ console.error(`[morphix-env] Failed ${unit.name}: ${outcome.detail}`);
38787
+ for (const backup of outcome.backups) console.error(`[morphix-env] Backup retained (0600): ${backup} \u2014 delete it by hand once verified.`);
38788
+ }
38789
+ }
38790
+ const failed = outcomes.filter((outcome) => outcome.status === "failed");
38791
+ const exitCode = failed.length ? 1 : 0;
38792
+ writeAudit({
38793
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
38794
+ profile: "migrate",
38795
+ sources: selected.map((unit) => unit.name),
38796
+ keys: [...new Set(selected.flatMap((unit) => unit.keyNames))].sort(),
38797
+ outcome: failed.length ? "failure" : "success",
38798
+ exitCode,
38799
+ durationMs: Date.now() - startedAt
38800
+ });
38801
+ if (outcomes.some((outcome) => outcome.backups.length)) {
38802
+ console.log("[morphix-env] Backups are 0600 copies of the original plaintext \u2014 delete them once your workflows are verified.");
38803
+ }
38804
+ return exitCode;
38805
+ }
38034
38806
  function cmdAudit(args) {
38035
- const file = (0, import_path9.join)((0, import_os6.homedir)(), ".mx-env", "audit", `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`);
38036
- if (!(0, import_fs9.existsSync)(file)) {
38807
+ const file = (0, import_path11.join)((0, import_os8.homedir)(), ".mx-env", "audit", `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`);
38808
+ if (!(0, import_fs11.existsSync)(file)) {
38037
38809
  console.log("[morphix-env] No audit events for today.");
38038
38810
  return 0;
38039
38811
  }
38040
- const lines = (0, import_fs9.readFileSync)(file, "utf8").trim().split("\n").filter(Boolean);
38812
+ const lines = (0, import_fs11.readFileSync)(file, "utf8").trim().split("\n").filter(Boolean);
38041
38813
  const selected = args.subArgs[0] === "tail" ? lines.slice(-20) : lines;
38042
38814
  for (const line of selected) console.log(line);
38043
38815
  return 0;
@@ -38054,11 +38826,13 @@ Usage:
38054
38826
  mx-env inject <template> <output> Resolve mx-env:// references into a file (mode 0600)
38055
38827
  mx-env generate [options] Generate public __env.js
38056
38828
  mx-env inspect [options] Print key names, sources, and status only
38829
+ mx-env migrate [--to dotenvx|keychain] Move plaintext local sources into encrypted storage
38057
38830
  mx-env doc [options] Generate a value-free project Skill
38058
38831
  mx-env audit [tail] Read value-free local audit events
38059
38832
  mx-env edit --profile <name> Open the one-shot visual editor
38060
38833
  mx-env skill install [--target <t>] Install the Agent Skill (global|project|trellis)
38061
38834
  mx-env skill show Show skill install status per target
38835
+ mx-env shell-init [--write|--remove] Generate PATH shims for declared commands
38062
38836
 
38063
38837
  Both mx-env and morphix-env are supported.
38064
38838
 
@@ -38073,8 +38847,12 @@ Options:
38073
38847
  --no-infisical Do not load Infisical sources
38074
38848
  --no-global Ignore ~/.mx-env/.env and ~/.mx-env/config.json
38075
38849
  --allow-insecure-global Allow a global .env with permissive file mode
38850
+ --warn-plaintext Warn once on stderr when plaintext local sources supply keys
38076
38851
  --print-editor-url Explicitly print the one-time local editor URL
38077
38852
  --target <scope> Skill install target: global, project, or trellis
38853
+ --to <provider> migrate destination: dotenvx or keychain (omit for a read-only plan)
38854
+ --source <name> edit/migrate: select a single source
38855
+ --service <name> migrate --to keychain: keychain service override (requires --source)
38078
38856
  --force Overwrite an installed skill that differs from this version
38079
38857
  -v, --verbose Print source status and key counts only
38080
38858
  --help, -h Show this help
@@ -38082,6 +38860,7 @@ Options:
38082
38860
  `);
38083
38861
  }
38084
38862
  async function main() {
38863
+ Object.assign(bootInfo, sanitizeBootEnv());
38085
38864
  const args = parseArgs(process.argv);
38086
38865
  switch (args.command) {
38087
38866
  case "run":
@@ -38099,12 +38878,16 @@ async function main() {
38099
38878
  return cmdGenerate(args);
38100
38879
  case "inspect":
38101
38880
  return cmdInspect(args);
38881
+ case "migrate":
38882
+ return cmdMigrate(args);
38102
38883
  case "audit":
38103
38884
  return cmdAudit(args);
38104
38885
  case "edit":
38105
38886
  return cmdEdit(args);
38106
38887
  case "doc":
38107
38888
  return cmdDoc(args);
38889
+ case "shell-init":
38890
+ return cmdShellInit(args);
38108
38891
  case "skill": {
38109
38892
  const positional = process.argv.slice(3).find((arg) => !arg.startsWith("-")) ?? "install";
38110
38893
  return cmdSkill(positional, { target: args.target, force: args.force });