@morphixai/mxenv 0.12.0 → 0.12.1

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/README.md CHANGED
@@ -2,12 +2,16 @@
2
2
 
3
3
  L2 run face for [mxenv](https://github.com/Morphicai/morphix-env) — the project-side CLI and SDK runtime. Secrets stay with their owners (Infisical / Doppler / dotenvx / macOS Keychain / local files) and are composed into one value-safe child environment.
4
4
 
5
+ > Upgrading from the old `morphix-env` package? See "Upgrading an existing project, step by step" in the [repository README](https://github.com/Morphicai/morphix-env#migrating-from-morphix-env--mx-env).
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
8
10
  npm i -D @morphixai/mxenv
9
11
  ```
10
12
 
13
+ > ⚠️ **Do not install `@morphixai/mxenv` and `@morphixai/mxenv-cli` globally side by side** — both register the `mxenv`/`mx-env`/`morphix-env` bin names and the later install silently takes over the symlinks. Keep this package as a project devDependency and install `mxenv-cli` globally (or vice versa, never both).
14
+
11
15
  ## Commands
12
16
 
13
17
  `mxenv run|export|inspect|render|inject|generate|fetch|migrate|audit|edit|doc|skill` — see `mxenv --help`. Old bin names `mx-env`/`morphix-env` remain as deprecated aliases in 0.12.x (removed in 0.13).
package/dist/SKILL.md CHANGED
@@ -22,11 +22,15 @@ mxenv 是密钥**组装层**,不是新的保险库:值永远留在 provider
22
22
 
23
23
  ```bash
24
24
  mxenv --version # 检测是否已安装
25
- npm install -g @morphixai/mxenv # 安装运行面(项目内可用 devDep / npx 替代)
25
+ npm install -D @morphixai/mxenv # 项目内安装运行面(推荐:run/inspect/edit/migrate 全在)
26
26
  npx @morphixai/mxenv@latest inspect # 免安装试用
27
- npm install -g @morphixai/mxenv-cli # 可选:全局管理面(shell-init 等)
27
+ npm install -g @morphixai/mxenv-cli # 可选:机器级管理面(shell-init 等)
28
28
  ```
29
29
 
30
+ > ⚠️ **不要把 `@morphixai/mxenv` 与 `@morphixai/mxenv-cli` 同时全局安装**:两者都注册
31
+ > `mxenv`/`mx-env`/`morphix-env` bin,后装者会静默接管符号链接。项目用 devDep,
32
+ > 全局只装 cli;若确要全局装运行面,就不要再装 cli。
33
+
30
34
  ## 配置(mxenv.config.json)
31
35
 
32
36
  配置文件**不含任何值**,只声明 source 接线与组合顺序。项目级放仓库根 `mxenv.config.json`;用户级 `~/.mxenv/config.json` 可选(全局基线,`--no-global` 可排除)。`~/.mxenv/.env` 是唯一自动发现的值文件(个人默认值)。
@@ -107,7 +111,7 @@ mxenv-cli shell-init [--write|--remove] # 为声明的命令生成 PATH shim(
107
111
  | `missing-optional, 0 keys` | `"optional": true` 的文件不存在,属预期;要强制存在就删掉 optional |
108
112
  | Keychain 权限弹框 / 拒绝 | 首次访问会请求 service 授权;拒绝后重试或换 service 命名空间 |
109
113
  | dotenvx 解密失败 | 解密密钥由 dotenvx 引导路径提供,不在本配置内 |
110
- | `mxenv: command not found` | `npm install -g @morphixai/mxenv`,或用 `npx @morphixai/mxenv@latest` 免安装 |
114
+ | `mxenv: command not found` | 项目内 `npm install -D @morphixai/mxenv`(或全局装它,但勿再装 cli);或用 `npx @morphixai/mxenv@latest` 免安装 |
111
115
  | `mxenv-cli: command not found` | shim 能力属全局管理面:`npm install -g @morphixai/mxenv-cli` |
112
116
 
113
117
  ---
package/dist/cli.js CHANGED
@@ -342,6 +342,37 @@ async function executeUnit(unit, target, options = {}, homeDir = (0, import_os.h
342
342
  return { unit, status: "failed", backups, warnings: [], detail: message };
343
343
  }
344
344
  }
345
+ function cleanupQuiet(path) {
346
+ try {
347
+ (0, import_fs.rmSync)(path, { recursive: true, force: true });
348
+ } catch {
349
+ }
350
+ }
351
+ function mergeScaffoldingCopy(source, dest, stamp) {
352
+ const merge = (src, dst) => {
353
+ (0, import_fs.mkdirSync)(dst, { recursive: true });
354
+ for (const entry of (0, import_fs.readdirSync)(src, { withFileTypes: true })) {
355
+ const from = (0, import_path.join)(src, entry.name);
356
+ const to = (0, import_path.join)(dst, entry.name);
357
+ if (entry.isDirectory()) {
358
+ merge(from, to);
359
+ continue;
360
+ }
361
+ if (!(0, import_fs.existsSync)(to)) {
362
+ (0, import_fs.copyFileSync)(from, to);
363
+ continue;
364
+ }
365
+ if (from.endsWith(".jsonl") && to.endsWith(".jsonl")) {
366
+ const base = (0, import_fs.readFileSync)(to, "utf8");
367
+ const extra = (0, import_fs.readFileSync)(from, "utf8");
368
+ (0, import_fs.appendFileSync)(to, (base && !base.endsWith("\n") ? "\n" : "") + extra);
369
+ continue;
370
+ }
371
+ (0, import_fs.copyFileSync)(from, (0, import_path.join)(dst, `${entry.name}.fold-${stamp}`));
372
+ }
373
+ };
374
+ merge(source, dest);
375
+ }
345
376
  function copyTree(src, dest) {
346
377
  (0, import_fs.mkdirSync)(dest, { recursive: true });
347
378
  (0, import_fs.chmodSync)(dest, (0, import_fs.statSync)(src).mode & 4095);
@@ -423,8 +454,22 @@ async function migrateHome(homeDir = (0, import_os.homedir)(), options = {}) {
423
454
  const targetDir = (0, import_path.resolve)(homeDir, import_mxenv_core.GLOBAL_CONFIG_DIR);
424
455
  const markerName = ".mxenv-verify-pending";
425
456
  const markerPath = (0, import_path.join)(targetDir, markerName);
457
+ const asideDir = `${targetDir}.scaffold-fold`;
426
458
  const allowInsecureGlobal = options.allowInsecureGlobal ?? false;
427
459
  const backupsOnDisk = () => (0, import_fs.readdirSync)(homeDir).filter((name) => name.startsWith(`${import_mxenv_core.LEGACY_GLOBAL_CONFIG_DIR}.backup-`)).sort();
460
+ let mergeScaffolding = false;
461
+ if ((0, import_fs.existsSync)(asideDir) && !(0, import_fs.existsSync)(targetDir)) {
462
+ try {
463
+ (0, import_fs.renameSync)(asideDir, targetDir);
464
+ } catch (error) {
465
+ return {
466
+ status: "failed",
467
+ exitCode: 1,
468
+ keyNames: [],
469
+ detail: `cannot restore pre-migration scaffolding from ${asideDir}: ${error instanceof Error ? error.message : "unknown failure"} \u2014 rename it to ${targetDir} by hand, then re-run.`
470
+ };
471
+ }
472
+ }
428
473
  if ((0, import_fs.existsSync)(targetDir)) {
429
474
  if ((0, import_fs.existsSync)(markerPath)) {
430
475
  let backupDir2 = "";
@@ -467,13 +512,17 @@ async function migrateHome(homeDir = (0, import_os.homedir)(), options = {}) {
467
512
  };
468
513
  }
469
514
  } else if ((0, import_fs.existsSync)(legacyDir)) {
470
- return {
471
- status: "conflict",
472
- exitCode: 1,
473
- keyNames: [],
474
- detail: `both ${targetDir} and ${legacyDir} exist \u2014 refusing to overwrite (no writes made). Resolve by hand: inspect both, keep what you need in ${targetDir}, then rename ${legacyDir} away and re-run.`
475
- };
515
+ if ((0, import_mxenv_core.hasGlobalDataAt)(targetDir)) {
516
+ return {
517
+ status: "conflict",
518
+ exitCode: 1,
519
+ keyNames: [],
520
+ detail: `both ${targetDir} and ${legacyDir} exist \u2014 refusing to overwrite (no writes made). Resolve by hand: inspect both, keep what you need in ${targetDir}, then rename ${legacyDir} away and re-run.`
521
+ };
522
+ }
523
+ mergeScaffolding = true;
476
524
  } else {
525
+ if ((0, import_fs.existsSync)(asideDir)) cleanupQuiet(asideDir);
477
526
  return { status: "noop", exitCode: 0, keyNames: [], detail: `already migrated: ${targetDir} is in use.` };
478
527
  }
479
528
  }
@@ -516,25 +565,55 @@ async function migrateHome(homeDir = (0, import_os.homedir)(), options = {}) {
516
565
  copyTree(legacyDir, partialDir);
517
566
  (0, import_fs.writeFileSync)((0, import_path.join)(partialDir, markerName), `${backupDir}
518
567
  `);
568
+ if (mergeScaffolding) {
569
+ (0, import_fs.renameSync)(targetDir, asideDir);
570
+ if ((0, import_mxenv_core.hasGlobalDataAt)(asideDir)) {
571
+ (0, import_fs.renameSync)(asideDir, targetDir);
572
+ cleanup(partialDir);
573
+ return {
574
+ status: "conflict",
575
+ exitCode: 1,
576
+ keyNames: [],
577
+ detail: `both ${targetDir} and ${legacyDir} appeared to hold real data mid-migration \u2014 nothing was changed; inspect both, keep what you need in ${targetDir}, then rename ${legacyDir} away and re-run.`
578
+ };
579
+ }
580
+ }
581
+ if ((0, import_fs.existsSync)(asideDir)) mergeScaffoldingCopy(asideDir, partialDir, stamp);
519
582
  (0, import_fs.renameSync)(partialDir, targetDir);
520
583
  } catch (error) {
521
584
  const cleanupIssue = cleanup(partialDir);
585
+ let asideIssue = "";
586
+ if ((0, import_fs.existsSync)(asideDir) && !(0, import_fs.existsSync)(targetDir)) {
587
+ try {
588
+ (0, import_fs.renameSync)(asideDir, targetDir);
589
+ } catch {
590
+ asideIssue = `; restore ${asideDir} to ${targetDir} failed \u2014 rename it back by hand`;
591
+ }
592
+ }
522
593
  return {
523
594
  status: "failed",
524
595
  exitCode: 1,
525
596
  keyNames: [],
526
- detail: `copy failed: ${error instanceof Error ? error.message : "unknown failure"}; ${cleanupIssue ?? `removed partial copy`}, original preserved at ${legacyDir}.`
597
+ detail: `copy failed: ${error instanceof Error ? error.message : "unknown failure"}; ${cleanupIssue ?? `removed partial copy`}, original preserved at ${legacyDir}.${asideIssue}`
527
598
  };
528
599
  }
529
600
  try {
530
601
  (0, import_fs.renameSync)(legacyDir, backupDir);
531
602
  } catch (error) {
532
603
  const cleanupIssue = [cleanup(partialDir), cleanup(targetDir)].find(Boolean) ?? null;
604
+ let asideIssue = "";
605
+ if ((0, import_fs.existsSync)(asideDir)) {
606
+ try {
607
+ (0, import_fs.renameSync)(asideDir, targetDir);
608
+ } catch {
609
+ asideIssue = `; restore ${asideDir} to ${targetDir} failed \u2014 rename it back by hand`;
610
+ }
611
+ }
533
612
  return {
534
613
  status: "failed",
535
614
  exitCode: 1,
536
615
  keyNames: [],
537
- detail: `backup rename failed: ${error instanceof Error ? error.message : "unknown failure"}; ${cleanupIssue ?? `removed partial copy`}, original preserved at ${legacyDir}.`
616
+ detail: `backup rename failed: ${error instanceof Error ? error.message : "unknown failure"}; ${cleanupIssue ?? `removed partial copy`}, original preserved at ${legacyDir}.${asideIssue}`
538
617
  };
539
618
  }
540
619
  const restoreFromBackup = (why) => {
@@ -545,11 +624,19 @@ async function migrateHome(homeDir = (0, import_os.homedir)(), options = {}) {
545
624
  } catch {
546
625
  }
547
626
  const cleanupIssue = cleanup(targetDir);
627
+ let asideIssue = "";
628
+ if ((0, import_fs.existsSync)(asideDir)) {
629
+ try {
630
+ (0, import_fs.renameSync)(asideDir, targetDir);
631
+ } catch {
632
+ asideIssue = `; RESTORE FAILED for ${asideDir} \u2014 rename it to ${targetDir} by hand`;
633
+ }
634
+ }
548
635
  return {
549
636
  status: "failed",
550
637
  exitCode: 1,
551
638
  keyNames: [],
552
- detail: `verification failed: ${why}; ${restored ? `restored from backup ${backupDir} (directory backup)` : `RESTORE FAILED \u2014 restore manually by renaming the directory backup ${backupDir} to ${legacyDir}`}; ${cleanupIssue ?? `removed partial ${targetDir}`}.`
639
+ detail: `verification failed: ${why}; ${restored ? `restored from backup ${backupDir} (directory backup)` : `RESTORE FAILED \u2014 restore manually by renaming the directory backup ${backupDir} to ${legacyDir}`}; ${cleanupIssue ?? `removed partial ${targetDir}`}${asideIssue}.`
553
640
  };
554
641
  };
555
642
  let after;
@@ -568,6 +655,7 @@ async function migrateHome(homeDir = (0, import_os.homedir)(), options = {}) {
568
655
  (0, import_fs.rmSync)(markerPath);
569
656
  } catch {
570
657
  }
658
+ if ((0, import_fs.existsSync)(asideDir)) cleanupQuiet(asideDir);
571
659
  const rc = rewriteRcBlocksForMigration(homeDir);
572
660
  const rcSummary = rc.updated.length ? `; rc PATH block rewired in ${rc.updated.join(", ")}` : "";
573
661
  const rcHint = !rc.found || rc.failures.length ? `[mxenv] shim PATH wiring: run \`mxenv-cli shell-init --write\` to point your rc block at ${(0, import_mxenv_core.shimsDir)(homeDir)}${rc.failures.length ? ` (rc rewrite failed: ${rc.failures.join("; ")})` : " (no marked rc block found)"}.` : void 0;
@@ -1209,7 +1297,7 @@ function warnOutputGitRisk(path) {
1209
1297
  // package.json
1210
1298
  var package_default = {
1211
1299
  name: "@morphixai/mxenv",
1212
- version: "0.12.0",
1300
+ version: "0.12.1",
1213
1301
  description: "L2 run face for mxenv \u2014 run/export/inspect, source management, edit, migrate, skills.",
1214
1302
  license: "MIT",
1215
1303
  author: "MorphixAI <dev@morphixai.com>",
@@ -1305,6 +1393,7 @@ function parseArgs(argv) {
1305
1393
  write: false,
1306
1394
  remove: false,
1307
1395
  warnPlaintext: false,
1396
+ help: false,
1308
1397
  unknownFlags: []
1309
1398
  };
1310
1399
  let i = 2;
@@ -1360,6 +1449,8 @@ function parseArgs(argv) {
1360
1449
  args.serve = true;
1361
1450
  } else if (arg === "--warn-plaintext") {
1362
1451
  args.warnPlaintext = true;
1452
+ } else if (arg === "--help" || arg === "-h") {
1453
+ args.help = true;
1363
1454
  } else if (args.command === "run") {
1364
1455
  args.subArgs = argv.slice(i);
1365
1456
  break;
@@ -1952,6 +2043,11 @@ async function main() {
1952
2043
  (0, import_mxenv_core3.warnDeprecatedBinInvocation)(process.argv[1]);
1953
2044
  Object.assign(bootInfo, (0, import_mxenv_core3.sanitizeBootEnv)());
1954
2045
  const args = parseArgs(process.argv);
2046
+ if (args.help) {
2047
+ showHelp();
2048
+ return 0;
2049
+ }
2050
+ if (args.noGlobal) (0, import_mxenv_core3.suppressLegacyHomeHint)();
1955
2051
  switch (args.command) {
1956
2052
  case "run":
1957
2053
  return cmdRun(args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphixai/mxenv",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "L2 run face for mxenv — run/export/inspect, source management, edit, migrate, skills.",
5
5
  "license": "MIT",
6
6
  "author": "MorphixAI <dev@morphixai.com>",