akm-cli 0.9.11 → 0.9.12

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 (127) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/STABILITY.md +6 -1
  3. package/dist/assets/hints/cli-hints-full.md +1 -1
  4. package/dist/assets/improve-strategies/consolidate.json +1 -1
  5. package/dist/assets/improve-strategies/default.json +1 -1
  6. package/dist/assets/improve-strategies/thorough.json +1 -2
  7. package/dist/cli/shared.js +16 -4
  8. package/dist/cli.js +15 -13
  9. package/dist/commands/agent/agent-dispatch.js +8 -0
  10. package/dist/commands/command/execution-source-loader.js +25 -22
  11. package/dist/commands/command/portable-template.js +4 -26
  12. package/dist/commands/config-cli.js +10 -4
  13. package/dist/commands/env/env-binding.js +10 -3
  14. package/dist/commands/env/env-cli.js +7 -0
  15. package/dist/commands/env/secret-cli.js +15 -4
  16. package/dist/commands/health/checks.js +186 -71
  17. package/dist/commands/health.js +16 -4
  18. package/dist/commands/improve/distill/quality-gate.js +2 -2
  19. package/dist/commands/improve/distill.js +28 -12
  20. package/dist/commands/improve/execution.js +1 -2
  21. package/dist/commands/improve/extract.js +82 -56
  22. package/dist/commands/improve/improve-strategies.js +26 -8
  23. package/dist/commands/improve/improve.js +13 -0
  24. package/dist/commands/improve/preparation.js +9 -6
  25. package/dist/commands/improve/reflect.js +61 -77
  26. package/dist/commands/lint/index.js +3 -1
  27. package/dist/commands/migrate-cli.js +6 -4
  28. package/dist/commands/proposal/drain-policies.js +22 -2
  29. package/dist/commands/proposal/repository.js +4 -4
  30. package/dist/commands/proposal/validators/proposal-quality-validators.js +23 -2
  31. package/dist/commands/proposal/validators/proposals.js +10 -19
  32. package/dist/commands/read/show.js +42 -31
  33. package/dist/commands/registry-cli.js +4 -2
  34. package/dist/commands/sources/init.js +4 -8
  35. package/dist/commands/sources/self-update.js +2 -2
  36. package/dist/commands/sources/source-clone.js +5 -7
  37. package/dist/commands/sources/sources-cli.js +3 -5
  38. package/dist/commands/tasks/tasks-cli.js +4 -12
  39. package/dist/commands/tasks/tasks.js +38 -35
  40. package/dist/commands/workflow-cli.js +17 -15
  41. package/dist/core/activation-policy.js +31 -3
  42. package/dist/core/adapter/execution-source.js +39 -11
  43. package/dist/core/asset/stash-meta.js +7 -41
  44. package/dist/core/common.js +8 -17
  45. package/dist/core/config/config-schema.js +3 -23
  46. package/dist/core/config/config-walker.js +50 -4
  47. package/dist/core/config/config.js +21 -5
  48. package/dist/core/config/legacy-source-shape-shim.js +79 -0
  49. package/dist/core/config/schema/embedding.js +2 -2
  50. package/dist/core/config/schema/engines.js +2 -2
  51. package/dist/core/config/schema/index-config.js +19 -21
  52. package/dist/core/config/schema/primitives.js +21 -10
  53. package/dist/core/config/schema/sources-bundles.js +1 -6
  54. package/dist/core/errors.js +2 -3
  55. package/dist/core/improve-types.js +17 -0
  56. package/dist/core/json-schema.js +1 -11
  57. package/dist/core/maintenance-barrier.js +17 -2
  58. package/dist/core/paths.js +12 -15
  59. package/dist/core/state/migrations.js +28 -0
  60. package/dist/core/state-db.js +28 -1
  61. package/dist/core/write-source.js +6 -6
  62. package/dist/indexer/bundle-identity-guard.js +3 -0
  63. package/dist/indexer/ensure-index.js +5 -0
  64. package/dist/indexer/indexer.js +11 -3
  65. package/dist/indexer/lookup/adapter-concept-owner.js +14 -3
  66. package/dist/indexer/passes/metadata.js +16 -5
  67. package/dist/indexer/search/search-fields.js +1 -30
  68. package/dist/integrations/agent/engine-resolution.js +15 -1
  69. package/dist/integrations/agent/model-map.js +16 -10
  70. package/dist/integrations/agent/prompts.js +13 -6
  71. package/dist/integrations/lockfile.js +22 -7
  72. package/dist/llm/client.js +16 -0
  73. package/dist/llm/index-passes.js +3 -2
  74. package/dist/output/shapes/passthrough.js +9 -3
  75. package/dist/output/shapes.js +5 -3
  76. package/dist/output/text/workflow-format.js +8 -1
  77. package/dist/scripts/akm-migrate-node.js +1461 -1267
  78. package/dist/scripts/akm-migrate.js +1460 -1266
  79. package/dist/setup/setup.js +14 -21
  80. package/dist/sources/include.js +150 -20
  81. package/dist/sources/providers/git-install.js +14 -12
  82. package/dist/sources/providers/git-provider.js +3 -3
  83. package/dist/sources/snapshot-fetchers/website-ingest.js +54 -16
  84. package/dist/sources/website-url.js +12 -4
  85. package/dist/storage/engines/sqlite-migrations.js +40 -10
  86. package/dist/storage/like-pattern.js +7 -0
  87. package/dist/storage/repositories/extract-sessions-repository.js +23 -0
  88. package/dist/storage/repositories/index-connection.js +27 -10
  89. package/dist/storage/repositories/index-entry-schema.js +19 -2
  90. package/dist/storage/repositories/index-schema.js +30 -9
  91. package/dist/storage/repositories/proposals-repository.js +2 -1
  92. package/dist/storage/repositories/task-history-repository.js +14 -7
  93. package/dist/storage/repositories/workflow-runs-repository.js +15 -1
  94. package/dist/storage/sqlite-read-snapshot.js +11 -9
  95. package/dist/tasks/backends/cron.js +34 -5
  96. package/dist/tasks/backends/launchd.js +23 -26
  97. package/dist/tasks/backends/schtasks.js +50 -3
  98. package/dist/tasks/frozen-script.js +2 -0
  99. package/dist/tasks/prepare/prepare.js +2 -7
  100. package/dist/tasks/prepare/script-capture.js +38 -6
  101. package/dist/tasks/schedule.js +154 -13
  102. package/dist/tasks/source/task-source-v3-frozen.js +0 -1
  103. package/dist/tasks/source/task-source-v4.js +0 -1
  104. package/dist/workflows/exec/child-workflow.js +2 -3
  105. package/dist/workflows/exec/exec-unit.js +3 -4
  106. package/dist/workflows/exec/run-workflow.js +19 -10
  107. package/dist/workflows/exec/step-work.js +35 -56
  108. package/dist/workflows/freeze/resolve-steps.js +19 -11
  109. package/dist/workflows/freeze/source-freeze.js +7 -0
  110. package/dist/workflows/freeze/targets/child-workflow.js +12 -18
  111. package/dist/workflows/freeze/targets/command.js +14 -2
  112. package/dist/workflows/ir/environment-v4.js +4 -2
  113. package/dist/workflows/ir/freeze-v4.js +2 -5
  114. package/dist/workflows/ir/plan-hash.js +0 -3
  115. package/dist/workflows/ir/schema-v4.js +14 -9
  116. package/dist/workflows/ir/schema.js +1 -3
  117. package/dist/workflows/resource-limits.js +35 -48
  118. package/dist/workflows/runtime/plan-classifier.js +89 -41
  119. package/dist/workflows/runtime/run-outputs.js +1 -21
  120. package/dist/workflows/runtime/runs.js +76 -150
  121. package/dist/workflows/source-files.js +28 -54
  122. package/dist/workflows/source-ir/program.js +2 -2
  123. package/dist/workflows/source-ir/semantics.js +5 -23
  124. package/docs/migration/v0.9.1-to-v0.9.2.md +20 -0
  125. package/docs/reference/cli.md +74 -17
  126. package/package.json +1 -1
  127. package/schemas/akm-config.json +5 -10
