akm-cli 0.9.0-beta.53 → 0.9.0-beta.55

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.
Files changed (123) hide show
  1. package/dist/cli/clack.js +56 -0
  2. package/dist/cli/confirm.js +1 -1
  3. package/dist/cli.js +5 -3
  4. package/dist/commands/agent/contribute-cli.js +2 -3
  5. package/dist/commands/env/env-cli.js +187 -202
  6. package/dist/commands/env/secret-cli.js +109 -121
  7. package/dist/commands/feedback-cli.js +152 -155
  8. package/dist/commands/health/advisories.js +151 -0
  9. package/dist/commands/health/html-report.js +33 -10
  10. package/dist/commands/health/improve-metrics.js +754 -0
  11. package/dist/commands/health/llm-usage.js +65 -0
  12. package/dist/commands/health/md-report.js +103 -0
  13. package/dist/commands/health/metrics.js +278 -0
  14. package/dist/commands/health/task-runs.js +135 -0
  15. package/dist/commands/health/types.js +18 -0
  16. package/dist/commands/health/windows.js +196 -0
  17. package/dist/commands/health.js +15 -1492
  18. package/dist/commands/improve/anti-collapse.js +170 -0
  19. package/dist/commands/improve/collapse-detector.js +3 -2
  20. package/dist/commands/improve/consolidate.js +636 -633
  21. package/dist/commands/improve/dedup.js +1 -1
  22. package/dist/commands/improve/distill/content-repair.js +202 -0
  23. package/dist/commands/improve/distill/promote-memory.js +228 -0
  24. package/dist/commands/improve/distill/quality-gate.js +233 -0
  25. package/dist/commands/improve/distill-guards.js +127 -0
  26. package/dist/commands/improve/distill.js +49 -575
  27. package/dist/commands/improve/extract-cli.js +74 -76
  28. package/dist/commands/improve/extract.js +6 -4
  29. package/dist/commands/improve/hot-probation.js +45 -0
  30. package/dist/commands/improve/improve-auto-accept.js +3 -2
  31. package/dist/commands/improve/improve-cli.js +14 -13
  32. package/dist/commands/improve/improve-result-file.js +2 -1
  33. package/dist/commands/improve/improve.js +6 -5
  34. package/dist/commands/improve/loop-stages.js +19 -21
  35. package/dist/commands/improve/outcome-loop.js +18 -16
  36. package/dist/commands/improve/preparation.js +23 -5
  37. package/dist/commands/improve/procedural.js +10 -31
  38. package/dist/commands/improve/recombine.js +19 -43
  39. package/dist/commands/improve/reflect.js +1 -1
  40. package/dist/commands/improve/schema-similarity-gate.js +168 -0
  41. package/dist/commands/improve/shared.js +48 -0
  42. package/dist/commands/observability-cli.js +4 -4
  43. package/dist/commands/proposal/drain-policies.js +2 -2
  44. package/dist/commands/proposal/drain.js +1 -1
  45. package/dist/commands/proposal/legacy-import.js +115 -0
  46. package/dist/commands/proposal/proposal-cli.js +3 -3
  47. package/dist/commands/proposal/proposal.js +2 -1
  48. package/dist/commands/proposal/propose.js +1 -1
  49. package/dist/commands/proposal/repository.js +829 -0
  50. package/dist/commands/proposal/validators/proposals.js +5 -920
  51. package/dist/commands/read/curate.js +4 -4
  52. package/dist/commands/read/remember-cli.js +132 -137
  53. package/dist/commands/read/search-cli.js +7 -5
  54. package/dist/commands/read/search.js +7 -3
  55. package/dist/commands/read/show.js +3 -5
  56. package/dist/commands/registry-cli.js +76 -87
  57. package/dist/commands/sources/add-cli.js +91 -95
  58. package/dist/commands/sources/history.js +1 -1
  59. package/dist/commands/sources/init.js +12 -0
  60. package/dist/commands/sources/schema-repair.js +1 -1
  61. package/dist/commands/sources/sources-cli.js +3 -3
  62. package/dist/commands/sources/stash-cli.js +2 -2
  63. package/dist/commands/tasks/default-tasks.js +12 -0
  64. package/dist/commands/tasks/tasks-cli.js +1 -2
  65. package/dist/commands/wiki-cli.js +2 -3
  66. package/dist/core/common.js +3 -3
  67. package/dist/core/config/config-schema.js +6 -0
  68. package/dist/core/config/config.js +12 -0
  69. package/dist/core/deep-merge.js +38 -0
  70. package/dist/core/events.js +2 -1
  71. package/dist/core/logs-db.js +8 -13
  72. package/dist/core/paths.js +14 -14
  73. package/dist/core/state-db.js +13 -1140
  74. package/dist/core/warn.js +21 -0
  75. package/dist/indexer/db/db.js +72 -709
  76. package/dist/indexer/db/entry-mapper.js +41 -0
  77. package/dist/indexer/db/schema.js +516 -0
  78. package/dist/indexer/ensure-index.js +3 -2
  79. package/dist/indexer/feedback/utility-policy.js +85 -0
  80. package/dist/indexer/graph/graph-extraction.js +2 -1
  81. package/dist/indexer/index-writer-lock.js +18 -0
  82. package/dist/indexer/indexer.js +94 -27
  83. package/dist/indexer/read-preflight.js +23 -0
  84. package/dist/indexer/search/fts-query.js +51 -0
  85. package/dist/indexer/walk/walker.js +21 -13
  86. package/dist/integrations/agent/detect.js +9 -0
  87. package/dist/integrations/agent/index.js +1 -1
  88. package/dist/integrations/agent/spawn.js +15 -66
  89. package/dist/llm/client.js +12 -0
  90. package/dist/llm/embedder.js +26 -2
  91. package/dist/llm/embedders/local.js +7 -1
  92. package/dist/output/text/helpers.js +13 -0
  93. package/dist/scripts/migrate-storage.js +6903 -7424
  94. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +49 -44
  95. package/dist/setup/detect.js +9 -0
  96. package/dist/setup/legacy-config.js +106 -0
  97. package/dist/setup/prompt.js +57 -0
  98. package/dist/setup/providers.js +14 -0
  99. package/dist/setup/registry-stash-loader.js +12 -0
  100. package/dist/setup/semantic-assets.js +124 -0
  101. package/dist/setup/setup.js +25 -1608
  102. package/dist/setup/steps/connection.js +734 -0
  103. package/dist/setup/steps/output.js +31 -0
  104. package/dist/setup/steps/platforms.js +124 -0
  105. package/dist/setup/steps/semantic.js +27 -0
  106. package/dist/setup/steps/sources.js +222 -0
  107. package/dist/setup/steps/stashdir.js +42 -0
  108. package/dist/setup/steps/tasks.js +152 -0
  109. package/dist/storage/repositories/canaries-repository.js +107 -0
  110. package/dist/storage/repositories/consolidation-repository.js +38 -0
  111. package/dist/storage/repositories/embeddings-repository.js +72 -0
  112. package/dist/storage/repositories/events-repository.js +187 -0
  113. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  114. package/dist/storage/repositories/improve-runs-repository.js +130 -0
  115. package/dist/storage/repositories/index-db.js +4 -7
  116. package/dist/storage/repositories/proposals-repository.js +220 -0
  117. package/dist/storage/repositories/recombine-repository.js +213 -0
  118. package/dist/storage/repositories/task-history-repository.js +93 -0
  119. package/dist/storage/sqlite-pragmas.js +3 -3
  120. package/dist/tasks/backends/index.js +9 -0
  121. package/dist/tasks/runner.js +11 -1
  122. package/package.json +2 -2
  123. package/dist/commands/improve/homeostatic.js +0 -497
