akm-cli 0.9.0-beta.9 → 0.9.0-rc.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/CHANGELOG.md +715 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +11 -3
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -21
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +228 -292
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +156 -155
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +3 -3
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1091
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +1295 -1277
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +228 -605
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +54 -3
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +157 -10
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +28 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2764
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +37 -35
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +206 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +2 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -895
- package/dist/commands/read/curate.js +410 -111
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +19 -39
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +291 -13
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +79 -5
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +403 -62
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/json-schema.js +142 -0
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +18 -18
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +770 -0
- package/dist/core/state-db.js +132 -1126
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +261 -770
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +36 -92
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +18 -11
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +182 -204
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +48 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +41 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +79 -6
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +83 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +206 -81
- package/dist/integrations/harnesses/aider/agent-builder.js +113 -0
- package/dist/integrations/harnesses/aider/index.js +58 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +153 -0
- package/dist/integrations/harnesses/amazonq/index.js +59 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -7
- package/dist/integrations/harnesses/claude/index.js +27 -23
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/claude/session-log.js +10 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +137 -0
- package/dist/integrations/harnesses/codex/index.js +63 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
- package/dist/integrations/harnesses/copilot/index.js +60 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +121 -0
- package/dist/integrations/harnesses/gemini/index.js +60 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +28 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +17 -16
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/harness.js +65 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +10 -34
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +642 -71
- package/dist/integrations/harnesses/openhands/agent-builder.js +126 -0
- package/dist/integrations/harnesses/openhands/index.js +58 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +104 -0
- package/dist/integrations/harnesses/pi/index.js +58 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -0
- package/dist/integrations/session-logs/index.js +40 -11
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +34 -11
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +87 -15
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +6 -1
- package/dist/output/text/helpers.js +241 -2
- package/dist/output/text/workflow.js +4 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/schemas/akm-config.json +14225 -0
- package/dist/schemas/akm-workflow.json +328 -0
- package/dist/scripts/migrate-storage.js +2743 -8390
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +1652 -607
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/repositories/workflow-runs-repository.js +189 -1
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +15 -13
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +5 -0
- package/dist/workflows/db.js +138 -4
- package/dist/workflows/exec/brief.js +484 -0
- package/dist/workflows/exec/native-executor.js +975 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1295 -0
- package/dist/workflows/exec/run-workflow.js +596 -0
- package/dist/workflows/exec/scheduler.js +100 -0
- package/dist/workflows/exec/step-work.js +1156 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +388 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +33 -0
- package/dist/workflows/ir/schema.js +4 -0
- package/dist/workflows/parser.js +3 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +760 -0
- package/dist/workflows/program/project.js +105 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/runs.js +248 -153
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +188 -0
- package/dist/workflows/validate-summary.js +26 -10
- package/dist/workflows/validator.js +1 -1
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +7 -5
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
|
@@ -10,28 +10,28 @@
|
|
|
10
10
|
* copy.
|
|
11
11
|
*
|
|
12
12
|
* `akm env` manages whole `.env` files under each stash's env/ directory.
|
|
13
|
-
* Values are NEVER written to stdout or structured output —
|
|
14
|
-
*
|
|
13
|
+
* Values and comment text are NEVER written to stdout or structured output —
|
|
14
|
+
* only key NAMES are surfaced (comments routinely contain commented-out
|
|
15
|
+
* credentials). akm does not manage individual entries;
|
|
15
16
|
* you edit the `.env` file yourself and akm loads it. Replaced the deprecated
|
|
16
17
|
* `vault` type (removed in 0.9.0).
|
|
17
18
|
*/
|
|
18
19
|
import { spawnSync } from "node:child_process";
|
|
19
20
|
import fs from "node:fs";
|
|
20
21
|
import path from "node:path";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { output, runWithJsonErrors } from "../../cli/shared.js";
|
|
22
|
+
import { getStringArg } from "../../cli/parse-args.js";
|
|
23
|
+
import { defineGroupCommand, defineJsonCommand, output } from "../../cli/shared.js";
|
|
24
24
|
import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "../../core/asset/asset-create.js";
|
|
25
25
|
import { deriveCanonicalAssetName, resolveAssetPathFromName } from "../../core/asset/asset-spec.js";
|
|
26
26
|
import { isWithin, writeFileAtomic } from "../../core/common.js";
|
|
27
27
|
import { loadConfig } from "../../core/config/config.js";
|
|
28
28
|
import { findEnvSource, makeEnvRef, parseEnvRef, resolveEnvPath } from "../../core/env-secret-ref.js";
|
|
29
29
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
30
|
-
import { appendEvent } from "../../core/events.js";
|
|
31
30
|
import { isQuiet } from "../../core/warn.js";
|
|
32
31
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
33
|
-
import {
|
|
32
|
+
import { parseFlagValue } from "../../output/context.js";
|
|
34
33
|
import { readStdin } from "../../runtime.js";
|
|
34
|
+
import { buildChildEnv } from "./child-env.js";
|
|
35
35
|
/**
|
|
36
36
|
* Walk each stash's env files and return one entry per `.env` file, using the
|
|
37
37
|
* env asset spec's canonical-name logic (e.g. `env/team/prod.env` →
|
|
@@ -69,16 +69,14 @@ function listEnvsRecursive(listKeysFn) {
|
|
|
69
69
|
}
|
|
70
70
|
return result;
|
|
71
71
|
}
|
|
72
|
-
const envListCommand =
|
|
72
|
+
const envListCommand = defineJsonCommand({
|
|
73
73
|
meta: { name: "list", description: "List all env files across all stashes with their key names (no values)" },
|
|
74
|
-
run() {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
78
|
-
});
|
|
74
|
+
async run() {
|
|
75
|
+
const { listKeys } = await import("./env.js");
|
|
76
|
+
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
79
77
|
},
|
|
80
78
|
});
|
|
81
|
-
const envCreateCommand =
|
|
79
|
+
const envCreateCommand = defineJsonCommand({
|
|
82
80
|
meta: {
|
|
83
81
|
name: "create",
|
|
84
82
|
description: "Create an env file (empty by default; seed an existing `.env` with --from-file or --from-stdin). No-op if it already exists and no source is given.",
|
|
@@ -101,58 +99,56 @@ const envCreateCommand = defineCommand({
|
|
|
101
99
|
default: false,
|
|
102
100
|
},
|
|
103
101
|
},
|
|
104
|
-
run({ args }) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
async run({ args }) {
|
|
103
|
+
const { createEnv, writeEnv } = await import("./env.js");
|
|
104
|
+
// `create` always targets env/, never the frozen vaults/ copy.
|
|
105
|
+
const parsed = parseEnvRef(args.name);
|
|
106
|
+
// `name` is flat; subdirectory placement is `--path`'s job.
|
|
107
|
+
assertFlatAssetName(parsed.name);
|
|
108
|
+
parsed.name = combineCreatePath(normalizeCreateSubPath(getStringArg(args, "path")), parsed.name);
|
|
109
|
+
const source = findEnvSource(parsed.origin);
|
|
110
|
+
const envRoot = path.join(source.path, "env");
|
|
111
|
+
const absPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
112
|
+
if (!isWithin(absPath, envRoot)) {
|
|
113
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
114
|
+
}
|
|
115
|
+
const fromFile = args["from-file"];
|
|
116
|
+
const fromStdin = args["from-stdin"] === true;
|
|
117
|
+
if (fromFile !== undefined && fromStdin) {
|
|
118
|
+
throw new UsageError("Pass only one of --from-file or --from-stdin.", "INVALID_FLAG_VALUE");
|
|
119
|
+
}
|
|
120
|
+
if (fromFile !== undefined || fromStdin) {
|
|
121
|
+
// Ingest path: never silently clobber an existing env file.
|
|
122
|
+
if (fs.existsSync(absPath)) {
|
|
123
|
+
throw new UsageError(`Env "${makeEnvRef(parsed.name, source)}" already exists. Remove it first (\`akm env remove\`) or edit the file directly.`, "RESOURCE_ALREADY_EXISTS");
|
|
122
124
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (fs.existsSync(
|
|
126
|
-
throw new
|
|
127
|
-
}
|
|
128
|
-
let content;
|
|
129
|
-
if (fromFile !== undefined) {
|
|
130
|
-
if (!fs.existsSync(fromFile)) {
|
|
131
|
-
throw new NotFoundError(`Source file not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
132
|
-
}
|
|
133
|
-
content = fs.readFileSync(fromFile, "utf8");
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
const MAX_ENV_BYTES = 1024 * 1024; // 1 MB
|
|
137
|
-
const buf = await readStdin(MAX_ENV_BYTES, () => new UsageError("Env file exceeds 1 MB limit.", "INVALID_FLAG_VALUE"));
|
|
138
|
-
content = buf.toString("utf8");
|
|
125
|
+
let content;
|
|
126
|
+
if (fromFile !== undefined) {
|
|
127
|
+
if (!fs.existsSync(fromFile)) {
|
|
128
|
+
throw new NotFoundError(`Source file not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
139
129
|
}
|
|
140
|
-
|
|
130
|
+
content = fs.readFileSync(fromFile, "utf8");
|
|
141
131
|
}
|
|
142
132
|
else {
|
|
143
|
-
|
|
133
|
+
const MAX_ENV_BYTES = 1024 * 1024; // 1 MB
|
|
134
|
+
const buf = await readStdin(MAX_ENV_BYTES, () => new UsageError("Env file exceeds 1 MB limit.", "INVALID_FLAG_VALUE"));
|
|
135
|
+
content = buf.toString("utf8");
|
|
144
136
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
137
|
+
writeEnv(absPath, content);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
createEnv(absPath);
|
|
141
|
+
}
|
|
142
|
+
if (args.sensitive) {
|
|
143
|
+
const markerPath = absPath.replace(/\.env$/, ".sensitive");
|
|
144
|
+
if (!fs.existsSync(markerPath)) {
|
|
145
|
+
fs.writeFileSync(markerPath, "", { mode: 0o600 });
|
|
150
146
|
}
|
|
151
|
-
|
|
152
|
-
});
|
|
147
|
+
}
|
|
148
|
+
output("env-create", { ref: makeEnvRef(parsed.name, source) });
|
|
153
149
|
},
|
|
154
150
|
});
|
|
155
|
-
const envPathCommand =
|
|
151
|
+
const envPathCommand = defineJsonCommand({
|
|
156
152
|
meta: {
|
|
157
153
|
name: "path",
|
|
158
154
|
description: "Print the absolute env file path (Docker `_FILE` convention / `--env-file`). To inject values, use `akm env run <ref> -- <cmd>` — do NOT `source` the raw file.",
|
|
@@ -161,24 +157,22 @@ const envPathCommand = defineCommand({
|
|
|
161
157
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
162
158
|
quiet: { type: "boolean", alias: "q", description: "Suppress the unsafe-source warning", default: false },
|
|
163
159
|
},
|
|
164
|
-
run({ args }) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
process.stdout.write(`${absPath}\n`);
|
|
178
|
-
});
|
|
160
|
+
async run({ args }) {
|
|
161
|
+
const { name, absPath, source } = resolveEnvPath(args.ref);
|
|
162
|
+
if (!fs.existsSync(absPath)) {
|
|
163
|
+
throw new NotFoundError(`Env not found: ${makeEnvRef(name, source)}`);
|
|
164
|
+
}
|
|
165
|
+
// The raw `.env` may contain `X=$(cmd)`, which executes if `source`d.
|
|
166
|
+
// Warning goes to stderr (never contaminates the path on stdout) and is
|
|
167
|
+
// suppressed with --quiet for the legitimate `_FILE` / `--env-file` use.
|
|
168
|
+
if (args.quiet !== true) {
|
|
169
|
+
process.stderr.write(`warning: this is the raw file path. Do NOT \`source\` it (shell substitutions in the file would execute).\n` +
|
|
170
|
+
` To inject values run: akm env run ${args.ref} -- <command>\n`);
|
|
171
|
+
}
|
|
172
|
+
process.stdout.write(`${absPath}\n`);
|
|
179
173
|
},
|
|
180
174
|
});
|
|
181
|
-
const envExportCommand =
|
|
175
|
+
const envExportCommand = defineJsonCommand({
|
|
182
176
|
meta: {
|
|
183
177
|
name: "export",
|
|
184
178
|
description: "Write safe `export KEY='value'` lines to a file (mode 0600) for `source`-ing — requires --out <path>. Values are re-serialised single-quoted so a raw `.env` cannot execute on load, and are NEVER printed to stdout. To use values directly, prefer `akm env run <ref> -- <command>`.",
|
|
@@ -187,23 +181,21 @@ const envExportCommand = defineCommand({
|
|
|
187
181
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
188
182
|
out: { type: "string", alias: "o", description: "Destination file (required). Written at mode 0600." },
|
|
189
183
|
},
|
|
190
|
-
run({ args }) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
output("env-export", { ref: makeEnvRef(name, source), out: resolvedOut });
|
|
206
|
-
});
|
|
184
|
+
async run({ args }) {
|
|
185
|
+
const outPath = args.out;
|
|
186
|
+
if (!outPath) {
|
|
187
|
+
throw new UsageError("`akm env export` writes to a file — pass --out <path>.\n" +
|
|
188
|
+
" To use values directly, run `akm env run <ref> -- <command>` (or `-- $SHELL` for an interactive\n" +
|
|
189
|
+
" session). export never prints values to stdout, to avoid leaking them into a captured context.", "MISSING_REQUIRED_ARGUMENT");
|
|
190
|
+
}
|
|
191
|
+
const { name, absPath, source } = resolveEnvPath(args.ref);
|
|
192
|
+
if (!fs.existsSync(absPath)) {
|
|
193
|
+
throw new NotFoundError(`Env not found: ${makeEnvRef(name, source)}`);
|
|
194
|
+
}
|
|
195
|
+
const { buildShellExportScript } = await import("./env.js");
|
|
196
|
+
const resolvedOut = path.resolve(outPath);
|
|
197
|
+
writeFileAtomic(resolvedOut, buildShellExportScript(absPath), 0o600);
|
|
198
|
+
output("env-export", { ref: makeEnvRef(name, source), out: resolvedOut });
|
|
207
199
|
},
|
|
208
200
|
});
|
|
209
201
|
/**
|
|
@@ -239,74 +231,21 @@ async function runEnvInjected(target, opts) {
|
|
|
239
231
|
}
|
|
240
232
|
throw new NotFoundError(`Env not found: ${makeEnvRef(name, source)}`);
|
|
241
233
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
envValues = Object.fromEntries(Object.entries(allValues).filter(([k]) => wanted.has(k)));
|
|
256
|
-
}
|
|
257
|
-
else if (opts.except) {
|
|
258
|
-
const excluded = new Set(opts.except);
|
|
259
|
-
envValues = Object.fromEntries(Object.entries(allValues).filter(([k]) => !excluded.has(k)));
|
|
260
|
-
}
|
|
261
|
-
// Substitute `${secret:NAME}` tokens in values with the value of the sibling
|
|
262
|
-
// secret asset in the SAME stash. The lookup is injected so commands/env.ts
|
|
263
|
-
// keeps its narrow dependency surface; we resolve each name against this env's
|
|
264
|
-
// own `source`. A missing secret is a hard error — inject NOTHING (no partial
|
|
265
|
-
// injection). Resolved values are never logged or printed.
|
|
266
|
-
const { resolveSecretTokens } = await import("./env.js");
|
|
267
|
-
const { readValue } = await import("./secret.js");
|
|
268
|
-
const secretsRoot = path.join(source.path, "secrets");
|
|
269
|
-
const resolveSecret = (secretName) => {
|
|
270
|
-
const secretPath = resolveAssetPathFromName("secret", secretsRoot, secretName);
|
|
271
|
-
// Defense-in-depth: ensure the resolved path stays inside the secrets dir.
|
|
272
|
-
if (!isWithin(secretPath, secretsRoot)) {
|
|
273
|
-
throw new UsageError(`Secret name "${secretName}" escapes the secrets directory.`);
|
|
274
|
-
}
|
|
275
|
-
if (!fs.existsSync(secretPath))
|
|
276
|
-
return undefined;
|
|
277
|
-
// Match `secret run`: read utf8, do not trim (stay consistent with that path).
|
|
278
|
-
return readValue(secretPath).toString("utf8");
|
|
279
|
-
};
|
|
280
|
-
const { values: substituted, missing } = resolveSecretTokens(envValues, resolveSecret);
|
|
281
|
-
if (missing.length > 0) {
|
|
282
|
-
const envRef = makeEnvRef(name, source);
|
|
283
|
-
throw new NotFoundError(`Env "${envRef}" references secret(s) not found in its stash: ${missing.map((n) => `secret:${n}`).join(", ")}. Nothing was injected.`, "FILE_NOT_FOUND", `Create the missing secret, e.g. \`akm secret set secret:${missing[0]}\`.`);
|
|
284
|
-
}
|
|
285
|
-
envValues = substituted;
|
|
286
|
-
const keys = Object.keys(envValues);
|
|
287
|
-
// Scan injected keys for known process-hijacking variables (LD_PRELOAD,
|
|
288
|
-
// PATH, ...). Block for third-party-sourced stashes (origin has a registryId);
|
|
289
|
-
// warn for the operator's own first-party stash, where they own the file.
|
|
290
|
-
const { isDangerousEnvKey } = await import("../lint/env-key-rules.js");
|
|
291
|
-
const dangerous = keys.filter(isDangerousEnvKey);
|
|
292
|
-
if (dangerous.length > 0) {
|
|
293
|
-
const detail = `Env "${makeEnvRef(name, source)}" injects process-hijacking variable(s): ${dangerous.join(", ")}.`;
|
|
294
|
-
if (source.registryId) {
|
|
295
|
-
throw new UsageError(`Refusing to inject env from a third-party stash. ${detail}\n` +
|
|
296
|
-
` Review the file, then copy the values into a first-party env if you trust them.`, "INVALID_FLAG_VALUE");
|
|
297
|
-
}
|
|
298
|
-
process.stderr.write(`warning: ${detail} Injecting anyway (first-party stash).\n`);
|
|
299
|
-
}
|
|
300
|
-
const mergedEnv = { ...process.env };
|
|
234
|
+
// Load → filter → secret-substitute → dangerous-key policy → keys-only
|
|
235
|
+
// audit event. Shared with the workflow engine's per-unit env bindings —
|
|
236
|
+
// see env-binding.ts for the extracted core and its safety invariants.
|
|
237
|
+
const { resolveEnvBinding } = await import("./env-binding.js");
|
|
238
|
+
const { values: envValues } = resolveEnvBinding(target, {
|
|
239
|
+
only: opts.only,
|
|
240
|
+
except: opts.except,
|
|
241
|
+
});
|
|
242
|
+
const mergedEnv = buildChildEnv(process.env, {
|
|
243
|
+
clean: opts.clean === true,
|
|
244
|
+
inherit: opts.inherit ?? [],
|
|
245
|
+
});
|
|
301
246
|
for (const [envKey, envValue] of Object.entries(envValues)) {
|
|
302
247
|
mergedEnv[envKey] = envValue;
|
|
303
248
|
}
|
|
304
|
-
// Audit trail: keys only, never values.
|
|
305
|
-
appendEvent({
|
|
306
|
-
eventType: "env_access",
|
|
307
|
-
ref: makeEnvRef(name, source),
|
|
308
|
-
metadata: { keys },
|
|
309
|
-
});
|
|
310
249
|
const result = spawnSync(command[0], command.slice(1), {
|
|
311
250
|
stdio: "inherit",
|
|
312
251
|
env: mergedEnv,
|
|
@@ -336,12 +275,12 @@ function parseKeyListFlag(raw) {
|
|
|
336
275
|
.filter(Boolean);
|
|
337
276
|
return keys.length > 0 ? keys : undefined;
|
|
338
277
|
}
|
|
339
|
-
const envRunCommand =
|
|
278
|
+
const envRunCommand = defineJsonCommand({
|
|
340
279
|
meta: {
|
|
341
280
|
name: "run",
|
|
342
281
|
description:
|
|
343
282
|
// biome-ignore lint/suspicious/noTemplateCurlyInString: literal `${secret:NAME}` token syntax documented for users, not interpolation
|
|
344
|
-
"Run a command with the env file injected into its environment: `akm env run <ref> -- <command>`. Use `-- $SHELL` for an interactive session. Restrict which variables are injected with --only / --except. Values may embed `${secret:NAME}` tokens, replaced at run time with the sibling `secret:NAME` value from the same stash.",
|
|
283
|
+
"Run a command with the env file injected into its environment: `akm env run <ref> -- <command>`. Use `-- $SHELL` for an interactive session. Restrict which variables are injected with --only / --except. Values may embed `${secret:NAME}` tokens, replaced at run time with the sibling `secret:NAME` value from the same stash. Pass --clean to start the child with a minimal inherited environment instead of the full parent environment.",
|
|
345
284
|
},
|
|
346
285
|
args: {
|
|
347
286
|
target: { type: "positional", description: "Env ref", required: true },
|
|
@@ -350,47 +289,56 @@ const envRunCommand = defineCommand({
|
|
|
350
289
|
description: "Inject ONLY these keys (comma-separated). Mutually exclusive with --except.",
|
|
351
290
|
},
|
|
352
291
|
except: { type: "string", description: "Inject all keys EXCEPT these (comma-separated)." },
|
|
292
|
+
clean: {
|
|
293
|
+
type: "boolean",
|
|
294
|
+
description: "Start the child with a minimal inherited environment (PATH/HOME/locale/terminal basics) instead of the full parent environment.",
|
|
295
|
+
default: false,
|
|
296
|
+
},
|
|
297
|
+
inherit: {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: "When used with --clean, also inherit these parent env vars (comma-separated). Ignored without --clean.",
|
|
300
|
+
},
|
|
353
301
|
},
|
|
354
|
-
run({ args }) {
|
|
355
|
-
|
|
356
|
-
only: parseKeyListFlag(
|
|
357
|
-
except: parseKeyListFlag(
|
|
358
|
-
|
|
302
|
+
async run({ args }) {
|
|
303
|
+
await runEnvInjected(args.target, {
|
|
304
|
+
only: parseKeyListFlag(args.only),
|
|
305
|
+
except: parseKeyListFlag(args.except),
|
|
306
|
+
clean: args.clean === true,
|
|
307
|
+
inherit: parseKeyListFlag(args.inherit) ?? [],
|
|
308
|
+
});
|
|
359
309
|
},
|
|
360
310
|
});
|
|
361
|
-
const envRemoveCommand =
|
|
311
|
+
const envRemoveCommand = defineJsonCommand({
|
|
362
312
|
meta: { name: "remove", description: "Remove an env file (and its .sensitive marker, if any)" },
|
|
363
313
|
args: {
|
|
364
314
|
ref: { type: "positional", description: "Env ref", required: true },
|
|
365
315
|
yes: { type: "boolean", alias: "y", description: "Skip confirmation prompt", default: false },
|
|
366
316
|
},
|
|
367
|
-
run({ args }) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
yes: args.yes === true,
|
|
379
|
-
});
|
|
380
|
-
if (!confirmed) {
|
|
381
|
-
process.stderr.write("Aborted.\n");
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
if (!fs.existsSync(absPath)) {
|
|
385
|
-
throw new NotFoundError(`Env not found: ${makeEnvRef(parsed.name, source)}`);
|
|
386
|
-
}
|
|
387
|
-
const { removeEnv } = await import("./env.js");
|
|
388
|
-
const removed = removeEnv(absPath);
|
|
389
|
-
output("env-remove", { ref: makeEnvRef(parsed.name, source), removed });
|
|
317
|
+
async run({ args }) {
|
|
318
|
+
const parsed = parseEnvRef(args.ref);
|
|
319
|
+
const source = findEnvSource(parsed.origin);
|
|
320
|
+
const envRoot = path.join(source.path, "env");
|
|
321
|
+
const absPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
322
|
+
if (!isWithin(absPath, envRoot)) {
|
|
323
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
324
|
+
}
|
|
325
|
+
const { confirmDestructive } = await import("../../cli/confirm.js");
|
|
326
|
+
const confirmed = await confirmDestructive(`Remove env "${args.ref}"? This cannot be undone.`, {
|
|
327
|
+
yes: args.yes === true,
|
|
390
328
|
});
|
|
329
|
+
if (!confirmed) {
|
|
330
|
+
process.stderr.write("Aborted.\n");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!fs.existsSync(absPath)) {
|
|
334
|
+
throw new NotFoundError(`Env not found: ${makeEnvRef(parsed.name, source)}`);
|
|
335
|
+
}
|
|
336
|
+
const { removeEnv } = await import("./env.js");
|
|
337
|
+
const removed = removeEnv(absPath);
|
|
338
|
+
output("env-remove", { ref: makeEnvRef(parsed.name, source), removed });
|
|
391
339
|
},
|
|
392
340
|
});
|
|
393
|
-
const envSetCommand =
|
|
341
|
+
const envSetCommand = defineJsonCommand({
|
|
394
342
|
meta: {
|
|
395
343
|
name: "set",
|
|
396
344
|
description: "Set (create or update) a single KEY in an env file: `akm env set <ref> <KEY>`. The value is read from stdin by default (never via argv); use --from-env <VAR> or --from-file <path>. Preserves existing comments and key order; the value is never printed. Creates the env file if it does not exist.",
|
|
@@ -401,59 +349,57 @@ const envSetCommand = defineCommand({
|
|
|
401
349
|
"from-env": { type: "string", description: "Read the value from the named environment variable" },
|
|
402
350
|
"from-file": { type: "string", description: "Read the value from this file" },
|
|
403
351
|
},
|
|
404
|
-
run({ args }) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (fromFile
|
|
426
|
-
|
|
427
|
-
throw new NotFoundError(`File not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
428
|
-
}
|
|
429
|
-
const buf = fs.readFileSync(fromFile);
|
|
430
|
-
if (buf.byteLength > MAX_ENV_VALUE_BYTES)
|
|
431
|
-
throw new UsageError("Value exceeds the 1 MB limit.");
|
|
432
|
-
value = buf.toString("utf8");
|
|
433
|
-
}
|
|
434
|
-
else if (fromEnv !== undefined) {
|
|
435
|
-
const v = process.env[fromEnv];
|
|
436
|
-
if (v === undefined) {
|
|
437
|
-
throw new UsageError(`Environment variable "${fromEnv}" is not set.`, "INVALID_FLAG_VALUE");
|
|
438
|
-
}
|
|
439
|
-
value = v;
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
const buf = await readStdin(MAX_ENV_VALUE_BYTES, () => new UsageError("Value exceeds the 1 MB limit."));
|
|
443
|
-
// Strip a single trailing newline so `echo "$VAL" | akm env set` is exact.
|
|
444
|
-
value = buf.toString("utf8").replace(/\n$/, "");
|
|
352
|
+
async run({ args }) {
|
|
353
|
+
const parsed = parseEnvRef(args.ref);
|
|
354
|
+
const source = findEnvSource(parsed.origin);
|
|
355
|
+
const envRoot = path.join(source.path, "env");
|
|
356
|
+
const absPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
357
|
+
if (!isWithin(absPath, envRoot)) {
|
|
358
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
359
|
+
}
|
|
360
|
+
const key = String(args.key);
|
|
361
|
+
const { ENV_KEY_RE, setEnvKey } = await import("./env.js");
|
|
362
|
+
if (!ENV_KEY_RE.test(key)) {
|
|
363
|
+
throw new UsageError(`Invalid env key "${key}". Keys match [A-Za-z_][A-Za-z0-9_]*.`, "INVALID_FLAG_VALUE");
|
|
364
|
+
}
|
|
365
|
+
const fromEnv = args["from-env"];
|
|
366
|
+
const fromFile = args["from-file"];
|
|
367
|
+
if (fromEnv !== undefined && fromFile !== undefined) {
|
|
368
|
+
throw new UsageError("Pass only one of --from-file or --from-env (or use stdin).", "INVALID_FLAG_VALUE");
|
|
369
|
+
}
|
|
370
|
+
const MAX_ENV_VALUE_BYTES = 1024 * 1024; // 1 MB
|
|
371
|
+
let value;
|
|
372
|
+
if (fromFile !== undefined) {
|
|
373
|
+
if (!fs.existsSync(fromFile)) {
|
|
374
|
+
throw new NotFoundError(`File not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
445
375
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
376
|
+
const buf = fs.readFileSync(fromFile);
|
|
377
|
+
if (buf.byteLength > MAX_ENV_VALUE_BYTES)
|
|
378
|
+
throw new UsageError("Value exceeds the 1 MB limit.");
|
|
379
|
+
value = buf.toString("utf8");
|
|
380
|
+
}
|
|
381
|
+
else if (fromEnv !== undefined) {
|
|
382
|
+
const v = process.env[fromEnv];
|
|
383
|
+
if (v === undefined) {
|
|
384
|
+
throw new UsageError(`Environment variable "${fromEnv}" is not set.`, "INVALID_FLAG_VALUE");
|
|
451
385
|
}
|
|
452
|
-
|
|
453
|
-
}
|
|
386
|
+
value = v;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
const buf = await readStdin(MAX_ENV_VALUE_BYTES, () => new UsageError("Value exceeds the 1 MB limit."));
|
|
390
|
+
// Strip a single trailing newline so `echo "$VAL" | akm env set` is exact.
|
|
391
|
+
value = buf.toString("utf8").replace(/\n$/, "");
|
|
392
|
+
}
|
|
393
|
+
setEnvKey(absPath, key, value);
|
|
394
|
+
// Warn (never block) on process-hijacking key names, matching the env-run audit.
|
|
395
|
+
const { isDangerousEnvKey } = await import("../lint/env-key-rules.js");
|
|
396
|
+
if (isDangerousEnvKey(key) && !isQuiet()) {
|
|
397
|
+
process.stderr.write(`warning: "${key}" can influence process execution when this env is loaded via 'akm env run'.\n`);
|
|
398
|
+
}
|
|
399
|
+
output("env-set", { ref: makeEnvRef(parsed.name, source), key });
|
|
454
400
|
},
|
|
455
401
|
});
|
|
456
|
-
const envUnsetCommand =
|
|
402
|
+
const envUnsetCommand = defineJsonCommand({
|
|
457
403
|
meta: {
|
|
458
404
|
name: "unset",
|
|
459
405
|
description: "Remove one or more KEYs from an env file: `akm env unset <ref> <KEY...>`. Preserves other keys and comments. To remove the whole file, use `akm env remove`.",
|
|
@@ -464,66 +410,56 @@ const envUnsetCommand = defineCommand({
|
|
|
464
410
|
// non-required so citty doesn't block before we emit a structured error.
|
|
465
411
|
key: { type: "positional", description: "Key name(s) to remove (one or more)", required: false },
|
|
466
412
|
},
|
|
467
|
-
run({ args }) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
output("env-unset", { ref: makeEnvRef(parsed.name, source), removed, missing });
|
|
499
|
-
});
|
|
413
|
+
async run({ args }) {
|
|
414
|
+
const parsed = parseEnvRef(args.ref);
|
|
415
|
+
const source = findEnvSource(parsed.origin);
|
|
416
|
+
const envRoot = path.join(source.path, "env");
|
|
417
|
+
const absPath = resolveAssetPathFromName("env", envRoot, parsed.name);
|
|
418
|
+
if (!isWithin(absPath, envRoot)) {
|
|
419
|
+
throw new UsageError(`Env name "${parsed.name}" escapes the env directory.`);
|
|
420
|
+
}
|
|
421
|
+
if (!fs.existsSync(absPath)) {
|
|
422
|
+
throw new NotFoundError(`Env not found: ${makeEnvRef(parsed.name, source)}`);
|
|
423
|
+
}
|
|
424
|
+
// citty puts every positional in `args._` (incl. the ref at [0]); the keys
|
|
425
|
+
// are the remaining positionals. citty also mis-captures the space-separated
|
|
426
|
+
// value of a global flag (`--format json`) as a positional, so drop any
|
|
427
|
+
// token that is actually a global flag's value (cli.ts:1335 documents this).
|
|
428
|
+
const globalFlagValues = new Set(["--format", "--shape", "--detail", "--scope", "--filter", "--target"]
|
|
429
|
+
.map((flag) => parseFlagValue(process.argv, flag))
|
|
430
|
+
.filter((v) => typeof v === "string"));
|
|
431
|
+
const keys = (Array.isArray(args._) ? args._.map(String) : [])
|
|
432
|
+
.slice(1)
|
|
433
|
+
.filter((k) => !globalFlagValues.has(k));
|
|
434
|
+
if (keys.length === 0) {
|
|
435
|
+
throw new UsageError("Usage: akm env unset <ref> <KEY...> (one or more keys).", "MISSING_REQUIRED_ARGUMENT");
|
|
436
|
+
}
|
|
437
|
+
const { ENV_KEY_RE, unsetEnvKeys } = await import("./env.js");
|
|
438
|
+
const invalid = keys.filter((k) => !ENV_KEY_RE.test(k));
|
|
439
|
+
if (invalid.length > 0) {
|
|
440
|
+
throw new UsageError(`Invalid env key(s): ${invalid.join(", ")}.`, "INVALID_FLAG_VALUE");
|
|
441
|
+
}
|
|
442
|
+
const { removed, missing } = unsetEnvKeys(absPath, keys);
|
|
443
|
+
output("env-unset", { ref: makeEnvRef(parsed.name, source), removed, missing });
|
|
500
444
|
},
|
|
501
445
|
});
|
|
502
|
-
|
|
503
|
-
// (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
|
|
504
|
-
const envSubCommands = {
|
|
505
|
-
list: envListCommand,
|
|
506
|
-
path: envPathCommand,
|
|
507
|
-
export: envExportCommand,
|
|
508
|
-
run: envRunCommand,
|
|
509
|
-
create: envCreateCommand,
|
|
510
|
-
set: envSetCommand,
|
|
511
|
-
unset: envUnsetCommand,
|
|
512
|
-
remove: envRemoveCommand,
|
|
513
|
-
};
|
|
514
|
-
const ENV_SUBCOMMAND_SET = new Set(Object.keys(envSubCommands));
|
|
515
|
-
export const envCommand = defineCommand({
|
|
446
|
+
export const envCommand = defineGroupCommand({
|
|
516
447
|
meta: {
|
|
517
448
|
name: "env",
|
|
518
449
|
description: "Manage `.env` files — a group of related CONFIGURATION values for an app or service (URLs, flags, plus any credentials it needs), loaded together. Values may or may not be sensitive; akm protects them all the same (key names visible, values never in structured output). For a single sensitive value used on its own (an auth token, key, or cert), use `akm secret`.",
|
|
519
450
|
},
|
|
520
|
-
subCommands:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
451
|
+
subCommands: {
|
|
452
|
+
list: envListCommand,
|
|
453
|
+
path: envPathCommand,
|
|
454
|
+
export: envExportCommand,
|
|
455
|
+
run: envRunCommand,
|
|
456
|
+
create: envCreateCommand,
|
|
457
|
+
set: envSetCommand,
|
|
458
|
+
unset: envUnsetCommand,
|
|
459
|
+
remove: envRemoveCommand,
|
|
460
|
+
},
|
|
461
|
+
async defaultRun() {
|
|
462
|
+
const { listKeys } = await import("./env.js");
|
|
463
|
+
output("env-list", { envs: listEnvsRecursive(listKeys) });
|
|
528
464
|
},
|
|
529
465
|
});
|