akm-cli 0.8.0-rc2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{.github/CHANGELOG.md → CHANGELOG.md} +238 -3
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/assets/help/help-accept.md +12 -0
- package/dist/assets/help/help-improve.md +81 -0
- package/dist/{commands → assets}/help/help-proposals.md +7 -4
- package/dist/assets/help/help-reject.md +11 -0
- package/dist/{output → assets/hints}/cli-hints-full.md +60 -32
- package/dist/{output → assets/hints}/cli-hints-short.md +10 -7
- package/dist/assets/profiles/default.json +15 -0
- package/dist/assets/profiles/graph-refresh.json +13 -0
- package/dist/assets/profiles/memory-focus.json +12 -0
- package/dist/assets/profiles/quick.json +15 -0
- package/dist/assets/profiles/thorough.json +15 -0
- package/dist/assets/prompts/extract-session.md +80 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +10 -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 +2141 -1268
- 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 +1557 -147
- 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 +217 -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 +1042 -55
- 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 +138 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +1736 -346
- package/dist/commands/info.js +26 -28
- 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 +199 -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 +13 -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 +661 -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 +86 -16
- 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 +110 -50
- 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 +402 -31
- 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/output/cli-hints.js +7 -4
- 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 +4 -1
- package/dist/tasks/backends/schtasks.js +4 -1
- 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 +6 -3
- package/dist/wiki/wiki.js +4 -1
- package/dist/workflows/authoring.js +4 -1
- 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/help/help-accept.md +0 -9
- package/dist/commands/help/help-improve.md +0 -53
- package/dist/commands/help/help-reject.md +0 -8
- package/dist/commands/install-audit.js +0 -385
- package/dist/commands/vault.js +0 -310
- package/dist/indexer/match-contributors.js +0 -141
- package/dist/integrations/agent/pipeline.js +0 -39
- package/dist/integrations/agent/runners.js +0 -31
- package/dist/llm/prompts/graph-extract-user-prompt.md +0 -12
- /package/dist/{tasks → assets}/backends/launchd-template.xml +0 -0
- /package/dist/{tasks → assets}/backends/schtasks-template.xml +0 -0
- /package/dist/{commands → assets}/help/help-propose.md +0 -0
- /package/dist/{wiki → assets/wiki}/index-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/ingest-workflow-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/log-template.md +0 -0
- /package/dist/{wiki → assets/wiki}/schema-template.md +0 -0
- /package/dist/{workflows → assets/workflows}/workflow-template.md +0 -0
package/dist/setup/setup.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
/**
|
|
2
5
|
* Interactive configuration wizard for akm.
|
|
3
6
|
*
|
|
@@ -5,23 +8,147 @@
|
|
|
5
8
|
* registry selection, stash sources, and agent platform discovery.
|
|
6
9
|
* Collects all choices and writes config once at the end.
|
|
7
10
|
*/
|
|
11
|
+
import { promises as dnsPromises } from "node:dns";
|
|
8
12
|
import fs from "node:fs";
|
|
9
13
|
import os from "node:os";
|
|
10
14
|
import path from "node:path";
|
|
11
15
|
import * as p from "@clack/prompts";
|
|
12
16
|
import { akmInit } from "../commands/init";
|
|
13
17
|
import { isHttpUrl } from "../core/common";
|
|
14
|
-
import { DEFAULT_CONFIG, loadUserConfig, saveConfig } from "../core/config";
|
|
15
|
-
import {
|
|
18
|
+
import { DEFAULT_CONFIG, getDefaultLlmConfig, loadUserConfig, saveConfig } from "../core/config";
|
|
19
|
+
import { ConfigError } from "../core/errors";
|
|
20
|
+
import { assertSafeStashDir, getConfigPath, getDefaultStashDir, isTransientStashPath } from "../core/paths";
|
|
16
21
|
import { warn } from "../core/warn";
|
|
17
22
|
import { closeDatabase, isVecAvailable, openDatabase } from "../indexer/db";
|
|
18
23
|
import { akmIndex } from "../indexer/indexer";
|
|
19
24
|
import { clearSemanticStatus, deriveSemanticProviderFingerprint, writeSemanticStatus, } from "../indexer/semantic-status";
|
|
20
|
-
import { detectAgentCliProfiles, pickDefaultAgentProfile
|
|
25
|
+
import { detectAgentCliProfiles, pickDefaultAgentProfile } from "../integrations/agent";
|
|
21
26
|
import { probeLlmCapabilities } from "../llm/client";
|
|
22
27
|
import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "../llm/embedder";
|
|
23
28
|
import { detectAgentPlatforms, detectOllama } from "./detect";
|
|
24
29
|
import { createSetupContext, runSetupSteps } from "./steps";
|
|
30
|
+
// ── Setup sandbox guard ─────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Refuse to persist an explicit `--dir /tmp/...` stashDir to the user's
|
|
33
|
+
* config. The OS may reap the directory at any time, and the next run will
|
|
34
|
+
* see a `stashDir` that points at a deleted path (falling back to ~/akm
|
|
35
|
+
* silently). Mirrors the `assertInitSandbox` check in commands/init.ts, but
|
|
36
|
+
* fires under all runtimes (not just `bun test`) because `akm setup --dir
|
|
37
|
+
* /tmp/X` is a documented isolation pattern that has been observed to
|
|
38
|
+
* silently clobber the host config — see
|
|
39
|
+
* `docs/technical/incidents/2026-05-23-setup-clobbers-user-config.md`.
|
|
40
|
+
*
|
|
41
|
+
* Escape hatch: set `AKM_FORCE_SETUP_TMP_STASH=1` to override. When the
|
|
42
|
+
* escape hatch is on, `applyStashIsolationToEnv` below also pre-sets
|
|
43
|
+
* `AKM_STASH_DIR` so that the `getConfigDir` / `getCacheDir` isolation
|
|
44
|
+
* rules fire and config + cache writes route into `$stashDir/.akm/`
|
|
45
|
+
* instead of the user's host `~/.config/akm`.
|
|
46
|
+
*/
|
|
47
|
+
function assertSetupSandbox(stashDir, dirExplicitlyProvided) {
|
|
48
|
+
if (!dirExplicitlyProvided)
|
|
49
|
+
return;
|
|
50
|
+
if (process.env.AKM_FORCE_SETUP_TMP_STASH === "1")
|
|
51
|
+
return;
|
|
52
|
+
if (!isTransientStashPath(stashDir))
|
|
53
|
+
return;
|
|
54
|
+
throw new ConfigError(`refusing to run \`akm setup --dir ${stashDir}\`: the path is in a transient/sandbox directory family the OS may reap. ` +
|
|
55
|
+
"Persisting it as the user's stashDir would leave the next run pointing at a deleted path (silently falling back to ~/akm). " +
|
|
56
|
+
"Use a persistent directory, OR set AKM_FORCE_SETUP_TMP_STASH=1 if you intentionally want a sandbox setup " +
|
|
57
|
+
"(setup will also auto-isolate config + cache writes into $stashDir/.akm/ so the host config is preserved).", "SETUP_TMP_STASH_REFUSED");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Propagate the explicit `--dir <stashDir>` choice to the env so that the
|
|
61
|
+
* `getConfigDir` / `getCacheDir` isolation rules in `src/core/paths.ts`
|
|
62
|
+
* actually fire for the duration of this setup run. Without this, a CLI
|
|
63
|
+
* caller who passes `--dir /tmp/X` but doesn't pre-export `AKM_STASH_DIR`
|
|
64
|
+
* would still write config to the host `~/.config/akm/config.json`. We
|
|
65
|
+
* only set the env var when:
|
|
66
|
+
* - `--dir` was explicitly provided (we have an operator-stated stash), AND
|
|
67
|
+
* - `AKM_STASH_DIR` is not already set (caller's explicit env wins).
|
|
68
|
+
* The set is process-wide; for the CLI that's the right scope (the process
|
|
69
|
+
* is about to do all its work against this stash). For tests, each test
|
|
70
|
+
* already isolates env via beforeEach/afterEach so there is no leak.
|
|
71
|
+
*/
|
|
72
|
+
function applyStashIsolationToEnv(stashDir, dirExplicitlyProvided) {
|
|
73
|
+
if (!dirExplicitlyProvided)
|
|
74
|
+
return;
|
|
75
|
+
if (process.env.AKM_STASH_DIR?.trim())
|
|
76
|
+
return;
|
|
77
|
+
process.env.AKM_STASH_DIR = stashDir;
|
|
78
|
+
}
|
|
79
|
+
/** Read the currently-configured LLM connection from a loaded config. */
|
|
80
|
+
function getCurrentLlm(config) {
|
|
81
|
+
return getDefaultLlmConfig(config);
|
|
82
|
+
}
|
|
83
|
+
/** Read a synthesised legacy-shape agent block from the new-shape AkmConfig. */
|
|
84
|
+
function getCurrentAgentBlock(config) {
|
|
85
|
+
if (!config.profiles?.agent && !config.defaults?.agent)
|
|
86
|
+
return undefined;
|
|
87
|
+
const block = {};
|
|
88
|
+
if (config.defaults?.agent)
|
|
89
|
+
block.default = config.defaults.agent;
|
|
90
|
+
if (config.profiles?.agent) {
|
|
91
|
+
const profiles = {};
|
|
92
|
+
for (const [name, raw] of Object.entries(config.profiles.agent)) {
|
|
93
|
+
profiles[name] = {
|
|
94
|
+
...(raw.platform === "opencode-sdk" ? { sdkMode: true } : {}),
|
|
95
|
+
...(raw.model ? { model: raw.model } : {}),
|
|
96
|
+
...(raw.bin ? { bin: raw.bin } : {}),
|
|
97
|
+
...(raw.args ? { args: raw.args } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
block.profiles = profiles;
|
|
101
|
+
}
|
|
102
|
+
return block;
|
|
103
|
+
}
|
|
104
|
+
/** Apply an LLM connection patch onto the new-shape config. */
|
|
105
|
+
function applyLegacyLlm(config, llm) {
|
|
106
|
+
if (!llm) {
|
|
107
|
+
// Clear the default LLM profile.
|
|
108
|
+
const name = config.defaults?.llm ?? "default";
|
|
109
|
+
const remaining = { ...(config.profiles?.llm ?? {}) };
|
|
110
|
+
delete remaining[name];
|
|
111
|
+
return {
|
|
112
|
+
profiles: { ...(config.profiles ?? {}), llm: remaining },
|
|
113
|
+
defaults: { ...(config.defaults ?? {}), llm: undefined },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const name = config.defaults?.llm ?? "default";
|
|
117
|
+
return {
|
|
118
|
+
profiles: {
|
|
119
|
+
...(config.profiles ?? {}),
|
|
120
|
+
llm: { ...(config.profiles?.llm ?? {}), [name]: llm },
|
|
121
|
+
},
|
|
122
|
+
defaults: { ...(config.defaults ?? {}), llm: name },
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Apply a legacy-shape agent block onto the new-shape config. */
|
|
126
|
+
function applyLegacyAgent(config, agent) {
|
|
127
|
+
if (!agent) {
|
|
128
|
+
return {
|
|
129
|
+
profiles: { ...(config.profiles ?? {}), agent: undefined },
|
|
130
|
+
defaults: { ...(config.defaults ?? {}), agent: undefined },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const v2Profiles = { ...(config.profiles?.agent ?? {}) };
|
|
134
|
+
for (const [name, profile] of Object.entries(agent.profiles ?? {})) {
|
|
135
|
+
const platform = profile.sdkMode
|
|
136
|
+
? "opencode-sdk"
|
|
137
|
+
: name.toLowerCase().includes("claude")
|
|
138
|
+
? "claude"
|
|
139
|
+
: "opencode";
|
|
140
|
+
v2Profiles[name] = {
|
|
141
|
+
platform,
|
|
142
|
+
...(profile.bin ? { bin: profile.bin } : {}),
|
|
143
|
+
...(profile.args ? { args: profile.args } : {}),
|
|
144
|
+
...(profile.model ? { model: profile.model } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
profiles: { ...(config.profiles ?? {}), agent: v2Profiles },
|
|
149
|
+
defaults: { ...(config.defaults ?? {}), agent: agent.default },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
25
152
|
/**
|
|
26
153
|
* Recommended GitHub repositories shown during setup.
|
|
27
154
|
*/
|
|
@@ -122,7 +249,6 @@ function cloneLlmConfig(llm) {
|
|
|
122
249
|
return {
|
|
123
250
|
...llm,
|
|
124
251
|
...(llm.capabilities ? { capabilities: { ...llm.capabilities } } : {}),
|
|
125
|
-
...(llm.features ? { features: { ...llm.features } } : {}),
|
|
126
252
|
...(llm.extraParams ? { extraParams: { ...llm.extraParams } } : {}),
|
|
127
253
|
};
|
|
128
254
|
}
|
|
@@ -202,15 +328,27 @@ async function stepAdditionalSources(currentSources) {
|
|
|
202
328
|
return sources;
|
|
203
329
|
}
|
|
204
330
|
/**
|
|
205
|
-
* Quick connectivity check. Returns true if we can
|
|
206
|
-
*
|
|
207
|
-
* dependent setup steps gracefully
|
|
331
|
+
* Quick connectivity check. Returns true if we can resolve a hostname
|
|
332
|
+
* the user has already implicitly trusted within 3 seconds, false
|
|
333
|
+
* otherwise. Used to skip network-dependent setup steps gracefully
|
|
334
|
+
* when offline.
|
|
335
|
+
*
|
|
336
|
+
* We use a DNS lookup against `github.com` rather than an HTTP request
|
|
337
|
+
* because (1) it doesn't actually send a request to anyone we aren't
|
|
338
|
+
* already talking to (the user got akm from GitHub and `akm upgrade`
|
|
339
|
+
* polls api.github.com), and (2) DNS is the right layer for "do we have
|
|
340
|
+
* working network" without making the user opt into yet another remote.
|
|
341
|
+
* The previous implementation pinged https://dns.google which
|
|
342
|
+
* contradicted the spirit of "no remote endpoints akm doesn't own."
|
|
208
343
|
*
|
|
209
344
|
* @internal Exported for testing only.
|
|
210
345
|
*/
|
|
211
346
|
export async function isOnline() {
|
|
212
347
|
try {
|
|
213
|
-
await
|
|
348
|
+
await Promise.race([
|
|
349
|
+
dnsPromises.lookup("github.com"),
|
|
350
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("dns lookup timed out")), 3000).unref()),
|
|
351
|
+
]);
|
|
214
352
|
return true;
|
|
215
353
|
}
|
|
216
354
|
catch {
|
|
@@ -358,6 +496,14 @@ async function stepStashDir(current, options) {
|
|
|
358
496
|
validate: (v) => {
|
|
359
497
|
if (!v?.trim())
|
|
360
498
|
return "Path cannot be empty";
|
|
499
|
+
try {
|
|
500
|
+
assertSafeStashDir(v.trim());
|
|
501
|
+
}
|
|
502
|
+
catch (err) {
|
|
503
|
+
if (err instanceof Error)
|
|
504
|
+
return err.message;
|
|
505
|
+
return "Refused: unsafe stash directory";
|
|
506
|
+
}
|
|
361
507
|
},
|
|
362
508
|
}));
|
|
363
509
|
return customPath.trim();
|
|
@@ -492,21 +638,22 @@ export async function stepLlm(current, ollamaEndpoint, ollamaChatModels) {
|
|
|
492
638
|
}
|
|
493
639
|
options.push({ value: "custom", label: "Custom OpenAI-compatible endpoint" });
|
|
494
640
|
options.push({ value: "none", label: "Skip LLM", hint: "no metadata enhancement during indexing" });
|
|
495
|
-
|
|
641
|
+
const currentLlm = getCurrentLlm(current);
|
|
642
|
+
if (currentLlm) {
|
|
496
643
|
options.push({
|
|
497
644
|
value: "keep",
|
|
498
|
-
label: `Keep current: ${
|
|
499
|
-
hint:
|
|
645
|
+
label: `Keep current: ${currentLlm.provider ?? currentLlm.endpoint}`,
|
|
646
|
+
hint: currentLlm.model,
|
|
500
647
|
});
|
|
501
648
|
}
|
|
502
|
-
const initialValue =
|
|
649
|
+
const initialValue = currentLlm ? "keep" : ollamaAvailable ? "ollama" : (LLM_PRESETS[0]?.value ?? "none");
|
|
503
650
|
const choice = await prompt(() => p.select({
|
|
504
651
|
message: "Configure an LLM for richer metadata during indexing:",
|
|
505
652
|
options,
|
|
506
653
|
initialValue,
|
|
507
654
|
}));
|
|
508
655
|
if (choice === "keep")
|
|
509
|
-
return cloneLlmConfig(
|
|
656
|
+
return cloneLlmConfig(currentLlm);
|
|
510
657
|
if (choice === "none")
|
|
511
658
|
return undefined;
|
|
512
659
|
let llm;
|
|
@@ -768,21 +915,22 @@ export async function stepSmallModelConnection(current) {
|
|
|
768
915
|
});
|
|
769
916
|
}
|
|
770
917
|
providerOptions.push({ value: "openai", label: "OpenAI", hint: "requires AKM_LLM_API_KEY" }, { value: "lmstudio", label: "LM Studio / local server", hint: "http://localhost:1234" }, { value: "custom", label: "Custom OpenAI-compatible endpoint" }, { value: "skip", label: "Skip — disable enrichment features" });
|
|
771
|
-
|
|
918
|
+
const currentLlmSmall = getCurrentLlm(current);
|
|
919
|
+
if (currentLlmSmall) {
|
|
772
920
|
providerOptions.push({
|
|
773
921
|
value: "keep",
|
|
774
|
-
label: `Keep current: ${
|
|
775
|
-
hint:
|
|
922
|
+
label: `Keep current: ${currentLlmSmall.provider ?? currentLlmSmall.endpoint}`,
|
|
923
|
+
hint: currentLlmSmall.model,
|
|
776
924
|
});
|
|
777
925
|
}
|
|
778
|
-
const initialValue =
|
|
926
|
+
const initialValue = currentLlmSmall ? "keep" : ollama.available ? "ollama" : "openai";
|
|
779
927
|
const providerChoice = await prompt(() => p.select({
|
|
780
928
|
message: "Provider:",
|
|
781
929
|
options: providerOptions,
|
|
782
930
|
initialValue,
|
|
783
931
|
}));
|
|
784
932
|
if (providerChoice === "keep") {
|
|
785
|
-
return { llm: cloneLlmConfig(
|
|
933
|
+
return { llm: cloneLlmConfig(currentLlmSmall), skipped: false, ollamaEndpoint };
|
|
786
934
|
}
|
|
787
935
|
if (providerChoice === "skip") {
|
|
788
936
|
p.note([
|
|
@@ -934,11 +1082,12 @@ export async function stepAgentConnection(current, smallModel) {
|
|
|
934
1082
|
p.note([
|
|
935
1083
|
"This connection is used for agentic commands:",
|
|
936
1084
|
" • akm propose (generate improvement proposals)",
|
|
937
|
-
" • akm
|
|
1085
|
+
" • akm improve (run the reflect/distill/consolidate self-improvement pipeline)",
|
|
938
1086
|
" • akm tasks run (run automated task prompts)",
|
|
939
1087
|
].join("\n"));
|
|
940
1088
|
// Detect available CLI agents.
|
|
941
|
-
const detections = detectAgentCliProfiles(current
|
|
1089
|
+
const detections = detectAgentCliProfiles(current);
|
|
1090
|
+
const currentAgentBlock = getCurrentAgentBlock(current);
|
|
942
1091
|
const availableClis = detections.filter((d) => d.available);
|
|
943
1092
|
const agentOptions = [];
|
|
944
1093
|
if (!smallModel.skipped && smallModel.llm) {
|
|
@@ -957,15 +1106,15 @@ export async function stepAgentConnection(current, smallModel) {
|
|
|
957
1106
|
});
|
|
958
1107
|
}
|
|
959
1108
|
agentOptions.push({ value: "none", label: "None — disable agentic features" });
|
|
960
|
-
if (
|
|
961
|
-
const currentDesc =
|
|
962
|
-
? `CLI: ${
|
|
963
|
-
:
|
|
964
|
-
? `SDK: ${
|
|
1109
|
+
if (currentAgentBlock) {
|
|
1110
|
+
const currentDesc = currentAgentBlock.default
|
|
1111
|
+
? `CLI: ${currentAgentBlock.default}`
|
|
1112
|
+
: currentAgentBlock.profiles?.default?.model
|
|
1113
|
+
? `SDK: ${currentAgentBlock.profiles.default.model}`
|
|
965
1114
|
: "configured";
|
|
966
1115
|
agentOptions.push({ value: "keep", label: `Keep current: ${currentDesc}` });
|
|
967
1116
|
}
|
|
968
|
-
const initialAgentValue =
|
|
1117
|
+
const initialAgentValue = currentAgentBlock
|
|
969
1118
|
? "keep"
|
|
970
1119
|
: availableClis.length > 0 && smallModel.skipped
|
|
971
1120
|
? "cli-agent"
|
|
@@ -980,13 +1129,13 @@ export async function stepAgentConnection(current, smallModel) {
|
|
|
980
1129
|
initialValue: initialAgentValue,
|
|
981
1130
|
}));
|
|
982
1131
|
if (agentChoice === "keep") {
|
|
983
|
-
return
|
|
1132
|
+
return currentAgentBlock;
|
|
984
1133
|
}
|
|
985
1134
|
if (agentChoice === "none") {
|
|
986
1135
|
p.note([
|
|
987
1136
|
"Agentic features disabled:",
|
|
988
1137
|
' • akm propose — will show "no agent configured" error',
|
|
989
|
-
' • akm
|
|
1138
|
+
' • akm improve — will show "no agent configured" error',
|
|
990
1139
|
' • akm tasks run — will show "no agent configured" error',
|
|
991
1140
|
"",
|
|
992
1141
|
"You can configure this later with `akm setup`.",
|
|
@@ -1008,11 +1157,11 @@ export async function stepAgentConnection(current, smallModel) {
|
|
|
1008
1157
|
}));
|
|
1009
1158
|
const profileName = smallModel.llm.provider ?? "default";
|
|
1010
1159
|
return {
|
|
1011
|
-
...(
|
|
1160
|
+
...(currentAgentBlock ?? {}),
|
|
1012
1161
|
profiles: {
|
|
1013
|
-
...(
|
|
1162
|
+
...(currentAgentBlock?.profiles ?? {}),
|
|
1014
1163
|
[profileName]: {
|
|
1015
|
-
...(
|
|
1164
|
+
...(currentAgentBlock?.profiles?.[profileName] ?? {}),
|
|
1016
1165
|
sdkMode: true,
|
|
1017
1166
|
model: agentModel.trim(),
|
|
1018
1167
|
endpoint: smallModel.llm.endpoint,
|
|
@@ -1025,9 +1174,9 @@ export async function stepAgentConnection(current, smallModel) {
|
|
|
1025
1174
|
if (agentChoice === "cli-agent") {
|
|
1026
1175
|
if (availableClis.length === 0) {
|
|
1027
1176
|
p.log.warn("No agent CLIs detected on PATH.");
|
|
1028
|
-
return
|
|
1177
|
+
return currentAgentBlock;
|
|
1029
1178
|
}
|
|
1030
|
-
const initialCli = pickDefaultAgentProfile(detections,
|
|
1179
|
+
const initialCli = pickDefaultAgentProfile(detections, currentAgentBlock?.default) ?? availableClis[0]?.name;
|
|
1031
1180
|
const selectedCli = await prompt(() => p.select({
|
|
1032
1181
|
message: "Which CLI agent?",
|
|
1033
1182
|
options: availableClis.map((d) => ({
|
|
@@ -1038,7 +1187,7 @@ export async function stepAgentConnection(current, smallModel) {
|
|
|
1038
1187
|
initialValue: initialCli,
|
|
1039
1188
|
}));
|
|
1040
1189
|
return {
|
|
1041
|
-
...(
|
|
1190
|
+
...(currentAgentBlock ?? {}),
|
|
1042
1191
|
default: selectedCli,
|
|
1043
1192
|
};
|
|
1044
1193
|
}
|
|
@@ -1069,9 +1218,9 @@ export async function stepAgentConnection(current, smallModel) {
|
|
|
1069
1218
|
...(newApiKeyInput?.trim() ? { apiKey: newApiKeyInput.trim() } : {}),
|
|
1070
1219
|
};
|
|
1071
1220
|
return {
|
|
1072
|
-
...(
|
|
1221
|
+
...(currentAgentBlock ?? {}),
|
|
1073
1222
|
profiles: {
|
|
1074
|
-
...(
|
|
1223
|
+
...(currentAgentBlock?.profiles ?? {}),
|
|
1075
1224
|
custom: customProfile,
|
|
1076
1225
|
},
|
|
1077
1226
|
default: "custom",
|
|
@@ -1090,19 +1239,20 @@ function printCapabilitySummary(smallModelSkipped, agentConfigured) {
|
|
|
1090
1239
|
lines.push(" ✗ akm index, akm distill, akm remember — run `akm setup` to enable");
|
|
1091
1240
|
}
|
|
1092
1241
|
if (agentConfigured) {
|
|
1093
|
-
lines.push(" ✓ akm propose, akm
|
|
1242
|
+
lines.push(" ✓ akm propose, akm improve, akm tasks — agent configured");
|
|
1094
1243
|
}
|
|
1095
1244
|
else {
|
|
1096
|
-
lines.push(" ✗ akm propose, akm
|
|
1245
|
+
lines.push(" ✗ akm propose, akm improve, akm tasks — run `akm setup` to enable");
|
|
1097
1246
|
}
|
|
1098
1247
|
p.note(lines.join("\n"), "Feature Summary");
|
|
1099
1248
|
}
|
|
1100
1249
|
export async function stepAgentSelection(current, detections) {
|
|
1250
|
+
const currentAgentBlock = getCurrentAgentBlock(current);
|
|
1101
1251
|
const available = detections.filter((d) => d.available);
|
|
1102
1252
|
if (available.length === 0) {
|
|
1103
|
-
return
|
|
1253
|
+
return currentAgentBlock;
|
|
1104
1254
|
}
|
|
1105
|
-
const initialValue = pickDefaultAgentProfile(detections,
|
|
1255
|
+
const initialValue = pickDefaultAgentProfile(detections, currentAgentBlock?.default) ?? available[0]?.name;
|
|
1106
1256
|
const selectedDefault = await prompt(() => p.select({
|
|
1107
1257
|
message: "Which detected agent CLI should be the default?",
|
|
1108
1258
|
options: [
|
|
@@ -1116,16 +1266,16 @@ export async function stepAgentSelection(current, detections) {
|
|
|
1116
1266
|
initialValue,
|
|
1117
1267
|
}));
|
|
1118
1268
|
if (selectedDefault === "disabled") {
|
|
1119
|
-
if (!
|
|
1269
|
+
if (!currentAgentBlock?.profiles && !currentAgentBlock?.timeoutMs) {
|
|
1120
1270
|
return undefined;
|
|
1121
1271
|
}
|
|
1122
1272
|
return {
|
|
1123
|
-
...(
|
|
1273
|
+
...(currentAgentBlock ?? {}),
|
|
1124
1274
|
default: undefined,
|
|
1125
1275
|
};
|
|
1126
1276
|
}
|
|
1127
1277
|
return {
|
|
1128
|
-
...(
|
|
1278
|
+
...(currentAgentBlock ?? {}),
|
|
1129
1279
|
default: selectedDefault,
|
|
1130
1280
|
};
|
|
1131
1281
|
}
|
|
@@ -1162,14 +1312,15 @@ export async function stepOutputConfig(current) {
|
|
|
1162
1312
|
* @internal Exported for testing only.
|
|
1163
1313
|
*/
|
|
1164
1314
|
export function stepAgentCliDetection(current, detectFn = detectAgentCliProfiles) {
|
|
1165
|
-
const detections = detectFn(current
|
|
1166
|
-
const
|
|
1315
|
+
const detections = detectFn(current);
|
|
1316
|
+
const currentAgentBlock = getCurrentAgentBlock(current);
|
|
1317
|
+
const defaultName = pickDefaultAgentProfile(detections, currentAgentBlock?.default);
|
|
1167
1318
|
// No installed agents found and no existing config → leave block absent.
|
|
1168
|
-
if (!defaultName && !
|
|
1319
|
+
if (!defaultName && !currentAgentBlock) {
|
|
1169
1320
|
return { detections };
|
|
1170
1321
|
}
|
|
1171
1322
|
const agent = {
|
|
1172
|
-
...(
|
|
1323
|
+
...(currentAgentBlock ?? {}),
|
|
1173
1324
|
...(defaultName ? { default: defaultName } : {}),
|
|
1174
1325
|
};
|
|
1175
1326
|
return { agent, detections };
|
|
@@ -1221,11 +1372,10 @@ export function buildSetupSteps(options) {
|
|
|
1221
1372
|
label: "LLM Provider",
|
|
1222
1373
|
async run(ctx) {
|
|
1223
1374
|
if (!options.online) {
|
|
1224
|
-
ctx.apply({ llm: ctx.config.llm });
|
|
1225
1375
|
return;
|
|
1226
1376
|
}
|
|
1227
1377
|
const llm = await stepLlm(ctx.config, ollamaEndpoint, ollamaChatModels);
|
|
1228
|
-
ctx.apply(
|
|
1378
|
+
ctx.apply(applyLegacyLlm(ctx.config, llm));
|
|
1229
1379
|
},
|
|
1230
1380
|
},
|
|
1231
1381
|
{
|
|
@@ -1273,8 +1423,11 @@ export function buildSetupSteps(options) {
|
|
|
1273
1423
|
else {
|
|
1274
1424
|
p.log.info("No agent CLIs detected on PATH. Agent commands will be disabled until one is installed and `akm setup` is re-run.");
|
|
1275
1425
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1426
|
+
// Inject the detected agent block into a synthetic AkmConfig so
|
|
1427
|
+
// stepAgentSelection can read it via getCurrentAgentBlock().
|
|
1428
|
+
const synthConfig = { ...ctx.config, ...applyLegacyAgent(ctx.config, result.agent) };
|
|
1429
|
+
const agent = await stepAgentSelection(synthConfig, result.detections);
|
|
1430
|
+
ctx.apply(applyLegacyAgent(ctx.config, agent));
|
|
1278
1431
|
},
|
|
1279
1432
|
},
|
|
1280
1433
|
{
|
|
@@ -1294,6 +1447,10 @@ export async function runSetupWizard(opts) {
|
|
|
1294
1447
|
const configPath = getConfigPath();
|
|
1295
1448
|
// Resolve stash directory early so akmInit can run before any prompts
|
|
1296
1449
|
const resolvedStashDir = opts?.dir ? path.resolve(opts.dir) : (current.stashDir ?? getDefaultStashDir());
|
|
1450
|
+
// Refuse explicit --dir /tmp/... before doing any work — protects the host
|
|
1451
|
+
// config from being clobbered with a stashDir that the OS may reap.
|
|
1452
|
+
assertSetupSandbox(resolvedStashDir, opts?.dir != null);
|
|
1453
|
+
applyStashIsolationToEnv(resolvedStashDir, opts?.dir != null);
|
|
1297
1454
|
// Bootstrap directory structure before any prompts so the stash exists
|
|
1298
1455
|
// even if the wizard is interrupted after this point.
|
|
1299
1456
|
if (!opts?.noInit) {
|
|
@@ -1326,11 +1483,11 @@ export async function runSetupWizard(opts) {
|
|
|
1326
1483
|
// Step 1/2: Small model connection (for enrichment features)
|
|
1327
1484
|
const smallModelResult = await stepSmallModelConnection(ctx.config);
|
|
1328
1485
|
if (!smallModelResult.skipped) {
|
|
1329
|
-
ctx.apply(
|
|
1486
|
+
ctx.apply(applyLegacyLlm(ctx.config, smallModelResult.llm));
|
|
1330
1487
|
}
|
|
1331
1488
|
// Step 2/2: Agent connection (for agentic features)
|
|
1332
1489
|
const agentConfig = await stepAgentConnection(ctx.config, smallModelResult);
|
|
1333
|
-
ctx.apply(
|
|
1490
|
+
ctx.apply(applyLegacyAgent(ctx.config, agentConfig));
|
|
1334
1491
|
const newConfig = {
|
|
1335
1492
|
...ctx.config,
|
|
1336
1493
|
// Preserve fields the steps don't manage explicitly.
|
|
@@ -1339,7 +1496,7 @@ export async function runSetupWizard(opts) {
|
|
|
1339
1496
|
const semanticSearchMode = outcome.semantic;
|
|
1340
1497
|
const stashDir = newConfig.stashDir ?? current.stashDir ?? getDefaultStashDir();
|
|
1341
1498
|
const embedding = newConfig.embedding;
|
|
1342
|
-
const llm = newConfig
|
|
1499
|
+
const llm = getDefaultLlmConfig(newConfig);
|
|
1343
1500
|
const registries = newConfig.registries;
|
|
1344
1501
|
const allStashes = newConfig.sources ?? [];
|
|
1345
1502
|
// Feature capability summary
|
|
@@ -1354,7 +1511,7 @@ export async function runSetupWizard(opts) {
|
|
|
1354
1511
|
`Semantic search: ${semanticSearchMode.mode}`,
|
|
1355
1512
|
`Registries: ${effectiveRegistries.filter((r) => r.enabled !== false).length} enabled`,
|
|
1356
1513
|
`Stash sources: ${allStashes.length}`,
|
|
1357
|
-
`Agent default: ${newConfig.agent
|
|
1514
|
+
`Agent default: ${newConfig.defaults?.agent ?? "disabled"}`,
|
|
1358
1515
|
`Output: ${newConfig.output?.format ?? "json"} / ${newConfig.output?.detail ?? "brief"}`,
|
|
1359
1516
|
].join("\n"), "Configuration Summary");
|
|
1360
1517
|
const shouldSave = await prompt(() => p.confirm({
|
|
@@ -1454,6 +1611,8 @@ export async function runSetupWizard(opts) {
|
|
|
1454
1611
|
export async function runSetupWithDefaults(opts) {
|
|
1455
1612
|
const current = loadUserConfig();
|
|
1456
1613
|
const stashDir = opts.dir ? path.resolve(opts.dir) : (current.stashDir ?? getDefaultStashDir());
|
|
1614
|
+
assertSetupSandbox(stashDir, opts.dir != null);
|
|
1615
|
+
applyStashIsolationToEnv(stashDir, opts.dir != null);
|
|
1457
1616
|
// Bootstrap directory structure first
|
|
1458
1617
|
let initResult;
|
|
1459
1618
|
if (!opts.noInit) {
|
|
@@ -1471,11 +1630,11 @@ export async function runSetupWithDefaults(opts) {
|
|
|
1471
1630
|
if (!ctx.config.stashDir)
|
|
1472
1631
|
ctx.apply({ stashDir });
|
|
1473
1632
|
// Auto-detect agent CLI if not already configured
|
|
1474
|
-
if (!ctx.config.agent) {
|
|
1633
|
+
if (!ctx.config.defaults?.agent) {
|
|
1475
1634
|
const detected = detectAgentCliProfiles(undefined);
|
|
1476
1635
|
const defaultProfile = pickDefaultAgentProfile(detected, undefined);
|
|
1477
1636
|
if (defaultProfile) {
|
|
1478
|
-
ctx.apply(
|
|
1637
|
+
ctx.apply(applyLegacyAgent(ctx.config, { default: defaultProfile }));
|
|
1479
1638
|
}
|
|
1480
1639
|
}
|
|
1481
1640
|
saveConfig(ctx.config);
|
|
@@ -1493,7 +1652,6 @@ export async function runSetupWithDefaults(opts) {
|
|
|
1493
1652
|
* Validates required sub-fields and strips unknown/restricted keys.
|
|
1494
1653
|
*/
|
|
1495
1654
|
export async function runSetupFromConfig(opts) {
|
|
1496
|
-
// Phase 1: Parse JSON
|
|
1497
1655
|
let incoming;
|
|
1498
1656
|
try {
|
|
1499
1657
|
incoming = JSON.parse(opts.configJson);
|
|
@@ -1502,7 +1660,16 @@ export async function runSetupFromConfig(opts) {
|
|
|
1502
1660
|
throw new Error(`Invalid JSON in --config: ${e.message}`);
|
|
1503
1661
|
}
|
|
1504
1662
|
// Phase 2: Validate — only allow safe top-level keys
|
|
1505
|
-
const ALLOWED_KEYS = new Set([
|
|
1663
|
+
const ALLOWED_KEYS = new Set([
|
|
1664
|
+
"stashDir",
|
|
1665
|
+
"llm",
|
|
1666
|
+
"embedding",
|
|
1667
|
+
"agent",
|
|
1668
|
+
"semanticSearchMode",
|
|
1669
|
+
"output",
|
|
1670
|
+
"profiles",
|
|
1671
|
+
"defaults",
|
|
1672
|
+
]);
|
|
1506
1673
|
for (const key of Object.keys(incoming)) {
|
|
1507
1674
|
if (!ALLOWED_KEYS.has(key)) {
|
|
1508
1675
|
warn(`[akm setup] Ignoring unknown or restricted config key: "${key}"`);
|
|
@@ -1529,22 +1696,42 @@ export async function runSetupFromConfig(opts) {
|
|
|
1529
1696
|
: incoming.stashDir
|
|
1530
1697
|
? path.resolve(incoming.stashDir)
|
|
1531
1698
|
: (current.stashDir ?? getDefaultStashDir());
|
|
1532
|
-
const
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1699
|
+
const stashDirExplicit = opts.dir != null || incoming.stashDir != null;
|
|
1700
|
+
assertSetupSandbox(stashDir, stashDirExplicit);
|
|
1701
|
+
applyStashIsolationToEnv(stashDir, stashDirExplicit);
|
|
1702
|
+
let merged = { ...current, stashDir };
|
|
1703
|
+
// Apply non-llm/agent keys directly.
|
|
1704
|
+
const mergedRec = merged;
|
|
1705
|
+
for (const key of Object.keys(incoming)) {
|
|
1706
|
+
if (key === "llm" || key === "agent")
|
|
1707
|
+
continue;
|
|
1708
|
+
mergedRec[key] = incoming[key];
|
|
1709
|
+
}
|
|
1710
|
+
// Translate legacy llm/agent inputs into the new shape.
|
|
1711
|
+
if (incoming.llm) {
|
|
1712
|
+
merged = { ...merged, ...applyLegacyLlm(merged, incoming.llm) };
|
|
1713
|
+
}
|
|
1714
|
+
if (incoming.agent) {
|
|
1715
|
+
merged = { ...merged, ...applyLegacyAgent(merged, incoming.agent) };
|
|
1716
|
+
}
|
|
1537
1717
|
// Bootstrap directory structure
|
|
1538
1718
|
let initResult;
|
|
1539
1719
|
if (!opts.noInit) {
|
|
1540
1720
|
initResult = await akmInit({ dir: stashDir });
|
|
1541
1721
|
}
|
|
1542
1722
|
// Optional probe
|
|
1543
|
-
|
|
1723
|
+
const mergedLlm = getDefaultLlmConfig(merged);
|
|
1724
|
+
if (opts.probe && mergedLlm) {
|
|
1544
1725
|
try {
|
|
1545
|
-
const caps = await probeLlmCapabilities(
|
|
1726
|
+
const caps = await probeLlmCapabilities(mergedLlm);
|
|
1546
1727
|
if (caps.reachable) {
|
|
1547
|
-
merged
|
|
1728
|
+
merged = {
|
|
1729
|
+
...merged,
|
|
1730
|
+
...applyLegacyLlm(merged, {
|
|
1731
|
+
...mergedLlm,
|
|
1732
|
+
capabilities: { structuredOutput: caps.structuredOutput ?? false },
|
|
1733
|
+
}),
|
|
1734
|
+
};
|
|
1548
1735
|
}
|
|
1549
1736
|
}
|
|
1550
1737
|
catch {
|
|
@@ -1561,3 +1748,55 @@ export async function runSetupFromConfig(opts) {
|
|
|
1561
1748
|
ripgrep: initResult?.ripgrep,
|
|
1562
1749
|
};
|
|
1563
1750
|
}
|
|
1751
|
+
// ── Setup --from <file> bootstrap helper ────────────────────────────────────
|
|
1752
|
+
/**
|
|
1753
|
+
* Resolve a `--from <file>` argument to a JSON-encoded config payload suitable
|
|
1754
|
+
* for `runSetupFromConfig({ configJson })`. Used by the CLI to bootstrap from
|
|
1755
|
+
* a JSON or YAML file on disk; extracted as a standalone function so its
|
|
1756
|
+
* filesystem and parser behaviour can be unit-tested directly.
|
|
1757
|
+
*
|
|
1758
|
+
* - Expands a leading `~` to the current user's home directory.
|
|
1759
|
+
* - Resolves the path against `cwd ?? process.cwd()` for relative inputs.
|
|
1760
|
+
* - Detects YAML vs JSON via the file extension (`.yml`/`.yaml` → YAML;
|
|
1761
|
+
* anything else, including `.json`, parses as JSON).
|
|
1762
|
+
* - Throws `ConfigError("INVALID_CONFIG_FILE")` when the file does not exist,
|
|
1763
|
+
* cannot be read, cannot be parsed, or contains a non-object top level.
|
|
1764
|
+
*
|
|
1765
|
+
* Returns `{ configJson, resolvedPath, format }` so callers can log which
|
|
1766
|
+
* file was actually loaded and which parser was used.
|
|
1767
|
+
*/
|
|
1768
|
+
export async function loadSetupConfigFromFile(filePath, opts) {
|
|
1769
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
1770
|
+
const homeDir = opts?.homeDir ?? os.homedir();
|
|
1771
|
+
const expanded = filePath.startsWith("~") ? path.join(homeDir, filePath.slice(1)) : filePath;
|
|
1772
|
+
const resolvedPath = path.resolve(cwd, expanded);
|
|
1773
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
1774
|
+
throw new ConfigError(`Config file not found: ${resolvedPath}`, "INVALID_CONFIG_FILE");
|
|
1775
|
+
}
|
|
1776
|
+
let raw;
|
|
1777
|
+
try {
|
|
1778
|
+
raw = fs.readFileSync(resolvedPath, "utf8");
|
|
1779
|
+
}
|
|
1780
|
+
catch (err) {
|
|
1781
|
+
throw new ConfigError(`Failed to read config file ${resolvedPath}: ${err instanceof Error ? err.message : String(err)}`, "INVALID_CONFIG_FILE");
|
|
1782
|
+
}
|
|
1783
|
+
const ext = path.extname(resolvedPath).toLowerCase();
|
|
1784
|
+
const format = ext === ".yml" || ext === ".yaml" ? "yaml" : "json";
|
|
1785
|
+
let parsed;
|
|
1786
|
+
try {
|
|
1787
|
+
if (format === "yaml") {
|
|
1788
|
+
const { parse: yamlParse } = await import("yaml");
|
|
1789
|
+
parsed = yamlParse(raw);
|
|
1790
|
+
}
|
|
1791
|
+
else {
|
|
1792
|
+
parsed = JSON.parse(raw);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
catch (err) {
|
|
1796
|
+
throw new ConfigError(`Failed to parse ${format.toUpperCase()} config file ${resolvedPath}: ${err instanceof Error ? err.message : String(err)}`, "INVALID_CONFIG_FILE");
|
|
1797
|
+
}
|
|
1798
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1799
|
+
throw new ConfigError(`Config file ${resolvedPath} must contain a top-level object, got ${Array.isArray(parsed) ? "array" : typeof parsed}.`, "INVALID_CONFIG_FILE");
|
|
1800
|
+
}
|
|
1801
|
+
return { configJson: JSON.stringify(parsed), resolvedPath, format };
|
|
1802
|
+
}
|