morphix-env 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  <p align="center">
2
+
3
+ [本仓文档入口](./docs/README.md) · [本地工程约定](./docs/engineering.md)
2
4
  <img src="docs/logo.png" width="160" alt="morphix-env" />
3
5
  </p>
4
6
 
@@ -77,10 +79,73 @@ Choosing between Infisical and Doppler still depends on each provider's own iden
77
79
 
78
80
  This is leakage minimization, not a cryptographic boundary: a process that receives a secret can still transform or exfiltrate it deliberately. For a high-risk agent capability, use a provider proxy/broker as a separate security design rather than passing that credential through `run`.
79
81
 
82
+ ## On-demand secrets with `run --serve`
83
+
84
+ `mx-env run --serve -- <command>` (modeled on aws-vault's `--ecs-server`) starts a loopback HTTP server on 127.0.0.1 at a random port that shares the child's lifetime:
85
+
86
+ - Composed non-public values never enter the child environment — not the direct child, its descendants, `/proc/<pid>/environ`, or core dumps. The child receives only `MXENV_ENDPOINT` and a random `MXENV_TOKEN`, and the token is redacted from child output like any injected value. Honest caveat on token visibility: an environment-delivered capability token is readable by same-uid processes on Linux via the child's `/proc/<pid>/environ` — the same inherent property as aws-vault's `--ecs-server`; the *values* themselves still appear in no process environment anywhere.
87
+ - The child fetches one value at a time: `mx-env fetch <KEY>` (thin client that reads endpoint/token from its own environment and prints the exact value with no trailing newline), or a direct `GET /key/<KEY>` with `Authorization: Bearer $MXENV_TOKEN`. The endpoint answers 401 without the token and 404 for unknown keys; no request or value is ever logged.
88
+ - Browser-public keys (`NEXT_PUBLIC_`, `VITE_`, `EXPO_PUBLIC_`) stay in the environment so build tools can inline them; they remain servable as well.
89
+ - The server is destroyed the moment the child exits (and dies with the parent process itself), so no listener outlives the run.
90
+
91
+ `mx-env run` without `--serve` is unchanged: values are injected into the environment and redacted from forwarded output.
92
+
93
+ ## Terminal shims
94
+
95
+ For commands you run constantly, shims remove the `mx-env run --` prefix entirely. Declare a command list and generate shims once:
96
+
97
+ ```jsonc
98
+ // ~/.mx-env/config.json (global) and/or mx-env.config.json (project)
99
+ { "shims": ["node", "pnpm"] }
100
+ ```
101
+
102
+ ```bash
103
+ mx-env shell-init # generate shims in ~/.mx-env/shims, print the PATH line
104
+ mx-env shell-init --write # also maintain a marked block in ~/.zshrc / ~/.bashrc
105
+ mx-env shell-init --remove # remove shims and the rc block
106
+ ```
107
+
108
+ After the PATH line is active, typing `pnpm dev` is exactly `mx-env run -- pnpm dev`: same composition, same redaction, same exit code. Undeclared commands are untouched.
109
+
110
+ - The effective list is the union of the global and project lists. A shim is only an entry hook: **what gets injected is always decided by the configuration in effect where the command runs**, so one `pnpm` shim serves every project's own profile.
111
+ - `run` removes the shims directory from the child's PATH before composing, so a tool invoked again inside the run (or by a provider plugin mid-composition) resolves to the real binary — injection happens exactly once, never recursively.
112
+ - `shell-init` is idempotent: re-running converges the directory to the declared list, `--write`/`--remove` use marked rc blocks and never touch your other lines or handmade files.
113
+ - Command names are validated (bare names only; mx-env's own command names are reserved). Invalid entries fail `shell-init` loudly but only warn in composition paths — a convenience layer never breaks `run`.
114
+ - Audit events record `via: "shim"` vs `"cli"` so the two channels stay distinguishable.
115
+
116
+ **Boundary.** Shims buy convenience, not concealment: injected values live in the child's environment and are visible to same-uid processes. When the consumer is an agent you do not trust with the values, use `run --serve` instead. macOS/Linux only; Windows keeps the explicit `run` form. Do not place files in `~/.mx-env/shims` by hand.
117
+
118
+ ## Provider plugins
119
+
120
+ Any executable on PATH named `mx-env-provider-<name>` is a provider — new remote stores need no changes to `config.ts` / `sources.ts` / `editor.ts`:
121
+
122
+ ```jsonc
123
+ // mx-env.config.json
124
+ {
125
+ "sources": {
126
+ "vault": { "provider": "plugin", "plugin": "bitwarden", "keys": ["OPENAI_API_KEY"] }
127
+ }
128
+ }
129
+ ```
130
+
131
+ Plugin protocol (summon-style):
132
+
133
+ - Invoked as `mx-env-provider-<plugin> <KEY>`, once per declared key; exit 0 with the value on stdout (one trailing line ending stripped).
134
+ - A non-zero exit or a missing executable fails the source with a value-free error — the plugin's stderr is never surfaced because it may contain sensitive material. `optional: true` downgrades a missing executable to `missing-optional`.
135
+ - Plugins intentionally inherit the already-composed environment at their precedence position (inherited env → global baseline → profile sources loaded so far), so wrapper scripts can authenticate to their own backend without a second credential channel. Least-privilege consequence: a plugin can read every value composed before it — declare plugin sources only after the sources they need and keep each `keys` list minimal. Each key invocation times out after 30s.
136
+ - Login and bootstrap stay inside the plugin; repository config carries only wiring (plugin name + key names), never values.
137
+
138
+ The five built-in providers (`infisical`, `doppler`, `os-keychain`, `dotenvx`, `local`) are unchanged.
139
+
80
140
  ## Commands
81
141
 
82
142
  ```bash
83
143
  mx-env run --profile dev -- <command> # compose and run (the only injection path)
144
+ mx-env run --serve -- <command> # serve values over loopback instead of injecting them into env
145
+ mx-env fetch <KEY> # read one value from a run --serve child environment
146
+ mx-env export [--profile agents] # eval-able export KEY='value' lines (values in stdout only for eval)
147
+ mx-env render <template> <output> # substitute ${VAR} into a config file (mode 0600)
148
+ mx-env inject <template> <output> # resolve mx-env://profile/KEY references into a file (mode 0600)
84
149
  mx-env inspect [--profile dev] # key names + provenance, no values
85
150
  mx-env edit [--profile dev | --source s] # one-shot visual editor
86
151
  mx-env generate --out public/__env.js # public browser runtime config
@@ -89,6 +154,8 @@ mx-env skill install | show # packaged Agent Skill (global / project
89
154
  mx-env audit tail # value-free audit events
90
155
  ```
91
156
 
157
+ `export`, `render`, `inject`, and `run` accept `--dry-run` (key/variable names only, never values). `eval "$(mx-env export --profile agents)"` injects a profile into the current shell without putting values in shell history. `render` and `inject` write mode-0600 outputs, warn about unresolved `${VAR}` / `mx-env://` references before writing, and warn when the output path is not git-ignored. `mx-env://profile/KEY` references (mirroring 1Password's `op://`) let MCP configs and dotenv templates stay in git without values: `mx-env inject` resolves them at the file level, `mx-env run` resolves them inside composed values before spawning.
158
+
92
159
  Full options live in `mx-env --help`; the packaged skill mirrors the commands of the installed version.
93
160
 
94
161
  ## Agent workflow
package/dist/SKILL.md CHANGED
@@ -29,14 +29,15 @@ npx morphix-env@latest inspect # 免安装试用
29
29
  配置文件**不含任何值**,只声明 source 接线与组合顺序。项目级放仓库根 `mx-env.config.json`;用户级 `~/.mx-env/config.json` 可选(全局基线,`--no-global` 可排除)。`~/.mx-env/.env` 是唯一自动发现的值文件(个人默认值)。
30
30
 
31
31
  ```jsonc
32
- // 项目 mx-env.config.json — 五种 provider 全示例
32
+ // 项目 mx-env.config.json — 全部 provider 示例
33
33
  {
34
34
  "sources": {
35
35
  "local": { "provider": "local", "files": [".env.local"], "optional": true, "override": true },
36
36
  "encrypted": { "provider": "dotenvx", "files": [".env.shared"] },
37
37
  "keychain": { "provider": "os-keychain", "platform": "macos", "service": "com.example/myproject" },
38
38
  "infisical": { "provider": "infisical", "paths": ["/team/api"] },
39
- "doppler": { "provider": "doppler", "project": "my-project", "config": "dev" }
39
+ "doppler": { "provider": "doppler", "project": "my-project", "config": "dev" },
40
+ "plugin": { "provider": "plugin", "plugin": "bitwarden", "keys": ["OPENAI_API_KEY"] }
40
41
  },
41
42
  "profiles": {
42
43
  "dev": { "sources": ["keychain", "infisical", "local"] },
@@ -50,6 +51,7 @@ npx morphix-env@latest inspect # 免安装试用
50
51
 
51
52
  - profile 内 `sources` **从低到高**排列;同 key 冲突默认**报错**,靠后 source 显式 `"override": true` 才覆盖(防止两个环境的同名凭据静默错选)。
52
53
  - provider 登录与引导留在 provider 侧(`infisical login`、`doppler login`、dotenvx 解密密钥引导),**不写进项目配置**。
54
+ - plugin source:PATH 上的 `mx-env-provider-<name> <KEY>` 把值写到 stdout(退出码 0);插件自带登录,失败信息不含值。
53
55
  - 固定基线顺序:`inherited process.env → ~/.mx-env/.env → 全局默认 profile → 项目 profile / legacy 配置 → -f 覆盖文件`。legacy 项目(`infisical` + `envFiles` 字段)无需改动即兼容。
54
56
 
55
57
  ## 命令速查
@@ -57,6 +59,11 @@ npx morphix-env@latest inspect # 免安装试用
57
59
  ```bash
58
60
  mx-env run --profile dev -- <command> # 组合环境并执行(子进程输出脱敏)
59
61
  mx-env run --no-global -- <command> # 排除全局基线,可复现的项目-only 运行
62
+ mx-env run --serve -- <command> # 值不进 env:子进程经 MXENV_ENDPOINT/MXENV_TOKEN 按需取值
63
+ mx-env fetch KEY # 在 --serve 子进程内取单个值(精确输出,无换行)
64
+ mx-env export [--profile dev] # 输出可 eval 的 export KEY='value' 行(值只经 stdout 进入 eval)
65
+ mx-env render <tpl> <out> # 把 ${VAR} 渲染进不支持插值的配置文件(0600,缺变量先告警)
66
+ mx-env inject <tpl> <out> # 解析 mx-env://profile/KEY 引用(0600,MCP 配置入仓不落值)
60
67
  mx-env generate --out public/__env.js # 生成浏览器公开变量(NEXT_PUBLIC_/VITE_/EXPO_PUBLIC_ 属公开配置例外)
61
68
  mx-env inspect [--profile dev] # key 名 + source 归属 + 状态(无值)
62
69
  mx-env edit --profile dev | --source X # 一次性可视化编辑器(loopback + 15 分钟 TTL,保存后自动关闭)
@@ -64,8 +71,13 @@ mx-env doc # 生成本项目的 key→source 工
64
71
  mx-env audit [tail] # 值无关的本地审计事件
65
72
  mx-env skill install [--target global|project|trellis] [--force] # 安装本 Skill
66
73
  mx-env skill show # 查看各目标安装状态
74
+ mx-env shell-init [--write|--remove] # 为声明的命令生成 PATH shim(shims: ["node","pnpm"] 配置项)
67
75
  ```
68
76
 
77
+ **shim 消费指引**:配置里声明过 `shims` 且执行过 `shell-init` 的机器上,裸敲被声明命令(如 `pnpm dev`)等价于 `mx-env run -- pnpm dev`——你可以依赖它,无需加前缀。但 shim 是方便性不是隐藏:值仍在子进程 env。**需要值不进 env 的场景(高风险 agent 能力、不可信子进程树)必须显式用 `run --serve`**,不要依赖 shim。audit 里 `via: "shim"` 与 `"cli"` 可区分两种来路。
78
+
79
+ `export` / `render` / `inject` / `run` 均支持 `--dry-run`(只报 key/变量名,不出值)。值永不出现在 shell 历史里:`eval "$(mx-env export --profile dev)"` 才是 export 的消费方式。
80
+
69
81
  ## 进入一个新项目的标准流程
70
82
 
71
83
  1. `mx-env inspect` — 看是否已有配置、哪些 key 已就绪;无配置时走 legacy `.env.local` / Infisical 兼容路径。