morphix-env 0.9.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_fs10 = require("fs");
29486
- var import_os7 = require("os");
29487
- var import_path10 = 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();
@@ -37438,9 +37721,9 @@ async function startSecretServer(values) {
37438
37721
  }
37439
37722
 
37440
37723
  // src/shims.ts
37441
- var import_fs8 = require("fs");
37442
- var import_os6 = require("os");
37443
- var import_path8 = require("path");
37724
+ var import_fs9 = require("fs");
37725
+ var import_os7 = require("os");
37726
+ var import_path9 = require("path");
37444
37727
  var SHIM_MARKER = "# mx-env shim \u2014 regenerated by `mx-env shell-init`; do not edit by hand.";
37445
37728
  var RESERVED_SHIM_NAMES = [
37446
37729
  "run",
@@ -37451,6 +37734,7 @@ var RESERVED_SHIM_NAMES = [
37451
37734
  "generate",
37452
37735
  "gen",
37453
37736
  "inspect",
37737
+ "migrate",
37454
37738
  "audit",
37455
37739
  "edit",
37456
37740
  "doc",
@@ -37462,8 +37746,8 @@ var RESERVED_SHIM_NAMES = [
37462
37746
  "__mx_editor"
37463
37747
  ];
37464
37748
  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");
37749
+ function shimsDir(homeDir = (0, import_os7.homedir)()) {
37750
+ return (0, import_path9.join)(homeDir, ".mx-env", "shims");
37467
37751
  }
37468
37752
  function validateShimList(list, label) {
37469
37753
  if (list === void 0 || list === null) return [];
@@ -37518,33 +37802,33 @@ MXENV_SHIM=1 exec mx-env run -- ${command} "$@"
37518
37802
  }
37519
37803
  function readIfExists(path) {
37520
37804
  try {
37521
- return (0, import_fs8.readFileSync)(path, "utf8");
37805
+ return (0, import_fs9.readFileSync)(path, "utf8");
37522
37806
  } catch {
37523
37807
  return null;
37524
37808
  }
37525
37809
  }
37526
- function writeShims(commands, homeDir = (0, import_os6.homedir)()) {
37810
+ function writeShims(commands, homeDir = (0, import_os7.homedir)()) {
37527
37811
  const dir = shimsDir(homeDir);
37528
- (0, import_fs8.mkdirSync)(dir, { recursive: true, mode: 448 });
37812
+ (0, import_fs9.mkdirSync)(dir, { recursive: true, mode: 448 });
37529
37813
  const wanted = new Set(commands.map((command) => command.toLowerCase()));
37530
37814
  const written = [];
37531
37815
  const removed = [];
37532
37816
  const skipped = [];
37533
- for (const file of (0, import_fs8.readdirSync)(dir)) {
37534
- const path = (0, import_path8.join)(dir, file);
37817
+ for (const file of (0, import_fs9.readdirSync)(dir)) {
37818
+ const path = (0, import_path9.join)(dir, file);
37535
37819
  const content = readIfExists(path);
37536
37820
  if (content === null || !content.includes(SHIM_MARKER)) continue;
37537
37821
  if (!wanted.has(file.toLowerCase())) {
37538
- (0, import_fs8.rmSync)(path);
37822
+ (0, import_fs9.rmSync)(path);
37539
37823
  removed.push(file);
37540
37824
  }
37541
37825
  }
37542
37826
  for (const command of commands) {
37543
- const path = (0, import_path8.join)(dir, command);
37827
+ const path = (0, import_path9.join)(dir, command);
37544
37828
  const content = renderShimScript(command);
37545
37829
  let isSymlink = false;
37546
37830
  try {
37547
- isSymlink = (0, import_fs8.lstatSync)(path).isSymbolicLink();
37831
+ isSymlink = (0, import_fs9.lstatSync)(path).isSymbolicLink();
37548
37832
  } catch {
37549
37833
  }
37550
37834
  const existing = isSymlink ? null : readIfExists(path);
@@ -37552,42 +37836,42 @@ function writeShims(commands, homeDir = (0, import_os6.homedir)()) {
37552
37836
  skipped.push(command);
37553
37837
  continue;
37554
37838
  }
37555
- if (isSymlink) (0, import_fs8.rmSync)(path);
37839
+ if (isSymlink) (0, import_fs9.rmSync)(path);
37556
37840
  else if (existing === content) continue;
37557
- (0, import_fs8.writeFileSync)(path, content, { mode: 493 });
37558
- (0, import_fs8.chmodSync)(path, 493);
37841
+ (0, import_fs9.writeFileSync)(path, content, { mode: 493 });
37842
+ (0, import_fs9.chmodSync)(path, 493);
37559
37843
  written.push(command);
37560
37844
  }
37561
37845
  return { written, removed, skipped };
37562
37846
  }
37563
- function removeAllShims(homeDir = (0, import_os6.homedir)()) {
37847
+ function removeAllShims(homeDir = (0, import_os7.homedir)()) {
37564
37848
  const dir = shimsDir(homeDir);
37565
- if (!(0, import_fs8.existsSync)(dir)) return [];
37849
+ if (!(0, import_fs9.existsSync)(dir)) return [];
37566
37850
  const removed = [];
37567
- for (const file of (0, import_fs8.readdirSync)(dir)) {
37568
- const path = (0, import_path8.join)(dir, file);
37851
+ for (const file of (0, import_fs9.readdirSync)(dir)) {
37852
+ const path = (0, import_path9.join)(dir, file);
37569
37853
  const content = readIfExists(path);
37570
37854
  if (content !== null && content.includes(SHIM_MARKER)) {
37571
- (0, import_fs8.rmSync)(path);
37855
+ (0, import_fs9.rmSync)(path);
37572
37856
  removed.push(file);
37573
37857
  }
37574
37858
  }
37575
37859
  return removed;
37576
37860
  }
37577
- function detectHandmadeReservedShims(homeDir = (0, import_os6.homedir)()) {
37861
+ function detectHandmadeReservedShims(homeDir = (0, import_os7.homedir)()) {
37578
37862
  const dir = shimsDir(homeDir);
37579
- if (!(0, import_fs8.existsSync)(dir)) return [];
37863
+ if (!(0, import_fs9.existsSync)(dir)) return [];
37580
37864
  const found = [];
37581
- for (const file of (0, import_fs8.readdirSync)(dir)) {
37865
+ for (const file of (0, import_fs9.readdirSync)(dir)) {
37582
37866
  if (!RESERVED_SHIM_NAMES.includes(file.toLowerCase())) continue;
37583
- const content = readIfExists((0, import_path8.join)(dir, file));
37867
+ const content = readIfExists((0, import_path9.join)(dir, file));
37584
37868
  if (content === null || !content.includes(SHIM_MARKER)) found.push(file);
37585
37869
  }
37586
37870
  return found;
37587
37871
  }
37588
37872
  var RC_BLOCK_BEGIN = "# >>> mx-env shims >>>";
37589
37873
  var RC_BLOCK_END = "# <<< mx-env shims <<<";
37590
- function rcBlock(homeDir = (0, import_os6.homedir)()) {
37874
+ function rcBlock(homeDir = (0, import_os7.homedir)()) {
37591
37875
  const dir = shimsDir(homeDir);
37592
37876
  if (/["$`\\]/.test(dir)) {
37593
37877
  throw new Error("[morphix-env] Shims directory path contains characters that are unsafe in a shell rc line.");
@@ -37596,18 +37880,18 @@ function rcBlock(homeDir = (0, import_os6.homedir)()) {
37596
37880
  export PATH="${dir}:$PATH"
37597
37881
  ${RC_BLOCK_END}`;
37598
37882
  }
37599
- function defaultRcPath(shell, homeDir = (0, import_os6.homedir)()) {
37883
+ function defaultRcPath(shell, homeDir = (0, import_os7.homedir)()) {
37600
37884
  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");
37885
+ if (name === "zsh") return (0, import_path9.join)(homeDir, ".zshrc");
37886
+ if (name === "bash") return (0, import_path9.join)(homeDir, ".bashrc");
37603
37887
  return null;
37604
37888
  }
37605
37889
  function writeFileAtomic(path, content) {
37606
37890
  const tmp = `${path}.mx-env-${process.pid}.tmp`;
37607
- (0, import_fs8.writeFileSync)(tmp, content);
37608
- (0, import_fs8.renameSync)(tmp, path);
37891
+ (0, import_fs9.writeFileSync)(tmp, content);
37892
+ (0, import_fs9.renameSync)(tmp, path);
37609
37893
  }
37610
- function upsertRcBlock(rcPath, homeDir = (0, import_os6.homedir)()) {
37894
+ function upsertRcBlock(rcPath, homeDir = (0, import_os7.homedir)()) {
37611
37895
  const existing = readIfExists(rcPath) ?? "";
37612
37896
  const block = rcBlock(homeDir);
37613
37897
  const pattern = new RegExp(`${escapeRegExp(RC_BLOCK_BEGIN)}[\\s\\S]*?${escapeRegExp(RC_BLOCK_END)}`, "g");
@@ -37634,32 +37918,32 @@ function removeRcBlock(rcPath) {
37634
37918
  function escapeRegExp(text) {
37635
37919
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
37636
37920
  }
37637
- function stripShimsFromPath(pathValue, homeDir = (0, import_os6.homedir)()) {
37921
+ function stripShimsFromPath(pathValue, homeDir = (0, import_os7.homedir)()) {
37638
37922
  if (!pathValue) return pathValue;
37639
37923
  const target = shimsDir(homeDir);
37640
37924
  let targetReal = target;
37641
37925
  try {
37642
- targetReal = (0, import_fs8.realpathSync)(target);
37926
+ targetReal = (0, import_fs9.realpathSync)(target);
37643
37927
  } catch {
37644
37928
  }
37645
37929
  const equal = (a, b) => process.platform === "darwin" ? a.toLowerCase() === b.toLowerCase() : a === b;
37646
- const entries = pathValue.split(import_path8.delimiter).filter((entry) => {
37930
+ const entries = pathValue.split(import_path9.delimiter).filter((entry) => {
37647
37931
  if (!entry) return true;
37648
37932
  const literal = entry.replace(/\/+$/, "");
37649
37933
  let resolved = literal;
37650
37934
  try {
37651
- resolved = (0, import_fs8.realpathSync)(literal);
37935
+ resolved = (0, import_fs9.realpathSync)(literal);
37652
37936
  } catch {
37653
37937
  }
37654
37938
  return !equal(literal, target) && !equal(resolved, targetReal);
37655
37939
  });
37656
- return entries.join(import_path8.delimiter);
37940
+ return entries.join(import_path9.delimiter);
37657
37941
  }
37658
37942
  function sanitizeBootEnv(env = process.env, homeDir) {
37659
37943
  const originalPath = env.PATH;
37660
37944
  const viaShim = env.MXENV_SHIM === "1";
37661
37945
  try {
37662
- const home = homeDir ?? (0, import_os6.homedir)();
37946
+ const home = homeDir ?? (0, import_os7.homedir)();
37663
37947
  const stripped = stripShimsFromPath(originalPath, home);
37664
37948
  if (stripped !== originalPath) {
37665
37949
  if (stripped) env.PATH = stripped;
@@ -37670,18 +37954,18 @@ function sanitizeBootEnv(env = process.env, homeDir) {
37670
37954
  delete env.MXENV_SHIM;
37671
37955
  return { viaShim, originalPath };
37672
37956
  }
37673
- function findShadowedCommands(commands, originalPath, homeDir = (0, import_os6.homedir)()) {
37957
+ function findShadowedCommands(commands, originalPath, homeDir = (0, import_os7.homedir)()) {
37674
37958
  if (!originalPath) return { absent: [...commands], shadowed: [] };
37675
- const dirs = originalPath.split(import_path8.delimiter).filter(Boolean);
37959
+ const dirs = originalPath.split(import_path9.delimiter).filter(Boolean);
37676
37960
  const absent = [];
37677
37961
  const shadowed = [];
37678
37962
  for (const command of commands) {
37679
- const shimPath = (0, import_path8.join)(shimsDir(homeDir), command);
37963
+ const shimPath = (0, import_path9.join)(shimsDir(homeDir), command);
37680
37964
  let resolvedTo = null;
37681
37965
  for (const dir of dirs) {
37682
- const candidate = (0, import_path8.join)(dir, command);
37966
+ const candidate = (0, import_path9.join)(dir, command);
37683
37967
  try {
37684
- (0, import_fs8.accessSync)(candidate, import_fs8.constants.X_OK);
37968
+ (0, import_fs9.accessSync)(candidate, import_fs9.constants.X_OK);
37685
37969
  resolvedTo = candidate;
37686
37970
  break;
37687
37971
  } catch {
@@ -37695,9 +37979,9 @@ function findShadowedCommands(commands, originalPath, homeDir = (0, import_os6.h
37695
37979
  }
37696
37980
 
37697
37981
  // src/template.ts
37698
- var import_child_process4 = require("child_process");
37699
- var import_fs9 = require("fs");
37700
- var import_path9 = require("path");
37982
+ var import_child_process5 = require("child_process");
37983
+ var import_fs10 = require("fs");
37984
+ var import_path10 = require("path");
37701
37985
  function shellQuote(value) {
37702
37986
  return `'${value.replace(/'/g, "'\\''")}'`;
37703
37987
  }
@@ -37744,20 +38028,20 @@ function renderTemplate(text, environment) {
37744
38028
  return { output, missing: [...missing] };
37745
38029
  }
37746
38030
  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);
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);
37751
38035
  }
37752
38036
  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" });
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" });
37757
38041
  if (inside.status !== 0) return "outside-repo";
37758
- 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" });
37759
38043
  if (tracked.status === 0) return "tracked";
37760
- 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" });
37761
38045
  if (ignored.status === 0) return "ignored";
37762
38046
  if (ignored.status === 1) return "untracked";
37763
38047
  return "outside-repo";
@@ -37774,7 +38058,7 @@ function warnOutputGitRisk(path) {
37774
38058
  // package.json
37775
38059
  var package_default = {
37776
38060
  name: "morphix-env",
37777
- version: "0.9.0",
38061
+ version: "0.10.0",
37778
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).",
37779
38063
  author: "MorphixAI <dev@morphixai.com>",
37780
38064
  homepage: "https://github.com/Morphicai/morphix-env#readme",
@@ -37879,11 +38163,14 @@ function parseArgs(argv) {
37879
38163
  printEditorUrl: false,
37880
38164
  source: null,
37881
38165
  target: null,
38166
+ to: null,
38167
+ service: null,
37882
38168
  force: false,
37883
38169
  dryRun: false,
37884
38170
  serve: false,
37885
38171
  write: false,
37886
38172
  remove: false,
38173
+ warnPlaintext: false,
37887
38174
  unknownFlags: []
37888
38175
  };
37889
38176
  let i = 2;
@@ -37921,6 +38208,10 @@ function parseArgs(argv) {
37921
38208
  args.printEditorUrl = true;
37922
38209
  } else if (arg === "--target") {
37923
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];
37924
38215
  } else if (arg === "--force") {
37925
38216
  args.force = true;
37926
38217
  } else if (arg === "--dry-run") {
@@ -37931,6 +38222,8 @@ function parseArgs(argv) {
37931
38222
  args.remove = true;
37932
38223
  } else if (arg === "--serve") {
37933
38224
  args.serve = true;
38225
+ } else if (arg === "--warn-plaintext") {
38226
+ args.warnPlaintext = true;
37934
38227
  } else if (args.command === "run") {
37935
38228
  args.subArgs = argv.slice(i);
37936
38229
  break;
@@ -37944,7 +38237,7 @@ function parseArgs(argv) {
37944
38237
  return args;
37945
38238
  }
37946
38239
  function configLayers() {
37947
- return loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os7.homedir)() });
38240
+ return loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os8.homedir)() });
37948
38241
  }
37949
38242
  function composeOptions(args) {
37950
38243
  return {
@@ -37954,7 +38247,7 @@ function composeOptions(args) {
37954
38247
  envName: args.env,
37955
38248
  allowInsecureGlobal: args.allowInsecureGlobal,
37956
38249
  envFiles: args.envFiles,
37957
- homeDir: (0, import_os7.homedir)()
38250
+ homeDir: (0, import_os8.homedir)()
37958
38251
  };
37959
38252
  }
37960
38253
  var bootInfo = { viaShim: false };
@@ -37975,8 +38268,19 @@ function resolveComposition(args) {
37975
38268
  warnInvalidShims(layers, !args.noGlobal);
37976
38269
  return composeEnvironment(layers, composeOptions(args));
37977
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.`);
38281
+ }
37978
38282
  function generatedOptions(args) {
37979
- const config = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os7.homedir)() }).project.config;
38283
+ const config = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os8.homedir)() }).project.config;
37980
38284
  return {
37981
38285
  outFile: args.outFile || config.generate?.out || "public/__env.js",
37982
38286
  filter: args.filter || config.generate?.filter || null
@@ -37985,12 +38289,12 @@ function generatedOptions(args) {
37985
38289
  function generateClientEnv(outFile, filter, environment) {
37986
38290
  let vars = extractPublicVars(environment);
37987
38291
  if (filter) vars = Object.fromEntries(Object.entries(vars).filter(([key]) => key.startsWith(filter)));
37988
- (0, import_fs10.mkdirSync)((0, import_path10.dirname)(outFile), { recursive: true });
37989
- (0, import_fs10.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)};`);
37990
38294
  console.log(`[morphix-env] Generated ${outFile} (${Object.keys(vars).length} client vars)`);
37991
38295
  }
37992
38296
  async function runChild(command, commandArgs, composition) {
37993
- return new Promise((resolve7) => {
38297
+ return new Promise((resolve8) => {
37994
38298
  if (composition.environment.PATH) {
37995
38299
  composition.environment.PATH = stripShimsFromPath(composition.environment.PATH) ?? composition.environment.PATH;
37996
38300
  }
@@ -38001,14 +38305,14 @@ async function runChild(command, commandArgs, composition) {
38001
38305
  child.stderr?.on("data", (chunk) => process.stderr.write(stderr.write(chunk)));
38002
38306
  child.on("error", () => {
38003
38307
  console.error(`[morphix-env] Failed to execute: ${command}`);
38004
- resolve7(1);
38308
+ resolve8(1);
38005
38309
  });
38006
38310
  child.on("close", (code) => {
38007
38311
  const stdoutFinal = stdout.flush();
38008
38312
  const stderrFinal = stderr.flush();
38009
38313
  if (stdoutFinal) process.stdout.write(stdoutFinal);
38010
38314
  if (stderrFinal) process.stderr.write(stderrFinal);
38011
- resolve7(code ?? 1);
38315
+ resolve8(code ?? 1);
38012
38316
  });
38013
38317
  });
38014
38318
  }
@@ -38074,6 +38378,7 @@ async function cmdRun(args) {
38074
38378
  }
38075
38379
  const startedAt = Date.now();
38076
38380
  const composition = await resolveComposition(args);
38381
+ warnPlaintextSources(composition, args);
38077
38382
  if (args.verbose) {
38078
38383
  for (const source of composition.sourceStatuses) {
38079
38384
  console.log(`[morphix-env] ${source.name}: ${source.availability} (${source.keyCount} keys)`);
@@ -38087,7 +38392,7 @@ async function cmdRun(args) {
38087
38392
  await resolveEnvironmentReferences(composition, args);
38088
38393
  if (args.outFile || generatedOptions(args).outFile) {
38089
38394
  const { outFile, filter } = generatedOptions(args);
38090
- const configured = Boolean(args.outFile || loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os7.homedir)() }).project.config.generate);
38395
+ const configured = Boolean(args.outFile || loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os8.homedir)() }).project.config.generate);
38091
38396
  if (configured) generateClientEnv(outFile, filter, composition.environment);
38092
38397
  }
38093
38398
  const [command, ...commandArgs] = args.subArgs;
@@ -38144,6 +38449,7 @@ async function cmdFetch(args) {
38144
38449
  }
38145
38450
  async function cmdGenerate(args) {
38146
38451
  const composition = await resolveComposition(args);
38452
+ warnPlaintextSources(composition, args);
38147
38453
  await resolveEnvironmentReferences(composition, args);
38148
38454
  const { outFile, filter } = generatedOptions(args);
38149
38455
  generateClientEnv(outFile, filter, composition.environment);
@@ -38156,7 +38462,8 @@ async function cmdInspect(args) {
38156
38462
  const composition = await resolveComposition(args);
38157
38463
  console.log(`profile: ${composition.profileName}`);
38158
38464
  for (const source of composition.sourceStatuses) {
38159
- 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}`);
38160
38467
  }
38161
38468
  for (const [key, provenance] of Object.entries(composition.provenance).sort(([a], [b]) => a.localeCompare(b))) {
38162
38469
  if (!args.filter || key.startsWith(args.filter)) {
@@ -38169,6 +38476,7 @@ async function cmdInspect(args) {
38169
38476
  async function cmdExport(args) {
38170
38477
  const startedAt = Date.now();
38171
38478
  const composition = await resolveComposition(args);
38479
+ warnPlaintextSources(composition, args);
38172
38480
  const keys = Object.keys(composition.provenance).sort();
38173
38481
  if (args.dryRun) {
38174
38482
  for (const key of keys) console.log(key);
@@ -38192,12 +38500,13 @@ async function cmdRender(args) {
38192
38500
  console.error("[morphix-env] Usage: mx-env render <template-file> <output-file> [options]");
38193
38501
  return 1;
38194
38502
  }
38195
- if (!(0, import_fs10.existsSync)(templatePath)) {
38503
+ if (!(0, import_fs11.existsSync)(templatePath)) {
38196
38504
  console.error(`[morphix-env] Template not found: ${templatePath}`);
38197
38505
  return 1;
38198
38506
  }
38199
- const template = (0, import_fs10.readFileSync)(templatePath, "utf8");
38507
+ const template = (0, import_fs11.readFileSync)(templatePath, "utf8");
38200
38508
  const composition = await resolveComposition(args);
38509
+ warnPlaintextSources(composition, args);
38201
38510
  const names = findTemplateVariables(template);
38202
38511
  const missing = names.filter((name) => composition.environment[name] === void 0 || composition.environment[name] === null);
38203
38512
  if (missing.length) console.warn(`[morphix-env] Missing variables: ${missing.join(", ")} (left unresolved)`);
@@ -38221,11 +38530,11 @@ async function cmdInject(args) {
38221
38530
  console.error("[morphix-env] Usage: mx-env inject <template-file> <output-file> [options]");
38222
38531
  return 1;
38223
38532
  }
38224
- if (!(0, import_fs10.existsSync)(templatePath)) {
38533
+ if (!(0, import_fs11.existsSync)(templatePath)) {
38225
38534
  console.error(`[morphix-env] Template not found: ${templatePath}`);
38226
38535
  return 1;
38227
38536
  }
38228
- const template = (0, import_fs10.readFileSync)(templatePath, "utf8");
38537
+ const template = (0, import_fs11.readFileSync)(templatePath, "utf8");
38229
38538
  const layers = configLayers();
38230
38539
  warnInvalidShims(layers, !args.noGlobal);
38231
38540
  const resolver = new ReferenceResolver(layers, composeOptions(args));
@@ -38261,7 +38570,7 @@ async function cmdEdit(args) {
38261
38570
  }
38262
38571
  const composition = await resolveComposition(args);
38263
38572
  if (args.source && !composition.sourceEntries.some((entry) => entry.name === args.source)) {
38264
- const layers = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os7.homedir)() });
38573
+ const layers = loadConfigLayers({ cwd: process.cwd(), homeDir: (0, import_os8.homedir)() });
38265
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;
38266
38575
  if (!declared) {
38267
38576
  console.error(`[morphix-env] Unknown source "${args.source}".`);
@@ -38279,7 +38588,7 @@ async function cmdEdit(args) {
38279
38588
  }
38280
38589
  async function cmdDoc(args) {
38281
38590
  const composition = await resolveComposition(args);
38282
- const outFile = args.outFile || (0, import_path10.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");
38283
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 |";
38284
38593
  const skill = `---
38285
38594
  name: morphix-env
@@ -38313,8 +38622,8 @@ ${keyRows}
38313
38622
 
38314
38623
  Secret values and value prefixes do not belong in this Skill, command output, audit records, or conversation context.
38315
38624
  `;
38316
- (0, import_fs10.mkdirSync)((0, import_path10.dirname)(outFile), { recursive: true });
38317
- (0, import_fs10.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 });
38318
38627
  console.log(`[morphix-env] Generated value-free workflow Skill: ${outFile}`);
38319
38628
  return 0;
38320
38629
  }
@@ -38341,7 +38650,7 @@ function cmdShellInit(args) {
38341
38650
  }
38342
38651
  function runShellInit(args) {
38343
38652
  const layers = configLayers();
38344
- const home = (0, import_os7.homedir)();
38653
+ const home = (0, import_os8.homedir)();
38345
38654
  const rcPath = defaultRcPath(process.env.SHELL, home);
38346
38655
  if (args.remove) {
38347
38656
  const removed2 = removeAllShims(home);
@@ -38390,13 +38699,117 @@ Add the shims directory to PATH${rcPath ? ` (e.g. in ${rcPath})` : ""}:
38390
38699
  Or re-run with --write to let mx-env maintain a marked block in your rc file.`);
38391
38700
  return 0;
38392
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
+ }
38393
38806
  function cmdAudit(args) {
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)) {
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)) {
38396
38809
  console.log("[morphix-env] No audit events for today.");
38397
38810
  return 0;
38398
38811
  }
38399
- const lines = (0, import_fs10.readFileSync)(file, "utf8").trim().split("\n").filter(Boolean);
38812
+ const lines = (0, import_fs11.readFileSync)(file, "utf8").trim().split("\n").filter(Boolean);
38400
38813
  const selected = args.subArgs[0] === "tail" ? lines.slice(-20) : lines;
38401
38814
  for (const line of selected) console.log(line);
38402
38815
  return 0;
@@ -38413,6 +38826,7 @@ Usage:
38413
38826
  mx-env inject <template> <output> Resolve mx-env:// references into a file (mode 0600)
38414
38827
  mx-env generate [options] Generate public __env.js
38415
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
38416
38830
  mx-env doc [options] Generate a value-free project Skill
38417
38831
  mx-env audit [tail] Read value-free local audit events
38418
38832
  mx-env edit --profile <name> Open the one-shot visual editor
@@ -38433,8 +38847,12 @@ Options:
38433
38847
  --no-infisical Do not load Infisical sources
38434
38848
  --no-global Ignore ~/.mx-env/.env and ~/.mx-env/config.json
38435
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
38436
38851
  --print-editor-url Explicitly print the one-time local editor URL
38437
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)
38438
38856
  --force Overwrite an installed skill that differs from this version
38439
38857
  -v, --verbose Print source status and key counts only
38440
38858
  --help, -h Show this help
@@ -38460,6 +38878,8 @@ async function main() {
38460
38878
  return cmdGenerate(args);
38461
38879
  case "inspect":
38462
38880
  return cmdInspect(args);
38881
+ case "migrate":
38882
+ return cmdMigrate(args);
38463
38883
  case "audit":
38464
38884
  return cmdAudit(args);
38465
38885
  case "edit":