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
|
@@ -1,311 +1,131 @@
|
|
|
1
|
-
|
|
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
|
+
* Config CLI commands — `akm config get/set/unset/list`.
|
|
6
|
+
*
|
|
7
|
+
* Thin wrappers around the schema walker in `core/config-walker.ts`. Adding a
|
|
8
|
+
* new config field is one line of Zod schema in `core/config-schema.ts` and
|
|
9
|
+
* zero lines here — the walker handles get/set/unset/coercion uniformly.
|
|
10
|
+
*
|
|
11
|
+
* Legacy behaviour preserved:
|
|
12
|
+
* - `akm config set llm.<x>` writes to `profiles.llm.<defaults.llm>` (or
|
|
13
|
+
* auto-creates a "default" profile), mirroring the pre-rewrite shim.
|
|
14
|
+
* - `akm config set embedding.ollamaOptions.numCtx` is sugar for
|
|
15
|
+
* `embedding.ollamaOptions.num_ctx` (camelCase ↔ snake_case bridge).
|
|
16
|
+
* - `parseConfigValue` returns a Partial<AkmConfig> so it can be merged with
|
|
17
|
+
* the runtime config object via `mergeConfigValue`.
|
|
18
|
+
*/
|
|
19
|
+
import { DEFAULT_CONFIG, getSources } from "../core/config";
|
|
20
|
+
import { configGet, configSet, configUnset, unknownKeyHint } from "../core/config-walker";
|
|
2
21
|
import { UsageError } from "../core/errors";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return { embedding: mergeLlmLikeEmbedding(undefined, { apiKey: requireNonEmptyString(value, key) }) };
|
|
43
|
-
case "embedding.contextLength":
|
|
44
|
-
return { embedding: mergeLlmLikeEmbedding(undefined, { contextLength: parsePositiveInteger(value, key) }) };
|
|
45
|
-
case "embedding.ollamaOptions.numCtx":
|
|
46
|
-
return {
|
|
47
|
-
embedding: mergeLlmLikeEmbedding(undefined, { ollamaOptions: { num_ctx: parsePositiveInteger(value, key) } }),
|
|
48
|
-
};
|
|
49
|
-
case "llm":
|
|
50
|
-
return { llm: parseLlmConnectionValue(value) };
|
|
51
|
-
case "llm.endpoint":
|
|
52
|
-
return { llm: mergeLlmLike(undefined, { endpoint: requireNonEmptyString(value, key) }) };
|
|
53
|
-
case "llm.model":
|
|
54
|
-
return { llm: mergeLlmLike(undefined, { model: requireNonEmptyString(value, key) }) };
|
|
55
|
-
case "llm.apiKey":
|
|
56
|
-
return { llm: mergeLlmLike(undefined, { apiKey: requireNonEmptyString(value, key) }) };
|
|
57
|
-
case "llm.contextLength":
|
|
58
|
-
return { llm: mergeLlmLike(undefined, { contextLength: parsePositiveInteger(value, key) }) };
|
|
59
|
-
case "registries":
|
|
60
|
-
return { registries: parseRegistriesValue(value) };
|
|
61
|
-
case "sources":
|
|
62
|
-
case "stashes":
|
|
63
|
-
// "stashes" is kept as an alias for backwards-compat; both write to `sources`.
|
|
64
|
-
return { sources: validateSources(parseStashesValue(value)) };
|
|
65
|
-
case "output.format":
|
|
66
|
-
return { output: { format: parseOutputFormat(value) } };
|
|
67
|
-
case "output.detail":
|
|
68
|
-
return { output: { detail: parseOutputDetail(value) } };
|
|
69
|
-
case "security.installAudit.enabled":
|
|
70
|
-
return { security: { installAudit: { enabled: parseBooleanValue(value, key) } } };
|
|
71
|
-
case "security.installAudit.blockOnCritical":
|
|
72
|
-
return { security: { installAudit: { blockOnCritical: parseBooleanValue(value, key) } } };
|
|
73
|
-
case "security.installAudit.blockUnlistedRegistries":
|
|
74
|
-
return { security: { installAudit: { blockUnlistedRegistries: parseBooleanValue(value, key) } } };
|
|
75
|
-
case "security.installAudit.registryAllowlist":
|
|
76
|
-
return { security: { installAudit: { registryAllowlist: parseStringArrayValue(value, key) } } };
|
|
77
|
-
case "security.installAudit.registryWhitelist":
|
|
78
|
-
return { security: { installAudit: { registryAllowlist: parseStringArrayValue(value, key) } } };
|
|
79
|
-
case "security.installAudit.allowedFindings":
|
|
80
|
-
return { security: { installAudit: { allowedFindings: parseAllowedFindingsValue(value, key) } } };
|
|
81
|
-
default:
|
|
82
|
-
throw new UsageError(`Unknown config key: ${key}`, "INVALID_FLAG_VALUE", UNKNOWN_CONFIG_KEY_HINT);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
const UNKNOWN_CONFIG_KEY_HINT = "Valid top-level keys: stashDir, embedding, llm, registries, sources, agent, output, semanticSearchMode. Use dotted paths like `embedding.endpoint` or `output.format` for nested values.";
|
|
22
|
+
// ── Legacy `llm.*` → `profiles.llm.<default>.*` aliasing ────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Map a legacy top-level `llm.<sub>` path onto the actual schema path. The
|
|
25
|
+
* default profile name is "default" when `defaults.llm` is unset.
|
|
26
|
+
*/
|
|
27
|
+
function rewriteLegacyLlmPath(config, key) {
|
|
28
|
+
if (key !== "llm" && !key.startsWith("llm."))
|
|
29
|
+
return key;
|
|
30
|
+
const sub = key === "llm" ? "" : key.slice("llm.".length);
|
|
31
|
+
const profileName = config.defaults?.llm ?? "default";
|
|
32
|
+
return sub ? `profiles.llm.${profileName}.${sub}` : `profiles.llm.${profileName}`;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Translate the legacy `embedding.ollamaOptions.numCtx` to the actual schema
|
|
36
|
+
* key `embedding.ollamaOptions.num_ctx`.
|
|
37
|
+
*/
|
|
38
|
+
function rewriteEmbeddingPath(key) {
|
|
39
|
+
if (key === "embedding.ollamaOptions.numCtx")
|
|
40
|
+
return "embedding.ollamaOptions.num_ctx";
|
|
41
|
+
return key;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Translate the deprecated `stashes` alias for `sources` (one-way: both read
|
|
45
|
+
* and write go through `sources`).
|
|
46
|
+
*/
|
|
47
|
+
function rewriteSourcesAlias(key) {
|
|
48
|
+
if (key === "stashes")
|
|
49
|
+
return "sources";
|
|
50
|
+
if (key.startsWith("stashes."))
|
|
51
|
+
return `sources.${key.slice("stashes.".length)}`;
|
|
52
|
+
return key;
|
|
53
|
+
}
|
|
54
|
+
function rewriteKey(config, key) {
|
|
55
|
+
let k = rewriteLegacyLlmPath(config, key);
|
|
56
|
+
k = rewriteEmbeddingPath(k);
|
|
57
|
+
k = rewriteSourcesAlias(k);
|
|
58
|
+
return k;
|
|
59
|
+
}
|
|
60
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
86
61
|
export function getConfigValue(config, key) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return config.stashDir ?? null;
|
|
90
|
-
case "defaultWriteTarget":
|
|
91
|
-
return config.defaultWriteTarget ?? null;
|
|
92
|
-
case "semanticSearchMode":
|
|
93
|
-
return config.semanticSearchMode;
|
|
94
|
-
case "embedding":
|
|
95
|
-
return config.embedding ?? null;
|
|
96
|
-
case "embedding.endpoint":
|
|
97
|
-
return config.embedding?.endpoint ?? null;
|
|
98
|
-
case "embedding.model":
|
|
99
|
-
return config.embedding?.model ?? null;
|
|
100
|
-
case "embedding.apiKey":
|
|
101
|
-
return config.embedding?.apiKey ?? null;
|
|
102
|
-
case "embedding.contextLength":
|
|
103
|
-
return config.embedding?.contextLength ?? null;
|
|
104
|
-
case "embedding.ollamaOptions.numCtx":
|
|
105
|
-
return config.embedding?.ollamaOptions?.num_ctx ?? null;
|
|
106
|
-
case "llm":
|
|
107
|
-
return config.llm ?? null;
|
|
108
|
-
case "llm.endpoint":
|
|
109
|
-
return config.llm?.endpoint ?? null;
|
|
110
|
-
case "llm.model":
|
|
111
|
-
return config.llm?.model ?? null;
|
|
112
|
-
case "llm.apiKey":
|
|
113
|
-
return config.llm?.apiKey ?? null;
|
|
114
|
-
case "llm.contextLength":
|
|
115
|
-
return config.llm?.contextLength ?? null;
|
|
116
|
-
case "registries":
|
|
117
|
-
return config.registries ?? DEFAULT_CONFIG.registries ?? [];
|
|
118
|
-
case "sources":
|
|
119
|
-
case "stashes":
|
|
120
|
-
// "stashes" is an alias for "sources" for backwards-compat.
|
|
121
|
-
return getSources(config);
|
|
122
|
-
case "output.format":
|
|
123
|
-
return config.output?.format ?? null;
|
|
124
|
-
case "output.detail":
|
|
125
|
-
return config.output?.detail ?? null;
|
|
126
|
-
case "security":
|
|
127
|
-
return config.security ?? null;
|
|
128
|
-
case "security.installAudit.enabled":
|
|
129
|
-
return config.security?.installAudit?.enabled ?? null;
|
|
130
|
-
case "security.installAudit.blockOnCritical":
|
|
131
|
-
return config.security?.installAudit?.blockOnCritical ?? null;
|
|
132
|
-
case "security.installAudit.blockUnlistedRegistries":
|
|
133
|
-
return config.security?.installAudit?.blockUnlistedRegistries ?? null;
|
|
134
|
-
case "security.installAudit.registryAllowlist":
|
|
135
|
-
return getInstallAuditAllowlist(config);
|
|
136
|
-
case "security.installAudit.registryWhitelist":
|
|
137
|
-
return getInstallAuditAllowlist(config);
|
|
138
|
-
case "security.installAudit.allowedFindings":
|
|
139
|
-
return config.security?.installAudit?.allowedFindings ?? null;
|
|
140
|
-
default:
|
|
141
|
-
throw new UsageError(`Unknown config key: ${key}`, "INVALID_FLAG_VALUE", UNKNOWN_CONFIG_KEY_HINT);
|
|
142
|
-
}
|
|
62
|
+
const k = rewriteKey(config, key);
|
|
63
|
+
return configGet(config, k);
|
|
143
64
|
}
|
|
144
65
|
export function setConfigValue(config, key, rawValue) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
66
|
+
// #454: reject the legacy aliases up front so the error message names the
|
|
67
|
+
// env var the user typed (AKM_LLM_API_KEY) rather than the rewritten profile
|
|
68
|
+
// env var (AKM_PROFILE_DEFAULT_API_KEY) — both work at runtime, but the
|
|
69
|
+
// shorter name matches the user's mental model.
|
|
70
|
+
if (key === "llm.apiKey") {
|
|
71
|
+
throw new UsageError("apiKey cannot be persisted in config; export AKM_LLM_API_KEY instead. (key: llm.apiKey)", "INVALID_FLAG_VALUE", "Storing API keys in config.json leaks them through backups, logs, and version control. " +
|
|
72
|
+
"Use the corresponding environment variable. AKM reads it at request time.");
|
|
73
|
+
}
|
|
74
|
+
if (key === "embedding.apiKey") {
|
|
75
|
+
throw new UsageError("apiKey cannot be persisted in config; export AKM_EMBED_API_KEY instead. (key: embedding.apiKey)", "INVALID_FLAG_VALUE", "Storing API keys in config.json leaks them through backups, logs, and version control. " +
|
|
76
|
+
"Use the corresponding environment variable. AKM reads it at request time.");
|
|
77
|
+
}
|
|
78
|
+
const k = rewriteKey(config, key);
|
|
79
|
+
// Legacy ergonomic: `akm config set semanticSearchMode true|false`
|
|
80
|
+
let coerced = rawValue;
|
|
81
|
+
if (k === "semanticSearchMode") {
|
|
82
|
+
if (rawValue === "true")
|
|
83
|
+
coerced = "auto";
|
|
84
|
+
else if (rawValue === "false")
|
|
85
|
+
coerced = "off";
|
|
86
|
+
}
|
|
87
|
+
let next = configSet(config, k, coerced);
|
|
88
|
+
// Legacy ergonomic shim: when the user sets `llm.<field>` and no
|
|
89
|
+
// `defaults.llm` is set, point it at the freshly-created profile so the
|
|
90
|
+
// value actually takes effect at runtime.
|
|
91
|
+
if (key === "llm" || key.startsWith("llm.")) {
|
|
92
|
+
if (!next.defaults?.llm) {
|
|
93
|
+
next = {
|
|
94
|
+
...next,
|
|
95
|
+
defaults: { ...(next.defaults ?? {}), llm: "default" },
|
|
172
96
|
};
|
|
173
|
-
case "embedding.apiKey":
|
|
174
|
-
return {
|
|
175
|
-
...config,
|
|
176
|
-
embedding: mergeLlmLikeEmbedding(config.embedding, { apiKey: requireNonEmptyString(rawValue, key) }),
|
|
177
|
-
};
|
|
178
|
-
case "embedding.contextLength":
|
|
179
|
-
return {
|
|
180
|
-
...config,
|
|
181
|
-
embedding: mergeLlmLikeEmbedding(config.embedding, { contextLength: parsePositiveInteger(rawValue, key) }),
|
|
182
|
-
};
|
|
183
|
-
case "embedding.ollamaOptions.numCtx":
|
|
184
|
-
return {
|
|
185
|
-
...config,
|
|
186
|
-
embedding: mergeLlmLikeEmbedding(config.embedding, {
|
|
187
|
-
ollamaOptions: { ...(config.embedding?.ollamaOptions ?? {}), num_ctx: parsePositiveInteger(rawValue, key) },
|
|
188
|
-
}),
|
|
189
|
-
};
|
|
190
|
-
case "llm.endpoint":
|
|
191
|
-
return { ...config, llm: mergeLlmLike(config.llm, { endpoint: requireNonEmptyString(rawValue, key) }) };
|
|
192
|
-
case "llm.model":
|
|
193
|
-
return { ...config, llm: mergeLlmLike(config.llm, { model: requireNonEmptyString(rawValue, key) }) };
|
|
194
|
-
case "llm.apiKey":
|
|
195
|
-
return { ...config, llm: mergeLlmLike(config.llm, { apiKey: requireNonEmptyString(rawValue, key) }) };
|
|
196
|
-
case "llm.contextLength":
|
|
197
|
-
return { ...config, llm: mergeLlmLike(config.llm, { contextLength: parsePositiveInteger(rawValue, key) }) };
|
|
198
|
-
case "defaultWriteTarget": {
|
|
199
|
-
const name = requireNonEmptyString(rawValue, key);
|
|
200
|
-
const knownNames = getSources(config)
|
|
201
|
-
.map((s) => s.name)
|
|
202
|
-
.filter((n) => typeof n === "string");
|
|
203
|
-
if (knownNames.length > 0 && !knownNames.includes(name)) {
|
|
204
|
-
throw new UsageError(`Unknown source name "${name}" for defaultWriteTarget; configured source names: ${knownNames.map((n) => `"${n}"`).join(", ")}`);
|
|
205
|
-
}
|
|
206
|
-
return { ...config, defaultWriteTarget: name };
|
|
207
97
|
}
|
|
208
|
-
default:
|
|
209
|
-
throw new UsageError(`Unknown config key: ${key}`, "INVALID_FLAG_VALUE", UNKNOWN_CONFIG_KEY_HINT);
|
|
210
98
|
}
|
|
99
|
+
return next;
|
|
211
100
|
}
|
|
212
101
|
export function unsetConfigValue(config, key) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (!config.embedding)
|
|
232
|
-
return config;
|
|
233
|
-
const { contextLength: _cl, ...rest } = config.embedding;
|
|
234
|
-
return { ...config, embedding: rest };
|
|
235
|
-
}
|
|
236
|
-
case "embedding.ollamaOptions.numCtx": {
|
|
237
|
-
if (!config.embedding?.ollamaOptions)
|
|
238
|
-
return config;
|
|
239
|
-
const { num_ctx: _nc, ...restOpts } = config.embedding.ollamaOptions;
|
|
240
|
-
const ollamaOptions = Object.keys(restOpts).length > 0 ? restOpts : undefined;
|
|
241
|
-
return { ...config, embedding: { ...config.embedding, ollamaOptions } };
|
|
242
|
-
}
|
|
243
|
-
case "llm":
|
|
244
|
-
return { ...config, llm: undefined };
|
|
245
|
-
case "llm.endpoint":
|
|
246
|
-
return { ...config, llm: mergeLlmLike(config.llm, { endpoint: "" }) };
|
|
247
|
-
case "llm.model":
|
|
248
|
-
return { ...config, llm: mergeLlmLike(config.llm, { model: "" }) };
|
|
249
|
-
case "llm.apiKey": {
|
|
250
|
-
if (!config.llm)
|
|
251
|
-
return config;
|
|
252
|
-
const { apiKey: _b, ...restLlm } = config.llm;
|
|
253
|
-
return { ...config, llm: restLlm };
|
|
254
|
-
}
|
|
255
|
-
case "llm.contextLength": {
|
|
256
|
-
if (!config.llm)
|
|
257
|
-
return config;
|
|
258
|
-
const { contextLength: _lctx, ...restLlm2 } = config.llm;
|
|
259
|
-
return { ...config, llm: restLlm2 };
|
|
102
|
+
const k = rewriteKey(config, key);
|
|
103
|
+
return configUnset(config, k);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Compatibility shim: returns a `Partial<AkmConfig>` containing just the
|
|
107
|
+
* change. Older code merged this onto the live config — new code should call
|
|
108
|
+
* `setConfigValue` directly (which returns the full merged config).
|
|
109
|
+
*/
|
|
110
|
+
export function parseConfigValue(key, value) {
|
|
111
|
+
// Use a "marker" base so we can detect which top-level fields actually got
|
|
112
|
+
// touched by the set call. Anything still equal to the marker is untouched.
|
|
113
|
+
const SENTINEL = Symbol("untouched");
|
|
114
|
+
const base = { semanticSearchMode: SENTINEL };
|
|
115
|
+
const next = setConfigValue(base, key, value);
|
|
116
|
+
const patch = {};
|
|
117
|
+
for (const k of Object.keys(next)) {
|
|
118
|
+
if (next[k] !== SENTINEL) {
|
|
119
|
+
patch[k] = next[k];
|
|
260
120
|
}
|
|
261
|
-
case "registries":
|
|
262
|
-
return { ...config, registries: undefined };
|
|
263
|
-
case "sources":
|
|
264
|
-
case "stashes":
|
|
265
|
-
// "stashes" is kept as an alias for backwards-compat; both clear `sources`.
|
|
266
|
-
return { ...config, sources: undefined };
|
|
267
|
-
case "output.format":
|
|
268
|
-
return { ...config, output: mergeOutputConfig(config.output, { format: undefined }) };
|
|
269
|
-
case "output.detail":
|
|
270
|
-
return { ...config, output: mergeOutputConfig(config.output, { detail: undefined }) };
|
|
271
|
-
case "security":
|
|
272
|
-
return { ...config, security: undefined };
|
|
273
|
-
case "security.installAudit.enabled":
|
|
274
|
-
return { ...config, security: mergeSecurityConfig(config.security, { installAudit: { enabled: undefined } }) };
|
|
275
|
-
case "security.installAudit.blockOnCritical":
|
|
276
|
-
return {
|
|
277
|
-
...config,
|
|
278
|
-
security: mergeSecurityConfig(config.security, { installAudit: { blockOnCritical: undefined } }),
|
|
279
|
-
};
|
|
280
|
-
case "security.installAudit.blockUnlistedRegistries":
|
|
281
|
-
return {
|
|
282
|
-
...config,
|
|
283
|
-
security: mergeSecurityConfig(config.security, { installAudit: { blockUnlistedRegistries: undefined } }),
|
|
284
|
-
};
|
|
285
|
-
case "security.installAudit.registryAllowlist":
|
|
286
|
-
case "security.installAudit.registryWhitelist":
|
|
287
|
-
return {
|
|
288
|
-
...config,
|
|
289
|
-
security: mergeSecurityConfig(config.security, {
|
|
290
|
-
installAudit: { registryAllowlist: undefined, registryWhitelist: undefined },
|
|
291
|
-
}),
|
|
292
|
-
};
|
|
293
|
-
case "security.installAudit.allowedFindings":
|
|
294
|
-
return {
|
|
295
|
-
...config,
|
|
296
|
-
security: mergeSecurityConfig(config.security, {
|
|
297
|
-
installAudit: { allowedFindings: undefined },
|
|
298
|
-
}),
|
|
299
|
-
};
|
|
300
|
-
default:
|
|
301
|
-
throw new UsageError(`Unknown or unsupported unset key: ${key}`, "INVALID_FLAG_VALUE", UNKNOWN_CONFIG_KEY_HINT);
|
|
302
121
|
}
|
|
122
|
+
return patch;
|
|
303
123
|
}
|
|
304
124
|
export function listConfig(config) {
|
|
305
125
|
const result = {
|
|
306
126
|
semanticSearchMode: config.semanticSearchMode,
|
|
307
127
|
registries: config.registries ?? DEFAULT_CONFIG.registries ?? [],
|
|
308
|
-
output:
|
|
128
|
+
output: { ...(DEFAULT_CONFIG.output ?? {}), ...(config.output ?? {}) },
|
|
309
129
|
stashDir: config.stashDir ?? null,
|
|
310
130
|
installed: config.installed ?? [],
|
|
311
131
|
sources: getSources(config),
|
|
@@ -314,230 +134,22 @@ export function listConfig(config) {
|
|
|
314
134
|
result.defaultWriteTarget = config.defaultWriteTarget;
|
|
315
135
|
if (config.embedding)
|
|
316
136
|
result.embedding = config.embedding;
|
|
317
|
-
if (config.
|
|
318
|
-
result.
|
|
319
|
-
if (config.
|
|
320
|
-
result.
|
|
137
|
+
if (config.profiles)
|
|
138
|
+
result.profiles = config.profiles;
|
|
139
|
+
if (config.defaults)
|
|
140
|
+
result.defaults = config.defaults;
|
|
141
|
+
if (config.search)
|
|
142
|
+
result.search = config.search;
|
|
143
|
+
if (config.index)
|
|
144
|
+
result.index = config.index;
|
|
145
|
+
if (config.feedback)
|
|
146
|
+
result.feedback = config.feedback;
|
|
147
|
+
if (config.improve)
|
|
148
|
+
result.improve = config.improve;
|
|
149
|
+
if (config.archiveRetentionDays !== undefined)
|
|
150
|
+
result.archiveRetentionDays = config.archiveRetentionDays;
|
|
151
|
+
if (config.configVersion !== undefined)
|
|
152
|
+
result.configVersion = config.configVersion;
|
|
321
153
|
return result;
|
|
322
154
|
}
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
...config,
|
|
326
|
-
...partial,
|
|
327
|
-
output: mergeOutputConfig(config.output, partial.output),
|
|
328
|
-
security: mergeSecurityConfig(config.security, partial.security),
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
function mergeOutputConfig(base, override) {
|
|
332
|
-
const merged = {
|
|
333
|
-
...(base ?? {}),
|
|
334
|
-
...(override ?? {}),
|
|
335
|
-
};
|
|
336
|
-
return merged.format || merged.detail ? merged : undefined;
|
|
337
|
-
}
|
|
338
|
-
function mergeSecurityConfig(base, override) {
|
|
339
|
-
const mergedInstallAudit = mergeInstallAuditConfig(base?.installAudit, override?.installAudit);
|
|
340
|
-
return mergedInstallAudit ? { installAudit: mergedInstallAudit } : undefined;
|
|
341
|
-
}
|
|
342
|
-
function mergeInstallAuditConfig(base, override) {
|
|
343
|
-
const merged = {
|
|
344
|
-
...(base ?? {}),
|
|
345
|
-
...(override ?? {}),
|
|
346
|
-
};
|
|
347
|
-
const hasValue = Object.values(merged).some((value) => value !== undefined);
|
|
348
|
-
return hasValue ? merged : undefined;
|
|
349
|
-
}
|
|
350
|
-
function parseOutputFormat(value) {
|
|
351
|
-
if (value === "json" || value === "yaml" || value === "text")
|
|
352
|
-
return value;
|
|
353
|
-
throw new UsageError(`Invalid value for output.format: expected one of json|yaml|text`);
|
|
354
|
-
}
|
|
355
|
-
function parseOutputDetail(value) {
|
|
356
|
-
if (value === "brief" || value === "normal" || value === "full")
|
|
357
|
-
return value;
|
|
358
|
-
throw new UsageError(`Invalid value for output.detail: expected one of brief|normal|full`);
|
|
359
|
-
}
|
|
360
|
-
function parseBooleanValue(value, key) {
|
|
361
|
-
if (value === "true")
|
|
362
|
-
return true;
|
|
363
|
-
if (value === "false")
|
|
364
|
-
return false;
|
|
365
|
-
throw new UsageError(`Invalid value for ${key}: expected true or false`);
|
|
366
|
-
}
|
|
367
|
-
function parseStringArrayValue(value, key) {
|
|
368
|
-
let parsed;
|
|
369
|
-
try {
|
|
370
|
-
parsed = JSON.parse(value);
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
throw new UsageError(`Invalid value for ${key}: expected a JSON array of strings`);
|
|
374
|
-
}
|
|
375
|
-
if (!Array.isArray(parsed) || parsed.some((entry) => typeof entry !== "string")) {
|
|
376
|
-
throw new UsageError(`Invalid value for ${key}: expected a JSON array of strings`);
|
|
377
|
-
}
|
|
378
|
-
return parsed;
|
|
379
|
-
}
|
|
380
|
-
function getInstallAuditAllowlist(config) {
|
|
381
|
-
return config.security?.installAudit?.registryAllowlist ?? config.security?.installAudit?.registryWhitelist ?? null;
|
|
382
|
-
}
|
|
383
|
-
function parseAllowedFindingsValue(value, key) {
|
|
384
|
-
let parsed;
|
|
385
|
-
try {
|
|
386
|
-
parsed = JSON.parse(value);
|
|
387
|
-
}
|
|
388
|
-
catch {
|
|
389
|
-
throw new UsageError(`Invalid value for ${key}: expected a JSON array of {id, ref?, path?, reason?} objects`);
|
|
390
|
-
}
|
|
391
|
-
if (!Array.isArray(parsed)) {
|
|
392
|
-
throw new UsageError(`Invalid value for ${key}: expected a JSON array`);
|
|
393
|
-
}
|
|
394
|
-
return parsed.map((entry, i) => {
|
|
395
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
396
|
-
throw new UsageError(`Invalid value for ${key}[${i}]: expected an object with an "id" field`);
|
|
397
|
-
}
|
|
398
|
-
const obj = entry;
|
|
399
|
-
if (typeof obj.id !== "string" || !obj.id) {
|
|
400
|
-
throw new UsageError(`Invalid value for ${key}[${i}]: "id" is required`);
|
|
401
|
-
}
|
|
402
|
-
const result = { id: obj.id };
|
|
403
|
-
if (typeof obj.ref === "string" && obj.ref)
|
|
404
|
-
result.ref = obj.ref;
|
|
405
|
-
if (typeof obj.path === "string" && obj.path)
|
|
406
|
-
result.path = obj.path;
|
|
407
|
-
if (typeof obj.reason === "string" && obj.reason)
|
|
408
|
-
result.reason = obj.reason;
|
|
409
|
-
return result;
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
function parseRegistriesValue(value) {
|
|
413
|
-
if (value === "null" || value === "")
|
|
414
|
-
return undefined;
|
|
415
|
-
let parsed;
|
|
416
|
-
try {
|
|
417
|
-
parsed = JSON.parse(value);
|
|
418
|
-
}
|
|
419
|
-
catch {
|
|
420
|
-
throw new UsageError(`Invalid value for registries: expected JSON array of {url, name?, enabled?, provider?, options?} objects` +
|
|
421
|
-
` (e.g. '[{"url":"https://example.com/index.json","name":"my-registry"}]')`);
|
|
422
|
-
}
|
|
423
|
-
if (!Array.isArray(parsed)) {
|
|
424
|
-
throw new UsageError(`Invalid value for registries: expected a JSON array`);
|
|
425
|
-
}
|
|
426
|
-
return parsed.map((entry, i) => {
|
|
427
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
428
|
-
throw new UsageError(`Invalid value for registries[${i}]: expected an object with a "url" field`);
|
|
429
|
-
}
|
|
430
|
-
const obj = entry;
|
|
431
|
-
if (typeof obj.url !== "string" || !obj.url) {
|
|
432
|
-
throw new UsageError(`Invalid value for registries[${i}]: "url" is required`);
|
|
433
|
-
}
|
|
434
|
-
// Spread the full entry so unknown/future fields round-trip intact.
|
|
435
|
-
return { ...obj };
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
function parseEmbeddingConnectionValue(value) {
|
|
439
|
-
if (value === "null" || value === "")
|
|
440
|
-
return undefined;
|
|
441
|
-
const parsed = parseJsonObject(value, "embedding", {
|
|
442
|
-
endpoint: "http://localhost:11434/v1/embeddings",
|
|
443
|
-
model: "nomic-embed-text",
|
|
444
|
-
});
|
|
445
|
-
// Require either a non-empty endpoint (remote) or a localModel (local-only).
|
|
446
|
-
const hasEndpoint = typeof parsed.endpoint === "string" && parsed.endpoint !== "";
|
|
447
|
-
const hasLocalModel = typeof parsed.localModel === "string" && parsed.localModel !== "";
|
|
448
|
-
if (!hasEndpoint && !hasLocalModel) {
|
|
449
|
-
throw new UsageError(`Invalid value for embedding: "endpoint" is required for remote embeddings, or provide "localModel" for local-only`);
|
|
450
|
-
}
|
|
451
|
-
// Validate the types of the required/structural fields that the runtime
|
|
452
|
-
// depends on, but do not reconstruct the object — pass everything through.
|
|
453
|
-
if (parsed.endpoint !== undefined && typeof parsed.endpoint !== "string") {
|
|
454
|
-
throw new UsageError(`Invalid value for embedding: "endpoint" must be a string`);
|
|
455
|
-
}
|
|
456
|
-
if (parsed.model !== undefined && typeof parsed.model !== "string") {
|
|
457
|
-
throw new UsageError(`Invalid value for embedding: "model" must be a string`);
|
|
458
|
-
}
|
|
459
|
-
if (parsed.dimension !== undefined && !Number.isInteger(parsed.dimension)) {
|
|
460
|
-
throw new UsageError(`embedding.dimension: expected a positive integer, got ${parsed.dimension}`, "INVALID_FLAG_VALUE");
|
|
461
|
-
}
|
|
462
|
-
// Spread the full parsed object so unknown/future fields round-trip intact.
|
|
463
|
-
return { endpoint: "", model: "", ...parsed };
|
|
464
|
-
}
|
|
465
|
-
function parseLlmConnectionValue(value) {
|
|
466
|
-
if (value === "null" || value === "")
|
|
467
|
-
return undefined;
|
|
468
|
-
const parsed = parseJsonObject(value, "llm", {
|
|
469
|
-
endpoint: "http://localhost:11434/v1/chat/completions",
|
|
470
|
-
model: "llama3.2",
|
|
471
|
-
});
|
|
472
|
-
if (parsed.endpoint !== undefined && typeof parsed.endpoint !== "string") {
|
|
473
|
-
throw new UsageError(`Invalid value for llm: "endpoint" is a required string field`);
|
|
474
|
-
}
|
|
475
|
-
if (parsed.model !== undefined && typeof parsed.model !== "string") {
|
|
476
|
-
throw new UsageError(`Invalid value for llm: "model" is a required string field`);
|
|
477
|
-
}
|
|
478
|
-
if (typeof parsed.endpoint !== "string" || !parsed.endpoint) {
|
|
479
|
-
throw new UsageError(`Invalid value for llm: "endpoint" is a required string field`);
|
|
480
|
-
}
|
|
481
|
-
if (parsed.model === undefined) {
|
|
482
|
-
return { endpoint: parsed.endpoint, model: "", ...parsed };
|
|
483
|
-
}
|
|
484
|
-
if (!parsed.model) {
|
|
485
|
-
throw new UsageError(`Invalid value for llm: "model" must be a non-empty string when provided`);
|
|
486
|
-
}
|
|
487
|
-
// Spread the full parsed object so unknown/future fields round-trip intact.
|
|
488
|
-
// The config loader (config.ts) handles warn-and-ignore for unknown sub-keys
|
|
489
|
-
// at read time, so we do not need to whitelist here.
|
|
490
|
-
return { ...parsed };
|
|
491
|
-
}
|
|
492
|
-
function parseJsonObject(value, key, example) {
|
|
493
|
-
let parsed;
|
|
494
|
-
try {
|
|
495
|
-
parsed = JSON.parse(value);
|
|
496
|
-
}
|
|
497
|
-
catch {
|
|
498
|
-
throw new UsageError(`Invalid value for ${key}: expected JSON object with endpoint and model` +
|
|
499
|
-
` (e.g. '{"endpoint":"${example.endpoint}","model":"${example.model}"}')`, "INVALID_JSON_CONFIG_VALUE");
|
|
500
|
-
}
|
|
501
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
502
|
-
throw new UsageError(`Invalid value for ${key}: expected a JSON object`);
|
|
503
|
-
}
|
|
504
|
-
return parsed;
|
|
505
|
-
}
|
|
506
|
-
function requireNonEmptyString(value, key) {
|
|
507
|
-
if (!value) {
|
|
508
|
-
throw new UsageError(`Invalid value for ${key}: expected a non-empty string`);
|
|
509
|
-
}
|
|
510
|
-
return value;
|
|
511
|
-
}
|
|
512
|
-
function parsePositiveInteger(value, key) {
|
|
513
|
-
const n = Number(value);
|
|
514
|
-
if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
|
|
515
|
-
throw new UsageError(`Invalid value for ${key}: expected a positive integer`);
|
|
516
|
-
}
|
|
517
|
-
return n;
|
|
518
|
-
}
|
|
519
|
-
function parseStashesValue(value) {
|
|
520
|
-
if (value === "null" || value === "")
|
|
521
|
-
return undefined;
|
|
522
|
-
let parsed;
|
|
523
|
-
try {
|
|
524
|
-
parsed = JSON.parse(value);
|
|
525
|
-
}
|
|
526
|
-
catch {
|
|
527
|
-
throw new UsageError(`Invalid value for sources: expected JSON array of {type, path?, url?, name?, enabled?, options?} objects`);
|
|
528
|
-
}
|
|
529
|
-
if (!Array.isArray(parsed)) {
|
|
530
|
-
throw new UsageError(`Invalid value for sources: expected a JSON array`);
|
|
531
|
-
}
|
|
532
|
-
return parsed.map((entry, i) => {
|
|
533
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
534
|
-
throw new UsageError(`Invalid value for sources[${i}]: expected an object with a "type" field`);
|
|
535
|
-
}
|
|
536
|
-
const obj = entry;
|
|
537
|
-
if (typeof obj.type !== "string" || !obj.type) {
|
|
538
|
-
throw new UsageError(`Invalid value for sources[${i}]: "type" is required`);
|
|
539
|
-
}
|
|
540
|
-
// Spread the full entry so unknown/future fields round-trip intact.
|
|
541
|
-
return { ...obj };
|
|
542
|
-
});
|
|
543
|
-
}
|
|
155
|
+
export { unknownKeyHint };
|