morphix-env 0.5.0 → 0.6.0-beta.2

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.
Files changed (3) hide show
  1. package/README.md +175 -254
  2. package/dist/cli.js +37546 -293
  3. package/package.json +17 -4
package/README.md CHANGED
@@ -1,329 +1,250 @@
1
- <p align="center">
2
- <img src="https://morphix.app/brand/logo-rounded.png" width="80" alt="MorphixAI" />
3
- </p>
4
-
5
1
  # morphix-env
6
2
 
7
- Environment variable toolkit for multi-project architectures. Combines [Infisical](https://infisical.com) secret management with local override files and client-side runtime injection.
3
+ `morphix-env` is a secret composition CLI for local development, CI, and agent-assisted workflows. It does not become another secret vault: Infisical, Doppler, macOS Keychain, dotenvx, and local files continue to own their values, authentication, access controls, rotation, and provider UI.
8
4
 
9
- ## Why
5
+ `morphix-env` assembles the sources chosen by a profile into one short-lived child-process environment, records value-free provenance, and keeps normal command output, inspection, audit records, and generated workflow guidance free of secret values.
10
6
 
11
- | Problem | Solution |
12
- |---------|----------|
13
- | `NEXT_PUBLIC_*` / `VITE_*` baked at build time | `morphix-env generate` creates `__env.js` for runtime injection |
14
- | Scattered env vars across hosting platforms | Single source of truth in Infisical, pulled at startup |
15
- | No local override when using remote config | `.env.local` always wins — edit one file, restart |
16
- | Different tools for different needs (dotenv, cross-env, infisical CLI) | One tool, one command |
7
+ The concise command is `mx-env`; the published `morphix-env` command remains fully supported.
17
8
 
18
- ## Install
9
+ ## Quick start: global local defaults
19
10
 
20
- ```bash
21
- pnpm add -D morphix-env
22
- # or
23
- npm install -D morphix-env
11
+ Create this file once:
12
+
13
+ ```dotenv
14
+ # ~/.mx-env/.env
15
+ PERSONAL_API_URL=http://localhost:4444
24
16
  ```
25
17
 
26
- ## Quick Start
18
+ Protect it on macOS/Linux:
27
19
 
28
20
  ```bash
29
- # Run a command with env injection
30
- morphix-env run -- next dev
21
+ mkdir -p ~/.mx-env
22
+ chmod 700 ~/.mx-env
23
+ chmod 600 ~/.mx-env/.env
24
+ ```
31
25
 
32
- # Generate client-side __env.js
33
- morphix-env generate --out public/__env.js
26
+ Then every normal project command can use it without repeating configuration:
34
27
 
35
- # Debug: see what's loaded
36
- morphix-env inspect
28
+ ```bash
29
+ mx-env run -- pnpm dev
37
30
  ```
38
31
 
39
- ## How It Works
32
+ `~/.mx-env/.env` is deliberately the only automatically discovered value file. `~/.mx-env/config.json` is optional and adds global Keychain, encrypted-file, or remote-provider sources. For CI, investigation, or an unfamiliar repository, use a reproducible project-only run:
40
33
 
41
- ### Core Flow
42
-
43
- ```
44
- morphix-env run -- <command>
45
- │
46
- ├─ 1. Read mx-env.config.json
47
- │
48
- ├─ 2. Load Infisical secrets ──────────────────────────┐
49
- │ │ │
50
- │ ├─ INFISICAL_CLIENT_ID exists? │
51
- │ │ ├─ Yes → SDK (Machine Identity) ── CI/Docker │
52
- │ │ └─ No ──┐ │
53
- │ │ ├─ infisical CLI installed? │
54
- │ │ │ ├─ Yes → CLI (user login) ── Local │
55
- │ │ │ └─ No → Skip │
56
- │ │ │
57
- │ └─ Inject into process.env (does NOT overwrite) │
58
- │ │
59
- ├─ 3. Load .env.local ─────────────────────────────────┐
60
- │ └─ Inject into process.env (OVERWRITES all) │
61
- │ │
62
- ├─ 4. Generate __env.js (if configured) │
63
- │ └─ Extract NEXT_PUBLIC_* / VITE_* → write file │
64
- │ │
65
- └─ 5. Spawn child command │
66
- └─ Inherits fully assembled process.env │
34
+ ```bash
35
+ mx-env run --no-global -- pnpm dev
67
36
  ```
68
37
 
69
- ### Priority (high → low)
38
+ ## Source precedence and conflicts
70
39
 
71
- ```
72
- ┌─────────────────────────────────────────────────┐
73
- │ .env.local ← HIGHEST │
74
- │ Always wins. Developer's local overrides. │
75
- ├─────────────────────────────────────────────────┤
76
- │ Infisical secrets ← MEDIUM │
77
- │ Pulled via SDK or CLI. Does not overwrite. │
78
- ├─────────────────────────────────────────────────┤
79
- │ process.env ← LOWEST │
80
- │ Docker ENV, CI vars, shell exports. │
81
- └─────────────────────────────────────────────────┘
40
+ The composed environment has a fixed low-to-high order:
41
+
42
+ ```text
43
+ inherited process.env
44
+ → ~/.mx-env/.env
45
+ → ~/.mx-env/config.json default profile
46
+ → project profile or legacy project configuration
47
+ → explicit CLI local override (-f)
82
48
  ```
83
49
 
84
- ### Authentication Flow
50
+ Two sources that provide the same key fail by default. The later source must explicitly declare `"override": true` to replace the earlier value. This catches accidental cross-environment collisions instead of silently selecting a database, API, or deployment credential.
85
51
 
86
- ```
87
- ┌──────────────────────────────────────────────────────────┐
88
- │ morphix-env starts │
89
- │ │ │
90
- │ INFISICAL_CLIENT_ID set? │
91
- │ / \ │
92
- │ Yes No │
93
- │ │ │ │
94
- │ ┌───────▼────────┐ infisical CLI installed? │
95
- │ │ SDK Auth │ / \ │
96
- │ │ (Machine ID) │ Yes No │
97
- │ │ │ │ │ │
98
- │ │ CI / Docker / │ ┌──▼───────────┐ │ │
99
- │ │ Production │ │ CLI Auth │ ▼ │
100
- │ └────────┬────────┘ │ (User Login) │ Skip │
101
- │ │ │ │ Infisical │
102
- │ │ │ Local Dev │ │
103
- │ │ └──────┬────────┘ │
104
- │ │ │ │
105
- │ ▼ ▼ │
106
- │ Pull secrets from Infisical │
107
- │ Inject into process.env │
108
- └──────────────────────────────────────────────────────────┘
109
- ```
52
+ Existing projects remain compatible: a legacy `mx-env.config.json` still composes the configured Infisical paths followed by `.env.local`, so `.env.local` remains the deliberate override layer.
53
+
54
+ ## Profiles and providers
110
55
 
111
- ### Local Development
56
+ Profiles compose complete source collections; do not enumerate every secret in repository configuration.
112
57
 
58
+ ```jsonc
59
+ // ~/.mx-env/config.json — optional user baseline, never contains values
60
+ {
61
+ "sources": {
62
+ "machine-keychain": {
63
+ "provider": "os-keychain",
64
+ "platform": "macos",
65
+ "service": "com.morphix.env/global"
66
+ },
67
+ "team-defaults": {
68
+ "provider": "infisical",
69
+ "paths": ["/shared"]
70
+ }
71
+ },
72
+ "profiles": {
73
+ "developer-default": {
74
+ "sources": ["machine-keychain", "team-defaults"]
75
+ }
76
+ },
77
+ "defaultProfile": "developer-default"
78
+ }
113
79
  ```
114
- Developer machine:
115
- 1. infisical login ← one-time, session cached
116
- 2. pnpm dev ← morphix-env auto-detects CLI
117
- └─ morphix-env run
118
- ├─ infisical CLI pulls 69 secrets
119
- ├─ .env.local overrides API_BASE_URL → localhost
120
- └─ next dev starts with all vars
80
+
81
+ ```jsonc
82
+ // project/mx-env.config.json
83
+ {
84
+ "sources": {
85
+ "api": {
86
+ "provider": "infisical",
87
+ "paths": ["/ai/api"]
88
+ },
89
+ "project-doppler": {
90
+ "provider": "doppler",
91
+ "project": "morphicai-api",
92
+ "config": "dev"
93
+ },
94
+ "local": {
95
+ "provider": "local",
96
+ "files": [".env.local"],
97
+ "override": true
98
+ }
99
+ },
100
+ "profiles": {
101
+ "api-dev": {
102
+ "extends": "developer-default",
103
+ "sources": ["api", "project-doppler", "local"]
104
+ }
105
+ },
106
+ "defaultProfile": "api-dev"
107
+ }
121
108
  ```
122
109
 
123
- ### CI / Docker
110
+ Run it with either CLI name:
124
111
 
125
- ```
126
- Container / CI runner:
127
- ENV INFISICAL_CLIENT_ID=xxx
128
- ENV INFISICAL_CLIENT_SECRET=xxx
129
- ENV DEPLOY_ENV=prod
130
-
131
- CMD morphix-env run -- node server.js
132
- └─ morphix-env run
133
- ├─ SDK pulls secrets (no CLI needed)
134
- ├─ .env.local not present → skip
135
- └─ server starts with prod vars
112
+ ```bash
113
+ mx-env run --profile api-dev -- pnpm dev
114
+ morphix-env run --profile api-dev -- pnpm dev
136
115
  ```
137
116
 
138
- ### `__env.js` — Client-Side Runtime Injection
117
+ ### Supported sources
139
118
 
140
- ```
141
- Build phase (CI):
142
- morphix-env run -- next build
143
- ├─ NEXT_PUBLIC_* injected at build time → baked into JS bundle
144
- └─ Works, but image is environment-specific
145
-
146
- Runtime injection (Docker, optional):
147
- morphix-env run -- node server.js
148
- ├─ morphix-env generates public/__env.js:
149
- │ window.__ENV = {
150
- │ "NEXT_PUBLIC_API_URL": "https://api.prod.example.com",
151
- │ "NEXT_PUBLIC_APP_NAME": "MyApp"
152
- │ };
153
- │
154
- ├─ Browser loads <script src="/__env.js"> before app
155
- └─ App reads: window.__ENV?.NEXT_PUBLIC_API_URL
156
- → One build, deploy to any environment
157
- ```
119
+ | Provider | Source selector | Value ownership / bootstrap |
120
+ |---|---|---|
121
+ | `infisical` | `paths`, optional project/site/environment settings | Local: `infisical login`. CI: Machine Identity through the usual `INFISICAL_CLIENT_ID`, `INFISICAL_CLIENT_SECRET`, and project identity flow. Do not put these in project config. |
122
+ | `doppler` | `project` and `config` | Local: `doppler login`. CI: a least-privilege Doppler Service Token supplied outside repository configuration, ideally via workload identity or the OS Keychain. |
123
+ | `os-keychain` | macOS generic-password `service`; each account is an environment key | macOS only in this release. Keychain owns the value; use `mx-env edit` to add/update it. |
124
+ | `dotenvx` | encrypted `files` | Install `dotenvx` separately and provide its private decryption key through a secure bootstrap path. The encrypted file stays provider-managed. |
125
+ | `local` | dotenv `files` | Intended for project `.env.local` and explicitly declared user files. A missing source is only allowed with `"optional": true`. |
158
126
 
159
- ## Commands
127
+ ### Doppler compared with Infisical
160
128
 
161
- ### `morphix-env run [options] -- <command>`
129
+ Both are strong team secret managers; `morphix-env` treats them as sources rather than attempting to reproduce their control planes.
162
130
 
163
- Load environment variables, then execute a command. The child process inherits all injected vars.
131
+ | Concern | Infisical | Doppler | What `morphix-env` adds |
132
+ |---|---|---|---|
133
+ | Organization | projects, environments, folders/paths, identities | project, config, root/branch config inheritance | One profile can combine either or both with local sources. |
134
+ | Local execution | SDK Machine Identity or `infisical` CLI | `doppler login` + `doppler run` | One stable `mx-env run --profile` interface. |
135
+ | CI least privilege | Machine Identity scoped by project/path/role | Service Token scoped to project/config | Provider bootstrap stays outside config; profile decides only which source is requested. |
136
+ | Visual value editing | Provider dashboard | Provider dashboard | `mx-env edit` opens provider-owned routes instead of copying values into a new dashboard. |
137
+ | Local Keychain / global dotenv | Not their primary concern | Not their primary concern | `~/.mx-env` and `os-keychain` participate in the same explicit order. |
164
138
 
165
- ```bash
166
- # Basic: load env, run dev server
167
- morphix-env run -- next dev --turbo -p 3004
139
+ Choose a provider based on its own identity, audit, rotation, price, and hosting requirements. Provider switching is not assumed to be a one-line operation: source selectors and permissions remain provider-specific even though the consuming command remains stable.
140
+
141
+ For a manual Doppler smoke test, authenticate with `doppler login`, declare a non-production project/config source, and run `mx-env inspect --profile <profile>`. It must report the Doppler source and key names only. Use `mx-env run --profile <profile> -- <non-echoing command>` to validate delivery; do not use `doppler secrets get --plain` or print the child environment.
168
142
 
169
- # Custom env file
170
- morphix-env run -f .env.staging -- npm start
143
+ ## Visual editor
171
144
 
172
- # Skip Infisical (use only local files)
173
- morphix-env run --no-infisical -- npm start
145
+ Use the one-shot editor instead of asking someone to paste a value into chat:
174
146
 
175
- # Verbose: show which vars were loaded
176
- morphix-env run -v -- node server.js
147
+ ```bash
148
+ mx-env edit --profile api-dev
149
+ mx-env edit --source machine-keychain
177
150
  ```
178
151
 
179
- ### `morphix-env generate [options]`
152
+ It starts a short-lived loopback-only browser session. The profile page shows names, source precedence, configured state, provenance, and collisions without values.
153
+
154
+ - Infisical and Doppler sources route the user to their provider-owned editor.
155
+ - Local dotenv files can be edited only in the selected local source.
156
+ - Keychain shows account names but never reveals existing values; it can add or update an account value.
157
+ - dotenvx writes through its CLI so it remains responsible for the encrypted representation.
158
+
159
+ The CLI prints status only, never a secret value.
180
160
 
181
- Extract public environment variables and write to a JS file for browser runtime injection.
161
+ On macOS the browser opens automatically and the default CLI output intentionally omits the one-time editor URL, so an Agent tool result does not receive a bearer-like local session capability. A human can explicitly request the URL when needed:
182
162
 
183
163
  ```bash
184
- morphix-env generate # → public/__env.js
185
- morphix-env generate --out dist/__env.js # Vite projects
186
- morphix-env generate --filter NEXT_PUBLIC_ # Only Next.js vars
164
+ mx-env edit --profile api-dev --print-editor-url
187
165
  ```
188
166
 
189
- ### `morphix-env inspect [options]`
167
+ On macOS, verify Keychain editing manually after installing a new version: use a temporary `os-keychain` service namespace, run `mx-env edit --source <name>`, add a generated test account in the browser, confirm it appears in the account-name list without revealing its value, then use **Delete** to remove it. The terminal transcript must contain only the editor URL/status.
190
168
 
191
- Print env var values for debugging. Secrets are masked (first 4 chars shown).
169
+ ## Context hygiene
192
170
 
193
171
  ```bash
194
- morphix-env inspect
195
- morphix-env inspect --filter NEXT_PUBLIC_
196
- morphix-env inspect -f .env.production
172
+ mx-env inspect --profile api-dev
173
+ mx-env audit tail
197
174
  ```
198
175
 
199
- ## Options
176
+ `inspect` lists key names, source provenance, and configured state only. Audit events contain profile/source/key names, outcome, duration, and exit code—never a value, value prefix, command arguments, or provider stderr.
200
177
 
201
- | Flag | Short | Description |
202
- |------|-------|-------------|
203
- | `--env-file <path>` | `-f` | Env file to load (default: `.env.local`, repeatable) |
204
- | `--out <path>` | `-o` | Output path for generate (default: `public/__env.js`) |
205
- | `--filter <prefix>` | | Only include vars with this prefix |
206
- | `--no-infisical` | | Skip Infisical fetch entirely |
207
- | `--verbose` | `-v` | Show loaded variable names |
178
+ When child output is forwarded through the CLI, exact non-public injected values are redacted as `[REDACTED]`, including when the value crosses stream chunks or the command fails.
208
179
 
209
- ## Config File
180
+ This is leakage minimization, not a cryptographic isolation boundary. A process that receives an environment variable can deliberately transform or exfiltrate it. For a high-risk Agent capability, use a provider proxy/broker as a separate security design rather than passing that credential through `run`.
210
181
 
211
- Create `mx-env.config.json` in your project root. Committed to git.
182
+ Browser variables (`NEXT_PUBLIC_`, `VITE_`, `EXPO_PUBLIC_`) are an intentional exception: they are public configuration, not protected secrets.
212
183
 
213
- ```json
214
- {
215
- "infisical": {
216
- "paths": ["/ai"],
217
- "env": "dev"
218
- },
219
- "envFiles": [".env.local"],
220
- "generate": {
221
- "out": "public/__env.js",
222
- "filter": "NEXT_PUBLIC_"
223
- }
224
- }
225
- ```
184
+ ## Commands
226
185
 
227
- ### Config vs Environment Variables
186
+ ```bash
187
+ # Run one command with its composed profile
188
+ mx-env run --profile api-dev -- pnpm dev
228
189
 
229
- ```
230
- ┌──────────────────────────────────────────────────────┐
231
- │ mx-env.config.json (committed to git) │
232
- │ ├─ paths → which secrets to pull │
233
- │ ├─ env → which environment │
234
- │ ├─ envFiles → which override files to load │
235
- │ └─ generate → __env.js output config │
236
- │ │
237
- │ These are PROJECT CONFIG, not secrets. │
238
- ├──────────────────────────────────────────────────────┤
239
- │ Environment Variables (NEVER committed) │
240
- │ ├─ INFISICAL_CLIENT_ID → Machine Identity │
241
- │ ├─ INFISICAL_CLIENT_SECRET → Machine Identity │
242
- │ └─ DEPLOY_ENV → prod / staging / dev │
243
- │ │
244
- │ These are CREDENTIALS, set in CI/Docker only. │
245
- │ Local dev uses infisical CLI login instead. │
246
- └──────────────────────────────────────────────────────┘
247
- ```
190
+ # Existing scripts remain valid
191
+ morphix-env run --env dev -- pnpm dev
248
192
 
249
- ## Usage Examples
193
+ # Generate public browser runtime configuration
194
+ mx-env generate --profile api-dev --out public/__env.js
250
195
 
251
- ### Next.js
196
+ # Value-free status/provenance
197
+ mx-env inspect --profile api-dev
252
198
 
253
- ```jsonc
254
- // package.json
255
- {
256
- "scripts": {
257
- "dev": "morphix-env run -- next dev --turbo -p 3004",
258
- "build": "morphix-env run -- next build",
259
- "start": "morphix-env run -- next start"
260
- }
261
- }
262
- ```
199
+ # Open the short-lived visual editor
200
+ mx-env edit --profile api-dev
263
201
 
264
- ```json
265
- // mx-env.config.json
266
- {
267
- "infisical": { "paths": ["/ai"], "env": "dev" },
268
- "envFiles": [".env.local"],
269
- "generate": { "out": "public/__env.js", "filter": "NEXT_PUBLIC_" }
270
- }
202
+ # Generate this project's value-free Agent workflow Skill
203
+ mx-env doc --profile api-dev
204
+
205
+ # Value-free audit events
206
+ mx-env audit tail
271
207
  ```
272
208
 
273
- ### Vite (React / Vue / Ionic)
209
+ Options:
274
210
 
275
- ```jsonc
276
- {
277
- "scripts": {
278
- "dev": "morphix-env run -- vite",
279
- "build": "morphix-env run -- vite build"
280
- }
281
- }
282
- ```
211
+ | Option | Meaning |
212
+ |---|---|
213
+ | `-p, --profile <name>` | Select a named profile. |
214
+ | `-f, --env-file <path>` | Append a local override source for this invocation. |
215
+ | `-e, --env <name>` | Override the Infisical environment for selected Infisical sources. |
216
+ | `--no-infisical` | Skip Infisical sources. |
217
+ | `--no-global` | Ignore `~/.mx-env/.env` and `~/.mx-env/config.json`. |
218
+ | `--allow-insecure-global` | Explicitly permit a global dotenv file readable by group/other users. Avoid this outside controlled troubleshooting. |
219
+ | `--print-editor-url` | Explicitly print the one-time local editor URL; normally the browser opens without emitting it to the terminal. |
220
+ | `-o, --out <path>` / `--filter <prefix>` | Configure public browser-env generation. |
221
+ | `-v, --verbose` | Show source state and key counts, not values. |
283
222
 
284
- ```json
285
- {
286
- "infisical": { "paths": ["/frontend"], "env": "dev" },
287
- "generate": { "out": "dist/__env.js", "filter": "VITE_" }
288
- }
289
- ```
223
+ ## Agent workflow
290
224
 
291
- ### Express API
225
+ `mx-env doc --profile <profile>` writes a value-free workflow Skill to `.agents/skills/morphix-env/SKILL.md` by default. Generated or maintained Skills contain this rule:
292
226
 
293
- ```jsonc
294
- {
295
- "scripts": {
296
- "dev": "morphix-env run -- tsx watch src/index.ts",
297
- "start": "morphix-env run -- node dist/index.js"
298
- }
299
- }
227
+ ```md
228
+ When the user asks to add, modify, or rotate a credential:
229
+ 1. Run `mx-env edit --profile <profile>` or `mx-env edit --source <source>`.
230
+ 2. Let the user complete the edit in the local visual editor or provider UI.
231
+ 3. Do not request the value in chat; do not run env, printenv, cat .env, or a reveal command.
232
+ 4. Validate only configured status and provenance, never the value.
300
233
  ```
301
234
 
235
+ ## Legacy configuration
236
+
237
+ This remains valid and needs no immediate migration:
238
+
302
239
  ```json
303
240
  {
304
- "infisical": { "paths": ["/ai"], "env": "dev" },
305
- "envFiles": [".env.local"]
241
+ "infisical": { "paths": ["/ai"], "envPrefix": "VITE_" },
242
+ "envFiles": [".env.local"],
243
+ "generate": { "out": "public/__env.js", "filter": "NEXT_PUBLIC_" }
306
244
  }
307
245
  ```
308
246
 
309
- No `generate` — server-side apps don't need `__env.js`.
310
-
311
- ### Docker
312
-
313
- ```dockerfile
314
- FROM node:20-alpine
315
- WORKDIR /app
316
- COPY . .
317
- RUN pnpm install && pnpm build
318
-
319
- ENV INFISICAL_CLIENT_ID=""
320
- ENV INFISICAL_CLIENT_SECRET=""
321
- ENV DEPLOY_ENV="prod"
322
-
323
- CMD ["npx", "morphix-env", "run", "--", "node", "server.js"]
324
- ```
325
-
326
- No Infisical CLI binary needed in the image.
247
+ Migrate when a project needs named profiles, multiple providers, or a source that can be edited through the unified local entry point.
327
248
 
328
249
  ## License
329
250