akm-cli 0.10.261012-beta → 0.10.261014-beta
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 +710 -3
- package/README.md +15 -0
- package/SECURITY.md +11 -10
- package/STABILITY.md +47 -51
- package/dist/assets/hints/cli-hints-full.md +21 -19
- package/dist/assets/hints/cli-hints-short.md +3 -4
- package/dist/assets/opencode/akm-plugin.js +177 -0
- package/dist/assets/stash-skeleton/README.md +1 -1
- package/dist/assets/tasks/core/improve.yml +1 -1
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
- package/dist/assets/ui/config.js +27 -10
- package/dist/assets/workflows/workflow-template.md +2 -2
- package/dist/cli/clack.js +3 -1
- package/dist/cli/selection.js +33 -0
- package/dist/cli/shared.js +14 -1
- package/dist/cli/unknown-flags.js +6 -37
- package/dist/cli.js +69 -48
- package/dist/commands/agent/agent-dispatch.js +17 -25
- package/dist/commands/agent/contribute-cli.js +9 -8
- package/dist/commands/command/command-cli.js +4 -8
- package/dist/commands/command/command-execution.js +25 -39
- package/dist/commands/command/execution-source-loader.js +10 -14
- package/dist/commands/completions.js +1 -1
- package/dist/commands/config-cli.js +14 -5
- package/dist/commands/env/child-env.js +12 -14
- package/dist/commands/env/env-binding.js +47 -58
- package/dist/commands/env/env-cli.js +22 -24
- package/dist/commands/env/env.js +10 -58
- package/dist/commands/env/secret-cli.js +11 -29
- package/dist/commands/env/secret.js +3 -10
- package/dist/commands/health/checks.js +10 -849
- package/dist/commands/health/connections-and-harnesses-check.js +109 -0
- package/dist/commands/health/egress.js +3 -3
- package/dist/commands/health/html-report.js +9 -10
- package/dist/commands/health/plugin-staleness.js +18 -11
- package/dist/commands/health/report-view-model.js +16 -12
- package/dist/commands/health/scheduler-binary.js +1 -1
- package/dist/commands/health/semantic-search-check.js +85 -0
- package/dist/commands/health/type-directory-check.js +0 -3
- package/dist/commands/health.js +30 -42
- package/dist/commands/improve/consolidate/pair-pass.js +10 -25
- package/dist/commands/improve/consolidate.js +10 -15
- package/dist/commands/improve/distill.js +5 -17
- package/dist/commands/improve/eligibility.js +1 -3
- package/dist/commands/improve/execution.js +37 -40
- package/dist/commands/improve/extract-cli.js +6 -11
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +67 -97
- package/dist/commands/improve/improve-cli.js +22 -128
- package/dist/commands/improve/improve-report.js +1 -1
- package/dist/commands/improve/improve-strategies.js +25 -108
- package/dist/commands/improve/improve.js +10 -14
- package/dist/commands/improve/loop-stages.js +3 -0
- package/dist/commands/improve/memory/memory-improve.js +1 -1
- package/dist/commands/improve/preparation.js +8 -13
- package/dist/commands/improve/reflect.js +203 -332
- package/dist/commands/improve/retrieval-scope.js +1 -1
- package/dist/commands/improve/session-asset.js +3 -2
- package/dist/commands/improve/stage.js +87 -115
- package/dist/commands/lint/base-linter.js +3 -3
- package/dist/commands/lint/env-key-rules.js +7 -6
- package/dist/commands/lint/index.js +3 -3
- package/dist/commands/models-cli.js +103 -63
- package/dist/commands/proposal/drain.js +26 -39
- package/dist/commands/proposal/proposal-cli.js +14 -18
- package/dist/commands/proposal/propose-cli.js +4 -2
- package/dist/commands/proposal/propose.js +37 -64
- package/dist/commands/proposal/repository.js +17 -17
- package/dist/commands/proposal/validators/proposal-validators.js +3 -6
- package/dist/commands/read/curate.js +10 -6
- package/dist/commands/read/registry-search.js +2 -48
- package/dist/commands/read/search-cli.js +1 -8
- package/dist/commands/read/search.js +5 -6
- package/dist/commands/read/show.js +6 -29
- package/dist/commands/remember.js +25 -40
- package/dist/commands/sources/add-cli.js +8 -63
- package/dist/commands/sources/bundle-cli.js +8 -9
- package/dist/commands/sources/bundle-config-ops.js +2 -2
- package/dist/commands/sources/info.js +12 -1
- package/dist/commands/sources/init.js +1 -1
- package/dist/commands/sources/installed-stashes.js +119 -63
- package/dist/commands/sources/migration-help.js +1 -1
- package/dist/commands/sources/plugin-upgrade.js +8 -15
- package/dist/commands/sources/schema-repair.js +4 -10
- package/dist/commands/sources/source-add.js +2 -2
- package/dist/commands/sources/source-clone.js +31 -7
- package/dist/commands/sources/source-manage.js +4 -4
- package/dist/commands/targets.js +175 -0
- package/dist/commands/tasks/explain.js +104 -69
- package/dist/commands/tasks/tasks-cli.js +9 -11
- package/dist/commands/tasks/tasks.js +75 -97
- package/dist/commands/ui/api.js +11 -3
- package/dist/commands/ui/redact.js +5 -31
- package/dist/commands/ui/workspace-actions.js +1 -1
- package/dist/commands/workflow/plan.js +48 -9
- package/dist/commands/workflow-cli.js +40 -45
- package/dist/core/adapter/adapters/agent-skills-adapter.js +10 -11
- package/dist/core/adapter/adapters/akm-adapter.js +36 -41
- package/dist/core/adapter/adapters/akm-lint.js +10 -12
- package/dist/core/adapter/adapters/akm-metadata.js +2 -2
- package/dist/core/adapter/adapters/akm-task-adapter.js +8 -9
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +5 -5
- package/dist/core/adapter/adapters/claude-adapter.js +1 -1
- package/dist/core/adapter/adapters/dotenv-adapter.js +7 -6
- package/dist/core/adapter/adapters/generic-files-adapter.js +5 -5
- package/dist/core/adapter/adapters/index.js +3 -4
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +12 -14
- package/dist/core/adapter/adapters/okf-adapter.js +8 -10
- package/dist/core/adapter/adapters/opencode-adapter.js +1 -1
- package/dist/core/adapter/adapters/shared.js +3 -3
- package/dist/core/adapter/adapters/tool-dir-shared.js +7 -25
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +3 -3
- package/dist/core/adapter/detect-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +13 -35
- package/dist/core/adapter/registry.js +1 -1
- package/dist/core/adapter/validate-context.js +2 -4
- package/dist/core/asset/asset-placement.js +1 -1
- package/dist/core/asset/asset-ref.js +3 -3
- package/dist/core/asset/resolve-ref.js +5 -0
- package/dist/core/bundle-rename.js +12 -4
- package/dist/core/closest-match.js +37 -0
- package/dist/core/common.js +37 -57
- package/dist/core/config/config-io.js +26 -18
- package/dist/core/config/config-schema.js +19 -106
- package/dist/core/config/config-sources.js +3 -3
- package/dist/core/config/config-types.js +4 -4
- package/dist/core/config/config-walker.js +50 -77
- package/dist/core/config/config.js +438 -109
- package/dist/core/config/convert-engine-references.js +114 -0
- package/dist/core/config/convert-engines-shared.js +40 -0
- package/dist/core/config/convert-engines.js +300 -0
- package/dist/core/config/convert-models-overlay.js +137 -0
- package/dist/core/config/credentials.js +304 -0
- package/dist/core/config/engine-semantics.js +3 -3
- package/dist/core/config/migrate-keys.js +222 -0
- package/dist/core/config/model-aliases.js +37 -0
- package/dist/core/config/models-file.js +34 -0
- package/dist/core/config/retired-engines.js +53 -0
- package/dist/core/config/schema/connections.js +41 -0
- package/dist/core/config/schema/embedding.js +2 -2
- package/dist/core/config/schema/harnesses.js +41 -0
- package/dist/core/config/schema/improve-processes.js +9 -28
- package/dist/core/config/schema/improve.js +36 -1
- package/dist/core/config/schema/index-config.js +18 -20
- package/dist/core/config/schema/primitives.js +42 -17
- package/dist/core/config/schema/search.js +6 -11
- package/dist/core/config/schema/sources-bundles.js +9 -9
- package/dist/core/config/schema/workflow.js +2 -6
- package/dist/core/config/unusable-entries.js +66 -0
- package/dist/core/content-safety.js +3 -6
- package/dist/core/endpoint-url.js +47 -0
- package/dist/core/env-secret-ref.js +84 -73
- package/dist/core/errors.js +10 -16
- package/dist/core/extra-params.js +3 -3
- package/dist/core/improve-result.js +8 -9
- package/dist/core/json-schema.js +1 -1
- package/dist/core/lesson-lint.js +1 -1
- package/dist/core/llm-inference.js +80 -0
- package/dist/core/parse.js +2 -2
- package/dist/core/paths.js +33 -10
- package/dist/core/recognition-util.js +8 -10
- package/dist/core/redaction.js +94 -55
- package/dist/core/ref-token.js +82 -0
- package/dist/core/secret-value.js +14 -0
- package/dist/core/state/migrations.js +17 -22
- package/dist/core/structured.js +2 -68
- package/dist/core/subprocess.js +63 -22
- package/dist/core/type-presentation.js +10 -14
- package/dist/core/write-source.js +20 -4
- package/dist/execution/input-contract.js +3 -6
- package/dist/execution/notices.js +49 -0
- package/dist/execution/resolved-request.js +24 -40
- package/dist/execution/source.js +21 -45
- package/dist/execution/target-ref.js +2 -2
- package/dist/indexer/bundle-identity-guard.js +6 -5
- package/dist/indexer/ensure-index.js +1 -1
- package/dist/indexer/index-writer-lock.js +1 -1
- package/dist/indexer/indexer.js +46 -42
- package/dist/indexer/installations.js +12 -22
- package/dist/indexer/lookup/adapter-concept-owner.js +40 -14
- package/dist/indexer/materialize-embeddings.js +20 -16
- package/dist/indexer/passes/memory-inference.js +21 -50
- package/dist/indexer/passes/metadata.js +5 -5
- package/dist/indexer/scan/doc-to-entry.js +1 -1
- package/dist/indexer/scan/drain-dir.js +9 -11
- package/dist/indexer/search/db-search.js +9 -7
- package/dist/indexer/search/search-source.js +42 -22
- package/dist/indexer/usage/usage-events.js +2 -3
- package/dist/indexer/walk/index-context.js +2 -1
- package/dist/indexer/walk/matchers.js +3 -11
- package/dist/indexer/walk/path-resolver.js +13 -68
- package/dist/indexer/walk/walker.js +32 -26
- package/dist/integrations/agent/akm-read-tools.js +81 -0
- package/dist/integrations/agent/config.js +1 -1
- package/dist/integrations/agent/detect.js +32 -36
- package/dist/integrations/agent/execution.js +130 -428
- package/dist/integrations/agent/index.js +1 -1
- package/dist/integrations/agent/profiles.js +24 -28
- package/dist/integrations/agent/prompts.js +17 -30
- package/dist/integrations/agent/request-lowering.js +67 -101
- package/dist/integrations/agent/resolution.js +332 -0
- package/dist/integrations/agent/runner-dispatch.js +156 -181
- package/dist/integrations/agent/runner.js +20 -70
- package/dist/integrations/agent/runtime-port.js +4 -0
- package/dist/integrations/agent/runtime.js +10 -0
- package/dist/integrations/agent/spawn.js +65 -190
- package/dist/integrations/harnesses/aider/index.js +19 -39
- package/dist/integrations/harnesses/amazonq/index.js +17 -32
- package/dist/integrations/harnesses/claude/index.js +59 -28
- package/dist/integrations/harnesses/claude/session-log.js +7 -5
- package/dist/integrations/harnesses/codex/index.js +19 -31
- package/dist/integrations/harnesses/copilot/index.js +17 -35
- package/dist/integrations/harnesses/gemini/index.js +18 -34
- package/dist/integrations/harnesses/harness.js +36 -0
- package/dist/integrations/harnesses/index.js +20 -51
- package/dist/integrations/harnesses/opencode/agent-builder.js +132 -100
- package/dist/integrations/harnesses/opencode/index.js +11 -33
- package/dist/integrations/harnesses/opencode/roots.js +50 -0
- package/dist/integrations/harnesses/opencode/runtime/binary.js +206 -0
- package/dist/integrations/harnesses/opencode/runtime/command.js +152 -0
- package/dist/integrations/harnesses/opencode/runtime/index.js +177 -0
- package/dist/integrations/harnesses/opencode/runtime/tar.js +107 -0
- package/dist/integrations/harnesses/opencode/session-log-v1.js +15 -7
- package/dist/integrations/harnesses/opencode/session-log-v2.js +72 -48
- package/dist/integrations/harnesses/opencode/session-log.js +58 -137
- package/dist/integrations/harnesses/opencode/version.js +34 -53
- package/dist/integrations/harnesses/openhands/index.js +13 -37
- package/dist/integrations/harnesses/pi/index.js +21 -32
- package/dist/integrations/lockfile.js +1 -1
- package/dist/integrations/session-logs/index.js +11 -31
- package/dist/llm/client.js +93 -111
- package/dist/llm/embedder.js +19 -34
- package/dist/llm/embedders/connection.js +31 -0
- package/dist/llm/embedders/local-model-files.js +56 -0
- package/dist/llm/embedders/local.js +146 -115
- package/dist/llm/embedders/remote.js +26 -23
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/index-passes.js +33 -39
- package/dist/llm/memory-infer.js +67 -77
- package/dist/llm/model-list.js +35 -0
- package/dist/llm/rerank-client.js +8 -14
- package/dist/llm/structured-call.js +45 -99
- package/dist/llm/structured.js +60 -0
- package/dist/output/renderers.js +2 -8
- package/dist/output/shapes/helpers.js +18 -44
- package/dist/output/shapes/passthrough.js +1 -0
- package/dist/output/shapes.js +1 -7
- package/dist/output/text/command-format.js +41 -39
- package/dist/output/text/health-format.js +8 -2
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +12 -0
- package/dist/output/text/models.js +3 -2
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/show-directives.js +16 -28
- package/dist/output/text/show-format.js +0 -2
- package/dist/output/text/targets-table.js +70 -0
- package/dist/output/text/workflow-format.js +44 -15
- package/dist/output/text.js +1 -1
- package/dist/registry/providers/skills-sh.js +1 -28
- package/dist/registry/providers/static-index.js +1 -71
- package/dist/runtime.js +6 -2
- package/dist/scripts/akm-migrate-node.js +47095 -44905
- package/dist/scripts/akm-migrate.js +46946 -44755
- package/dist/setup/detect.js +28 -273
- package/dist/setup/endpoint.js +36 -0
- package/dist/setup/semantic-assets.js +13 -19
- package/dist/setup/setup-flags.js +77 -0
- package/dist/setup/setup-plan.js +144 -0
- package/dist/setup/setup.js +134 -358
- package/dist/setup/steps/connection-shared.js +49 -101
- package/dist/setup/steps/connection.js +121 -524
- package/dist/setup/steps/embeddings.js +151 -0
- package/dist/setup/steps/harnesses.js +118 -0
- package/dist/setup/steps/platforms.js +2 -81
- package/dist/setup/steps/tasks.js +1 -2
- package/dist/setup/steps.js +10 -0
- package/dist/setup/summary.js +67 -0
- package/dist/sources/freshness.js +16 -21
- package/dist/sources/providers/git-provider.js +2 -9
- package/dist/sources/providers/provider-utils.js +13 -5
- package/dist/sources/resolve.js +12 -16
- package/dist/sources/snapshot-fetchers/robots.js +11 -12
- package/dist/sources/snapshot-fetchers/secret-seam.js +2 -4
- package/dist/sources/snapshot-fetchers/website-ingest.js +8 -15
- package/dist/sources/snapshot-fetchers/x.js +1 -1
- package/dist/standalone/onnxruntime-library.js +52 -0
- package/dist/storage/repositories/improve-ledger-repository.js +1 -1
- package/dist/storage/repositories/improve-runs-repository.js +0 -13
- package/dist/storage/repositories/index-entries-repository.js +2 -2
- package/dist/storage/repositories/proposals-repository.js +2 -2
- package/dist/storage/repositories/task-history-repository.js +2 -2
- package/dist/storage/repositories/workflow-runs-repository.js +44 -129
- package/dist/storage/sqlite-read-snapshot.js +33 -29
- package/dist/tasks/embedded.js +1 -1
- package/dist/tasks/log-redaction.js +7 -119
- package/dist/tasks/prepare/prepare-support.js +8 -7
- package/dist/tasks/prepare/prepare.js +8 -9
- package/dist/tasks/prepare/script-capture.js +9 -25
- package/dist/tasks/run/attempt-lifecycle.js +17 -35
- package/dist/tasks/run/load-task.js +54 -27
- package/dist/tasks/run/run-command-task.js +15 -10
- package/dist/tasks/run/run-native-task.js +8 -21
- package/dist/tasks/run/run-task.js +5 -8
- package/dist/tasks/run/run-workflow-task.js +14 -21
- package/dist/tasks/run/task-history.js +9 -17
- package/dist/tasks/run/task-log.js +1 -5
- package/dist/tasks/scheduler-invocation.js +13 -18
- package/dist/tasks/scheduler-sync.js +0 -1
- package/dist/tasks/source/bounded-document.js +31 -117
- package/dist/tasks/source/parse-task-source.js +0 -1
- package/dist/tasks/source/project-v4.js +5 -3
- package/dist/tasks/source/task-input-diagnostics.js +3 -4
- package/dist/tasks/source/task-source-v4.js +48 -72
- package/dist/tasks/source-v3.js +2 -5
- package/dist/tasks/task-run-reserved-flags.js +1 -2
- package/dist/workflows/authoring/authoring.js +2 -7
- package/dist/workflows/compile.js +1 -15
- package/dist/workflows/concurrency-policy.js +4 -4
- package/dist/workflows/exec/child-workflow.js +1 -4
- package/dist/workflows/exec/dispatch-redaction.js +35 -86
- package/dist/workflows/exec/environment.js +13 -77
- package/dist/workflows/exec/exec-unit.js +14 -55
- package/dist/workflows/exec/frozen-judge.js +26 -28
- package/dist/workflows/exec/native-executor.js +126 -71
- package/dist/workflows/exec/run-workflow.js +38 -42
- package/dist/workflows/exec/step-work.js +13 -17
- package/dist/workflows/exec/unit-dispatch.js +7 -21
- package/dist/workflows/freeze/environment.js +14 -119
- package/dist/workflows/freeze/freeze.js +14 -33
- package/dist/workflows/freeze/step-values.js +6 -13
- package/dist/workflows/freeze/targets/child-workflow.js +3 -5
- package/dist/workflows/freeze/targets/command.js +9 -15
- package/dist/workflows/freeze/targets/shell.js +0 -3
- package/dist/workflows/freeze/task-bindings.js +0 -1
- package/dist/workflows/parser.js +50 -59
- package/dist/workflows/plan.js +8 -2
- package/dist/workflows/renderer.js +83 -9
- package/dist/workflows/resource-limits.js +2 -2
- package/dist/workflows/runtime/run-plan.js +38 -23
- package/dist/workflows/runtime/runs.js +55 -93
- package/dist/workflows/runtime/workflow-asset-loader.js +55 -86
- package/dist/workflows/source-files.js +7 -19
- package/docs/README.md +10 -16
- package/docs/architecture/README.md +2 -37
- package/docs/architecture/adapters.md +2 -2
- package/docs/architecture/akm-core-principles.md +256 -118
- package/docs/architecture/architecture.md +283 -99
- package/docs/architecture/improvement.md +8 -2
- package/docs/architecture/internals/classification.md +1 -3
- package/docs/architecture/internals/fresh-host-rebuild-runbook.md +7 -6
- package/docs/architecture/internals/health-advisories.md +35 -74
- package/docs/architecture/internals/improve-workflow.md +27 -26
- package/docs/architecture/internals/indexing.md +3 -3
- package/docs/architecture/internals/storage-locations.md +48 -15
- package/docs/architecture/persisted-data-compat.md +10 -6
- package/docs/architecture/specs/okf-support.md +9 -11
- package/docs/architecture/specs/ref.md +7 -9
- package/docs/architecture/testing/testing-workflow.md +172 -52
- package/docs/architecture/workflow-engine.md +51 -34
- package/docs/guides/README.md +0 -1
- package/docs/guides/author-bundles.md +15 -20
- package/docs/guides/author-workflows.md +93 -71
- package/docs/guides/bundles.md +20 -16
- package/docs/guides/discover-and-load.md +1 -1
- package/docs/guides/getting-started.md +49 -3
- package/docs/guides/improve-the-library.md +10 -4
- package/docs/guides/recipes/headless-install.md +11 -16
- package/docs/guides/run-workflows.md +29 -20
- package/docs/guides/scheduling.md +2 -2
- package/docs/guides/use-with-any-agent.md +20 -22
- package/docs/integration/bundling-akm.md +19 -9
- package/docs/maintainers/README.md +2 -6
- package/docs/maintainers/release-checklist.md +73 -126
- package/docs/migration/README.md +10 -3
- package/docs/migration/release-notes/0.10.0.md +81 -16
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0.md +1 -1
- package/docs/migration/release-notes/README.md +1 -1
- package/docs/migration/v0.9-to-v0.10.md +300 -60
- package/docs/migration/v0.9.1-to-v0.9.2.md +6 -5
- package/docs/posts/config-v2-lessons-16.md +1 -1
- package/docs/posts/llm-mode-reflect-13.md +1 -1
- package/docs/posts/release-0.6.0.md +11 -11
- package/docs/posts/release-0.8.0.md +4 -4
- package/docs/posts/workflows-vaults-09.md +1 -1
- package/docs/reference/README.md +1 -1
- package/docs/reference/asset-types.md +2 -2
- package/docs/reference/cli.md +477 -388
- package/docs/reference/configuration.md +569 -550
- package/docs/reference/data-and-telemetry.md +9 -6
- package/docs/reference/env-and-secrets.md +72 -15
- package/docs/reference/refs.md +3 -20
- package/docs/reference/registry.md +12 -38
- package/docs/reference/supported-formats.md +1 -1
- package/docs/reference/tasks.md +22 -11
- package/docs/reference/ui-api.md +14 -9
- package/docs/reference/workflow-schema.md +199 -154
- package/docs/reference/workflows.md +4 -6
- package/package.json +9 -11
- package/schemas/akm-config.json +1696 -2922
- package/schemas/akm-task.json +4 -4
- package/schemas/akm-workflow.json +36 -9
- package/ROADMAP.md +0 -112
- package/dist/assets/models.json +0 -35
- package/dist/commands/health/engine-usage.js +0 -45
- package/dist/commands/sources/dangerous-env-audit.js +0 -134
- package/dist/core/activation-policy.js +0 -67
- package/dist/core/config/schema/engines.js +0 -160
- package/dist/core/config/schema/execution.js +0 -23
- package/dist/core/spawn-env.js +0 -268
- package/dist/execution/directory-identity.js +0 -78
- package/dist/integrations/agent/builder-shared.js +0 -33
- package/dist/integrations/agent/builders.js +0 -43
- package/dist/integrations/agent/engine-fallback.js +0 -59
- package/dist/integrations/agent/engine-resolution.js +0 -333
- package/dist/integrations/agent/model-map.js +0 -475
- package/dist/integrations/harnesses/aider/agent-builder.js +0 -107
- package/dist/integrations/harnesses/aider/result-extractor.js +0 -53
- package/dist/integrations/harnesses/amazonq/agent-builder.js +0 -138
- package/dist/integrations/harnesses/amazonq/result-extractor.js +0 -48
- package/dist/integrations/harnesses/claude/agent-builder.js +0 -106
- package/dist/integrations/harnesses/claude/config-import.js +0 -67
- package/dist/integrations/harnesses/claude/result-extractor.js +0 -67
- package/dist/integrations/harnesses/codex/agent-builder.js +0 -150
- package/dist/integrations/harnesses/codex/result-extractor.js +0 -110
- package/dist/integrations/harnesses/copilot/agent-builder.js +0 -117
- package/dist/integrations/harnesses/copilot/result-extractor.js +0 -178
- package/dist/integrations/harnesses/gemini/agent-builder.js +0 -114
- package/dist/integrations/harnesses/gemini/result-extractor.js +0 -149
- package/dist/integrations/harnesses/opencode/config-import.js +0 -149
- package/dist/integrations/harnesses/opencode/model-config.js +0 -33
- package/dist/integrations/harnesses/opencode/model-work-agent.js +0 -115
- package/dist/integrations/harnesses/opencode/session-log-types.js +0 -12
- package/dist/integrations/harnesses/opencode-sdk/index.js +0 -44
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -1052
- package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +0 -199
- package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +0 -254
- package/dist/integrations/harnesses/opencode-sdk/wire.js +0 -66
- package/dist/integrations/harnesses/openhands/agent-builder.js +0 -120
- package/dist/integrations/harnesses/openhands/result-extractor.js +0 -135
- package/dist/integrations/harnesses/pi/agent-builder.js +0 -89
- package/dist/integrations/harnesses/pi/result-extractor.js +0 -168
- package/dist/integrations/harnesses/shared.js +0 -18
- package/dist/integrations/harnesses/types.js +0 -55
- package/dist/llm/feature-gate.js +0 -115
- package/dist/output/shapes/models-list.js +0 -16
- package/dist/output/shapes/registry-search.js +0 -10
- package/dist/setup/detected-engines.js +0 -136
- package/dist/setup/engine-config.js +0 -97
- package/dist/setup/harness-config-import.js +0 -47
- package/dist/setup/providers.js +0 -14
- package/dist/setup/steps/semantic.js +0 -39
- package/dist/workflows/authoring/scope-key.js +0 -79
- package/dist/workflows/exec/lowering-notices.js +0 -23
- package/dist/workflows/source-semantics.js +0 -110
- package/docs/architecture/0.10-process-rules.md +0 -832
- package/docs/architecture/akm-architecture-decision-history.md +0 -2055
- package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +0 -550
- package/docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md +0 -72
- package/docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md +0 -142
- package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +0 -189
- package/docs/architecture/decisions/0004-task-input-contract-and-flag-coercion.md +0 -97
- package/docs/architecture/decisions/0005-task-result-vocabulary-and-legacy-read-mapping.md +0 -108
- package/docs/architecture/decisions/0006-task-source-version-routing.md +0 -124
- package/docs/architecture/decisions/0007-workflow-composition-bounds.md +0 -83
- package/docs/architecture/decisions/0008-task-binding-normalization.md +0 -88
- package/docs/architecture/decisions/0009-child-run-publication-column-parity.md +0 -80
- package/docs/architecture/decisions/0010-driver-protocol-cut.md +0 -36
- package/docs/architecture/decisions/0011-engine-run-loop-invariants.md +0 -125
- package/docs/architecture/decisions/README.md +0 -62
- package/docs/architecture/internals/functional-contract-patterns.md +0 -340
- package/docs/architecture/internals/registry-network-boundary.md +0 -57
- package/docs/architecture/internals/search.md +0 -248
- package/docs/architecture/reviews/env-secret-access.md +0 -319
- package/docs/architecture/runtime-boundary-design.md +0 -153
- package/docs/architecture/specs/0.9.0-close-out-plan.md +0 -361
- package/docs/architecture/specs/0.9.0-decisions.md +0 -532
- package/docs/architecture/specs/0.9.0-docs-code-drift-register.md +0 -745
- package/docs/architecture/specs/0.9.0-open-items-register.md +0 -135
- package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +0 -740
- package/docs/architecture/specs/0.9.0-release-surface-review.md +0 -636
- package/docs/architecture/specs/agent-command-engine-model-design.md +0 -513
- package/docs/architecture/specs/akm-0.9.0-bundle-adapter-spec.md +0 -501
- package/docs/architecture/specs/akm-0.9.0-ref-grammar-decision.md +0 -291
- package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +0 -2059
- package/docs/architecture/specs/di-seams-plan.md +0 -539
- package/docs/architecture/specs/driver-protocol-keep-or-cut.md +0 -275
- package/docs/architecture/specs/fact-asset-type.md +0 -142
- package/docs/architecture/specs/improve-collapse-churn-detector-design.md +0 -634
- package/docs/architecture/specs/reviews/task-workflow-unification/README.md +0 -72
- package/docs/architecture/specs/reviews/task-workflow-unification/findings-consolidated.md +0 -152
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-alternatives.md +0 -352
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-bunqueue.md +0 -300
- package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-openworkflow.md +0 -402
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-1.md +0 -758
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-2.md +0 -645
- package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-3.md +0 -602
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-adversarial.md +0 -27
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-architecture.md +0 -27
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-coverage.md +0 -74
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-security.md +0 -15
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-adversarial.md +0 -132
- package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-verification.md +0 -147
- package/docs/architecture/specs/stash-conventions-code-spec.md +0 -275
- package/docs/architecture/specs/stash-organization-conventions.md +0 -377
- package/docs/architecture/specs/task-workflow-format-unification.md +0 -721
- package/docs/architecture/specs/workflow-engine-buy-vs-build.md +0 -231
- package/docs/architecture/specs/workflow-format-unification.md +0 -432
- package/docs/architecture/testing/manual-testing-checklist.md +0 -3411
- package/docs/architecture/testing/okf-v0.2-conformance-runbook.md +0 -924
- package/docs/architecture/testing/task-workflow-manual-tests.md +0 -1720
- package/docs/guides/claude-code-vs-akm-workflows.md +0 -29
- package/docs/guides/knowledge-management.md +0 -18
- package/docs/maintainers/curate-workmap.md +0 -335
- package/docs/migration/v0.5-to-v0.6.md +0 -759
- package/docs/migration/v0.7-to-v0.8.md +0 -1378
- package/docs/plans/0.9.2-agent-command-workflow-plan.md +0 -423
|
@@ -0,0 +1,304 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* API keys akm holds for the user: `<configDir>/credentials.json` (0600).
|
|
6
|
+
*
|
|
7
|
+
* { "connections": { "<name>": { "key": "…", "endpoint": "…" } },
|
|
8
|
+
* "services": { "embedding": { "key": "…", "endpoint": "…" },
|
|
9
|
+
* "curateRerank": { … } } }
|
|
10
|
+
*
|
|
11
|
+
* A key is stored for one endpoint and is read for that endpoint alone: the
|
|
12
|
+
* entry records the endpoint it was stored for, and a block whose endpoint is
|
|
13
|
+
* now another gets no key, with a word saying so. Only the connections this
|
|
14
|
+
* host's own config file defines have a stored key: one that only an `extends`
|
|
15
|
+
* base defines gets none, whatever the store holds under its name. A connection's name and a
|
|
16
|
+
* service's slot are different namespaces, so a connection called `embedding`
|
|
17
|
+
* and the embedding service hold different keys. A block whose `apiKey` is a
|
|
18
|
+
* `$VAR` reference reads that variable and never touches this file. The bundle's
|
|
19
|
+
* `secrets/` store is for secrets the user's own agents need and has nothing to
|
|
20
|
+
* do with these.
|
|
21
|
+
*/
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { isRecord, writeFileAtomic } from "../common.js";
|
|
25
|
+
import { sameEndpoint } from "../endpoint-url.js";
|
|
26
|
+
import { ConfigError } from "../errors.js";
|
|
27
|
+
import { getConfigDir } from "../paths.js";
|
|
28
|
+
import { collectSensitiveValues, redactUrlCredentials } from "../redaction.js";
|
|
29
|
+
import { warnOnce } from "../warn.js";
|
|
30
|
+
import { isInheritedConnection } from "./config.js";
|
|
31
|
+
import { copyToConfigBackups } from "./config-io.js";
|
|
32
|
+
import { envReferenceName, isEnvReference } from "./schema/primitives.js";
|
|
33
|
+
const NAMESPACES = ["connections", "services"];
|
|
34
|
+
function storePath() {
|
|
35
|
+
return path.join(getConfigDir(), "credentials.json");
|
|
36
|
+
}
|
|
37
|
+
function namespaceOf(slot) {
|
|
38
|
+
return "connection" in slot
|
|
39
|
+
? { group: "connections", name: slot.connection }
|
|
40
|
+
: { group: "services", name: slot.service };
|
|
41
|
+
}
|
|
42
|
+
function describeSlot(slot) {
|
|
43
|
+
return "connection" in slot ? `connection "${slot.connection}"` : `the ${slot.service} service`;
|
|
44
|
+
}
|
|
45
|
+
/** The stored entries; an absent file holds none. */
|
|
46
|
+
function readStore() {
|
|
47
|
+
let text;
|
|
48
|
+
try {
|
|
49
|
+
text = fs.readFileSync(storePath(), "utf8");
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const code = err.code;
|
|
53
|
+
return code === "ENOENT"
|
|
54
|
+
? { entries: {} }
|
|
55
|
+
: { problem: `${storePath()} cannot be read (${code ?? "unknown error"})` };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(text);
|
|
59
|
+
if (isRecord(parsed))
|
|
60
|
+
return { entries: parsed };
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Falls through: not JSON is not a JSON object.
|
|
64
|
+
}
|
|
65
|
+
return { problem: `${storePath()} is not a JSON object` };
|
|
66
|
+
}
|
|
67
|
+
function writeStore(entries) {
|
|
68
|
+
fs.mkdirSync(path.dirname(storePath()), { recursive: true, mode: 0o700 });
|
|
69
|
+
writeFileAtomic(storePath(), `${JSON.stringify(entries, null, 2)}\n`, 0o600);
|
|
70
|
+
}
|
|
71
|
+
/** The group of entries (`connections` or `services`) a store holds; anything else there is not one. */
|
|
72
|
+
function groupOf(entries, group) {
|
|
73
|
+
const held = entries[group];
|
|
74
|
+
return isRecord(held) ? held : {};
|
|
75
|
+
}
|
|
76
|
+
/** Whether a stored value is the current shape of an entry. */
|
|
77
|
+
function isStoredCredential(value) {
|
|
78
|
+
return isRecord(value) && typeof value.key === "string" && typeof value.endpoint === "string";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The stored entry for `slot`, if the store has one in the current shape. One
|
|
82
|
+
* the store has in any other shape (the flat `{ "<name>": "<key>" }` an earlier
|
|
83
|
+
* build wrote, which names no endpoint) is named once and ignored: `akm migrate
|
|
84
|
+
* apply` files it under the endpoint it belongs to.
|
|
85
|
+
*/
|
|
86
|
+
export function storedCredential(slot) {
|
|
87
|
+
const store = readStore();
|
|
88
|
+
if ("problem" in store) {
|
|
89
|
+
warnOnce(`credentials:unusable:${storePath()}`, `${store.problem}; ignoring it.`);
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const { group, name } = namespaceOf(slot);
|
|
93
|
+
const held = groupOf(store.entries, group)[name];
|
|
94
|
+
if (held === undefined) {
|
|
95
|
+
const flat = store.entries[name];
|
|
96
|
+
if (typeof flat === "string") {
|
|
97
|
+
warnOnce(`credentials:flat:${storePath()}:${name}`, `${storePath()}: the key "${name}" is in the older form, which names no endpoint, so it is not used; \`akm migrate apply\` files it under the endpoint it belongs to.`);
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
if (!isStoredCredential(held)) {
|
|
102
|
+
warnOnce(`credentials:malformed:${storePath()}:${group}:${name}`, `${storePath()}: the entry for ${describeSlot(slot)} is not { "key": "…", "endpoint": "…" }; ignoring it.`);
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return held;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The key stored for `slot` and `endpoint`. A key stored for another endpoint is
|
|
109
|
+
* not read: it is named once, without its value, and the block goes without.
|
|
110
|
+
* `storedKey` is a connection's eligibility already decided (a runner carries it);
|
|
111
|
+
* absent, the config on this host decides now.
|
|
112
|
+
*/
|
|
113
|
+
export function readCredential(slot, endpoint, storedKey) {
|
|
114
|
+
if ("connection" in slot) {
|
|
115
|
+
const eligible = storedKey ?? !isInheritedConnection(slot.connection);
|
|
116
|
+
if (!eligible)
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const held = storedCredential(slot);
|
|
120
|
+
if (!held)
|
|
121
|
+
return undefined;
|
|
122
|
+
if (!sameEndpoint(held.endpoint, endpoint)) {
|
|
123
|
+
const shownHeld = redactUrlCredentials(held.endpoint);
|
|
124
|
+
const shownNow = redactUrlCredentials(endpoint);
|
|
125
|
+
warnOnce(`credentials:endpoint:${storePath()}:${describeSlot(slot)}:${shownHeld}:${shownNow}`, `The key stored for ${describeSlot(slot)} belongs to ${shownHeld}, not ${shownNow}, so it is not sent there. Store a key for the new endpoint with \`akm setup\`.`);
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
return held.key || undefined;
|
|
129
|
+
}
|
|
130
|
+
function writableStore() {
|
|
131
|
+
const store = readStore();
|
|
132
|
+
if ("problem" in store)
|
|
133
|
+
throw new ConfigError(`${store.problem}; refusing to overwrite it.`, "INVALID_CONFIG_FILE");
|
|
134
|
+
return store.entries;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Store `key` for `slot` and `endpoint`, keeping every other entry as it is. A root
|
|
138
|
+
* `connections` or `services` that is not an object is a key of the older flat form
|
|
139
|
+
* (a connection of that name) in the namespace's place: it is kept, and the write stops,
|
|
140
|
+
* because the write would replace it. `akm migrate apply` files it under its endpoint.
|
|
141
|
+
*/
|
|
142
|
+
export function writeCredential(slot, key, endpoint) {
|
|
143
|
+
const entries = writableStore();
|
|
144
|
+
const { group, name } = namespaceOf(slot);
|
|
145
|
+
const root = entries[group];
|
|
146
|
+
if (root !== undefined && !isRecord(root)) {
|
|
147
|
+
throw new ConfigError(`${storePath()}: "${group}" holds ${typeof root === "string" ? "a key in the older form" : "something that is not an object"}, which is where the ${group} are stored, so ${describeSlot(slot)} cannot be stored beside it; refusing to overwrite it. ${typeof root === "string" ? "`akm migrate apply` files it under its endpoint; then store this key again." : "Fix the file, then store this key again."}`, "INVALID_CONFIG_FILE");
|
|
148
|
+
}
|
|
149
|
+
writeStore({ ...entries, [group]: { ...groupOf(entries, group), [name]: { key, endpoint } } });
|
|
150
|
+
}
|
|
151
|
+
/** The entries the store holds in the current shape, by slot, whatever endpoint each is stored for. */
|
|
152
|
+
export function storedCredentials() {
|
|
153
|
+
const store = readStore();
|
|
154
|
+
if ("problem" in store)
|
|
155
|
+
return [];
|
|
156
|
+
return NAMESPACES.flatMap((group) => Object.entries(groupOf(store.entries, group)).flatMap(([name, held]) => isStoredCredential(held)
|
|
157
|
+
? [
|
|
158
|
+
{
|
|
159
|
+
slot: group === "connections" ? { connection: name } : { service: name },
|
|
160
|
+
credential: held,
|
|
161
|
+
},
|
|
162
|
+
]
|
|
163
|
+
: []));
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Every migration into a credential slot asks this one question. An empty slot is
|
|
167
|
+
* `free`; one holding exactly this key for this endpoint is `same` (filing it again
|
|
168
|
+
* changes nothing); anything else, a malformed entry included, is `different`, and
|
|
169
|
+
* is never replaced: the migration leaves both and says so. `ahead` is the entry the
|
|
170
|
+
* same migration has already planned for the slot, which stands in for the store's.
|
|
171
|
+
*/
|
|
172
|
+
export function slotStanding(slot, key, endpoint, ahead) {
|
|
173
|
+
let held = ahead;
|
|
174
|
+
if (held === undefined) {
|
|
175
|
+
const store = readStore();
|
|
176
|
+
// An unusable store is not a conflict: it refuses every write, which stops the migration with nothing lost.
|
|
177
|
+
if ("problem" in store)
|
|
178
|
+
return "free";
|
|
179
|
+
const { group, name } = namespaceOf(slot);
|
|
180
|
+
held = groupOf(store.entries, group)[name];
|
|
181
|
+
}
|
|
182
|
+
if (held === undefined)
|
|
183
|
+
return "free";
|
|
184
|
+
return isStoredCredential(held) && held.key === key && sameEndpoint(held.endpoint, endpoint) ? "same" : "different";
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* File every key the store holds in the older flat `{ "<name>": "<key>" }` form under
|
|
188
|
+
* the endpoint of the block it belongs to, as `endpointOf` says: a name that is a
|
|
189
|
+
* connection's, or `curateRerank`, or `embedding` (the service's, or a connection
|
|
190
|
+
* called that: one of the two must have an endpoint, or it is not filed). `pending` is
|
|
191
|
+
* what the same migration is about to file in the current form, which a flat key is
|
|
192
|
+
* measured against like the store's own entries ({@link slotStanding}): a slot that
|
|
193
|
+
* already holds the same key for the same endpoint just drops the older entry, and one
|
|
194
|
+
* that holds anything else keeps the older entry, with a note. An entry that cannot be
|
|
195
|
+
* filed stays as it is, and a note says why. Writes only when `apply`, after copying
|
|
196
|
+
* the store to the config-backups directory (`backupPath`).
|
|
197
|
+
*/
|
|
198
|
+
export function fileFlatCredentials(endpointOf, apply, pending = []) {
|
|
199
|
+
const store = readStore();
|
|
200
|
+
if ("problem" in store)
|
|
201
|
+
return { filed: [], notes: [`${store.problem}; its keys were not filed.`] };
|
|
202
|
+
const entries = { ...store.entries };
|
|
203
|
+
const filed = [];
|
|
204
|
+
const notes = [];
|
|
205
|
+
// A key called `connections` or `services` is told from the namespace of that name by its shape: a string. It sits
|
|
206
|
+
// where the namespace goes, so it is filed first, and until it is, no other key can be filed into that namespace.
|
|
207
|
+
const rank = (name) => {
|
|
208
|
+
const index = NAMESPACES.indexOf(name);
|
|
209
|
+
return index < 0 ? NAMESPACES.length : index;
|
|
210
|
+
};
|
|
211
|
+
for (const name of Object.keys(store.entries).sort((a, b) => rank(a) - rank(b))) {
|
|
212
|
+
const value = store.entries[name];
|
|
213
|
+
if (typeof value !== "string")
|
|
214
|
+
continue;
|
|
215
|
+
const slots = name === "curateRerank"
|
|
216
|
+
? [{ service: "curateRerank" }]
|
|
217
|
+
: name === "embedding"
|
|
218
|
+
? [{ service: "embedding" }, { connection: name }]
|
|
219
|
+
: [{ connection: name }];
|
|
220
|
+
const homes = slots.flatMap((slot) => {
|
|
221
|
+
const endpoint = endpointOf(slot);
|
|
222
|
+
return endpoint === undefined ? [] : [{ slot, endpoint }];
|
|
223
|
+
});
|
|
224
|
+
const home = homes[0];
|
|
225
|
+
if (homes.length !== 1 || home === undefined) {
|
|
226
|
+
notes.push(homes.length > 1
|
|
227
|
+
? `credentials.json: the key "${name}" could belong to the embedding service or to the connection of that name; store it again with \`akm setup\`. It stays as it is.`
|
|
228
|
+
: `credentials.json: the key "${name}" belongs to no block this config gives an endpoint, so it cannot be bound to one. It stays as it is.`);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const { group, name: slotName } = namespaceOf(home.slot);
|
|
232
|
+
if (name !== group && entries[group] !== undefined && !isRecord(entries[group])) {
|
|
233
|
+
notes.push(`credentials.json: the key "${name}" cannot be filed while the key "${group}" in the older form holds the place the ${group} go. It stays as it is; give the block called "${group}" an endpoint so it can be filed first, or remove it.`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const ahead = pending.find((move) => JSON.stringify(move.slot) === JSON.stringify(home.slot));
|
|
237
|
+
const standing = slotStanding(home.slot, value, home.endpoint, ahead && { key: ahead.value, endpoint: ahead.endpoint });
|
|
238
|
+
if (standing === "different") {
|
|
239
|
+
notes.push(`credentials.json: ${describeSlot(home.slot)} already holds a different key or endpoint than the older entry "${name}"; neither is replaced. Remove the older entry when the stored one is the key you want.`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (standing === "free" || name === group) {
|
|
243
|
+
entries[group] = { ...groupOf(entries, group), [slotName]: { key: value, endpoint: home.endpoint } };
|
|
244
|
+
}
|
|
245
|
+
// The key called like its own namespace has just become that namespace's child, in the same place.
|
|
246
|
+
if (name !== group)
|
|
247
|
+
delete entries[name];
|
|
248
|
+
filed.push(name);
|
|
249
|
+
notes.push(standing === "free"
|
|
250
|
+
? `credentials.json: the key "${name}" is filed for ${describeSlot(home.slot)} at ${redactUrlCredentials(home.endpoint)}.`
|
|
251
|
+
: `credentials.json: the key "${name}" is already stored for ${describeSlot(home.slot)} at ${redactUrlCredentials(home.endpoint)}; the older entry is removed.`);
|
|
252
|
+
}
|
|
253
|
+
if (!apply || filed.length === 0)
|
|
254
|
+
return { filed, notes };
|
|
255
|
+
const backupPath = copyToConfigBackups(storePath(), (stamp) => `credentials.json.${stamp}`);
|
|
256
|
+
writeStore(entries);
|
|
257
|
+
return { filed, notes, backupPath };
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The key of the block stored in `slot`, whose config `apiKey` is `apiKey` and
|
|
261
|
+
* whose endpoint is `endpoint`: the variable it references, else the key stored
|
|
262
|
+
* for that endpoint. An unset variable is named once and sends no key. `quiet`
|
|
263
|
+
* is for callers that only collect values to redact; `storedKey` is as for
|
|
264
|
+
* {@link readCredential}.
|
|
265
|
+
*/
|
|
266
|
+
export function resolveApiKey(apiKey, slot, endpoint, env = process.env, quiet = false, storedKey) {
|
|
267
|
+
const variable = apiKey === undefined ? undefined : envReferenceName(apiKey);
|
|
268
|
+
if (variable === undefined)
|
|
269
|
+
return readCredential(slot, endpoint, storedKey);
|
|
270
|
+
const value = env[variable]?.trim();
|
|
271
|
+
if (!value && !quiet) {
|
|
272
|
+
warnOnce(`credentials:env-unset:${variable}`, `Environment variable ${variable} referenced by an apiKey is unset or empty; requests are sent without a key.`);
|
|
273
|
+
}
|
|
274
|
+
return value || undefined;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* The `$VAR` reference an `apiKey` holds. Anything else in that field is a key
|
|
278
|
+
* the user left in the file, which akm never reads or sends and never passes on.
|
|
279
|
+
*/
|
|
280
|
+
export function apiKeyReference(apiKey) {
|
|
281
|
+
return apiKey !== undefined && isEnvReference(apiKey) ? apiKey : undefined;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Every key akm could send or has been given, for redaction: whatever the store
|
|
285
|
+
* holds, the variables the config's blocks reference, and any key left in the
|
|
286
|
+
* file as written.
|
|
287
|
+
*/
|
|
288
|
+
export function collectCredentialValues(config, env = process.env) {
|
|
289
|
+
const blocks = [
|
|
290
|
+
...Object.values(config.connections ?? {}),
|
|
291
|
+
config.embedding,
|
|
292
|
+
config.search?.curateRerank,
|
|
293
|
+
];
|
|
294
|
+
return collectSensitiveValues([
|
|
295
|
+
...storedCredentials().map(({ credential }) => credential.key),
|
|
296
|
+
...blocks.flatMap((block) => {
|
|
297
|
+
const variable = block?.apiKey === undefined ? undefined : envReferenceName(block.apiKey);
|
|
298
|
+
return [
|
|
299
|
+
variable === undefined ? undefined : env[variable]?.trim(),
|
|
300
|
+
apiKeyReference(block?.apiKey) === undefined ? block?.apiKey : undefined,
|
|
301
|
+
];
|
|
302
|
+
}),
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
@@ -16,8 +16,8 @@ export const REMOVED_IMPROVE_STRATEGY_NAMES = [
|
|
|
16
16
|
"proactive-maintenance",
|
|
17
17
|
];
|
|
18
18
|
/**
|
|
19
|
-
* The improve processes that
|
|
20
|
-
* each of the others makes the process's own
|
|
19
|
+
* The improve processes that make model calls, so each runs on a connection or
|
|
20
|
+
* harness. Triage's is its judgment's; each of the others makes the process's own calls.
|
|
21
21
|
*/
|
|
22
22
|
export const IMPROVE_ENGINE_PROCESSES = [
|
|
23
23
|
"reflect",
|
|
@@ -28,5 +28,5 @@ export const IMPROVE_ENGINE_PROCESSES = [
|
|
|
28
28
|
"validation",
|
|
29
29
|
"triage",
|
|
30
30
|
];
|
|
31
|
-
/** Every improve process, in plan order: the
|
|
31
|
+
/** Every improve process, in plan order: the model-calling processes and `proactiveMaintenance`, which makes no model call. */
|
|
32
32
|
export const IMPROVE_PROCESS_NAMES = [...IMPROVE_ENGINE_PROCESSES, "proactiveMaintenance"];
|
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* `akm migrate apply`'s key step. akm reads a key from a `$VAR` reference or
|
|
6
|
+
* from `credentials.json`, nowhere else. The keys a config still holds in any
|
|
7
|
+
* other form are read once, filed in `credentials.json` for the block they
|
|
8
|
+
* belong to (a connection, or the `embedding` or `curateRerank` service) and the
|
|
9
|
+
* endpoint that block names, and removed from the document:
|
|
10
|
+
*
|
|
11
|
+
* - an engine's `apiKeyFile`, a literal `apiKey` or a `${secrets/...}` one,
|
|
12
|
+
* which the conversion to a connection cannot carry;
|
|
13
|
+
* - the same literal or secret-store `apiKey` on a connection, `embedding` or
|
|
14
|
+
* `search.curateRerank` that the file already says in the current form.
|
|
15
|
+
*
|
|
16
|
+
* A key that cannot be read, whose block names no endpoint to file it for, whose
|
|
17
|
+
* engine wrote it for another endpoint than the connection now names (the file's
|
|
18
|
+
* current-form entries overlay the converted engine), or whose slot
|
|
19
|
+
* `credentials.json` already holds a different key or endpoint for, is not moved. It stays in the file: on the block that held
|
|
20
|
+
* it, or, for an engine's `apiKey`, put back on the connection as `apiKey`
|
|
21
|
+
* exactly as it was, so the rewrite never leaves it in the backups alone (akm
|
|
22
|
+
* ignores a key held there, with a warning, until it is moved). An engine's
|
|
23
|
+
* `apiKeyFile` is the exception: it names a file, not a key, and a connection
|
|
24
|
+
* cannot hold it (an unowned key is sent as a request field), so a note names
|
|
25
|
+
* the path. A note names the key's place and its entry, never its value.
|
|
26
|
+
*/
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import os from "node:os";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import { isRecord } from "../common.js";
|
|
31
|
+
import { sameEndpoint } from "../endpoint-url.js";
|
|
32
|
+
import { redactUrlCredentials } from "../redaction.js";
|
|
33
|
+
import { isSecretRefToken } from "../ref-token.js";
|
|
34
|
+
import { slotStanding } from "./credentials.js";
|
|
35
|
+
import { isEnvReference } from "./schema/primitives.js";
|
|
36
|
+
/** An `apiKeyFile`'s key: the file's contents less one trailing newline, `~` expanded as it always was. */
|
|
37
|
+
function readKeyFile(file) {
|
|
38
|
+
try {
|
|
39
|
+
const expanded = file.startsWith("~") ? path.join(os.homedir(), file.slice(1)) : file;
|
|
40
|
+
return fs.readFileSync(expanded, "utf8").replace(/\r?\n$/, "") || undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** A key held in the config itself: the literal, or the secret it references. */
|
|
47
|
+
function heldValue(value, options) {
|
|
48
|
+
if (!isSecretRefToken(value))
|
|
49
|
+
return value;
|
|
50
|
+
try {
|
|
51
|
+
return options.resolveSecretRef?.(value) || undefined;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** The keys of the converted document that are not `$VAR` references. */
|
|
58
|
+
function documentCandidates(config, options) {
|
|
59
|
+
const blocks = [];
|
|
60
|
+
if (isRecord(config.connections)) {
|
|
61
|
+
for (const [name, block] of Object.entries(config.connections)) {
|
|
62
|
+
if (isRecord(block))
|
|
63
|
+
blocks.push({ name, slot: { connection: name }, from: `connections.${name}.apiKey`, block });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const search = isRecord(config.search) ? config.search : {};
|
|
67
|
+
if (isRecord(config.embedding)) {
|
|
68
|
+
blocks.push({
|
|
69
|
+
name: "embedding",
|
|
70
|
+
slot: { service: "embedding" },
|
|
71
|
+
from: "embedding.apiKey",
|
|
72
|
+
block: config.embedding,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (isRecord(search.curateRerank)) {
|
|
76
|
+
blocks.push({
|
|
77
|
+
name: "curateRerank",
|
|
78
|
+
slot: { service: "curateRerank" },
|
|
79
|
+
from: "search.curateRerank.apiKey",
|
|
80
|
+
block: search.curateRerank,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return blocks.flatMap(({ name, slot, from, block }) => {
|
|
84
|
+
const endpoint = typeof block.endpoint === "string" ? block.endpoint : undefined;
|
|
85
|
+
const found = [];
|
|
86
|
+
const apiKey = block.apiKey;
|
|
87
|
+
if (typeof apiKey === "string" && !isEnvReference(apiKey)) {
|
|
88
|
+
found.push({
|
|
89
|
+
name,
|
|
90
|
+
slot,
|
|
91
|
+
endpoint,
|
|
92
|
+
from,
|
|
93
|
+
read: () => heldValue(apiKey, options),
|
|
94
|
+
source: isSecretRefToken(apiKey) ? "the secret store reference" : "the literal key",
|
|
95
|
+
remove: () => {
|
|
96
|
+
delete block.apiKey;
|
|
97
|
+
},
|
|
98
|
+
stays: "in the file",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// A key file an earlier run could not read is still named here, so a later run can move its key.
|
|
102
|
+
const keyFile = block.apiKeyFile;
|
|
103
|
+
if (typeof keyFile === "string" && "connection" in slot) {
|
|
104
|
+
found.push({
|
|
105
|
+
name,
|
|
106
|
+
slot,
|
|
107
|
+
endpoint,
|
|
108
|
+
from: from.replace(/apiKey$/, "apiKeyFile"),
|
|
109
|
+
read: () => readKeyFile(keyFile),
|
|
110
|
+
source: `its key file ${keyFile}`,
|
|
111
|
+
remove: () => {
|
|
112
|
+
delete block.apiKeyFile;
|
|
113
|
+
},
|
|
114
|
+
stays: "in the file",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return found;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** The endpoint the document gives the block that `slot` stands for, if it gives one. */
|
|
121
|
+
export function documentEndpoint(config, slot) {
|
|
122
|
+
const search = isRecord(config.search) ? config.search : {};
|
|
123
|
+
const block = "connection" in slot
|
|
124
|
+
? isRecord(config.connections)
|
|
125
|
+
? config.connections[slot.connection]
|
|
126
|
+
: undefined
|
|
127
|
+
: slot.service === "embedding"
|
|
128
|
+
? config.embedding
|
|
129
|
+
: search.curateRerank;
|
|
130
|
+
return isRecord(block) && typeof block.endpoint === "string" ? block.endpoint : undefined;
|
|
131
|
+
}
|
|
132
|
+
/** The keys the conversion could not carry from an engine, read from the original document. */
|
|
133
|
+
function engineCandidates(original, converted, config, options) {
|
|
134
|
+
const engines = isRecord(original.engines) ? original.engines : {};
|
|
135
|
+
return converted.keys.flatMap(({ engine, connection, field }) => {
|
|
136
|
+
const source = isRecord(engines[engine]) ? engines[engine] : {};
|
|
137
|
+
const held = source[field];
|
|
138
|
+
if (typeof held !== "string")
|
|
139
|
+
return [];
|
|
140
|
+
const owner = typeof source.endpoint === "string" ? source.endpoint : undefined;
|
|
141
|
+
if (field === "apiKeyFile") {
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
name: connection,
|
|
145
|
+
slot: { connection },
|
|
146
|
+
endpoint: documentEndpoint(config, { connection }),
|
|
147
|
+
owner,
|
|
148
|
+
from: `engines.${engine}.${field}`,
|
|
149
|
+
read: () => readKeyFile(held),
|
|
150
|
+
source: `its key file ${held}`,
|
|
151
|
+
restore: () => {
|
|
152
|
+
const block = isRecord(config.connections) ? config.connections[connection] : undefined;
|
|
153
|
+
if (isRecord(block))
|
|
154
|
+
block.apiKeyFile = held;
|
|
155
|
+
},
|
|
156
|
+
stays: "on the connection as apiKeyFile",
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
return [
|
|
161
|
+
{
|
|
162
|
+
name: connection,
|
|
163
|
+
slot: { connection },
|
|
164
|
+
endpoint: documentEndpoint(config, { connection }),
|
|
165
|
+
owner,
|
|
166
|
+
from: `engines.${engine}.${field}`,
|
|
167
|
+
read: () => heldValue(held, options),
|
|
168
|
+
source: isSecretRefToken(held) ? `the secret store reference ${held}` : "the literal key",
|
|
169
|
+
restore: () => {
|
|
170
|
+
const block = isRecord(config.connections) ? config.connections[connection] : undefined;
|
|
171
|
+
if (isRecord(block))
|
|
172
|
+
block.apiKey = held;
|
|
173
|
+
},
|
|
174
|
+
stays: "on the connection as apiKey",
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
export function planKeyMoves(original, converted, options = {}) {
|
|
180
|
+
const config = structuredClone(converted.config);
|
|
181
|
+
const planned = new Map();
|
|
182
|
+
const moves = [];
|
|
183
|
+
const notes = [];
|
|
184
|
+
for (const candidate of [
|
|
185
|
+
...engineCandidates(original, converted, config, options),
|
|
186
|
+
...documentCandidates(config, options),
|
|
187
|
+
]) {
|
|
188
|
+
const stays = ` It stays ${candidate.stays}.`;
|
|
189
|
+
const { owner } = candidate;
|
|
190
|
+
if (owner !== undefined && candidate.endpoint !== undefined && !sameEndpoint(owner, candidate.endpoint)) {
|
|
191
|
+
candidate.restore?.();
|
|
192
|
+
notes.push(`${candidate.from}: this key was written for ${redactUrlCredentials(owner)}, and the file now gives "${candidate.name}" ${redactUrlCredentials(candidate.endpoint)}, so no key moves and it is not sent there.${stays} Store a key for the new endpoint with \`akm setup\`.`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const value = candidate.read();
|
|
196
|
+
if (value === undefined) {
|
|
197
|
+
candidate.restore?.();
|
|
198
|
+
notes.push(`${candidate.from}: ${candidate.source} could not be read, so no key moves and this is still to do.${stays} A later \`akm migrate apply\` moves it once it can be read.`);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const { endpoint } = candidate;
|
|
202
|
+
if (endpoint === undefined) {
|
|
203
|
+
candidate.restore?.();
|
|
204
|
+
notes.push(`${candidate.from}: the block names no endpoint of its own, so its key cannot be filed for one and no key moves.${stays} Store it for the endpoint that serves it with \`akm setup\`.`);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const slotKey = JSON.stringify(candidate.slot);
|
|
208
|
+
const earlier = planned.get(slotKey);
|
|
209
|
+
const standing = slotStanding(candidate.slot, value, endpoint, earlier && { key: earlier.value, endpoint: earlier.endpoint });
|
|
210
|
+
if (standing === "different") {
|
|
211
|
+
candidate.restore?.();
|
|
212
|
+
notes.push(`${candidate.from}: credentials.json already holds a different key or endpoint for "${candidate.name}"; it is not replaced.${stays}`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const move = { slot: candidate.slot, endpoint, name: candidate.name, value, from: candidate.from };
|
|
216
|
+
planned.set(slotKey, move);
|
|
217
|
+
moves.push(move);
|
|
218
|
+
notes.push(`${candidate.from}: its key moves to credentials.json for "${candidate.name}" at ${redactUrlCredentials(endpoint)}.`);
|
|
219
|
+
candidate.remove?.();
|
|
220
|
+
}
|
|
221
|
+
return { config, moves, notes };
|
|
222
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* The model aliases akm itself speaks: a call asks for a model by one of these
|
|
6
|
+
* (or by an exact name), and each connection or harness says which of its own
|
|
7
|
+
* models answers to them.
|
|
8
|
+
*/
|
|
9
|
+
export const STANDARD_ALIASES = ["fast", "balanced", "reasoning"];
|
|
10
|
+
/**
|
|
11
|
+
* The alias each kind of call asks for when nothing nearer (an asset, a process,
|
|
12
|
+
* a flag) names a model. It is the farthest layer of the call.
|
|
13
|
+
*/
|
|
14
|
+
export const SHIPPED_ALIAS = {
|
|
15
|
+
/** Improve's passes: reflect, distill, consolidate, memory inference, validation. */
|
|
16
|
+
improve: "fast",
|
|
17
|
+
/** Judges: the reflect and distill quality gates, the triage judgment, a workflow gate. */
|
|
18
|
+
judge: "balanced",
|
|
19
|
+
/** Agent work: `akm agent`, `command run`, `proposal new`, task dispatch, workflow agent units. */
|
|
20
|
+
agent: "balanced",
|
|
21
|
+
extract: "fast",
|
|
22
|
+
enrich: "fast",
|
|
23
|
+
/** The index passes. */
|
|
24
|
+
index: "fast",
|
|
25
|
+
};
|
|
26
|
+
/** One model answering to every standard alias: what a connection with a single model has. */
|
|
27
|
+
export function modelForEveryAlias(model) {
|
|
28
|
+
return { fast: model, balanced: model, reasoning: model };
|
|
29
|
+
}
|
|
30
|
+
/** The model name an alias entry holds. */
|
|
31
|
+
export function entryModel(entry) {
|
|
32
|
+
return typeof entry === "string" ? entry : entry.model;
|
|
33
|
+
}
|
|
34
|
+
/** The entry for `entry` once it names `model`: an entry with request fields keeps them. */
|
|
35
|
+
export function entryWithModel(entry, model) {
|
|
36
|
+
return typeof entry === "object" ? { ...entry, model } : model;
|
|
37
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* The user's `models.json` (retired): `<configDir>/models.json`, an alias to
|
|
6
|
+
* model table per engine. It is read, in memory, as part of the conversion of
|
|
7
|
+
* the `engines` form ({@link convertEngineConfig}); `akm migrate apply` moves it
|
|
8
|
+
* out of the config directory once its aliases are folded into the config.
|
|
9
|
+
*/
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { getConfigDir } from "../paths.js";
|
|
13
|
+
import { copyToConfigBackups } from "./config-io.js";
|
|
14
|
+
function modelsFilePath() {
|
|
15
|
+
return path.join(getConfigDir(), "models.json");
|
|
16
|
+
}
|
|
17
|
+
/** The user's `models.json`, parsed; absent or unreadable is no overlay. */
|
|
18
|
+
export function readModelsFile() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(modelsFilePath(), "utf8"));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function modelsFileExists() {
|
|
27
|
+
return fs.existsSync(modelsFilePath());
|
|
28
|
+
}
|
|
29
|
+
/** Move `models.json` to `<cacheDir>/config-backups/models.json.<timestamp>` and return where it went. */
|
|
30
|
+
export function retireModelsFile(now = new Date()) {
|
|
31
|
+
const target = copyToConfigBackups(modelsFilePath(), (stamp) => `models.json.${stamp}`, now);
|
|
32
|
+
fs.rmSync(modelsFilePath());
|
|
33
|
+
return target;
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* What the engine names of the retired `engines` form became, kept in the data
|
|
6
|
+
* directory (`migrate/engine-targets.json`) after `akm migrate apply` has rewritten
|
|
7
|
+
* the config. The conversion renames the engine `defaults` named to `default` and
|
|
8
|
+
* moves an old `default` aside, so an `engine:` selector that is still in a file the
|
|
9
|
+
* migration has not rewritten (a read-only bundle, a block it could not splice) keeps
|
|
10
|
+
* its meaning only if the old names outlive the config that had them. The migrator
|
|
11
|
+
* writes and removes this file; the resolver of `engine:` selectors reads it
|
|
12
|
+
* ({@link retiredEngineTargets}); nothing else does.
|
|
13
|
+
*/
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { isRecord } from "../common.js";
|
|
17
|
+
import { getDataDir } from "../paths.js";
|
|
18
|
+
function rememberedPath() {
|
|
19
|
+
return path.join(getDataDir(), "migrate", "engine-targets.json");
|
|
20
|
+
}
|
|
21
|
+
/** An old engine name's entry, when it is `{ "connection": "<name>" }` or `{ "harness": "<name>" }`. */
|
|
22
|
+
function targetOf(value) {
|
|
23
|
+
if (!isRecord(value))
|
|
24
|
+
return undefined;
|
|
25
|
+
if (typeof value.connection === "string" && value.connection)
|
|
26
|
+
return { connection: value.connection };
|
|
27
|
+
if (typeof value.harness === "string" && value.harness)
|
|
28
|
+
return { harness: value.harness };
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
/** The names an earlier run's config step recorded; a file that is not a name map, or an entry that is not a target, holds none. */
|
|
32
|
+
export function rememberedEngineTargets() {
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(fs.readFileSync(rememberedPath(), "utf8"));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(parsed))
|
|
41
|
+
return {};
|
|
42
|
+
return Object.fromEntries(Object.entries(parsed).flatMap(([engine, value]) => {
|
|
43
|
+
const target = targetOf(value);
|
|
44
|
+
return target ? [[engine, target]] : [];
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
export function rememberEngineTargets(targets) {
|
|
48
|
+
fs.mkdirSync(path.dirname(rememberedPath()), { recursive: true });
|
|
49
|
+
fs.writeFileSync(rememberedPath(), `${JSON.stringify(targets, null, 2)}\n`);
|
|
50
|
+
}
|
|
51
|
+
export function forgetEngineTargets() {
|
|
52
|
+
fs.rmSync(rememberedPath(), { force: true });
|
|
53
|
+
}
|