package/CHANGELOG.md CHANGED
@@ -4,6 +4,172 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [0.9.12] - 2026-09-03
8
+
9
+ ### Added
10
+
11
+ - **`akm health` probes LLM engine reachability (#914).** `default-llm-engine`
12
+ and `configured-engines` now send one bounded `GET` to the endpoint's
13
+ `/models` route (3 s timeout, one probe per distinct endpoint per
14
+ invocation, no cross-run cache) instead of only checking that a credential is
15
+ present. Any HTTP response counts as reachable, so a cold local server is
16
+ never asked to load a model just to be checked. An unreachable default LLM engine is a hard `fail` naming the
17
+ connection error; an unreachable non-default engine is a `warn`. Pass
18
+ `--no-probe` on an offline or air-gapped host to keep the credential-only
19
+ verdict; the message then says reachability was not probed.
20
+ - **`akm proposal extract` reports the engine it resolved (#913).** The
21
+ envelope carries `engine` and `engineKind` (`llm`, `sdk`, or `agent`), every
22
+ `sessions[]` entry carries `engine`, and the `extract_sessions_seen` ledger
23
+ metadata records it, so "which engine did that run actually use" is one
24
+ field instead of config archaeology. `akm health`'s `active-improve-strategy`
25
+ check names the engine each improve process resolved to, which makes a
26
+ strategy-level `engine` pin that shadows `defaults.llmEngine` visible.
27
+ - **`akm workflow run <ref>` says when it resumes, and `--new` starts fresh
28
+ (#919).** Resolving a ref to an already-active run in the current scope is
29
+ unchanged, but the `workflow-run` envelope now carries `resumed: true` and
30
+ the text output leads with `resuming existing run <id> for <ref>; pass --new
31
+ to start a fresh run`. `--new` starts a second run and leaves the active one
32
+ untouched. `status`, `abandon`, `resume`, and `run <id>` all accept a unique
33
+ run-id prefix of eight or more characters.
34
+
35
+ ### Changed
36
+
37
+ - **A run that skipped every session for an infrastructure reason is visible
38
+ in the extract envelope (#912).** `warnings[]` gains one aggregate line per
39
+ infrastructure skip reason (`llm_unavailable`, `read_failed`, `exception`,
40
+ `locked_concurrent`), for example `25 of 25 sessions skipped: llm_unavailable
41
+ (engine "default")`, and a `skipReasons` count map is present whenever
42
+ `sessionsSkipped > 0`. `ok` keeps meaning "the command ran" and the exit code
43
+ does not change; that meaning is now written down on the type.
44
+ - **`akm health`'s `session-extraction` check reads the extraction ledger
45
+ (#914).** It used to read only `improve_runs`, which the hook-driven
46
+ `akm proposal extract --session-id` never writes, so a plugin-driven machine
47
+ reported "not active" as `pass` forever. It now derives its verdict from the
48
+ last seven days of `extract_sessions_seen`: `unknown` when nothing was
49
+ recorded, `warn` naming the reason and engine when every session was skipped
50
+ or failed for an infrastructure reason (`llm_unavailable`, `read_failed`,
51
+ `exception`, `locked_concurrent`), otherwise `pass` with per-outcome counts.
52
+ - **Every passthrough success envelope carries `ok: true` (#918).**
53
+ `akm config set` and `akm config unset` printed the resulting config with no
54
+ `ok` field while their failure envelope had `ok: false`, so a caller
55
+ branching on `.ok` read success as failure. The shared passthrough stamp now
56
+ adds `ok: true` when a result has no `ok` of its own; commands whose exit
57
+ code grades the outcome (`task sync`, `task sync --dry-run`, `task prune`,
58
+ `workflow run`, `upgrade`, and the `migrate` subcommands) set `ok` and the
59
+ exit code from the same value, so the two cannot disagree. `--silent` still
60
+ prints nothing.
61
+ - **The unsupported-plan error names the real situation (#919).** A frozen
62
+ workflow plan with an `irVersion` above the current one no longer reports
63
+ "pre-irVersion-5 ... after the 0.9.2 upgrade"; it says the plan was probably
64
+ written by a newer akm. The reported `irVersion 111` did not reproduce
65
+ against this tree (the column is only ever written as `5` and no path
66
+ rewrites it on a source edit); a regression test pins that editing a
67
+ workflow source leaves its in-flight run executable.
68
+
69
+ ### Fixed
70
+
71
+ - **`akm proposal accept` no longer rewrites your content.** Two "repairs" ran
72
+ before validation: one deleted every body line matching `description:` or
73
+ `when_to_use:`, the other deleted every `---` in a body that had
74
+ frontmatter. Both fired inside fenced code blocks, so any asset documenting
75
+ frontmatter — a note about akm, Claude Code skills, Jekyll, Hugo — was
76
+ silently gutted on accept, and the rewritten bytes were saved back over the
77
+ original in the proposals database. Nothing was printed. Both repairs are
78
+ gone; the truncated-description repair, which only ever rewrote a
79
+ frontmatter value, stays.
80
+ - **Prose-quality findings no longer block `proposal accept`.** A description
81
+ that read like a heading, an odd number of backticks, or a reflect revision
82
+ outside the size ratio refused the promotion and told the user to "fix the
83
+ proposal payload and try again" — but there is no `akm proposal edit` and
84
+ `accept` has no `--force`, so the only way out was hand-editing the
85
+ proposals database. These findings are now reported as warnings on a
86
+ command a human typed. Structural defects that genuinely cannot be written
87
+ (empty content, an unparseable ref, malformed frontmatter, a broken
88
+ workflow shape) still block.
89
+ - **akm no longer refuses to open a state database migrated by a newer akm.**
90
+ Two akm versions sharing one data directory is a supported deployment — a
91
+ bundled CLI beside a newer global install — and the old binary was bricked
92
+ for every command that touches `state.db`, not degraded. It protected
93
+ nothing: an older binary's entire migration registry is already applied, so
94
+ it has no pending migration to run. It now opens, warns once naming the
95
+ migrations it does not know, and reads and writes the tables it knows. A
96
+ ledger that genuinely diverges (a migration this akm has was never applied
97
+ and something else was applied in its place) is still refused.
98
+ - **`akm agent --prompt` no longer rejects prose that looks like a template.**
99
+ A prompt was validated as if it were a portable command template, so any
100
+ prompt containing `}}` from compact JSON, a `$VAR`, a `${...}`, a `$(...)`
101
+ shell snippet, an `@path`, or a `` !` `` was rejected with "unsupported
102
+ portable template construct" before the agent started. akm substitutes
103
+ nothing into a prompt, so it is now sent verbatim. The template language is
104
+ unchanged for stored command files, which are templates.
105
+
106
+ - **The 0.9.2 `claude-code` -> `claude` harness rename left the ledgers split
107
+ (#915).** State migration `027-extract-sessions-seen-harness-rename` moves
108
+ `extract_sessions_seen` and `workflow_runs.agent_harness` rows off the old
109
+ key, keeping a session already recorded under `claude` as the authoritative
110
+ row, and empties the old key space. `PERSISTED_HARNESS_IDS` in the harness
111
+ registry is now pinned by a test so the next rename cannot ship without a
112
+ migration. Scripts querying the ledger by `claude-code` will find it empty
113
+ after upgrade; see the 0.9.1 -> 0.9.2 migration guide.
114
+
115
+ ### Changed — refusals that now degrade
116
+
117
+ A repo-wide audit reviewed every defensive refusal in the codebase against
118
+ three tests: has it demonstrably helped a real user, does its failure mode cost
119
+ less than the hazard it guards, and is the hazard already gated behind a
120
+ deliberate human command. Refusals that failed those tests were removed or
121
+ downgraded to a warning, in that order of preference. Machinery that prevents
122
+ data loss or corruption — atomic writes, backups, write-path validation, path
123
+ containment — was explicitly out of scope and is unchanged.
124
+
125
+ The user-visible effect is that akm stops aborting on conditions it can
126
+ survive. Highlights:
127
+
128
+ - **Config load no longer bricks every command over one bad key.** Retired
129
+ vocabulary (`profiles`, `llm`, `agent`, `features`, `stashes`,
130
+ `modelAliases`, `bindings`, top-level `writable`) warns and passes through,
131
+ and the pre-`bundles` `stashDir`/`sources[]`/`installed[]` shape folds into
132
+ the current shape in memory. An unknown `config set` key warns and stores.
133
+ - **A newer or unfamiliar state.db migration ledger no longer refuses to
134
+ open.** A ledger carrying migrations this binary does not know warns once and
135
+ degrades; only a genuinely inconsistent ledger still aborts. This is what let
136
+ a bundled older akm keep working against a newer host's data directory.
137
+ A `plan_ir_version` of NULL (a row predating the column) decodes normally.
138
+ - **Free text is no longer validated as code.** `akm agent` accepts prompts
139
+ containing `{{`/`}}`; inline workflow `akm/command` content and portable
140
+ command templates accept `$HOME`, `@file`, `${...}` and the rest as the prose
141
+ they are. Only `$ARGUMENTS[N]`, which merely looks like the one placeholder
142
+ akm expands, still warns.
143
+ - **Version skew stops being treated as corruption.** A newer index is left
144
+ alone rather than wiped, a stale indexed workflow identity falls back, and
145
+ frozen-plan spine drift from a formatting change between releases warns
146
+ instead of marking every in-flight run corrupt.
147
+ - **Deliberate commands stop being second-guessed.** `--since` bypasses
148
+ `extract`'s per-run cap the way `--force` already did; `workflow create
149
+ --force` no longer also demands `--reset`; scheduler writes, `setup --dir`
150
+ and `bundle create --dir` under transient paths warn instead of refusing.
151
+ - **Symlinks are followed on read paths** (task sources, `models.json`, stash
152
+ meta, `akm.include` entries) with realpath containment doing the actual
153
+ safety work, rather than being refused outright.
154
+ - **Blocks that had no escape hatch got one, or got removed.** `env run` gained
155
+ `--allow-insecure` for the third-party dangerous-key block that previously
156
+ had no bypass; `registry add` accepts a credentialed URL with a warning that
157
+ redacts the credential; promotion lint findings on `proposal accept` report
158
+ instead of blocking, since there is no `proposal edit` and no `accept
159
+ --force` to work around them.
160
+ - **Limits that bounded nothing real were deleted.** Workflow plan and embedded
161
+ child-plan byte caps, step-evidence tombstoning, search-text truncation, and
162
+ the JSON-schema node budget are gone; the akm.lock acquisition budget went
163
+ from 300 ms to ~30 s with backoff and a "waiting for another akm process"
164
+ notice.
165
+ - **A cron line too long for vixie-cron now spills into a wrapper script**
166
+ instead of refusing the install. The length limit stays, because a truncated
167
+ cron line would execute a partial command.
168
+
169
+ Guards that passed the three tests were kept and documented, including the
170
+ registry-URL credential inspection limit, the third-party dangerous-key block
171
+ itself, `MAX_SCHTASKS_TRIGGERS`, and every path-containment check.
172
+
7
173
  ## [0.9.11] - 2026-09-03
8
174
 
9
175
  ### Added
package/STABILITY.md CHANGED
@@ -219,6 +219,10 @@ enumeration of the whole `proposal` noun group.
219
219
  | `4` | Health warning (`akm health` only) |
220
220
  | `70` | Internal / unclassified |
221
221
  | `78` | Configuration error |
222
+
223
+ **From 0.9.12**, every success envelope produced by the passthrough stamp
224
+ (`config`, `clone`, `models`, `task-*`, `workflow-*`, `registry-*`, …) also
225
+ carries `ok: true`; a command that already computes its own `ok` keeps it.
222
226
  - **Install scripts** — `install.sh` and `install.ps1` URLs; the `--prefix`
223
227
  / `AKM_INSTALL_DIR` environment override.
224
228
  - **Runtime** — the npm package requires Node.js >= 22 as its bootstrap and
@@ -439,7 +443,6 @@ on them.
439
443
  | `AKM_SQLITE_JOURNAL_MODE` | SQLite journal mode (network filesystems) |
440
444
  | `AKM_BIN` | Absolute `akm` path for scheduler registration |
441
445
  | `AKM_INSTALL_DIR` | Install-script prefix |
442
- | `AKM_FORCE_SETUP_TMP_STASH` | Documented escape hatch for intentional temp-directory bundles |
443
446
  | `AKM_UPGRADE_SKIP_CHECKSUM` | Recovery hatch for a broken upgrade checksum |
444
447
 
445
448
  **Internal** — no compatibility guarantee, may vanish without notice:
@@ -481,6 +484,8 @@ lives in this repo). **D3** shipped too, in the end: `akm mv` was removed in
481
484
  0.9.0 (see the Renames bullet above), with `scripts/rekey-asset-ref.ts` as the
482
485
  Internal replacement for the one capability nothing else covered.
483
486
 
487
+ - **0.10 — `config set`/`config unset` may drop the config dump** in favor of
488
+ a compact `{ok, shape, key}` result; `akm config list` remains the full read.
484
489
  - **0.10 — migration extraction.** The migration machinery leaves the CLI for
485
490
  a separately published `akm-migrate` package (see Internal above).
486
491
  - **0.10 — `--auto-accept` hard error.** It is currently accepted-and-warned;
@@ -414,7 +414,7 @@ Result-envelope commands accept `--format`, `--detail`, and `--shape` flags:
414
414
  - `--detail full` — includes scores, paths, timing, debug info
415
415
  - `--shape human` (default) — standard projection
416
416
  - `--shape agent` — agent-optimized output: strips non-actionable fields
417
- - `--shape summary` — metadata only (no content/template/prompt), under 200 tokens; only valid on `akm show`
417
+ - `--shape summary` — metadata only (no content/template/prompt), under 200 tokens; only `akm show` has a dedicated summary projection — elsewhere it falls back to `agent` with a warning
418
418
 
419
419
  Run `akm help <command>` or `akm <command> -h` for per-command help. Run
420
420
  `akm --help` for the sectioned command overview.
@@ -3,7 +3,7 @@
3
3
  "processes": {
4
4
  "reflect": { "enabled": false },
5
5
  "distill": { "enabled": false },
6
- "consolidate": { "enabled": true, "allowedTypes": ["memory"], "maxChunkSize": 25, "minPoolSize": 500 },
6
+ "consolidate": { "enabled": true, "allowedTypes": ["memory"], "maxChunkSize": 25 },
7
7
  "memoryInference": { "enabled": false },
8
8
  "graphExtraction": { "enabled": false },
9
9
  "extract": { "enabled": false },
@@ -7,7 +7,7 @@
7
7
  "allowedTypes": ["agent", "command", "knowledge", "lesson", "memory", "skill", "workflow"]
8
8
  },
9
9
  "distill": { "enabled": true, "allowedTypes": ["memory"], "requirePlannedRefs": true },
10
- "consolidate": { "enabled": true, "allowedTypes": ["memory"], "minPoolSize": 500 },
10
+ "consolidate": { "enabled": true, "allowedTypes": ["memory"] },
11
11
  "memoryInference": { "enabled": true },
12
12
  "graphExtraction": { "enabled": true },
13
13
  "extract": { "enabled": false, "triage": { "enabled": true, "minScore": 2 } },
@@ -13,8 +13,7 @@
13
13
  },
14
14
  "consolidate": {
15
15
  "enabled": true,
16
- "allowedTypes": ["memory"],
17
- "minPoolSize": 500
16
+ "allowedTypes": ["memory"]
18
17
  },
19
18
  "memoryInference": {
20
19
  "enabled": true
@@ -150,13 +150,14 @@ export const GLOBAL_OUTPUT_ARGS = {
150
150
  // R-050(c): single-sourced with the root command's own `--shape` help
151
151
  // (`main.args.shape` in src/cli.ts, which spreads this object) so the
152
152
  // caveat is visible from every leaf's own `--help`, not only the top-level
153
- // one. `summary` outside `show` is a hard usage error (exit 2,
154
- // INVALID_SHAPE_VALUE), enforced at startup in src/cli.ts before any
155
- // command body runs.
153
+ // one. `summary` outside `show` falls back to `agent` with a warning
154
+ // (shapeForCommand in src/output/shapes.ts) `--shape` is a global flag,
155
+ // so a script that passes it to a mixed batch of commands still works.
156
156
  shape: {
157
157
  type: "string",
158
158
  description: "Output projection: human|agent|summary (global flag). 'agent' trims to agent-essential fields; " +
159
- "'summary' is only valid on 'akm show' (a usage error, exit 2, everywhere else). Default: human.",
159
+ "'summary' only has a dedicated projection on 'akm show' elsewhere it falls back to 'agent' with a " +
160
+ "warning. Default: human.",
160
161
  },
161
162
  output: {
162
163
  type: "string",
@@ -269,6 +270,17 @@ export function defineGroupCommand(def) {
269
270
  * document is written to that file instead of stdout (jsonl excepted — it is
270
271
  * a line-streaming protocol and always goes to stdout).
271
272
  */
273
+ /**
274
+ * Emit a result whose success is graded by an exit code. `ok` on the envelope
275
+ * and `process.exitCode` are set from the same value here, so the two cannot
276
+ * disagree (#918); `exitCode` undefined or 0 means success.
277
+ */
278
+ export function outputWithExitCode(command, result, exitCode) {
279
+ const failed = exitCode !== undefined && exitCode !== EXIT_CODES.SUCCESS;
280
+ output(command, { ...result, ok: !failed });
281
+ if (failed)
282
+ process.exitCode = exitCode;
283
+ }
272
284
  export function output(command, result) {
273
285
  const mode = getOutputMode();
274
286
  const shaped = shapeForCommand(command, result, mode.detail, mode.shape);
package/dist/cli.js CHANGED
@@ -57,7 +57,7 @@ process.on("uncaughtException", (err) => {
57
57
  });
58
58
  import fs from "node:fs";
59
59
  import { defineCommand, parseArgs, renderUsage, runCommand, showUsage } from "citty";
60
- import { findCittyTopLevelCommand, findCittyTopLevelCommandIndex, getParsedInvocation, parseAllFlagValues, resolveHelpMigrateVersionArg, setParsedInvocation, } from "./cli/invocation.js";
60
+ import { findCittyTopLevelCommandIndex, getParsedInvocation, parseAllFlagValues, resolveHelpMigrateVersionArg, setParsedInvocation, } from "./cli/invocation.js";
61
61
  import { retiredCommandHint } from "./cli/retired-commands.js";
62
62
  import { defineGroupCommand, EXIT_CODES, emitJsonError, GLOBAL_OUTPUT_ARGS, output, runWithJsonErrors, } from "./cli/shared.js";
63
63
  import { assertKnownFlags, closestMatch } from "./cli/unknown-flags.js";
@@ -284,6 +284,18 @@ const healthCommand = defineCommand({
284
284
  description: "Fetch the full report dataset: per-run rows, trend deltas vs the prior window, and the pending proposal queue. Renders as the rich report under --format md/html and as complete data under any other format.",
285
285
  default: false,
286
286
  },
287
+ // #914: citty strips any `--no-X` argument and treats it as negating `X`
288
+ // (see `parseArgs` in citty's dist), regardless of whether an arg literally
289
+ // named "no-X" is declared — declaring "no-probe" directly would silently
290
+ // never populate `args["no-probe"]` and the flag would do nothing. Declare
291
+ // the positive flag instead; `--no-probe` is citty's automatic negation of
292
+ // it (rendered in `--help` via `negativeDescription`).
293
+ probe: {
294
+ type: "boolean",
295
+ default: true,
296
+ description: "Probe default-llm-engine / configured-engines reachability (on by default).",
297
+ negativeDescription: "Skip the reachability probes (for an offline or air-gapped host).",
298
+ },
287
299
  },
288
300
  async run({ args }) {
289
301
  let resultStatus;
@@ -316,11 +328,12 @@ const healthCommand = defineCommand({
316
328
  const sinceIsDuration = args.since !== undefined && parseDuration(args.since, DURATION_UNITS) !== null;
317
329
  const implicitCompare = explicitWindows ? undefined : ((sinceIsDuration ? args.since : undefined) ?? "24h");
318
330
  const windowCompare = report ? (args["window-compare"] ?? implicitCompare) : args["window-compare"];
319
- const base = akmHealth({
331
+ const base = await akmHealth({
320
332
  since: args.since,
321
333
  groupBy: report ? "run" : groupBy,
322
334
  windowCompare,
323
335
  windows,
336
+ probe: args.probe !== false,
324
337
  });
325
338
  const reportCompare = windowCompare ??
326
339
  (explicitWindows
@@ -945,18 +958,7 @@ async function runCli() {
945
958
  emitJsonError(error);
946
959
  return;
947
960
  }
948
- // `--shape summary` is only meaningful on `akm show`. Reject it up front for
949
- // every other command so a write command (e.g. `akm proposal accept …`)
950
- // fails fast BEFORE performing its mutation, rather than throwing at
951
- // output-shaping time after the side effect has already happened. The
952
- // shape-registry gate in shapeForCommand() remains as defense-in-depth (and
953
- // covers the in-process test harness, which skips this startup block).
954
961
  const commandPath = resolveCittyCommandPath(main, process.argv.slice(2));
955
- const topLevelCommand = commandPath[0] ?? findCittyTopLevelCommand(process.argv.slice(2), MAIN_TOP_LEVEL_ARGS);
956
- if (getOutputMode().shape === "summary" && topLevelCommand !== "show") {
957
- emitJsonError(new UsageError("'--shape summary' is only valid on 'akm show'.", "INVALID_SHAPE_VALUE"));
958
- return;
959
- }
960
962
  // D7 — every command that renders through output() honours all six --format
961
963
  // values. The declared exempt set (src/output/format-exempt.ts) does not
962
964
  // render an envelope at all, so warn rather than pretend: silently ignoring
@@ -32,10 +32,18 @@ function rejectInvalidAgentRef(agentRef) {
32
32
  return;
33
33
  throw new UsageError(`agent expects an agent asset ref under agents/...; received ${JSON.stringify(agentRef)}.`, "INVALID_FLAG_VALUE");
34
34
  }
35
+ /**
36
+ * Dispatch a `--prompt` / `--prompt-stdin` task through the canonical command
37
+ * path. The prompt is a person's free text, not a template: it is sent to the
38
+ * agent verbatim (`inlineContentMode: "literal"`), so prose containing `}}`
39
+ * from compact JSON, a `$VAR`, a shell snippet, or an `@path` reaches the
40
+ * agent instead of being rejected as an unsupported template construct.
41
+ */
35
42
  async function delegateCanonicalCommand(options, seams, action) {
36
43
  const execute = seams.executeCommand ?? executeCommandInvocation;
37
44
  const result = await execute({
38
45
  action,
46
+ inlineContentMode: "literal",
39
47
  config: options.agentConfig,
40
48
  current: canonicalCurrent(options),
41
49
  });
@@ -10,6 +10,7 @@ import { isWithin } from "../../core/common.js";
10
10
  import { loadConfig } from "../../core/config/config.js";
11
11
  import { bundleComponentConfig, bundlesToSourceEntries } from "../../core/config/config-sources.js";
12
12
  import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
13
+ import { warnOnce } from "../../core/warn.js";
13
14
  import { lookupBundleRef } from "../../indexer/indexer.js";
14
15
  import { deriveInstallations } from "../../indexer/installations.js";
15
16
  import { resolveEntryContentDir, resolveSourceEntries } from "../../indexer/search/search-source.js";
@@ -66,10 +67,10 @@ function implicitComponentForEntry(entry, config, realRoot) {
66
67
  continue;
67
68
  const canonicalRoot = realDirectory(component.root, `Canonical implicit source root for ${entry.bundleId}`);
68
69
  if (canonicalRoot !== realRoot) {
69
- throw new ConfigError(`Canonical implicit source root drift for ${JSON.stringify(entry.itemRef)}; the indexed root no longer matches the working bundle source.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing AKM_BUNDLE_DIR or the default bundle directory.");
70
+ warnOnce(`execution-source-root-drift:${entry.itemRef}`, `[command] Indexed root for ${JSON.stringify(entry.itemRef)} no longer matches the working bundle source; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
70
71
  }
71
72
  if (component.adapter !== entry.adapterId) {
72
- throw new ConfigError(`Implicit adapter drift for ${JSON.stringify(entry.itemRef)}: the index records ${JSON.stringify(entry.adapterId)} but the canonical working source selects ${JSON.stringify(component.adapter)}.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing the working source adapter.");
73
+ warnOnce(`execution-source-adapter-drift:${entry.itemRef}`, `[command] Indexed adapter ${JSON.stringify(entry.adapterId)} for ${JSON.stringify(entry.itemRef)} no longer matches the working source's adapter ${JSON.stringify(component.adapter)}; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
73
74
  }
74
75
  return Object.freeze({
75
76
  id: installation.id,
@@ -91,31 +92,33 @@ function componentForEntry(entry, config, realRoot) {
91
92
  const component = bundleComponentConfig(configured);
92
93
  const configuredAdapter = component?.adapter;
93
94
  if (configuredAdapter && configuredAdapter !== entry.adapterId) {
94
- throw new ConfigError(`Configured adapter drift for ${JSON.stringify(entry.itemRef)}: the index records ${JSON.stringify(entry.adapterId)} but the bundle config selects ${JSON.stringify(configuredAdapter)}.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing a bundle adapter.");
95
+ warnOnce(`execution-source-adapter-drift:${entry.itemRef}`, `[command] Indexed adapter ${JSON.stringify(entry.adapterId)} for ${JSON.stringify(entry.itemRef)} no longer matches the bundle config's adapter ${JSON.stringify(configuredAdapter)}; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
95
96
  }
96
97
  const configuredSource = bundlesToSourceEntries(config)?.find((source) => source.name === entry.bundleId);
97
98
  const configuredContentRoot = configuredSource ? resolveEntryContentDir(configuredSource) : undefined;
98
99
  if (!configuredSource || !configuredContentRoot) {
99
- throw new ConfigError(`Configured source for ${JSON.stringify(entry.itemRef)} no longer resolves to materialized content.`, "INVALID_CONFIG_FILE", "Restore or update the bundle, then run `akm index --full` before dispatching this asset.");
100
+ warnOnce(`execution-source-unresolved:${entry.itemRef}`, `[command] Configured source for ${JSON.stringify(entry.itemRef)} no longer resolves to materialized content; dispatching from the indexed location instead. Run \`akm index --full\` after restoring or updating the bundle.`);
100
101
  }
101
- const lexicalSourceRoot = path.resolve(configuredContentRoot);
102
- const lexicalConfiguredRoot = path.resolve(configuredContentRoot, component?.root ?? ".");
103
- if (!isLexicallyWithin(lexicalConfiguredRoot, lexicalSourceRoot)) {
104
- throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Keep component roots inside their owning bundle source, then run `akm index --full`.");
105
- }
106
- const configuredSourceRoot = realDirectory(lexicalSourceRoot, `Configured source root for ${entry.bundleId}`);
107
- let configuredRoot;
108
- try {
109
- configuredRoot = realDirectory(lexicalConfiguredRoot, `Configured component root for ${entry.bundleId}`);
110
- }
111
- catch {
112
- throw new ConfigError(`Configured ${JSON.stringify(configuredSource.type)} source for ${JSON.stringify(entry.itemRef)} is not materialized at its current component root.`, "INVALID_CONFIG_FILE", "Restore or update the bundle, then run `akm index --full` before dispatching this asset.");
113
- }
114
- if (!isWithin(configuredRoot, configuredSourceRoot)) {
115
- throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Remove the escaping symlink or component path, then run `akm index --full`.");
116
- }
117
- if (configuredRoot !== realRoot) {
118
- throw new ConfigError(`Configured source or component root drift for ${JSON.stringify(entry.itemRef)}; the indexed root no longer matches the ${JSON.stringify(configuredSource.type)} bundle source.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing a bundle source or component root.");
102
+ else {
103
+ const lexicalSourceRoot = path.resolve(configuredContentRoot);
104
+ const lexicalConfiguredRoot = path.resolve(configuredContentRoot, component?.root ?? ".");
105
+ if (!isLexicallyWithin(lexicalConfiguredRoot, lexicalSourceRoot)) {
106
+ throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Keep component roots inside their owning bundle source, then run `akm index --full`.");
107
+ }
108
+ const configuredSourceRoot = realDirectory(lexicalSourceRoot, `Configured source root for ${entry.bundleId}`);
109
+ let configuredRoot;
110
+ try {
111
+ configuredRoot = realDirectory(lexicalConfiguredRoot, `Configured component root for ${entry.bundleId}`);
112
+ }
113
+ catch {
114
+ throw new ConfigError(`Configured ${JSON.stringify(configuredSource.type)} source for ${JSON.stringify(entry.itemRef)} is not materialized at its current component root.`, "INVALID_CONFIG_FILE", "Restore or update the bundle, then run `akm index --full` before dispatching this asset.");
115
+ }
116
+ if (!isWithin(configuredRoot, configuredSourceRoot)) {
117
+ throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Remove the escaping symlink or component path, then run `akm index --full`.");
118
+ }
119
+ if (configuredRoot !== realRoot) {
120
+ warnOnce(`execution-source-root-drift:${entry.itemRef}`, `[command] Indexed root for ${JSON.stringify(entry.itemRef)} no longer matches the ${JSON.stringify(configuredSource.type)} bundle source; re-resolving from live config. Run \`akm index --full\` to refresh it.`);
121
+ }
119
122
  }
120
123
  const writable = component?.writable ?? configured.writable ?? configured.path !== undefined;
121
124
  return Object.freeze({
@@ -1,22 +1,9 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { UsageError } from "../../core/errors.js";
4
+ import { warn } from "../../core/warn.js";
5
5
  export const PORTABLE_ARGUMENTS_PLACEHOLDER = "$ARGUMENTS";
6
- const UNSUPPORTED_TEMPLATE_CONSTRUCTS = Object.freeze([
7
- { label: "$ARGUMENTS[N]", pattern: /\$ARGUMENTS\s*\[/u },
8
- { label: "```! ... ```", pattern: /(?:^|\r?\n)[\t ]*```!/u },
9
- { label: "!`...`", pattern: /!`/u },
10
- { label: "@file", pattern: /(?<![A-Za-z0-9._%+-])@(?:\.{0,2}\/)?[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*/u },
11
- { label: "$" + "{...}", pattern: /\$\{/u },
12
- { label: "$(...)", pattern: /\$\(/u },
13
- { label: "$N", pattern: /\$\d/u },
14
- { label: "$NAME", pattern: /\$[A-Za-z_][A-Za-z0-9_]*/u },
15
- { label: "{{...}}", pattern: /\{\{|\}\}/u },
16
- ]);
17
- function unsupportedTemplate(source, label) {
18
- return new UsageError(`Command ${JSON.stringify(source)} uses unsupported portable template construct ${label}.`, "INVALID_FLAG_VALUE", `AKM command execution supports only the literal ${PORTABLE_ARGUMENTS_PLACEHOLDER} placeholder. Invoke native-only templates through their owning tool instead.`);
19
- }
6
+ const INDEXED_ARGUMENTS_PATTERN = /\$ARGUMENTS\s*\[/u;
20
7
  /**
21
8
  * Validate the deliberately small portable command-template language.
22
9
  *
@@ -28,17 +15,8 @@ export function validatePortableCommandTemplate(template, source) {
28
15
  throw new TypeError("command template must be a string");
29
16
  if (typeof source !== "string" || source.length === 0)
30
17
  throw new TypeError("command source must be a string");
31
- // Check native extensions of the portable spelling before masking the one
32
- // supported token. AKM never interprets indexed native placeholders.
33
- const indexedArguments = UNSUPPORTED_TEMPLATE_CONSTRUCTS[0];
34
- if (indexedArguments?.pattern.test(template))
35
- throw unsupportedTemplate(source, indexedArguments.label);
36
- // Mask only the exact portable token. Everything else remains visible to the
37
- // unsupported-construct detectors, including `$ARGUMENTS_SUFFIX`.
38
- const portableMasked = template.replace(/\$ARGUMENTS(?![A-Za-z0-9_])/gu, "");
39
- for (const construct of UNSUPPORTED_TEMPLATE_CONSTRUCTS.slice(1)) {
40
- if (construct.pattern.test(portableMasked))
41
- throw unsupportedTemplate(source, construct.label);
18
+ if (INDEXED_ARGUMENTS_PATTERN.test(template)) {
19
+ warn(`Command ${JSON.stringify(source)} uses "$ARGUMENTS[N]", which akm does not support. Only the literal $ARGUMENTS placeholder is expanded; the indexed form is left as-is.`);
42
20
  }
43
21
  }
44
22
  /**
@@ -111,7 +111,10 @@ export const configCommand = defineGroupCommand({
111
111
  },
112
112
  }),
113
113
  set: defineJsonCommand({
114
- meta: { name: "set", description: "Set a configuration value by key" },
114
+ meta: {
115
+ name: "set",
116
+ description: "Set a configuration value by key; prints the resulting config with ok: true",
117
+ },
115
118
  args: {
116
119
  key: {
117
120
  type: "positional",
@@ -124,7 +127,7 @@ export const configCommand = defineGroupCommand({
124
127
  // writes don't pollute their host's output stream.
125
128
  silent: {
126
129
  type: "boolean",
127
- description: "Suppress the post-write config dump on stdout. Use from hooks and CI scripts; the write still happens and errors still print.",
130
+ description: "Suppress the post-write config dump on stdout entirely (prints nothing; exit code is the status). Use from hooks and CI scripts; the write still happens and errors still print.",
128
131
  default: false,
129
132
  },
130
133
  },
@@ -136,12 +139,15 @@ export const configCommand = defineGroupCommand({
136
139
  },
137
140
  }),
138
141
  unset: defineJsonCommand({
139
- meta: { name: "unset", description: "Unset an optional configuration key or whole embedding/engine section" },
142
+ meta: {
143
+ name: "unset",
144
+ description: "Unset an optional configuration key or whole embedding/engine section; prints the resulting config with ok: true",
145
+ },
140
146
  args: {
141
147
  key: { type: "positional", required: true, description: "Config key to unset" },
142
148
  silent: {
143
149
  type: "boolean",
144
- description: "Suppress the post-write config dump on stdout.",
150
+ description: "Suppress the post-write config dump on stdout entirely (prints nothing; exit code is the status).",
145
151
  default: false,
146
152
  },
147
153
  },
@@ -84,13 +84,20 @@ export function resolveEnvBinding(target, options = {}) {
84
84
  const dangerous = keys.filter(isDangerousEnvKey);
85
85
  if (dangerous.length > 0) {
86
86
  const detail = `Env "${envRef}" injects process-hijacking variable(s): ${dangerous.join(", ")}.`;
87
- const decision = decideDangerousEnvInjection({ dangerousKeys: dangerous, thirdParty: Boolean(source.registryId) });
87
+ const decision = decideDangerousEnvInjection({
88
+ dangerousKeys: dangerous,
89
+ thirdParty: Boolean(source.registryId),
90
+ allowInsecure: options.allowInsecure,
91
+ });
88
92
  if (decision === "block") {
89
93
  throw new UsageError(`Refusing to inject env from a third-party stash. ${detail}\n` +
90
- ` Review the file, then copy the values into a first-party env if you trust them.`, "INVALID_FLAG_VALUE");
94
+ ` Review the file, then copy the values into a first-party env if you trust them, ` +
95
+ `or pass --allow-insecure once you have.`, "INVALID_FLAG_VALUE");
91
96
  }
92
97
  if (decision === "warn") {
93
- warn(`${detail} Injecting anyway (first-party stash).`);
98
+ warn(options.allowInsecure && source.registryId
99
+ ? `${detail} Injecting anyway (--allow-insecure).`
100
+ : `${detail} Injecting anyway (first-party stash).`);
94
101
  }
95
102
  }
96
103
  // Audit trail: keys only, never values.
@@ -255,6 +255,7 @@ async function runEnvInjected(target, opts) {
255
255
  const { values: envValues } = resolveEnvBinding(target, {
256
256
  only: opts.only,
257
257
  except: opts.except,
258
+ allowInsecure: opts.allowInsecure,
258
259
  });
259
260
  const mergedEnv = buildChildEnv(process.env, {
260
261
  clean: opts.clean === true,
@@ -323,6 +324,11 @@ const envRunCommand = defineJsonCommand({
323
324
  type: "string",
324
325
  description: "When used with --clean, also inherit these parent env vars (comma-separated). Ignored without --clean.",
325
326
  },
327
+ "allow-insecure": {
328
+ type: "boolean",
329
+ description: "Allow injecting a process-hijacking variable (e.g. LD_PRELOAD, GIT_SSH_COMMAND) from a third-party stash, which otherwise blocks. Use only after explicitly reviewing the env file.",
330
+ default: false,
331
+ },
326
332
  },
327
333
  async run({ args }) {
328
334
  await runEnvInjected(args.target, {
@@ -330,6 +336,7 @@ const envRunCommand = defineJsonCommand({
330
336
  except: parseKeyListFlag(args.except),
331
337
  clean: args.clean === true,
332
338
  inherit: parseKeyListFlag(args.inherit) ?? [],
339
+ allowInsecure: args["allow-insecure"] === true,
333
340
  });
334
341
  },
335
342
  });
@@ -32,11 +32,13 @@ import path from "node:path";
32
32
  import { getParsedInvocation } from "../../cli/invocation.js";
33
33
  import { getStringArg } from "../../cli/parse-args.js";
34
34
  import { defineGroupCommand, defineJsonCommand, output } from "../../cli/shared.js";
35
+ import { decideDangerousEnvInjection } from "../../core/activation-policy.js";
35
36
  import { deriveCanonicalAssetName } from "../../core/asset/asset-placement.js";
36
37
  import { loadConfig } from "../../core/config/config.js";
37
38
  import { makeSecretRef, resolveSecretPath, resolveSecretWriteTarget, withEnvSecretWrite, } from "../../core/env-secret-ref.js";
38
39
  import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
39
40
  import { appendEvent } from "../../core/events.js";
41
+ import { warn } from "../../core/warn.js";
40
42
  import { resolveSourceEntries } from "../../indexer/search/search-source.js";
41
43
  import { readStdin } from "../../runtime.js";
42
44
  import { buildChildEnv } from "./child-env.js";
@@ -176,10 +178,6 @@ const secretRunCommand = defineJsonCommand({
176
178
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(varName)) {
177
179
  throw new UsageError(`"${varName}" is not a valid environment variable name.`, "INVALID_FLAG_VALUE");
178
180
  }
179
- const { isDangerousEnvKey } = await import("../lint/env-key-rules.js");
180
- if (isDangerousEnvKey(varName)) {
181
- throw new UsageError(`Refusing to inject a secret into "${varName}": it is a known process-hijacking variable (e.g. LD_PRELOAD, PATH).`, "INVALID_FLAG_VALUE");
182
- }
183
181
  const command = getParsedInvocation().passthroughArgs();
184
182
  if (command.length === 0) {
185
183
  throw new UsageError("Missing command. Usage: akm secret run <ref> <VAR> -- <command>");
@@ -188,6 +186,19 @@ const secretRunCommand = defineJsonCommand({
188
186
  if (!fs.existsSync(absPath)) {
189
187
  throw new NotFoundError(`Secret not found: ${makeSecretRef(name, source)}`);
190
188
  }
189
+ const { isDangerousEnvKey } = await import("../lint/env-key-rules.js");
190
+ if (isDangerousEnvKey(varName)) {
191
+ const detail = `"${varName}" is a known process-hijacking variable (e.g. LD_PRELOAD, PATH).`;
192
+ const decision = decideDangerousEnvInjection({
193
+ dangerousKeys: [varName],
194
+ thirdParty: Boolean(source.registryId),
195
+ });
196
+ if (decision === "block") {
197
+ throw new UsageError(`Refusing to inject a secret from a third-party stash into ${detail}\n` +
198
+ " Review the secret, then copy it into a first-party secret if you trust it.", "INVALID_FLAG_VALUE");
199
+ }
200
+ warn(`Injecting a secret into ${detail} Injecting anyway (first-party stash).`);
201
+ }
191
202
  const { readValue } = await import("./secret.js");
192
203
  const mergedEnv = buildChildEnv(process.env, {
193
204
  clean: args.clean === true,