akm-cli 0.9.0-rc.8 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1063 -44
- package/README.md +51 -25
- package/SECURITY.md +14 -1
- package/STABILITY.md +497 -0
- package/dist/akm +148 -35
- package/dist/{akm-migrate-storage → akm-migrate} +6 -9
- package/dist/assets/hints/cli-hints-full.md +223 -95
- package/dist/assets/hints/cli-hints-short.md +85 -22
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/prompts/memory-infer-user.md +2 -3
- package/dist/assets/stash-skeleton/README.md +6 -5
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
- package/dist/assets/tasks/core/extract.yml +1 -1
- package/dist/assets/tasks/core/version-check.yml +1 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
- package/dist/assets/templates/html/health.html +1 -3
- package/dist/assets/workflows/workflow-template.md +32 -15
- package/dist/cli/invocation.js +40 -15
- package/dist/cli/parse-args.js +0 -22
- package/dist/cli/retired-commands.js +121 -0
- package/dist/cli/shared.js +154 -22
- package/dist/cli/unknown-flags.js +236 -0
- package/dist/cli-node.mjs +2 -1
- package/dist/cli.js +696 -258
- package/dist/commands/agent/agent-dispatch.js +14 -3
- package/dist/commands/agent/contribute-cli.js +73 -88
- package/dist/commands/completions.js +79 -22
- package/dist/commands/config-cli.js +17 -150
- package/dist/commands/env/env-cli.js +59 -143
- package/dist/commands/env/env.js +12 -163
- package/dist/commands/env/marker-path.js +6 -0
- package/dist/commands/env/secret-cli.js +36 -66
- package/dist/commands/env/secret.js +24 -57
- package/dist/commands/feedback-cli.js +141 -87
- package/dist/commands/health/accept-rate.js +58 -0
- package/dist/commands/health/advisories.js +3 -4
- package/dist/commands/health/checks.js +85 -23
- package/dist/commands/health/html-report.js +7 -10
- package/dist/commands/health/improve-metrics.js +25 -83
- package/dist/commands/health/md-report.js +5 -9
- package/dist/commands/health/metrics.js +62 -20
- package/dist/commands/health/renderers.js +47 -0
- package/dist/commands/health/report-view-model.js +4 -5
- package/dist/commands/health/stash-exposure.js +1 -1
- package/dist/commands/health/surfaces.js +3 -48
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/commands/health/types-improve.js +7 -0
- package/dist/commands/health.js +99 -28
- package/dist/commands/improve/anti-collapse.js +2 -2
- package/dist/commands/improve/autonomy-gate.js +68 -0
- package/dist/commands/improve/collapse-detector.js +41 -40
- package/dist/commands/improve/consolidate/eligibility.js +1 -23
- package/dist/commands/improve/consolidate/merge.js +4 -0
- package/dist/commands/improve/consolidate.js +140 -1000
- package/dist/commands/improve/distill/promote-memory.js +12 -12
- package/dist/commands/improve/distill/quality-gate.js +6 -6
- package/dist/commands/improve/distill.js +58 -69
- package/dist/commands/improve/eligibility.js +105 -57
- package/dist/commands/improve/extract-cli.js +14 -133
- package/dist/commands/improve/improve-cli.js +98 -114
- package/dist/commands/improve/improve-result-file.js +1 -28
- package/dist/commands/improve/improve-strategies.js +8 -5
- package/dist/commands/improve/improve.js +128 -91
- package/dist/commands/improve/loop-stages.js +182 -20
- package/dist/commands/improve/memory/derived-ref.js +45 -43
- package/dist/commands/improve/memory/memory-belief.js +1 -1
- package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
- package/dist/commands/improve/memory/memory-improve.js +6 -5
- package/dist/commands/improve/outcome-loop.js +22 -65
- package/dist/commands/improve/preparation.js +114 -123
- package/dist/commands/improve/proactive-maintenance.js +2 -5
- package/dist/commands/improve/reflect.js +56 -160
- package/dist/commands/improve/salience.js +11 -122
- package/dist/commands/improve/source-identity.js +10 -38
- package/dist/commands/lint/base-linter.js +20 -124
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +249 -43
- package/dist/commands/{events.js → log.js} +33 -38
- package/dist/commands/migrate-cli.js +92 -12
- package/dist/commands/migration-tool.js +46 -0
- package/dist/commands/observability-cli.js +70 -209
- package/dist/commands/proposal/drain.js +101 -29
- package/dist/commands/proposal/proposal-cli.js +76 -48
- package/dist/commands/proposal/proposal.js +54 -18
- package/dist/commands/proposal/propose-cli.js +88 -0
- package/dist/commands/proposal/propose.js +23 -15
- package/dist/commands/proposal/repository.js +701 -278
- package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
- package/dist/commands/proposal/validators/proposal-validators.js +55 -7
- package/dist/commands/proposal/validators/proposals.js +4 -7
- package/dist/commands/read/curate.js +34 -53
- package/dist/commands/read/knowledge.js +150 -95
- package/dist/commands/read/registry-search.js +2 -2
- package/dist/commands/read/remember-cli.js +42 -15
- package/dist/commands/read/search-cli.js +180 -78
- package/dist/commands/read/search.js +58 -43
- package/dist/commands/read/show.js +197 -141
- package/dist/commands/registry-cli.js +12 -51
- package/dist/commands/remember.js +14 -57
- package/dist/commands/sources/add-cli.js +100 -31
- package/dist/commands/sources/bundle-cli.js +166 -0
- package/dist/commands/sources/bundle-config-ops.js +7 -2
- package/dist/commands/sources/info.js +18 -5
- package/dist/commands/sources/init.js +12 -12
- package/dist/commands/sources/installed-stashes.js +382 -98
- package/dist/commands/sources/schema-repair.js +3 -2
- package/dist/commands/sources/self-update.js +131 -38
- package/dist/commands/sources/source-add.js +72 -17
- package/dist/commands/sources/source-clone.js +129 -45
- package/dist/commands/sources/source-manage.js +43 -23
- package/dist/commands/sources/sources-cli.js +57 -208
- package/dist/commands/sources/stash-cli.js +46 -53
- package/dist/commands/tasks/tasks-cli.js +91 -97
- package/dist/commands/tasks/tasks.js +276 -421
- package/dist/commands/workflow-cli.js +175 -450
- package/dist/core/adapter/adapters/akm-adapter.js +47 -28
- package/dist/core/adapter/adapters/akm-lint.js +42 -27
- package/dist/core/adapter/adapters/akm-metadata.js +15 -44
- package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
- package/dist/core/adapter/adapters/index.js +6 -6
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
- package/dist/core/adapter/adapters/okf-adapter.js +187 -19
- package/dist/core/adapter/adapters/shared.js +3 -19
- package/dist/core/adapter/adapters/tool-dir-shared.js +8 -3
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
- package/dist/core/adapter/detect-adapter.js +17 -0
- package/dist/core/adapter/recognize-match.js +6 -4
- package/dist/core/adapter/validate-context.js +214 -0
- package/dist/core/asset/akm-markdown.js +63 -0
- package/dist/core/asset/asset-placement.js +20 -6
- package/dist/core/asset/asset-ref.js +11 -9
- package/dist/core/asset/frontmatter-lint.js +30 -0
- package/dist/core/asset/frontmatter.js +37 -9
- package/dist/core/asset/markdown.js +40 -51
- package/dist/core/asset/resolve-ref.js +89 -18
- package/dist/core/asset/stash-meta.js +1 -1
- package/dist/core/bundle-id.js +51 -0
- package/dist/core/common.js +152 -38
- package/dist/core/config/config-io.js +12 -1
- package/dist/core/config/config-schema.js +35 -8
- package/dist/core/config/config-sources.js +55 -11
- package/dist/core/config/config-walker.js +25 -9
- package/dist/core/config/config.js +9 -48
- package/dist/core/config/experimental.js +21 -0
- package/dist/core/config/schema/embedding.js +5 -1
- package/dist/core/config/schema/experimental.js +30 -0
- package/dist/core/config/schema/improve-processes.js +0 -6
- package/dist/core/config/schema/improve.js +21 -3
- package/dist/core/config/schema/index-config.js +8 -15
- package/dist/core/config/schema/output.js +4 -1
- package/dist/core/config/schema/setup.js +9 -18
- package/dist/core/config/schema/sources-bundles.js +49 -33
- package/dist/core/config/schema/workflow.js +3 -3
- package/dist/core/env-secret-ref.js +76 -46
- package/dist/core/errors.js +18 -12
- package/dist/core/events.js +46 -128
- package/dist/core/file-change.js +6 -5
- package/dist/core/fs-txn.js +83 -7
- package/dist/core/git-message.js +2 -2
- package/dist/core/improve-result.js +1 -100
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +2 -1
- package/dist/core/migration-operation.js +16 -0
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/parse.js +4 -1
- package/dist/core/paths.js +17 -20
- package/dist/core/recognition-util.js +12 -14
- package/dist/core/redaction.js +34 -0
- package/dist/core/standards/resolve-standards-context.js +2 -14
- package/dist/core/standards/resolve-stash-standards.js +2 -2
- package/dist/core/standards/resolve-type-conventions.js +2 -2
- package/dist/core/state/migrations.js +41 -18
- package/dist/core/state-db.js +5 -14
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +6 -4
- package/dist/core/text-truncation.js +9 -5
- package/dist/core/type-presentation.js +3 -3
- package/dist/core/warn.js +0 -3
- package/dist/core/write-source.js +771 -95
- package/dist/indexer/bundle-identity-guard.js +3 -2
- package/dist/indexer/db/graph-db.js +0 -24
- package/dist/indexer/ensure-index.js +1 -0
- package/dist/indexer/graph/graph-boost.js +9 -34
- package/dist/indexer/graph/graph-extraction.js +8 -5
- package/dist/indexer/index-writer-lock.js +53 -17
- package/dist/indexer/index-written-assets.js +16 -22
- package/dist/indexer/indexer.js +497 -239
- package/dist/indexer/installations.js +14 -96
- package/dist/indexer/passes/dir-staleness.js +16 -9
- package/dist/indexer/passes/memory-inference.js +11 -9
- package/dist/indexer/passes/metadata.js +113 -47
- package/dist/indexer/scan/doc-to-entry.js +38 -1
- package/dist/indexer/scan/drain-dir.js +13 -23
- package/dist/indexer/search/db-search.js +99 -54
- package/dist/indexer/search/fts-query.js +47 -24
- package/dist/indexer/search/ranking-contributors.js +42 -20
- package/dist/indexer/search/ranking.js +18 -99
- package/dist/indexer/search/search-fields.js +7 -2
- package/dist/indexer/search/search-source.js +82 -93
- package/dist/indexer/usage/usage-events.js +0 -89
- package/dist/indexer/walk/file-context.js +2 -1
- package/dist/indexer/walk/matchers.js +30 -43
- package/dist/indexer/walk/path-resolver.js +7 -2
- package/dist/indexer/walk/walker.js +38 -12
- package/dist/integrations/agent/builders.js +0 -6
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/engine-fallback.js +76 -0
- package/dist/integrations/agent/profiles.js +14 -0
- package/dist/integrations/agent/prompts.js +12 -8
- package/dist/integrations/agent/runner-dispatch.js +4 -2
- package/dist/integrations/agent/runner.js +0 -1
- package/dist/integrations/agent/spawn.js +5 -6
- package/dist/integrations/github.js +1 -1
- package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
- package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
- package/dist/integrations/harnesses/claude/session-log.js +0 -10
- package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
- package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
- package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
- package/dist/integrations/harnesses/index.js +3 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
- package/dist/integrations/harnesses/opencode/session-log.js +0 -15
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
- package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
- package/dist/integrations/lockfile.js +101 -6
- package/dist/integrations/session-logs/index.js +3 -28
- package/dist/llm/client.js +136 -100
- package/dist/llm/embedders/remote.js +13 -5
- package/dist/llm/feature-gate.js +4 -12
- package/dist/llm/graph-extract.js +5 -11
- package/dist/llm/memory-infer.js +144 -1
- package/dist/llm/metadata-enhance.js +5 -7
- package/dist/llm/structured-call.js +1 -1
- package/dist/llm/usage-persist.js +26 -5
- package/dist/llm/usage-telemetry.js +25 -2
- package/dist/output/cli-hints.js +1 -2
- package/dist/output/context.js +22 -7
- package/dist/output/format-exempt.js +80 -0
- package/dist/output/generic-render.js +259 -0
- package/dist/output/render-registry.js +57 -0
- package/dist/output/renderers.js +14 -36
- package/dist/output/shapes/curate.js +10 -1
- package/dist/output/shapes/events.js +12 -7
- package/dist/output/shapes/helpers.js +56 -83
- package/dist/output/shapes/migrate.js +8 -0
- package/dist/output/shapes/passthrough.js +7 -41
- package/dist/output/shapes/proposal/producer.js +15 -7
- package/dist/output/shapes.js +2 -9
- package/dist/output/text/{init.js → bundle-create.js} +3 -1
- package/dist/output/text/bundle-show.js +7 -0
- package/dist/output/text/command-format.js +164 -96
- package/dist/output/text/env.js +1 -3
- package/dist/output/text/events.js +8 -7
- package/dist/output/text/health-format.js +103 -0
- package/dist/output/text/health.js +7 -0
- package/dist/output/text/helpers.js +10 -8
- package/dist/output/text/lint-format.js +43 -0
- package/dist/output/text/{save.js → lint.js} +2 -2
- package/dist/output/text/migrate.js +88 -0
- package/dist/output/text/proposal/producer.js +4 -2
- package/dist/output/text/proposal-format.js +44 -72
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/show-directives.js +15 -7
- package/dist/output/text/status-list.js +32 -0
- package/dist/output/text/sync.js +5 -0
- package/dist/output/text/workflow-format.js +24 -203
- package/dist/output/text/workflow.js +1 -7
- package/dist/output/text.js +16 -17
- package/dist/registry/factory.js +4 -6
- package/dist/registry/origin-resolve.js +16 -27
- package/dist/registry/providers/skills-sh.js +3 -3
- package/dist/registry/providers/static-index.js +13 -23
- package/dist/registry/resolve.js +42 -7
- package/dist/registry/semver.js +34 -84
- package/dist/runtime.js +2 -23
- package/dist/scripts/akm-migrate-node.js +60290 -0
- package/dist/scripts/akm-migrate.js +59628 -0
- package/dist/setup/detect.js +42 -15
- package/dist/setup/registry-stash-loader.js +2 -2
- package/dist/setup/setup.js +236 -136
- package/dist/setup/steps/connection.js +7 -9
- package/dist/setup/steps/platforms.js +9 -9
- package/dist/setup/steps/semantic.js +15 -3
- package/dist/setup/steps/sources.js +12 -13
- package/dist/setup/steps/stashdir.js +2 -3
- package/dist/setup/steps/tasks.js +237 -120
- package/dist/sources/freshness.js +1 -1
- package/dist/sources/provider-factory.js +11 -17
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +278 -34
- package/dist/sources/providers/git-provider.js +25 -23
- package/dist/sources/providers/git-stash.js +395 -106
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/providers/npm.js +16 -19
- package/dist/sources/providers/provider-utils.js +7 -4
- package/dist/sources/providers/sync-from-ref.js +3 -9
- package/dist/sources/providers/website.js +6 -1
- package/dist/sources/resolve.js +6 -5
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
- package/dist/sources/snapshot-fetchers/github.js +100 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
- package/dist/sources/snapshot-fetchers/registry.js +17 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +282 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +566 -268
- package/dist/sources/snapshot-fetchers/x.js +910 -0
- package/dist/storage/database.js +7 -0
- package/dist/storage/engines/sqlite-migrations.js +23 -111
- package/dist/storage/managed-db.js +2 -2
- package/dist/storage/repositories/canaries-repository.js +1 -1
- package/dist/storage/repositories/events-repository.js +27 -11
- package/dist/storage/repositories/improve-runs-repository.js +6 -12
- package/dist/storage/repositories/index-connection.js +17 -6
- package/dist/storage/repositories/index-entries-repository.js +151 -240
- package/dist/storage/repositories/index-entry-mapper.js +15 -11
- package/dist/storage/repositories/index-fts-repository.js +5 -2
- package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
- package/dist/storage/repositories/index-meta-repository.js +2 -3
- package/dist/storage/repositories/index-schema.js +10 -25
- package/dist/storage/repositories/index-utility-repository.js +15 -28
- package/dist/storage/repositories/index-vec-repository.js +6 -1
- package/dist/storage/repositories/outcome-repository.js +119 -0
- package/dist/storage/repositories/proposals-repository.js +296 -59
- package/dist/storage/repositories/registry-cache.js +19 -0
- package/dist/storage/repositories/salience-repository.js +172 -0
- package/dist/storage/repositories/task-history-repository.js +15 -13
- package/dist/storage/repositories/workflow-runs-repository.js +52 -40
- package/dist/tasks/backends/cron.js +105 -15
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +85 -38
- package/dist/tasks/backends/schtasks.js +135 -15
- package/dist/tasks/embedded.js +56 -40
- package/dist/tasks/parser.js +7 -157
- package/dist/tasks/resolve-akm-bin.js +137 -59
- package/dist/tasks/runner.js +79 -42
- package/dist/tasks/scheduler-invocation.js +220 -10
- package/dist/tasks/schema.js +24 -1
- package/dist/tasks/task-id.js +1 -3
- package/dist/tasks/validator.js +20 -6
- package/dist/workflows/authoring/authoring.js +94 -143
- package/dist/workflows/authoring/scope-key.js +1 -1
- package/dist/workflows/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +77 -57
- package/dist/workflows/exec/param-secrets.js +9 -9
- package/dist/workflows/exec/run-workflow.js +133 -79
- package/dist/workflows/exec/step-work.js +219 -346
- package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
- package/dist/workflows/ir/compile.js +141 -270
- package/dist/workflows/ir/freeze.js +40 -30
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +25 -26
- package/dist/workflows/parser.js +872 -307
- package/dist/workflows/program/expressions.js +20 -208
- package/dist/workflows/program/schema.js +7 -10
- package/dist/workflows/renderer.js +95 -68
- package/dist/workflows/resource-limits.js +2 -0
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/plan-classifier.js +16 -75
- package/dist/workflows/runtime/runs.js +186 -127
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
- package/dist/workflows/schema.js +1 -11
- package/dist/workflows/validate-summary.js +30 -36
- package/dist/workflows/validator.js +21 -62
- package/docs/README.md +68 -0
- package/docs/migration/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +11 -11
- package/docs/migration/release-notes/0.9.0.md +208 -27
- package/docs/migration/v0.7-to-v0.8.md +46 -47
- package/docs/migration/v0.8-to-v0.9.md +564 -208
- package/docs/migration/v0.9.0-troubleshooting.md +561 -0
- package/docs/reference/README.md +12 -0
- package/docs/reference/cli.md +2253 -0
- package/docs/reference/configuration.md +358 -0
- package/docs/reference/data-and-telemetry.md +105 -42
- package/docs/reference/workflows.md +647 -0
- package/package.json +22 -11
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +81 -128
- package/schemas/akm-workflow.json +74 -73
- package/dist/assets/tasks/core/backup.yml +0 -5
- package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
- package/dist/cli/config-migrate.js +0 -1806
- package/dist/cli/config-validate.js +0 -41
- package/dist/commands/backup-cli.js +0 -56
- package/dist/commands/bundle/bundle-cli.js +0 -68
- package/dist/commands/bundle/bundle.js +0 -219
- package/dist/commands/graph/graph-cli.js +0 -124
- package/dist/commands/graph/graph.js +0 -489
- package/dist/commands/improve/extract-watch.js +0 -140
- package/dist/commands/mv-cli.js +0 -1221
- package/dist/commands/sources/history.js +0 -201
- package/dist/commands/tasks/default-tasks.js +0 -186
- package/dist/core/migration-backup.js +0 -1234
- package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
- package/dist/llm/memory-infer-impl.js +0 -138
- package/dist/migrate/legacy/config-source-migration.js +0 -223
- package/dist/migrate/legacy/content-migration.js +0 -305
- package/dist/migrate/legacy/legacy-layout.js +0 -779
- package/dist/migrate/legacy/legacy-paths.js +0 -25
- package/dist/migrate/legacy/legacy-stash-json.js +0 -72
- package/dist/migrate/legacy/proposal-fs-import.js +0 -168
- package/dist/migrate/legacy/task-target-ref-migration.js +0 -272
- package/dist/migrate/legacy/three-db-cutover.js +0 -841
- package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
- package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
- package/dist/migrate/legacy-ref-grammar.js +0 -214
- package/dist/output/shapes/distill.js +0 -14
- package/dist/output/shapes/history.js +0 -11
- package/dist/output/text/distill.js +0 -6
- package/dist/output/text/enable-disable.js +0 -8
- package/dist/output/text/history.js +0 -6
- package/dist/registry/build-index.js +0 -382
- package/dist/schemas/akm-config.json +0 -4704
- package/dist/schemas/akm-task.json +0 -87
- package/dist/schemas/akm-workflow.json +0 -372
- package/dist/scripts/migrate-storage.js +0 -3816
- package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
- package/dist/workflows/cli.js +0 -53
- package/dist/workflows/exec/brief.js +0 -481
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/watch.js +0 -116
- package/dist/workflows/program/parser.js +0 -813
- package/dist/workflows/program/project.js +0 -104
package/dist/commands/env/env.js
CHANGED
|
@@ -10,13 +10,10 @@
|
|
|
10
10
|
* them all the same. For a single sensitive value used on its own for
|
|
11
11
|
* authentication (a token, key, or cert), use the `secret` type instead.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* file with your own editor. Values are quoted/escaped only when necessary and
|
|
18
|
-
* round-trip through `dotenv`; the shell-load safety guarantee still lives on
|
|
19
|
-
* the READ path (see `buildShellExportScript` + `akm env export`).
|
|
13
|
+
* akm does not manage individual keys — edit the `.env` file with your own
|
|
14
|
+
* editor. Values are quoted/escaped only when necessary and round-trip
|
|
15
|
+
* through `dotenv`; the shell-load safety guarantee lives on the READ path
|
|
16
|
+
* (see `buildShellExportScript` + `akm env export`).
|
|
20
17
|
*
|
|
21
18
|
* Invariant: nothing from an env file except key NAMES may be written to
|
|
22
19
|
* stdout, returned through the indexer, the `akm show` renderer, or any
|
|
@@ -27,20 +24,18 @@
|
|
|
27
24
|
* are:
|
|
28
25
|
*
|
|
29
26
|
* - `akm env run <ref> -- <command>` — values injected into the child
|
|
30
|
-
* process env (never via a shell), see `
|
|
31
|
-
*
|
|
32
|
-
*
|
|
27
|
+
* process env (never via a shell), see `loadEnv` (loaded by
|
|
28
|
+
* `env-binding.ts#resolveEnvBinding`, then merged into the child env by
|
|
29
|
+
* `env-cli.ts`). This is the primary path and the only one safe for AI
|
|
30
|
+
* agents (no values ever reach stdout). For an interactive shell, `akm
|
|
31
|
+
* env run <ref> -- $SHELL`.
|
|
33
32
|
* - `akm env export <ref> --out <file>` — write parse-then-reserialized safe
|
|
34
33
|
* `export KEY='value'` lines to a file (mode 0600) for `source`-ing. Values
|
|
35
34
|
* are re-emitted single-quoted so a raw `.env` containing `X=$(cmd)` cannot
|
|
36
35
|
* execute on load. `export` never prints values to stdout (would leak into
|
|
37
36
|
* an agent's context); `path` prints only the file path.
|
|
38
37
|
*
|
|
39
|
-
* Value parsing is delegated to the `dotenv` package
|
|
40
|
-
* serialisation oracle for `env set` (`setEnvKey`): a written value is only
|
|
41
|
-
* committed if `dotenv.parse` reads it back exactly, and the whole edit is
|
|
42
|
-
* re-parsed to confirm no other key was disturbed. We never hand-roll a
|
|
43
|
-
* quoting representation we cannot read back.
|
|
38
|
+
* Value parsing is delegated to the `dotenv` package.
|
|
44
39
|
*
|
|
45
40
|
* Secret-token substitution: env VALUES may embed `${secret:NAME}` tokens, which
|
|
46
41
|
* are replaced at `env run` time with the value of the sibling `secret:NAME`
|
|
@@ -54,7 +49,7 @@ import fs from "node:fs";
|
|
|
54
49
|
import path from "node:path";
|
|
55
50
|
import dotenv from "dotenv";
|
|
56
51
|
import { writeFileAtomic } from "../../core/common.js";
|
|
57
|
-
import {
|
|
52
|
+
import { sensitiveMarkerPath } from "./marker-path.js";
|
|
58
53
|
/** Matches a KEY=value assignment line, capturing only the key. */
|
|
59
54
|
const ASSIGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
|
|
60
55
|
/** Scan lines and return KEY names in file order, without duplicates. */
|
|
@@ -101,21 +96,6 @@ export function loadEnv(envPath) {
|
|
|
101
96
|
const buf = fs.readFileSync(envPath);
|
|
102
97
|
return dotenv.parse(buf);
|
|
103
98
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Load an env file and assign its values into `target` (defaults to
|
|
106
|
-
* `process.env`). Returns the list of keys that were set so the caller can
|
|
107
|
-
* log/observe without touching values.
|
|
108
|
-
*
|
|
109
|
-
* Existing keys in `target` are overwritten — callers who want to preserve
|
|
110
|
-
* pre-existing environment variables should filter before calling.
|
|
111
|
-
*/
|
|
112
|
-
export function injectIntoEnv(envPath, target = process.env) {
|
|
113
|
-
const env = loadEnv(envPath);
|
|
114
|
-
for (const [key, value] of Object.entries(env)) {
|
|
115
|
-
target[key] = value;
|
|
116
|
-
}
|
|
117
|
-
return Object.keys(env);
|
|
118
|
-
}
|
|
119
99
|
/**
|
|
120
100
|
* Serialise an env file's values as a POSIX shell script of `export KEY='value'`
|
|
121
101
|
* lines, with single-quote escaping (`'\''`). Every line is an assignment of a
|
|
@@ -214,142 +194,11 @@ export function removeEnv(envPath) {
|
|
|
214
194
|
if (!fs.existsSync(envPath))
|
|
215
195
|
return false;
|
|
216
196
|
fs.rmSync(envPath);
|
|
217
|
-
const marker =
|
|
197
|
+
const marker = sensitiveMarkerPath(envPath, "env");
|
|
218
198
|
if (fs.existsSync(marker))
|
|
219
199
|
fs.rmSync(marker);
|
|
220
200
|
return true;
|
|
221
201
|
}
|
|
222
|
-
/** A valid env KEY name (same grammar as the assignment scanner). */
|
|
223
|
-
export const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
224
|
-
/**
|
|
225
|
-
* Build a `KEY=value` assignment line whose value is GUARANTEED to round-trip
|
|
226
|
-
* through `dotenv.parse` — dotenv is the serialisation oracle, so we never
|
|
227
|
-
* write a representation we cannot read back. Candidate representations are
|
|
228
|
-
* tried in order of readability (bare → double-quoted → single-quoted) and the
|
|
229
|
-
* first one `dotenv.parse` recovers exactly is used. If a value contains
|
|
230
|
-
* characters no inline representation can round-trip (e.g. both quote styles),
|
|
231
|
-
* we throw rather than silently corrupt the file.
|
|
232
|
-
*/
|
|
233
|
-
function serializeEnvAssignment(key, value) {
|
|
234
|
-
const candidates = [];
|
|
235
|
-
// Bare — only for simple values (no whitespace/quotes/#/$/control chars).
|
|
236
|
-
if (/^[A-Za-z0-9_@%+=:,./-]*$/.test(value))
|
|
237
|
-
candidates.push(`${key}=${value}`);
|
|
238
|
-
// Double-quoted — dotenv expands \n \r \t inside double quotes.
|
|
239
|
-
const dq = value
|
|
240
|
-
.replace(/\\/g, "\\\\")
|
|
241
|
-
.replace(/"/g, '\\"')
|
|
242
|
-
.replace(/\n/g, "\\n")
|
|
243
|
-
.replace(/\r/g, "\\r")
|
|
244
|
-
.replace(/\t/g, "\\t");
|
|
245
|
-
candidates.push(`${key}="${dq}"`);
|
|
246
|
-
// Single-quoted — dotenv takes the content literally (no escapes/expansion).
|
|
247
|
-
candidates.push(`${key}='${value}'`);
|
|
248
|
-
for (const line of candidates) {
|
|
249
|
-
try {
|
|
250
|
-
if (dotenv.parse(line)[key] === value)
|
|
251
|
-
return line;
|
|
252
|
-
}
|
|
253
|
-
catch {
|
|
254
|
-
// Not parseable as written; try the next representation.
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
throw new UsageError(`Value for "${key}" cannot be stored inline in a .env file (it contains characters dotenv cannot round-trip). ` +
|
|
258
|
-
"Edit the .env file directly, or choose a different value.");
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Assert (using `dotenv.parse` as the oracle) that `after` set `key` to
|
|
262
|
-
* `expected` and left every other key from `before` byte-identical. This
|
|
263
|
-
* catches a line-level edit accidentally disturbing a quoted/multiline value.
|
|
264
|
-
*/
|
|
265
|
-
function assertEnvEditSafe(before, after, key) {
|
|
266
|
-
for (const [k, v] of Object.entries(before)) {
|
|
267
|
-
if (k !== key && after[k] !== v) {
|
|
268
|
-
throw new UsageError(`Editing "${key}" would disturb "${k}" (the .env file has a value layout dotenv could not safely round-trip). ` +
|
|
269
|
-
"Edit the .env file directly.");
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
/**
|
|
274
|
-
* Set (create or update) a single `KEY=value` entry in an env file, preserving
|
|
275
|
-
* the file's existing lines, comments, and key order. The first existing
|
|
276
|
-
* assignment of `key` is replaced in place; otherwise the entry is appended.
|
|
277
|
-
* Creates the file (and parent dirs) if absent. The value is never logged.
|
|
278
|
-
*
|
|
279
|
-
* The serialised value and the whole resulting file are verified with
|
|
280
|
-
* `dotenv.parse` before the write commits.
|
|
281
|
-
*/
|
|
282
|
-
export function setEnvKey(envPath, key, value) {
|
|
283
|
-
ensureParentDir(envPath);
|
|
284
|
-
const existing = fs.existsSync(envPath) ? fs.readFileSync(envPath, "utf8") : "";
|
|
285
|
-
const assignment = serializeEnvAssignment(key, value);
|
|
286
|
-
const keyLineRe = new RegExp(`^\\s*(?:export\\s+)?${key}\\s*=`);
|
|
287
|
-
const lines = existing.split(/\r?\n/);
|
|
288
|
-
let replaced = false;
|
|
289
|
-
const out = lines.map((line) => {
|
|
290
|
-
if (!replaced && keyLineRe.test(line)) {
|
|
291
|
-
replaced = true;
|
|
292
|
-
return assignment;
|
|
293
|
-
}
|
|
294
|
-
return line;
|
|
295
|
-
});
|
|
296
|
-
if (!replaced) {
|
|
297
|
-
while (out.length > 0 && out[out.length - 1] === "")
|
|
298
|
-
out.pop();
|
|
299
|
-
out.push(assignment);
|
|
300
|
-
}
|
|
301
|
-
let content = out.join("\n");
|
|
302
|
-
if (!content.endsWith("\n"))
|
|
303
|
-
content += "\n";
|
|
304
|
-
// Verify the edit with dotenv before committing it.
|
|
305
|
-
const after = dotenv.parse(content);
|
|
306
|
-
if (after[key] !== value) {
|
|
307
|
-
throw new UsageError(`Could not set "${key}" reliably (the .env file has a value layout dotenv could not round-trip). ` +
|
|
308
|
-
"Edit the .env file directly.");
|
|
309
|
-
}
|
|
310
|
-
assertEnvEditSafe(dotenv.parse(existing), after, key);
|
|
311
|
-
writeFileAtomic(envPath, content, 0o600);
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Remove one or more `KEY=value` entries from an env file, preserving all other
|
|
315
|
-
* lines and comments. Returns which keys were present (removed) vs. absent.
|
|
316
|
-
*
|
|
317
|
-
* The result is verified with `dotenv.parse`: the removed keys are gone and
|
|
318
|
-
* every surviving key is byte-identical to before.
|
|
319
|
-
*/
|
|
320
|
-
export function unsetEnvKeys(envPath, keys) {
|
|
321
|
-
if (!fs.existsSync(envPath))
|
|
322
|
-
return { removed: [], missing: keys };
|
|
323
|
-
const text = fs.readFileSync(envPath, "utf8");
|
|
324
|
-
const before = dotenv.parse(text);
|
|
325
|
-
const present = new Set(Object.keys(before));
|
|
326
|
-
const toRemove = new Set(keys);
|
|
327
|
-
const out = text.split(/\r?\n/).filter((line) => {
|
|
328
|
-
const m = line.match(ASSIGN_RE);
|
|
329
|
-
return !(m && toRemove.has(m[1]));
|
|
330
|
-
});
|
|
331
|
-
let content = out.join("\n");
|
|
332
|
-
if (content.length > 0 && !content.endsWith("\n"))
|
|
333
|
-
content += "\n";
|
|
334
|
-
// Verify with dotenv: removed keys gone, survivors unchanged.
|
|
335
|
-
const after = dotenv.parse(content);
|
|
336
|
-
for (const k of toRemove) {
|
|
337
|
-
if (k in after) {
|
|
338
|
-
throw new UsageError(`Could not remove "${k}" reliably (the .env file has a value layout dotenv could not round-trip). ` +
|
|
339
|
-
"Edit the .env file directly.");
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
for (const [k, v] of Object.entries(before)) {
|
|
343
|
-
if (!toRemove.has(k) && after[k] !== v) {
|
|
344
|
-
throw new UsageError(`Removing those keys would disturb "${k}" (multiline/quoted value). Edit the .env file directly.`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
writeFileAtomic(envPath, content, 0o600);
|
|
348
|
-
return {
|
|
349
|
-
removed: keys.filter((k) => present.has(k)),
|
|
350
|
-
missing: keys.filter((k) => !present.has(k)),
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
202
|
function ensureParentDir(filePath) {
|
|
354
203
|
const dir = path.dirname(filePath);
|
|
355
204
|
if (!fs.existsSync(dir))
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
export function sensitiveMarkerPath(assetPath, type) {
|
|
5
|
+
return type === "env" ? assetPath.replace(/\.env$/, ".sensitive") : `${assetPath}.sensitive`;
|
|
6
|
+
}
|
|
@@ -11,9 +11,20 @@
|
|
|
11
11
|
*
|
|
12
12
|
* `akm secret` manages whole-file secrets under each stash's secrets/ directory.
|
|
13
13
|
* Unlike env files (.env key/value), the ENTIRE file is the secret value. The bytes
|
|
14
|
-
* are NEVER written to stdout or structured output.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* are NEVER written to stdout or structured output. The only value-use path is
|
|
15
|
+
* `akm secret run` (injected into a child env var).
|
|
16
|
+
*
|
|
17
|
+
* `secret path` and `secret remove` were REMOVED in 0.9.0 (R-027 / D-49): an
|
|
18
|
+
* audit found `path` resolved the ref through the read-side, all-sources
|
|
19
|
+
* resolver while `remove` resolved it through the write-target resolver, so
|
|
20
|
+
* `akm secret path <ref>` and `akm secret remove <ref>` could silently name
|
|
21
|
+
* two DIFFERENT files when a ref existed in more than one stash — inspect one,
|
|
22
|
+
* delete the other. The owner's ruling was to drop both subcommands rather
|
|
23
|
+
* than reconcile the resolvers. A ref's file lives at `<bundle>/secrets/<name>`
|
|
24
|
+
* (matching the ref exactly, e.g. `secrets/deploy-key` -> `secrets/deploy-key`
|
|
25
|
+
* under a bundle root — `akm bundle list` prints each configured bundle's root
|
|
26
|
+
* path); locate or delete it directly, or consume its value without touching
|
|
27
|
+
* disk via `akm secret run <ref> <VAR> -- <command>`.
|
|
17
28
|
*/
|
|
18
29
|
import { spawnSync } from "node:child_process";
|
|
19
30
|
import fs from "node:fs";
|
|
@@ -23,12 +34,13 @@ import { getStringArg } from "../../cli/parse-args.js";
|
|
|
23
34
|
import { defineGroupCommand, defineJsonCommand, output } from "../../cli/shared.js";
|
|
24
35
|
import { deriveCanonicalAssetName } from "../../core/asset/asset-placement.js";
|
|
25
36
|
import { loadConfig } from "../../core/config/config.js";
|
|
26
|
-
import {
|
|
37
|
+
import { makeSecretRef, resolveSecretPath, resolveSecretWriteTarget, withEnvSecretWrite, } from "../../core/env-secret-ref.js";
|
|
27
38
|
import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
|
|
28
39
|
import { appendEvent } from "../../core/events.js";
|
|
29
40
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
30
41
|
import { readStdin } from "../../runtime.js";
|
|
31
42
|
import { buildChildEnv } from "./child-env.js";
|
|
43
|
+
import { sensitiveMarkerPath } from "./marker-path.js";
|
|
32
44
|
function parseKeyListFlag(raw) {
|
|
33
45
|
if (raw === undefined)
|
|
34
46
|
return undefined;
|
|
@@ -57,7 +69,7 @@ function listSecretsRecursive() {
|
|
|
57
69
|
if (entry.name.endsWith(".lock") || entry.name.endsWith(".sensitive"))
|
|
58
70
|
continue;
|
|
59
71
|
// A sibling `<name>.sensitive` marker suppresses listing.
|
|
60
|
-
if (fs.existsSync(
|
|
72
|
+
if (fs.existsSync(sensitiveMarkerPath(full, "secret")))
|
|
61
73
|
continue;
|
|
62
74
|
const canonical = deriveCanonicalAssetName("secret", secretsDir, full);
|
|
63
75
|
if (!canonical)
|
|
@@ -72,7 +84,7 @@ function listSecretsRecursive() {
|
|
|
72
84
|
const secretListCommand = defineJsonCommand({
|
|
73
85
|
meta: {
|
|
74
86
|
name: "list",
|
|
75
|
-
description: "List all secrets across all
|
|
87
|
+
description: "List all secrets across all bundles by name (the file contents are never shown)",
|
|
76
88
|
},
|
|
77
89
|
async run() {
|
|
78
90
|
output("secret-list", { secrets: listSecretsRecursive() });
|
|
@@ -86,7 +98,7 @@ const secretSetCommand = defineJsonCommand({
|
|
|
86
98
|
args: {
|
|
87
99
|
ref: {
|
|
88
100
|
type: "positional",
|
|
89
|
-
description: "Secret ref (flat name, e.g.
|
|
101
|
+
description: "Secret ref (flat name, e.g. secrets/deploy-key or just deploy-key; use --path for a subdirectory)",
|
|
90
102
|
required: true,
|
|
91
103
|
},
|
|
92
104
|
path: {
|
|
@@ -97,15 +109,14 @@ const secretSetCommand = defineJsonCommand({
|
|
|
97
109
|
"from-env": { type: "string", description: "Read the value from the named environment variable" },
|
|
98
110
|
target: {
|
|
99
111
|
type: "string",
|
|
100
|
-
description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working
|
|
112
|
+
description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working bundle.",
|
|
101
113
|
},
|
|
102
114
|
},
|
|
103
115
|
async run({ args }) {
|
|
104
116
|
const { setSecret } = await import("./secret.js");
|
|
105
|
-
const { name, absPath, target } = resolveSecretWriteTarget(args.ref, args.target, {
|
|
117
|
+
const { name, absPath, target, ref } = resolveSecretWriteTarget(args.ref, args.target, {
|
|
106
118
|
subPath: getStringArg(args, "path"),
|
|
107
119
|
});
|
|
108
|
-
const displaySource = writeTargetDisplaySource(target);
|
|
109
120
|
const fromEnv = args["from-env"];
|
|
110
121
|
const fromFile = args["from-file"];
|
|
111
122
|
if (fromEnv !== undefined && fromFile !== undefined) {
|
|
@@ -139,25 +150,8 @@ const secretSetCommand = defineJsonCommand({
|
|
|
139
150
|
// byte-exact storage of multi-line material (PEM keys, certs).
|
|
140
151
|
value = Buffer.from(stdinBuf.toString("utf8").replace(/\n$/, ""), "utf8");
|
|
141
152
|
}
|
|
142
|
-
setSecret(absPath, value);
|
|
143
|
-
|
|
144
|
-
output("secret-set", { ref: makeSecretRef(name, displaySource) });
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
const secretPathCommand = defineJsonCommand({
|
|
148
|
-
meta: {
|
|
149
|
-
name: "path",
|
|
150
|
-
description: "Print the absolute secret file path for the Docker `_FILE` convention, e.g. `MY_SECRET_FILE=$(akm secret path secret:deploy-key)`.",
|
|
151
|
-
},
|
|
152
|
-
args: {
|
|
153
|
-
ref: { type: "positional", description: "Secret ref", required: true },
|
|
154
|
-
},
|
|
155
|
-
async run({ args }) {
|
|
156
|
-
const { name, absPath, source } = resolveSecretPath(args.ref);
|
|
157
|
-
if (!fs.existsSync(absPath)) {
|
|
158
|
-
throw new NotFoundError(`Secret not found: ${makeSecretRef(name, source)}`);
|
|
159
|
-
}
|
|
160
|
-
process.stdout.write(`${absPath}\n`);
|
|
153
|
+
withEnvSecretWrite(target, { type: "secret", name }, "Update", [absPath], () => setSecret(absPath, value));
|
|
154
|
+
output("secret-set", { ref });
|
|
161
155
|
},
|
|
162
156
|
});
|
|
163
157
|
const secretRunCommand = defineJsonCommand({
|
|
@@ -224,52 +218,28 @@ const secretRunCommand = defineJsonCommand({
|
|
|
224
218
|
}
|
|
225
219
|
throw err;
|
|
226
220
|
}
|
|
227
|
-
process.exit(result.status ?? 0)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
target: {
|
|
236
|
-
type: "string",
|
|
237
|
-
description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working stash.",
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
async run({ args }) {
|
|
241
|
-
const { name, absPath, target } = resolveSecretWriteTarget(args.ref, args.target);
|
|
242
|
-
const displaySource = writeTargetDisplaySource(target);
|
|
243
|
-
const { confirmDestructive } = await import("../../cli/confirm.js");
|
|
244
|
-
const confirmed = await confirmDestructive(`Remove secret "${args.ref}"? This cannot be undone.`, {
|
|
245
|
-
yes: args.yes === true,
|
|
246
|
-
});
|
|
247
|
-
if (!confirmed) {
|
|
248
|
-
process.stderr.write("Aborted.\n");
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const { removeSecret } = await import("./secret.js");
|
|
252
|
-
if (!fs.existsSync(absPath)) {
|
|
253
|
-
throw new NotFoundError(`Secret not found: ${makeSecretRef(name, displaySource)}`);
|
|
254
|
-
}
|
|
255
|
-
const removed = removeSecret(absPath);
|
|
256
|
-
commitEnvSecretWrite(target, { type: "secret", name }, "Remove", [absPath, `${absPath}.sensitive`]);
|
|
257
|
-
output("secret-remove", { ref: makeSecretRef(name, displaySource), removed });
|
|
221
|
+
// R-067: was `process.exit(result.status ?? 0)`, unconditional even on
|
|
222
|
+
// the success path — it skipped the `finally { await
|
|
223
|
+
// disposeDispatchResources(); }` cleanup in src/cli.ts's `runCommand`.
|
|
224
|
+
// Setting `process.exitCode` and returning still exits with the child's
|
|
225
|
+
// exact status once the event loop drains, but lets cleanup run first —
|
|
226
|
+
// same pattern `emitJsonError` (src/cli/shared.ts) already established.
|
|
227
|
+
process.exitCode = result.status ?? 0;
|
|
228
|
+
return;
|
|
258
229
|
},
|
|
259
230
|
});
|
|
260
231
|
export const secretCommand = defineGroupCommand({
|
|
261
232
|
meta: {
|
|
262
233
|
name: "secret",
|
|
263
|
-
description: "Manage secrets —
|
|
234
|
+
description: "Manage secrets — one standalone sensitive value per file (an API token, a PEM private key, a TLS cert).\n\n" +
|
|
235
|
+
"Names are visible; the file contents are the value and never appear in structured output. For a group of related configuration loaded together, use `akm env`. A secret's file lives at `<bundle>/secrets/<name>` (`akm bundle list` shows bundle roots) — read or delete it there directly, or consume its value without writing it anywhere via `akm secret run <ref> <VAR> -- <command>`.",
|
|
264
236
|
},
|
|
265
237
|
subCommands: {
|
|
266
238
|
list: secretListCommand,
|
|
267
|
-
path: secretPathCommand,
|
|
268
239
|
run: secretRunCommand,
|
|
269
240
|
set: secretSetCommand,
|
|
270
|
-
remove: secretRemoveCommand,
|
|
271
|
-
},
|
|
272
|
-
defaultRun() {
|
|
273
|
-
output("secret-list", { secrets: listSecretsRecursive() });
|
|
274
241
|
},
|
|
242
|
+
// No `defaultRun`: bare `akm secret` is a usage error (exit 2), the canonical
|
|
243
|
+
// bare-group behavior — owner ruling 12. Run `akm secret list` for what the
|
|
244
|
+
// bare form used to print.
|
|
275
245
|
});
|
|
@@ -14,20 +14,26 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Invariant: a secret's bytes must never be written to stdout, returned
|
|
16
16
|
* through the indexer / `akm show` renderer, or any structured output channel.
|
|
17
|
-
* The supported value-use
|
|
17
|
+
* The supported value-use path is:
|
|
18
18
|
*
|
|
19
19
|
* - `akm secret run <ref> <VAR> -- <cmd>` — value injected into the child
|
|
20
20
|
* process env as `VAR=<value>` (see `readValue`).
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
*
|
|
22
|
+
* `akm secret path <ref>` (print the file path for the Docker `/run/secrets` +
|
|
23
|
+
* `_FILE` convention) was removed in 0.9.0 alongside `akm secret remove` — an
|
|
24
|
+
* audit found the two resolved a ref through different stash-selection logic
|
|
25
|
+
* and could silently target different files. A ref's file still lives at
|
|
26
|
+
* `<stash>/secrets/<name>`; locate or delete it there directly.
|
|
23
27
|
*
|
|
24
28
|
* Values are stored as raw bytes (no quoting, multi-line allowed) so they
|
|
25
29
|
* round-trip byte-exact, unlike env values which forbid literal newlines.
|
|
26
30
|
*/
|
|
31
|
+
import { createHash } from "node:crypto";
|
|
27
32
|
import fs from "node:fs";
|
|
28
33
|
import path from "node:path";
|
|
29
|
-
import { writeFileAtomic } from "../../core/common.js";
|
|
34
|
+
import { safeRealpath, writeFileAtomic } from "../../core/common.js";
|
|
30
35
|
import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquireLockSync, } from "../../core/file-lock.js";
|
|
36
|
+
import { getDataDir } from "../../core/paths.js";
|
|
31
37
|
import { sleepSync } from "../../runtime.js";
|
|
32
38
|
// ── Write-lock helper ─────────────────────────────────────────────────────────
|
|
33
39
|
/**
|
|
@@ -37,7 +43,9 @@ import { sleepSync } from "../../runtime.js";
|
|
|
37
43
|
* throw rather than silently proceeding.
|
|
38
44
|
*/
|
|
39
45
|
export function withSecretLock(secretPath, fn) {
|
|
40
|
-
const lockPath =
|
|
46
|
+
const lockPath = secretLockPath(secretPath);
|
|
47
|
+
const lockDir = path.dirname(lockPath);
|
|
48
|
+
fs.mkdirSync(lockDir, { recursive: true, mode: 0o700 });
|
|
41
49
|
const deadline = Date.now() + 5000;
|
|
42
50
|
let ownership;
|
|
43
51
|
while (!ownership) {
|
|
@@ -49,10 +57,8 @@ export function withSecretLock(secretPath, fn) {
|
|
|
49
57
|
continue;
|
|
50
58
|
}
|
|
51
59
|
if (Date.now() > deadline) {
|
|
52
|
-
const holderHint = probe.state === "held"
|
|
53
|
-
|
|
54
|
-
: ` Lock file ${lockPath} could not be inspected.`;
|
|
55
|
-
throw new Error(`Could not acquire secret lock for ${secretPath} after 5s.${holderHint} Retry once any other akm secret operation finishes, or remove the stale lock file.`);
|
|
60
|
+
const holderHint = probe.state === "held" ? ` It is held by live PID ${probe.holderPid}.` : "";
|
|
61
|
+
throw new Error(`Could not acquire the secret write lock after 5s.${holderHint} Retry once any other akm secret operation finishes.`);
|
|
56
62
|
}
|
|
57
63
|
sleepSync(10);
|
|
58
64
|
}
|
|
@@ -63,6 +69,12 @@ export function withSecretLock(secretPath, fn) {
|
|
|
63
69
|
releaseLock(ownership);
|
|
64
70
|
}
|
|
65
71
|
}
|
|
72
|
+
export function secretLockPath(secretPath, platform = process.platform) {
|
|
73
|
+
const canonical = safeRealpath(secretPath);
|
|
74
|
+
const identity = platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
75
|
+
const key = createHash("sha256").update(identity).digest("hex");
|
|
76
|
+
return path.join(getDataDir(), "secret-locks", `${key}.lock`);
|
|
77
|
+
}
|
|
66
78
|
// ── Atomic byte write ──────────────────────────────────────────────────────────
|
|
67
79
|
function ensureParentDir(filePath) {
|
|
68
80
|
const dir = path.dirname(filePath);
|
|
@@ -71,38 +83,8 @@ function ensureParentDir(filePath) {
|
|
|
71
83
|
}
|
|
72
84
|
// ── Public API ──────────────────────────────────────────────────────────────
|
|
73
85
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* secrets with a sibling `<name>.sensitive` marker are excluded. The file
|
|
77
|
-
* bodies are NEVER read.
|
|
78
|
-
*/
|
|
79
|
-
export function listNames(secretsRoot) {
|
|
80
|
-
if (!fs.existsSync(secretsRoot))
|
|
81
|
-
return [];
|
|
82
|
-
const names = [];
|
|
83
|
-
const walk = (dir) => {
|
|
84
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
85
|
-
const full = path.join(dir, entry.name);
|
|
86
|
-
if (entry.isDirectory()) {
|
|
87
|
-
walk(full);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (!entry.isFile())
|
|
91
|
-
continue;
|
|
92
|
-
if (entry.name.endsWith(".lock") || entry.name.endsWith(".sensitive"))
|
|
93
|
-
continue;
|
|
94
|
-
// A sibling `<name>.sensitive` marker suppresses listing.
|
|
95
|
-
if (fs.existsSync(`${full}.sensitive`))
|
|
96
|
-
continue;
|
|
97
|
-
names.push(path.relative(secretsRoot, full).split(path.sep).join("/"));
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
walk(secretsRoot);
|
|
101
|
-
return names.sort();
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Read a secret's raw bytes. Internal use only (for `secret run` / `secret
|
|
105
|
-
* path`). Callers MUST NOT write the returned value to stdout or any log.
|
|
86
|
+
* Read a secret's raw bytes. Internal use only (for `secret run`). Callers
|
|
87
|
+
* MUST NOT write the returned value to stdout or any log.
|
|
106
88
|
*/
|
|
107
89
|
export function readValue(secretPath) {
|
|
108
90
|
return fs.readFileSync(secretPath);
|
|
@@ -112,24 +94,9 @@ export function readValue(secretPath) {
|
|
|
112
94
|
* mode 0600 under a write-lock. No quoting; multi-line / binary allowed.
|
|
113
95
|
*/
|
|
114
96
|
export function setSecret(secretPath, value) {
|
|
115
|
-
ensureParentDir(secretPath);
|
|
116
97
|
withSecretLock(secretPath, () => {
|
|
98
|
+
ensureParentDir(secretPath);
|
|
117
99
|
// Mode 0600: secrets must never be world-readable, even transiently.
|
|
118
100
|
writeFileAtomic(secretPath, value, 0o600);
|
|
119
101
|
});
|
|
120
102
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Remove a secret file (and its `.sensitive` marker, if present). Returns true
|
|
123
|
-
* if the secret existed.
|
|
124
|
-
*/
|
|
125
|
-
export function removeSecret(secretPath) {
|
|
126
|
-
return withSecretLock(secretPath, () => {
|
|
127
|
-
if (!fs.existsSync(secretPath))
|
|
128
|
-
return false;
|
|
129
|
-
fs.rmSync(secretPath);
|
|
130
|
-
const marker = `${secretPath}.sensitive`;
|
|
131
|
-
if (fs.existsSync(marker))
|
|
132
|
-
fs.rmSync(marker);
|
|
133
|
-
return true;
|
|
134
|
-
});
|
|
135
|
-
}
|