@@ -53,12 +53,16 @@ function appendToLogFile(level, args) {
53
53
  }
54
54
  }
55
55
  function warn(...args) {
56
+ if (sinkOverride) {
57
+ sinkOverride("warn", args);
58
+ return;
59
+ }
56
60
  appendToLogFile("WARN", args);
57
61
  if (!quiet) {
58
62
  console.warn(...args);
59
63
  }
60
64
  }
61
- var quiet = false, logFilePath;
65
+ var quiet = false, logFilePath, sinkOverride;
62
66
  var init_warn = () => {};
63
67
 
64
68
  // node_modules/dotenv/lib/main.js
@@ -8608,8 +8612,8 @@ function resolveJournalMode(raw) {
8608
8612
  warnInvalidJournalModeOnce(raw);
8609
8613
  return "WAL";
8610
8614
  }
8611
- function resolveConfiguredJournalMode() {
8612
- return resolveJournalMode(process.env.AKM_SQLITE_JOURNAL_MODE);
8615
+ function resolveConfiguredJournalMode(env = process.env) {
8616
+ return resolveJournalMode(env.AKM_SQLITE_JOURNAL_MODE);
8613
8617
  }
8614
8618
  function warnInvalidJournalModeOnce(raw) {
8615
8619
  if (warnedInvalid)
@@ -8635,7 +8639,7 @@ function isNetworkFilesystem(fsType) {
8635
8639
  return NETWORK_FS_MAGICS.has(fsType);
8636
8640
  }
8637
8641
  function applyStandardPragmas(db, opts = {}) {
8638
- let mode = resolveConfiguredJournalMode();
8642
+ let mode = resolveConfiguredJournalMode(opts.env);
8639
8643
  if (mode === "WAL" && opts.dataDir) {
8640
8644
  const probe = opts.fsTypeProbe ?? statfsType;
8641
8645
  if (isNetworkFilesystem(probe(opts.dataDir))) {
@@ -8672,48 +8676,8 @@ function openManagedDatabase(spec) {
8672
8676
  return db;
8673
8677
  }
8674
8678
 
8675
- // src/core/assert.ts
8676
- function assertNever(x, context) {
8677
- let serialized;
8678
- try {
8679
- serialized = JSON.stringify(x);
8680
- } catch {
8681
- serialized = String(x);
8682
- }
8683
- if (serialized === undefined) {
8684
- serialized = String(x);
8685
- }
8686
- const where = context ? ` (${context})` : "";
8687
- throw new Error(`Unexpected value reached assertNever${where}: ${serialized}`);
8688
- }
8689
-
8690
- // src/core/improve-types.ts
8691
- function classifyImproveAction(mode) {
8692
- switch (mode) {
8693
- case "reflect":
8694
- case "distill":
8695
- case "memory-inference":
8696
- case "graph-extraction":
8697
- return "accepted";
8698
- case "reflect-cooldown":
8699
- case "reflect-skipped":
8700
- case "distill-skipped":
8701
- return "skipped";
8702
- case "reflect-guard-rejected":
8703
- return "rejected";
8704
- case "reflect-failed":
8705
- case "error":
8706
- return "error";
8707
- case "memory-prune":
8708
- return "noop";
8709
- default:
8710
- return assertNever(mode);
8711
- }
8712
- }
8713
-
8714
8679
  // src/core/state-db.ts
8715
8680
  init_paths();
8716
- init_warn();
8717
8681
 
8718
8682
  // src/storage/engines/sqlite-migrations.ts
8719
8683
  function ensureMigrationsTable(db) {
@@ -9178,6 +9142,47 @@ function getStateDbPath() {
9178
9142
  function openStateDatabase(dbPath) {
9179
9143
  return openManagedDatabase({ path: dbPath ?? getStateDbPath(), init: runMigrations2 });
9180
9144
  }
9145
+
9146
+ // src/core/assert.ts
9147
+ function assertNever(x, context) {
9148
+ let serialized;
9149
+ try {
9150
+ serialized = JSON.stringify(x);
9151
+ } catch {
9152
+ serialized = String(x);
9153
+ }
9154
+ if (serialized === undefined) {
9155
+ serialized = String(x);
9156
+ }
9157
+ const where = context ? ` (${context})` : "";
9158
+ throw new Error(`Unexpected value reached assertNever${where}: ${serialized}`);
9159
+ }
9160
+
9161
+ // src/core/improve-types.ts
9162
+ function classifyImproveAction(mode) {
9163
+ switch (mode) {
9164
+ case "reflect":
9165
+ case "distill":
9166
+ case "memory-inference":
9167
+ case "graph-extraction":
9168
+ return "accepted";
9169
+ case "reflect-cooldown":
9170
+ case "reflect-skipped":
9171
+ case "distill-skipped":
9172
+ return "skipped";
9173
+ case "reflect-guard-rejected":
9174
+ return "rejected";
9175
+ case "reflect-failed":
9176
+ case "error":
9177
+ return "error";
9178
+ case "memory-prune":
9179
+ return "noop";
9180
+ default:
9181
+ return assertNever(mode);
9182
+ }
9183
+ }
9184
+
9185
+ // src/storage/repositories/improve-runs-repository.ts
9181
9186
  function computeImproveRunMetrics(result) {
9182
9187
  const plannedCount = Array.isArray(result.plannedRefs) ? result.plannedRefs.length : 0;
9183
9188
  const actions = Array.isArray(result.actions) ? result.actions : [];
@@ -14,6 +14,11 @@ import { defaultWhich } from "../integrations/agent/detect.js";
14
14
  import { SESSION_LOG_HARNESSES } from "../integrations/harnesses/index.js";
15
15
  import { spawn } from "../runtime.js";
16
16
  import { detectHarnessConfigs } from "./harness-config-import.js";
17
+ let detectOverrides;
18
+ /** TEST-ONLY. Swap the network/host probes; pass undefined to restore. */
19
+ export function _setDetectForTests(fakes) {
20
+ detectOverrides = fakes;
21
+ }
17
22
  // ── Ollama Detection ────────────────────────────────────────────────────────
18
23
  const OLLAMA_BASE = "http://localhost:11434";
19
24
  /**
@@ -23,6 +28,8 @@ const OLLAMA_BASE = "http://localhost:11434";
23
28
  * via subprocess. Returns available models sorted alphabetically.
24
29
  */
25
30
  export async function detectOllama() {
31
+ if (detectOverrides?.detectOllama)
32
+ return detectOverrides.detectOllama();
26
33
  const result = { available: false, models: [], endpoint: OLLAMA_BASE };
27
34
  // Try HTTP API first
28
35
  try {
@@ -120,6 +127,8 @@ const AGENT_PLATFORMS = SESSION_LOG_HARNESSES.filter((h) => h.setupDetectionDir)
120
127
  * Supports both HOME (Unix) and USERPROFILE (Windows).
121
128
  */
122
129
  export function detectAgentPlatforms() {
130
+ if (detectOverrides?.detectAgentPlatforms)
131
+ return detectOverrides.detectAgentPlatforms();
123
132
  const home = process.env.HOME?.trim() || process.env.USERPROFILE?.trim();
124
133
  if (!home)
125
134
  return [];
@@ -0,0 +1,106 @@
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
+ import { getDefaultLlmConfig } from "../core/config/config.js";
5
+ import { warn } from "../core/warn.js";
6
+ import { v1ProfilePlatform } from "../integrations/harnesses/index.js";
7
+ /** Read the currently-configured LLM connection from a loaded config. */
8
+ export function getCurrentLlm(config) {
9
+ return getDefaultLlmConfig(config);
10
+ }
11
+ /** Read a synthesised legacy-shape agent block from the new-shape AkmConfig. */
12
+ export function getCurrentAgentBlock(config) {
13
+ if (!config.profiles?.agent && !config.defaults?.agent)
14
+ return undefined;
15
+ const block = {};
16
+ if (config.defaults?.agent)
17
+ block.default = config.defaults.agent;
18
+ if (config.profiles?.agent) {
19
+ const profiles = {};
20
+ for (const [name, raw] of Object.entries(config.profiles.agent)) {
21
+ profiles[name] = {
22
+ ...(raw.platform === "opencode-sdk" ? { sdkMode: true } : {}),
23
+ ...(raw.model ? { model: raw.model } : {}),
24
+ ...(raw.bin ? { bin: raw.bin } : {}),
25
+ ...(raw.args ? { args: raw.args } : {}),
26
+ };
27
+ }
28
+ block.profiles = profiles;
29
+ }
30
+ return block;
31
+ }
32
+ /** Apply an LLM connection patch onto the new-shape config. */
33
+ export function applyLegacyLlm(config, llm) {
34
+ if (!llm) {
35
+ // Clear the default LLM profile.
36
+ const name = config.defaults?.llm ?? "default";
37
+ const remaining = { ...(config.profiles?.llm ?? {}) };
38
+ delete remaining[name];
39
+ return {
40
+ profiles: { ...(config.profiles ?? {}), llm: remaining },
41
+ defaults: { ...(config.defaults ?? {}), llm: undefined },
42
+ };
43
+ }
44
+ const name = config.defaults?.llm ?? "default";
45
+ return {
46
+ profiles: {
47
+ ...(config.profiles ?? {}),
48
+ llm: { ...(config.profiles?.llm ?? {}), [name]: llm },
49
+ },
50
+ defaults: { ...(config.defaults ?? {}), llm: name },
51
+ };
52
+ }
53
+ /** Apply a legacy-shape agent block onto the new-shape config. */
54
+ export function applyLegacyAgent(config, agent) {
55
+ if (!agent) {
56
+ return {
57
+ profiles: { ...(config.profiles ?? {}), agent: undefined },
58
+ defaults: { ...(config.defaults ?? {}), agent: undefined },
59
+ };
60
+ }
61
+ const v2Profiles = { ...(config.profiles?.agent ?? {}) };
62
+ for (const [name, profile] of Object.entries(agent.profiles ?? {})) {
63
+ // #566: resolve the platform via the harness registry instead of the old
64
+ // `name.includes("claude") ? "claude" : "opencode"` heuristic, which
65
+ // silently mapped Cursor/Copilot/any new harness to "opencode". An explicit
66
+ // sdkMode flag still wins; otherwise we ask the registry. A name the
67
+ // registry does not recognize is surfaced (warn) rather than silently
68
+ // misclassified, then kept as a best-effort "opencode" profile so the user
69
+ // does not lose a profile they explicitly configured.
70
+ let platform;
71
+ if (profile.sdkMode) {
72
+ platform = "opencode-sdk";
73
+ }
74
+ else {
75
+ const resolved = v1ProfilePlatform(name);
76
+ if (resolved) {
77
+ platform = resolved;
78
+ }
79
+ else {
80
+ warn(`[akm setup] Agent profile "${name}" did not match any known harness; ` +
81
+ `defaulting its platform to "opencode". Set its platform explicitly in config if this is wrong.`);
82
+ platform = "opencode";
83
+ }
84
+ }
85
+ v2Profiles[name] = {
86
+ platform,
87
+ ...(profile.bin ? { bin: profile.bin } : {}),
88
+ ...(profile.args ? { args: profile.args } : {}),
89
+ ...(profile.model ? { model: profile.model } : {}),
90
+ };
91
+ }
92
+ return {
93
+ profiles: { ...(config.profiles ?? {}), agent: v2Profiles },
94
+ defaults: { ...(config.defaults ?? {}), agent: agent.default },
95
+ };
96
+ }
97
+ /** Deep-ish clone of an LLM connection config (capabilities + extraParams). */
98
+ export function cloneLlmConfig(llm) {
99
+ if (!llm)
100
+ return undefined;
101
+ return {
102
+ ...llm,
103
+ ...(llm.capabilities ? { capabilities: { ...llm.capabilities } } : {}),
104
+ ...(llm.extraParams ? { extraParams: { ...llm.extraParams } } : {}),
105
+ };
106
+ }
@@ -0,0 +1,57 @@
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
+ * Clack prompt shims for the setup wizard: cancel-aware prompting so pressing
6
+ * Escape offers a confirm-to-quit rather than crashing the wizard.
7
+ */
8
+ import * as p from "../cli/clack.js";
9
+ export function bail() {
10
+ p.cancel("Setup cancelled. No changes were saved.");
11
+ process.exit(0);
12
+ }
13
+ /**
14
+ * Check if a prompt result was cancelled (Escape). If so, ask the user
15
+ * whether they really want to quit. Returns true if the user chose to
16
+ * stay (i.e. the caller should re-prompt), or calls bail() to exit.
17
+ *
18
+ * @internal Exported for testing only.
19
+ */
20
+ export async function onCancel(value) {
21
+ if (!p.isCancel(value))
22
+ return false;
23
+ const confirmExit = await p.confirm({
24
+ message: "Exit the wizard? No changes will be saved.",
25
+ initialValue: false,
26
+ });
27
+ // Only exit when the user explicitly confirms "Yes".
28
+ // Pressing Escape on the confirmation (isCancel) or choosing "No"
29
+ // both mean "stay in the wizard".
30
+ if (confirmExit === true) {
31
+ bail();
32
+ }
33
+ // User chose to stay
34
+ return true;
35
+ }
36
+ /**
37
+ * Run a prompt function in a loop, retrying if the user presses Escape
38
+ * but decides to stay. Returns the non-cancelled result.
39
+ */
40
+ export async function prompt(fn) {
41
+ for (;;) {
42
+ const result = await fn();
43
+ if (await onCancel(result))
44
+ continue;
45
+ return result;
46
+ }
47
+ }
48
+ /**
49
+ * Like `prompt`, but pressing Escape returns `null` instead of re-prompting.
50
+ * Use inside sub-actions so the user can back out to the parent menu.
51
+ */
52
+ export async function promptOrBack(fn) {
53
+ const result = await fn();
54
+ if (p.isCancel(result))
55
+ return null;
56
+ return result;
57
+ }
@@ -0,0 +1,14 @@
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
+ * Endpoint + default model for each cloud provider akm can recommend from a
6
+ * detected API key. A provider absent from this table yields no recommendation
7
+ * (the previous paired `switch`es both returned `undefined`).
8
+ */
9
+ export const PROVIDER_DEFAULTS = {
10
+ anthropic: { endpoint: "https://api.anthropic.com/v1", model: "claude-sonnet-4-5" },
11
+ openai: { endpoint: "https://api.openai.com/v1", model: "gpt-4o-mini" },
12
+ gemini: { endpoint: "https://generativelanguage.googleapis.com/v1beta/openai", model: "gemini-1.5-flash" },
13
+ groq: { endpoint: "https://api.groq.com/openai/v1", model: "llama-3.3-70b-versatile" },
14
+ };
@@ -44,6 +44,13 @@ const FALLBACK_STASHES = [
44
44
  defaultSelected: false,
45
45
  },
46
46
  ];
47
+ // ── Test seam ────────────────────────────────────────────────────────────────
48
+ // Swap-and-restore override. Inert in production; only tests call the setter.
49
+ let loadSetupStashesOverride;
50
+ /** TEST-ONLY. Swap the implementation of `loadSetupStashes`; pass undefined to restore. */
51
+ export function _setLoadSetupStashesForTests(fake) {
52
+ loadSetupStashesOverride = fake;
53
+ }
47
54
  // ── Loader ──────────────────────────────────────────────────────────────────
48
55
  /**
49
56
  * Fetch available stashes from the registry and map to SetupStashEntry[].
@@ -55,6 +62,11 @@ const FALLBACK_STASHES = [
55
62
  * @param timeoutMs Fetch timeout in ms (default: 4000).
56
63
  */
57
64
  export async function loadSetupStashes(registryUrl, timeoutMs = 4000) {
65
+ if (loadSetupStashesOverride)
66
+ return loadSetupStashesOverride(registryUrl, timeoutMs);
67
+ return loadSetupStashesReal(registryUrl, timeoutMs);
68
+ }
69
+ async function loadSetupStashesReal(registryUrl, timeoutMs = 4000) {
58
70
  try {
59
71
  const response = await fetch(registryUrl, {
60
72
  signal: AbortSignal.timeout(timeoutMs),
@@ -0,0 +1,124 @@
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
+ * Semantic-search asset preparation for the setup wizard. Isolates the one
6
+ * `bun add @huggingface/transformers` subprocess and the sqlite-vec probe so
7
+ * the rest of setup stays free of subprocess/DB I/O.
8
+ */
9
+ import fs from "node:fs";
10
+ import os from "node:os";
11
+ import path from "node:path";
12
+ import * as p from "../cli/clack.js";
13
+ import { isHttpUrl } from "../core/common.js";
14
+ import { closeDatabase, isVecAvailable, openIndexDatabase } from "../indexer/db/db.js";
15
+ import { checkEmbeddingAvailability, DEFAULT_LOCAL_MODEL, isTransformersAvailable } from "../llm/embedder.js";
16
+ import { getDirname, spawn } from "../runtime.js";
17
+ // Approximate first-download sizes used in the setup note.
18
+ // LOCAL_MODEL_APPROX_SIZE_MB tracks the default local model (DEFAULT_LOCAL_MODEL).
19
+ const LOCAL_MODEL_APPROX_SIZE_MB = 130;
20
+ // SQLITE_VEC_APPROX_SIZE_MB reflects the optional sqlite-vec install footprint.
21
+ const SQLITE_VEC_APPROX_SIZE_MB = 5;
22
+ export function isRemoteEmbeddingConfig(embedding) {
23
+ return isHttpUrl(embedding?.endpoint);
24
+ }
25
+ /**
26
+ * @internal Exported for testing only.
27
+ */
28
+ export function describeSemanticSearchAssets(embedding) {
29
+ if (isRemoteEmbeddingConfig(embedding)) {
30
+ return [
31
+ `• Embedding endpoint: ${embedding?.provider ?? "custom"} / ${embedding?.model} (no local model download)`,
32
+ `• sqlite-vec acceleration: optional native extension (~${SQLITE_VEC_APPROX_SIZE_MB} MB when installed separately)`,
33
+ ];
34
+ }
35
+ return [
36
+ `• Local embedding model: ${embedding?.localModel ?? DEFAULT_LOCAL_MODEL} (~${LOCAL_MODEL_APPROX_SIZE_MB} MB download on first use)`,
37
+ `• sqlite-vec acceleration: optional native extension (~${SQLITE_VEC_APPROX_SIZE_MB} MB when installed separately)`,
38
+ ];
39
+ }
40
+ export async function prepareSemanticSearchAssets(config) {
41
+ const remote = isRemoteEmbeddingConfig(config.embedding);
42
+ // For local embeddings, ensure the required package is installed first.
43
+ if (!remote) {
44
+ if (!isTransformersAvailable()) {
45
+ const spin = p.spinner();
46
+ spin.start("Installing @huggingface/transformers...");
47
+ try {
48
+ const pkgRoot = path.resolve(getDirname(import.meta.url), "../..");
49
+ const proc = spawn(["bun", "add", "@huggingface/transformers"], {
50
+ cwd: pkgRoot,
51
+ stdout: "pipe",
52
+ stderr: "pipe",
53
+ });
54
+ await proc.exited;
55
+ if (proc.exitCode !== 0) {
56
+ const stderr = await new Response(proc.stderr).text();
57
+ throw new Error(stderr || `exit code ${proc.exitCode}`);
58
+ }
59
+ spin.stop("@huggingface/transformers installed.");
60
+ }
61
+ catch (err) {
62
+ const msg = err instanceof Error ? err.message : String(err);
63
+ spin.stop("Could not install @huggingface/transformers.");
64
+ p.log.warn(`Automatic install failed: ${msg}\n` +
65
+ "Install it manually with: bun add @huggingface/transformers\n" +
66
+ "Then re-run `akm setup` or `akm index --full --verbose`.");
67
+ return { ok: false, reason: "missing-package", message: `Automatic install failed: ${msg}` };
68
+ }
69
+ }
70
+ }
71
+ const spin = p.spinner();
72
+ spin.start(remote
73
+ ? "Checking remote embedding endpoint..."
74
+ : `Downloading local embedding model (${config.embedding?.localModel ?? DEFAULT_LOCAL_MODEL})...`);
75
+ const result = await checkEmbeddingAvailability(config.embedding);
76
+ if (!result.available) {
77
+ spin.stop("Semantic-search assets could not be prepared.");
78
+ if (result.reason === "remote-unreachable") {
79
+ p.log.warn("The remote embedding endpoint is not reachable. Check your endpoint and credentials, then retry `akm index --full --verbose`.");
80
+ return { ok: false, reason: "remote-network", message: "The remote embedding endpoint is not reachable." };
81
+ }
82
+ else if (result.reason === "missing-package") {
83
+ p.log.warn("@huggingface/transformers is not installed. Install it with: bun add @huggingface/transformers\n" +
84
+ "Then re-run `akm setup` or `akm index --full --verbose`.");
85
+ return { ok: false, reason: "missing-package", message: "@huggingface/transformers is not installed." };
86
+ }
87
+ else {
88
+ p.log.warn(`The local embedding model could not be downloaded: ${result.message}\n` +
89
+ "Retry `akm index --full --verbose` after confirming local model downloads are permitted.");
90
+ return { ok: false, reason: "local-model-download", message: result.message };
91
+ }
92
+ }
93
+ spin.stop(remote ? "Remote embedding endpoint is ready." : "Local embedding model downloaded and ready.");
94
+ let db;
95
+ let probeDir;
96
+ try {
97
+ probeDir = fs.mkdtempSync(path.join(os.tmpdir(), "akm-setup-vec-probe-"));
98
+ db = openIndexDatabase(path.join(probeDir, "probe.db"), config.embedding?.dimension ? { embeddingDim: config.embedding.dimension } : undefined);
99
+ if (isVecAvailable(db)) {
100
+ p.log.info("sqlite-vec is available for fast vector search.");
101
+ }
102
+ else {
103
+ p.log.info("sqlite-vec is not available. Semantic search will use the JS fallback until the optional extension is installed.");
104
+ }
105
+ }
106
+ catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ p.log.warn(`Could not open the local database or check for sqlite-vec. Semantic search will use the JS fallback. (${message})\n` +
109
+ "Check file permissions and available disk space in the cache directory, or run `akm index --full --verbose` to diagnose.");
110
+ }
111
+ finally {
112
+ if (db)
113
+ closeDatabase(db);
114
+ if (probeDir) {
115
+ try {
116
+ fs.rmSync(probeDir, { recursive: true, force: true });
117
+ }
118
+ catch {
119
+ /* ignore cleanup failure */
120
+ }
121
+ }
122
+ }
123
+ return { ok: true };
124
+ }