akm-cli 0.8.0-rc1 → 0.8.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/{.github/CHANGELOG.md → CHANGELOG.md} +191 -3
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +93 -3
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2162 -1258
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +20 -12
- package/dist/commands/agent-support.js +11 -5
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +129 -517
- package/dist/commands/consolidate.js +1533 -144
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +5 -3
- package/dist/commands/distill.js +906 -100
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +3 -0
- package/dist/commands/events.js +3 -0
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +260 -5
- package/dist/commands/health.js +977 -51
- package/dist/commands/help/help-accept.md +6 -3
- package/dist/commands/help/help-improve.md +36 -8
- package/dist/commands/help/help-proposals.md +7 -4
- package/dist/commands/help/help-reject.md +5 -2
- package/dist/commands/history.js +51 -16
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +236 -0
- package/dist/commands/improve-profiles.js +184 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +1725 -332
- package/dist/commands/info.js +3 -0
- package/dist/commands/init.js +49 -1
- package/dist/commands/installed-stashes.js +6 -23
- package/dist/commands/knowledge.js +3 -0
- package/dist/commands/lint/agent-linter.js +3 -0
- package/dist/commands/lint/base-linter.js +233 -5
- package/dist/commands/lint/command-linter.js +3 -0
- package/dist/commands/lint/default-linter.js +3 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +92 -3
- package/dist/commands/lint/knowledge-linter.js +3 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +3 -0
- package/dist/commands/lint/registry.js +3 -0
- package/dist/commands/lint/skill-linter.js +3 -0
- package/dist/commands/lint/task-linter.js +15 -12
- package/dist/commands/lint/types.js +3 -0
- package/dist/commands/lint/workflow-linter.js +3 -0
- package/dist/commands/lint.js +3 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal-drain-policies.js +128 -0
- package/dist/commands/proposal-drain.js +477 -0
- package/dist/commands/proposal.js +60 -6
- package/dist/commands/propose.js +24 -19
- package/dist/commands/reflect.js +1004 -94
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +3 -0
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +15 -6
- package/dist/commands/schema-repair.js +88 -15
- package/dist/commands/search.js +99 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +32 -13
- package/dist/commands/source-add.js +7 -35
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +3 -0
- package/dist/commands/tasks.js +161 -95
- package/dist/commands/url-checker.js +3 -0
- package/dist/core/action-contributors.js +3 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +9 -2
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +61 -5
- package/dist/core/common.js +93 -5
- package/dist/core/concurrent.js +3 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +558 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +366 -1077
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +31 -25
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -10
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +3 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +142 -14
- package/dist/core/parse.js +3 -0
- package/dist/core/paths.js +218 -50
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +11 -3
- package/dist/core/proposals.js +464 -5
- package/dist/core/state-db.js +349 -56
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +3 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +7 -2
- package/dist/core/write-source.js +12 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +136 -28
- package/dist/indexer/db.js +662 -166
- package/dist/indexer/ensure-index.js +3 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +162 -40
- package/dist/indexer/graph-db.js +241 -51
- package/dist/indexer/graph-dedup.js +3 -7
- package/dist/indexer/graph-extraction.js +242 -149
- package/dist/indexer/index-context.js +3 -9
- package/dist/indexer/indexer.js +84 -14
- package/dist/indexer/llm-cache.js +24 -19
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +184 -11
- package/dist/indexer/memory-inference.js +94 -50
- package/dist/indexer/metadata-contributors.js +3 -0
- package/dist/indexer/metadata.js +114 -48
- package/dist/indexer/path-resolver.js +3 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +134 -7
- package/dist/indexer/ranking.js +8 -1
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +91 -2
- package/dist/indexer/search-source.js +20 -1
- package/dist/indexer/semantic-status.js +4 -1
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +3 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +121 -401
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +6 -14
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +3 -0
- package/dist/integrations/agent/prompts.js +137 -8
- package/dist/integrations/agent/runner.js +208 -0
- package/dist/integrations/agent/sdk-runner.js +8 -2
- package/dist/integrations/agent/spawn.js +54 -14
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +22 -51
- package/dist/integrations/session-logs/index.js +4 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +226 -0
- package/dist/integrations/session-logs/providers/opencode.js +231 -25
- package/dist/integrations/session-logs/types.js +3 -0
- package/dist/llm/call-ai.js +14 -26
- package/dist/llm/client.js +16 -2
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +92 -56
- package/dist/llm/graph-extract.js +401 -30
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +30 -2
- package/dist/llm/metadata-enhance.js +3 -7
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +24 -1
- package/dist/output/cli-hints-full.md +60 -32
- package/dist/output/cli-hints-short.md +10 -7
- package/dist/output/cli-hints.js +5 -2
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +170 -194
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +105 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -549
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1059 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +12 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1329
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +11 -2
- package/dist/registry/providers/static-index.js +10 -1
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17767 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +306 -67
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +3 -11
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +171 -21
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +3 -0
- package/dist/tasks/backends/cron.js +3 -0
- package/dist/tasks/backends/exec-utils.js +3 -0
- package/dist/tasks/backends/index.js +3 -11
- package/dist/tasks/backends/launchd.js +3 -0
- package/dist/tasks/backends/schtasks.js +3 -0
- package/dist/tasks/parser.js +51 -38
- package/dist/tasks/resolveAkmBin.js +3 -0
- package/dist/tasks/runner.js +35 -9
- package/dist/tasks/schedule.js +20 -1
- package/dist/tasks/schema.js +5 -3
- package/dist/tasks/validator.js +6 -3
- package/dist/version.js +3 -0
- package/dist/wiki/wiki-templates.js +3 -0
- package/dist/wiki/wiki.js +3 -0
- package/dist/workflows/authoring.js +3 -0
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +140 -10
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +3 -0
- package/dist/workflows/runs.js +18 -1
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +5 -9
- package/docs/README.md +7 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +57 -5
- package/docs/migration/v0.7-to-v0.8.md +1378 -0
- package/package.json +28 -11
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -385
- package/dist/commands/vault.js +0 -307
- package/dist/indexer/match-contributors.js +0 -141
- package/dist/integrations/agent/pipeline.js +0 -39
- package/dist/integrations/agent/runners.js +0 -31
|
@@ -0,0 +1,337 @@
|
|
|
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
|
+
* Dotted-path getter/setter/unsetter for AKM config objects, driven by the
|
|
6
|
+
* Zod schema in `./config-schema.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Replaces the per-key switch statement that used to live in
|
|
9
|
+
* `src/commands/config-cli.ts`. Adding a new config field is now zero lines
|
|
10
|
+
* of CLI code — the schema describes how to get/set/unset, and this walker
|
|
11
|
+
* does the rest.
|
|
12
|
+
*
|
|
13
|
+
* Coercion rules for `configSet`:
|
|
14
|
+
* - z.string() → use raw value
|
|
15
|
+
* - z.number() → Number(value), must be finite
|
|
16
|
+
* - z.boolean() → "true" | "false" (case-sensitive)
|
|
17
|
+
* - z.enum([...]) → must match one of the literal values
|
|
18
|
+
* - z.array(...) → JSON-parse value, expect array, validate items
|
|
19
|
+
* - z.object({...}) → JSON-parse value, expect object, schema-validate
|
|
20
|
+
* - z.union([a,b,...]) → try each branch in order
|
|
21
|
+
* - z.record(...) → JSON-parse value, validate
|
|
22
|
+
*/
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
import { AkmConfigBaseSchema, listTopLevelConfigKeys } from "./config-schema";
|
|
25
|
+
import { UsageError } from "./errors";
|
|
26
|
+
/**
|
|
27
|
+
* Parse a dotted path into segments. Empty segments are rejected. Bracket
|
|
28
|
+
* notation (e.g. `sources[0]`) is NOT supported — arrays are set as JSON.
|
|
29
|
+
*/
|
|
30
|
+
function parsePath(dotted) {
|
|
31
|
+
if (!dotted) {
|
|
32
|
+
throw new UsageError("Config key is required.", "INVALID_FLAG_VALUE", unknownKeyHint(""));
|
|
33
|
+
}
|
|
34
|
+
const segments = dotted.split(".");
|
|
35
|
+
if (segments.some((s) => !s)) {
|
|
36
|
+
throw new UsageError(`Invalid config key "${dotted}": empty segment between dots.`, "INVALID_FLAG_VALUE", unknownKeyHint(dotted));
|
|
37
|
+
}
|
|
38
|
+
return segments;
|
|
39
|
+
}
|
|
40
|
+
/** Strip Zod wrappers down to the inner schema. */
|
|
41
|
+
function unwrap(schema) {
|
|
42
|
+
let current = schema;
|
|
43
|
+
while (true) {
|
|
44
|
+
if (current instanceof z.ZodOptional) {
|
|
45
|
+
current = current._def.innerType;
|
|
46
|
+
}
|
|
47
|
+
else if (current instanceof z.ZodDefault) {
|
|
48
|
+
current = current._def.innerType;
|
|
49
|
+
}
|
|
50
|
+
else if (current instanceof z.ZodNullable) {
|
|
51
|
+
current = current._def.innerType;
|
|
52
|
+
}
|
|
53
|
+
else if (current instanceof z.ZodCatch) {
|
|
54
|
+
// `.catch(...)` wraps an inner schema with a fallback value.
|
|
55
|
+
current = current._def.innerType;
|
|
56
|
+
}
|
|
57
|
+
else if (current instanceof z.ZodEffects) {
|
|
58
|
+
// `.refine()` / `.superRefine()` / `.transform()` / `.preprocess()` —
|
|
59
|
+
// descend into the inner schema (`schema` for refine/transform, `out`
|
|
60
|
+
// for preprocess — both are exposed at `_def.schema`).
|
|
61
|
+
current = current._def.schema;
|
|
62
|
+
}
|
|
63
|
+
else if (current instanceof z.ZodReadonly) {
|
|
64
|
+
current = current._def.innerType;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
return current;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve the Zod schema for a given dotted path, walking from the top-level
|
|
73
|
+
* AkmConfig schema. Returns `undefined` if any path segment doesn't match a
|
|
74
|
+
* known schema field.
|
|
75
|
+
*/
|
|
76
|
+
function resolveSchemaAt(path) {
|
|
77
|
+
let schema = AkmConfigBaseSchema;
|
|
78
|
+
for (const segment of path) {
|
|
79
|
+
schema = unwrap(schema);
|
|
80
|
+
if (schema instanceof z.ZodObject) {
|
|
81
|
+
const next = schema.shape[segment];
|
|
82
|
+
if (!next) {
|
|
83
|
+
// Catchall (e.g. index.<passName>) — descend into the catchall schema.
|
|
84
|
+
const catchall = schema._def.catchall;
|
|
85
|
+
if (catchall && !(catchall instanceof z.ZodNever)) {
|
|
86
|
+
schema = catchall;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
schema = next;
|
|
92
|
+
}
|
|
93
|
+
else if (schema instanceof z.ZodRecord) {
|
|
94
|
+
// Records (profiles.llm, profiles.agent, profiles.improve, sources, etc.)
|
|
95
|
+
// accept any string key — descend into the value schema.
|
|
96
|
+
schema = schema._def.valueType;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Cannot descend into a non-object schema.
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return unwrap(schema);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the value at the dotted path from a config-shaped object. Returns
|
|
107
|
+
* `undefined` when the path is unset, `null` for paths that are explicitly
|
|
108
|
+
* set to `null`.
|
|
109
|
+
*/
|
|
110
|
+
export function configGet(config, dotted) {
|
|
111
|
+
const path = parsePath(dotted);
|
|
112
|
+
const schema = resolveSchemaAt(path);
|
|
113
|
+
if (!schema) {
|
|
114
|
+
throw new UsageError(`Unknown config key: ${dotted}`, "INVALID_FLAG_VALUE", unknownKeyHint(dotted));
|
|
115
|
+
}
|
|
116
|
+
let cursor = config;
|
|
117
|
+
for (const segment of path) {
|
|
118
|
+
if (cursor === null || cursor === undefined)
|
|
119
|
+
return null;
|
|
120
|
+
if (typeof cursor !== "object")
|
|
121
|
+
return null;
|
|
122
|
+
cursor = cursor[segment];
|
|
123
|
+
}
|
|
124
|
+
return cursor ?? null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Set the value at the dotted path. Coerces `raw` according to the schema at
|
|
128
|
+
* that path. Returns a new config object — the input is not mutated.
|
|
129
|
+
*
|
|
130
|
+
* Throws {@link UsageError} on:
|
|
131
|
+
* - unknown path
|
|
132
|
+
* - apiKey paths (#454)
|
|
133
|
+
* - coercion failure
|
|
134
|
+
*/
|
|
135
|
+
export function configSet(config, dotted, raw) {
|
|
136
|
+
const path = parsePath(dotted);
|
|
137
|
+
// #454: apiKey paths are not persistable. Throw at set time.
|
|
138
|
+
rejectApiKeyPath(path, dotted);
|
|
139
|
+
const schema = resolveSchemaAt(path);
|
|
140
|
+
if (!schema) {
|
|
141
|
+
throw new UsageError(`Unknown config key: ${dotted}`, "INVALID_FLAG_VALUE", unknownKeyHint(dotted));
|
|
142
|
+
}
|
|
143
|
+
const value = coerceForSchema(schema, raw, dotted);
|
|
144
|
+
// Validate the coerced value against the leaf schema. This catches enum
|
|
145
|
+
// mismatches, out-of-range numbers, schema-level shape errors (writable
|
|
146
|
+
// npm/website sources via .superRefine, strict-mode unknown keys in nested
|
|
147
|
+
// objects, etc.) BEFORE we apply the patch.
|
|
148
|
+
const parsed = schema.safeParse(value);
|
|
149
|
+
if (!parsed.success) {
|
|
150
|
+
const lines = parsed.error.issues
|
|
151
|
+
.map((i) => {
|
|
152
|
+
const path = i.path.length > 0 ? `${dotted}.${i.path.join(".")}` : dotted;
|
|
153
|
+
return ` - ${path}: ${i.message}`;
|
|
154
|
+
})
|
|
155
|
+
.join("\n");
|
|
156
|
+
throw new UsageError(`Invalid value for ${dotted}:\n${lines}`, "INVALID_FLAG_VALUE");
|
|
157
|
+
}
|
|
158
|
+
const next = setPath(config, path, parsed.data);
|
|
159
|
+
// Targeted invariant: defaultWriteTarget must point at a configured source
|
|
160
|
+
// (#464.a). Whole-config validation happens at save time; this check fires
|
|
161
|
+
// at set time so the user sees the typo immediately. Empty-sources case is
|
|
162
|
+
// accepted here (legacy behaviour) — saveConfig will reject if it persists.
|
|
163
|
+
if (dotted === "defaultWriteTarget" && typeof value === "string") {
|
|
164
|
+
const sources = next.sources ?? [];
|
|
165
|
+
const knownNames = sources.map((s) => s.name).filter((n) => typeof n === "string" && n.length > 0);
|
|
166
|
+
if (knownNames.length > 0 && !knownNames.includes(value)) {
|
|
167
|
+
throw new UsageError(`Unknown source name "${value}" for defaultWriteTarget; configured source names: ${knownNames.map((n) => `"${n}"`).join(", ")}.`, "INVALID_FLAG_VALUE");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return next;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Unset the value at the dotted path. Removes the leaf key (and prunes empty
|
|
174
|
+
* parent objects if they become empty). Returns a new config object.
|
|
175
|
+
*/
|
|
176
|
+
export function configUnset(config, dotted) {
|
|
177
|
+
const path = parsePath(dotted);
|
|
178
|
+
// Validate the path resolves to a real schema field (so typos don't no-op).
|
|
179
|
+
const schema = resolveSchemaAt(path);
|
|
180
|
+
if (!schema) {
|
|
181
|
+
throw new UsageError(`Unknown config key: ${dotted}`, "INVALID_FLAG_VALUE", unknownKeyHint(dotted));
|
|
182
|
+
}
|
|
183
|
+
return unsetPath(config, path);
|
|
184
|
+
}
|
|
185
|
+
// ── apiKey rejection (#454) ─────────────────────────────────────────────────
|
|
186
|
+
/**
|
|
187
|
+
* Reject any attempt to persist an apiKey via `akm config set`. Matches:
|
|
188
|
+
* - `llm.apiKey` (legacy alias mapped to default LLM profile)
|
|
189
|
+
* - `embedding.apiKey`
|
|
190
|
+
* - `profiles.llm.<name>.apiKey`
|
|
191
|
+
*/
|
|
192
|
+
function rejectApiKeyPath(path, dotted) {
|
|
193
|
+
const last = path[path.length - 1];
|
|
194
|
+
if (last !== "apiKey")
|
|
195
|
+
return;
|
|
196
|
+
const recipe = recipeForApiKey(path, dotted);
|
|
197
|
+
throw new UsageError(`apiKey cannot be persisted in config; export ${recipe} instead. (key: ${dotted})`, "INVALID_FLAG_VALUE", "Storing API keys in config.json leaks them through backups, logs, and version control. " +
|
|
198
|
+
"Use the corresponding environment variable. AKM reads it at request time.");
|
|
199
|
+
}
|
|
200
|
+
function recipeForApiKey(path, _dotted) {
|
|
201
|
+
if (path[0] === "embedding")
|
|
202
|
+
return "AKM_EMBED_API_KEY";
|
|
203
|
+
if (path[0] === "llm")
|
|
204
|
+
return "AKM_LLM_API_KEY";
|
|
205
|
+
if (path[0] === "profiles" && path[1] === "llm" && typeof path[2] === "string") {
|
|
206
|
+
const upper = path[2].toUpperCase().replace(/-/g, "_");
|
|
207
|
+
return `AKM_PROFILE_${upper}_API_KEY (or AKM_LLM_API_KEY for the default profile)`;
|
|
208
|
+
}
|
|
209
|
+
return "AKM_LLM_API_KEY / AKM_EMBED_API_KEY";
|
|
210
|
+
}
|
|
211
|
+
// ── Coercion ────────────────────────────────────────────────────────────────
|
|
212
|
+
/**
|
|
213
|
+
* Coerce a CLI string into the value expected by the given schema. JSON-object
|
|
214
|
+
* and JSON-array schemas accept either a JSON literal or the string `null`/
|
|
215
|
+
* empty (which clears the value, equivalent to unset).
|
|
216
|
+
*/
|
|
217
|
+
function coerceForSchema(schema, raw, key) {
|
|
218
|
+
const target = unwrap(schema);
|
|
219
|
+
if (target instanceof z.ZodString) {
|
|
220
|
+
return raw;
|
|
221
|
+
}
|
|
222
|
+
if (target instanceof z.ZodNumber) {
|
|
223
|
+
const n = Number(raw);
|
|
224
|
+
if (!Number.isFinite(n)) {
|
|
225
|
+
throw new UsageError(`Invalid value for ${key}: expected a number, got "${raw}".`);
|
|
226
|
+
}
|
|
227
|
+
return n;
|
|
228
|
+
}
|
|
229
|
+
if (target instanceof z.ZodBoolean) {
|
|
230
|
+
if (raw === "true")
|
|
231
|
+
return true;
|
|
232
|
+
if (raw === "false")
|
|
233
|
+
return false;
|
|
234
|
+
throw new UsageError(`Invalid value for ${key}: expected true or false, got "${raw}".`);
|
|
235
|
+
}
|
|
236
|
+
if (target instanceof z.ZodEnum) {
|
|
237
|
+
return raw;
|
|
238
|
+
}
|
|
239
|
+
if (target instanceof z.ZodLiteral) {
|
|
240
|
+
return target._def.value;
|
|
241
|
+
}
|
|
242
|
+
if (target instanceof z.ZodArray || target instanceof z.ZodObject || target instanceof z.ZodRecord) {
|
|
243
|
+
if (raw === "" || raw === "null")
|
|
244
|
+
return undefined;
|
|
245
|
+
try {
|
|
246
|
+
return JSON.parse(raw);
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
250
|
+
throw new UsageError(`Invalid JSON for ${key}: ${detail}`, "INVALID_JSON_CONFIG_VALUE");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (target instanceof z.ZodUnion) {
|
|
254
|
+
// Try each option in order. Use the first that the schema accepts after
|
|
255
|
+
// coercion. Falls through to JSON parsing if all fail.
|
|
256
|
+
const options = target._def.options;
|
|
257
|
+
let lastErr;
|
|
258
|
+
for (const opt of options) {
|
|
259
|
+
try {
|
|
260
|
+
const coerced = coerceForSchema(opt, raw, key);
|
|
261
|
+
const parsed = opt.safeParse(coerced);
|
|
262
|
+
if (parsed.success)
|
|
263
|
+
return parsed.data;
|
|
264
|
+
lastErr = parsed.error;
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
lastErr = err;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (lastErr instanceof Error)
|
|
271
|
+
throw new UsageError(`Invalid value for ${key}: ${lastErr.message}`);
|
|
272
|
+
throw new UsageError(`Invalid value for ${key}: did not match any expected type.`);
|
|
273
|
+
}
|
|
274
|
+
if (target instanceof z.ZodNull) {
|
|
275
|
+
if (raw === "" || raw === "null")
|
|
276
|
+
return null;
|
|
277
|
+
throw new UsageError(`Invalid value for ${key}: expected null.`);
|
|
278
|
+
}
|
|
279
|
+
// Fallback: try JSON parse, then raw string.
|
|
280
|
+
if (raw === "" || raw === "null")
|
|
281
|
+
return undefined;
|
|
282
|
+
try {
|
|
283
|
+
return JSON.parse(raw);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return raw;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// ── Path mutators ───────────────────────────────────────────────────────────
|
|
290
|
+
function setPath(config, path, value) {
|
|
291
|
+
if (path.length === 0)
|
|
292
|
+
return config;
|
|
293
|
+
const [head, ...rest] = path;
|
|
294
|
+
const next = { ...config };
|
|
295
|
+
if (rest.length === 0) {
|
|
296
|
+
if (value === undefined) {
|
|
297
|
+
delete next[head];
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
next[head] = value;
|
|
301
|
+
}
|
|
302
|
+
return next;
|
|
303
|
+
}
|
|
304
|
+
const existing = next[head];
|
|
305
|
+
const child = typeof existing === "object" && existing !== null && !Array.isArray(existing)
|
|
306
|
+
? existing
|
|
307
|
+
: {};
|
|
308
|
+
next[head] = setPath(child, rest, value);
|
|
309
|
+
return next;
|
|
310
|
+
}
|
|
311
|
+
function unsetPath(config, path) {
|
|
312
|
+
if (path.length === 0)
|
|
313
|
+
return config;
|
|
314
|
+
const [head, ...rest] = path;
|
|
315
|
+
const next = { ...config };
|
|
316
|
+
if (rest.length === 0) {
|
|
317
|
+
delete next[head];
|
|
318
|
+
return next;
|
|
319
|
+
}
|
|
320
|
+
const existing = next[head];
|
|
321
|
+
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
322
|
+
return next; // nothing to unset
|
|
323
|
+
}
|
|
324
|
+
const updated = unsetPath(existing, rest);
|
|
325
|
+
if (Object.keys(updated).length === 0) {
|
|
326
|
+
delete next[head];
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
next[head] = updated;
|
|
330
|
+
}
|
|
331
|
+
return next;
|
|
332
|
+
}
|
|
333
|
+
// ── Hint generation (#460) ──────────────────────────────────────────────────
|
|
334
|
+
export function unknownKeyHint(_attempted) {
|
|
335
|
+
const keys = listTopLevelConfigKeys();
|
|
336
|
+
return `Valid top-level keys: ${keys.join(", ")}. Use dotted paths for nested values (e.g. embedding.endpoint, profiles.llm.<name>.model).`;
|
|
337
|
+
}
|