akm-cli 0.8.1 → 0.9.0-beta.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.
Files changed (318) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/dist/assets/help/help-proposals.md +1 -2
  3. package/dist/assets/hints/cli-hints-full.md +34 -19
  4. package/dist/assets/hints/cli-hints-short.md +1 -1
  5. package/dist/assets/profiles/catchup.json +13 -0
  6. package/dist/assets/profiles/consolidate.json +13 -0
  7. package/dist/assets/profiles/frequent.json +13 -0
  8. package/dist/assets/stash-skeleton/README.md +76 -0
  9. package/dist/assets/tasks/core/backup.yml +4 -0
  10. package/dist/assets/tasks/core/extract.yml +4 -0
  11. package/dist/assets/tasks/core/improve.yml +4 -0
  12. package/dist/assets/tasks/core/index-refresh.yml +4 -0
  13. package/dist/assets/tasks/core/sync.yml +4 -0
  14. package/dist/assets/tasks/core/update-stashes.yml +4 -0
  15. package/dist/assets/tasks/core/version-check.yml +4 -0
  16. package/dist/cli/config-migrate.js +6 -6
  17. package/dist/cli/config-validate.js +4 -4
  18. package/dist/cli/confirm.js +3 -3
  19. package/dist/cli/parse-args.js +1 -1
  20. package/dist/cli/shared.js +51 -14
  21. package/dist/cli-node.mjs +26 -0
  22. package/dist/cli.js +171 -3857
  23. package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
  24. package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
  25. package/dist/commands/agent/contribute-cli.js +200 -0
  26. package/dist/commands/completions.js +1 -1
  27. package/dist/commands/config-cli.js +240 -3
  28. package/dist/commands/config-edit.js +344 -0
  29. package/dist/commands/db-cli.js +2 -2
  30. package/dist/commands/env/env-cli.js +529 -0
  31. package/dist/commands/env/env.js +410 -0
  32. package/dist/commands/env/secret-cli.js +259 -0
  33. package/dist/commands/{secret.js → env/secret.js} +6 -47
  34. package/dist/commands/events.js +4 -4
  35. package/dist/commands/feedback-cli.js +18 -34
  36. package/dist/commands/graph/graph-cli.js +132 -0
  37. package/dist/commands/{graph.js → graph/graph.js} +22 -16
  38. package/dist/commands/health/checks.js +279 -0
  39. package/dist/commands/health.js +101 -249
  40. package/dist/commands/{consolidate.js → improve/consolidate.js} +52 -40
  41. package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
  42. package/dist/commands/{distill.js → improve/distill.js} +39 -18
  43. package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
  44. package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
  45. package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
  46. package/dist/commands/{extract.js → improve/extract.js} +185 -26
  47. package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +4 -4
  48. package/dist/commands/{improve-cli.js → improve/improve-cli.js} +45 -23
  49. package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
  50. package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +10 -5
  51. package/dist/commands/{improve.js → improve/improve.js} +536 -248
  52. package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
  53. package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
  54. package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
  55. package/dist/commands/{reflect.js → improve/reflect.js} +33 -28
  56. package/dist/commands/improve/session-asset.js +248 -0
  57. package/dist/commands/lint/agent-linter.js +1 -1
  58. package/dist/commands/lint/base-linter.js +55 -37
  59. package/dist/commands/lint/command-linter.js +1 -1
  60. package/dist/commands/lint/default-linter.js +1 -1
  61. package/dist/commands/lint/env-key-rules.js +1 -1
  62. package/dist/commands/lint/index.js +19 -25
  63. package/dist/commands/lint/knowledge-linter.js +1 -1
  64. package/dist/commands/lint/memory-linter.js +1 -1
  65. package/dist/commands/lint/registry.js +8 -8
  66. package/dist/commands/lint/skill-linter.js +1 -1
  67. package/dist/commands/lint/task-linter.js +1 -1
  68. package/dist/commands/lint/workflow-linter.js +1 -1
  69. package/dist/commands/lint.js +1 -1
  70. package/dist/commands/observability-cli.js +244 -0
  71. package/dist/commands/{proposal-drain-policies.js → proposal/drain-policies.js} +3 -3
  72. package/dist/commands/{proposal-drain.js → proposal/drain.js} +15 -10
  73. package/dist/commands/proposal/proposal-cli.js +478 -0
  74. package/dist/commands/{proposal.js → proposal/proposal.js} +5 -5
  75. package/dist/commands/{propose.js → proposal/propose.js} +11 -11
  76. package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
  77. package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
  78. package/dist/{core → commands/proposal/validators}/proposals.js +13 -7
  79. package/dist/commands/{curate.js → read/curate.js} +7 -7
  80. package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
  81. package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
  82. package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
  83. package/dist/commands/read/search-cli.js +207 -0
  84. package/dist/commands/{search.js → read/search.js} +22 -27
  85. package/dist/commands/{show.js → read/show.js} +77 -44
  86. package/dist/commands/registry-cli.js +8 -8
  87. package/dist/commands/remember.js +8 -8
  88. package/dist/commands/sources/add-cli.js +293 -0
  89. package/dist/commands/{history.js → sources/history.js} +27 -25
  90. package/dist/commands/{info.js → sources/info.js} +6 -6
  91. package/dist/commands/{init.js → sources/init.js} +10 -5
  92. package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
  93. package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
  94. package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
  95. package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
  96. package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
  97. package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
  98. package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
  99. package/dist/commands/sources/sources-cli.js +305 -0
  100. package/dist/commands/sources/stash-cli.js +219 -0
  101. package/dist/commands/sources/stash-skeleton.js +79 -0
  102. package/dist/commands/tasks/default-tasks.js +173 -0
  103. package/dist/commands/tasks/tasks-cli.js +210 -0
  104. package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
  105. package/dist/commands/wiki-cli.js +307 -0
  106. package/dist/commands/workflow-cli.js +329 -0
  107. package/dist/core/action-contributors.js +1 -1
  108. package/dist/core/assert.js +40 -0
  109. package/dist/core/asset/asset-create.js +54 -0
  110. package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
  111. package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
  112. package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
  113. package/dist/core/{markdown.js → asset/markdown.js} +1 -1
  114. package/dist/core/asset/stash-meta.js +110 -0
  115. package/dist/core/best-effort.js +64 -0
  116. package/dist/core/common.js +32 -18
  117. package/dist/core/{config-io.js → config/config-io.js} +29 -19
  118. package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
  119. package/dist/core/{config-schema.js → config/config-schema.js} +45 -1
  120. package/dist/core/config/config-types.js +16 -0
  121. package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
  122. package/dist/core/{config.js → config/config.js} +10 -8
  123. package/dist/core/env-secret-ref.js +90 -0
  124. package/dist/core/errors.js +13 -3
  125. package/dist/core/events.js +27 -4
  126. package/dist/core/file-lock.js +1 -1
  127. package/dist/core/improve-types.js +48 -0
  128. package/dist/core/lesson-lint.js +2 -2
  129. package/dist/core/paths.js +2 -2
  130. package/dist/{setup/ripgrep-install.js → core/ripgrep/install.js} +2 -2
  131. package/dist/{setup/ripgrep-resolve.js → core/ripgrep/resolve.js} +2 -2
  132. package/dist/core/state-db.js +88 -46
  133. package/dist/core/text-truncation.js +148 -0
  134. package/dist/core/time.js +1 -1
  135. package/dist/core/write-source.js +98 -85
  136. package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
  137. package/dist/indexer/{db.js → db/db.js} +126 -116
  138. package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
  139. package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
  140. package/dist/indexer/ensure-index.js +4 -4
  141. package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
  142. package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
  143. package/dist/indexer/indexer.js +37 -30
  144. package/dist/indexer/init.js +54 -0
  145. package/dist/indexer/manifest.js +10 -10
  146. package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +92 -23
  147. package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
  148. package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
  149. package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
  150. package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
  151. package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
  152. package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
  153. package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
  154. package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
  155. package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
  156. package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
  157. package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
  158. package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
  159. package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
  160. package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
  161. package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
  162. package/dist/indexer/{walker.js → walk/walker.js} +4 -3
  163. package/dist/integrations/agent/builder-shared.js +39 -0
  164. package/dist/integrations/agent/builders.js +14 -81
  165. package/dist/integrations/agent/config.js +6 -4
  166. package/dist/integrations/agent/detect.js +1 -1
  167. package/dist/integrations/agent/index.js +23 -8
  168. package/dist/integrations/agent/prompts.js +2 -3
  169. package/dist/integrations/agent/runner.js +22 -3
  170. package/dist/integrations/agent/spawn.js +9 -10
  171. package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
  172. package/dist/integrations/harnesses/claude/config-import.js +70 -0
  173. package/dist/integrations/harnesses/claude/index.js +64 -0
  174. package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +16 -1
  175. package/dist/integrations/harnesses/index.js +144 -0
  176. package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
  177. package/dist/integrations/harnesses/opencode/config-import.js +82 -0
  178. package/dist/integrations/harnesses/opencode/index.js +59 -0
  179. package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
  180. package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
  181. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
  182. package/dist/integrations/harnesses/types.js +43 -0
  183. package/dist/integrations/lockfile.js +7 -16
  184. package/dist/integrations/session-logs/index.js +82 -9
  185. package/dist/llm/call-ai.js +4 -4
  186. package/dist/llm/client.js +131 -6
  187. package/dist/llm/embedder.js +6 -6
  188. package/dist/llm/embedders/local.js +9 -22
  189. package/dist/llm/embedders/remote.js +2 -2
  190. package/dist/llm/embedders/types.js +1 -1
  191. package/dist/llm/graph-extract.js +31 -12
  192. package/dist/llm/index-passes.js +1 -1
  193. package/dist/llm/memory-infer.js +12 -5
  194. package/dist/llm/metadata-enhance.js +2 -2
  195. package/dist/output/context.js +6 -44
  196. package/dist/output/renderers.js +88 -58
  197. package/dist/output/shapes/curate.js +7 -3
  198. package/dist/output/shapes/distill.js +7 -3
  199. package/dist/output/shapes/env-list.js +18 -16
  200. package/dist/output/shapes/events.js +5 -4
  201. package/dist/output/shapes/helpers.js +2 -4
  202. package/dist/output/shapes/history.js +7 -3
  203. package/dist/output/shapes/passthrough.js +8 -11
  204. package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
  205. package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
  206. package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
  207. package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
  208. package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
  209. package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
  210. package/dist/output/shapes/registry-search.js +7 -3
  211. package/dist/output/shapes/registry.js +12 -0
  212. package/dist/output/shapes/search.js +7 -3
  213. package/dist/output/shapes/secret-list.js +18 -16
  214. package/dist/output/shapes/show.js +7 -3
  215. package/dist/output/shapes.js +55 -30
  216. package/dist/output/text/add.js +2 -3
  217. package/dist/output/text/clone.js +2 -3
  218. package/dist/output/text/config.js +2 -3
  219. package/dist/output/text/curate.js +4 -3
  220. package/dist/output/text/distill.js +2 -3
  221. package/dist/output/text/enable-disable.js +5 -4
  222. package/dist/output/text/env.js +13 -0
  223. package/dist/output/text/events.js +5 -4
  224. package/dist/output/text/feedback.js +4 -3
  225. package/dist/output/text/helpers.js +54 -39
  226. package/dist/output/text/history.js +2 -3
  227. package/dist/output/text/import.js +2 -3
  228. package/dist/output/text/index.js +2 -3
  229. package/dist/output/text/info.js +2 -3
  230. package/dist/output/text/init.js +2 -3
  231. package/dist/output/text/list.js +2 -3
  232. package/dist/output/text/proposal/producer.js +9 -0
  233. package/dist/output/text/proposal/proposal.js +13 -0
  234. package/dist/output/text/registry-commands.js +8 -7
  235. package/dist/output/text/registry.js +12 -0
  236. package/dist/output/text/remember.js +4 -3
  237. package/dist/output/text/remove.js +2 -3
  238. package/dist/output/text/save.js +2 -3
  239. package/dist/output/text/search.js +4 -3
  240. package/dist/output/text/show.js +4 -3
  241. package/dist/output/text/update.js +2 -3
  242. package/dist/output/text/upgrade.js +2 -3
  243. package/dist/output/text/wiki.js +12 -11
  244. package/dist/output/text/workflow.js +12 -10
  245. package/dist/output/text.js +66 -32
  246. package/dist/registry/build-index.js +11 -10
  247. package/dist/registry/factory.js +1 -1
  248. package/dist/registry/origin-resolve.js +1 -1
  249. package/dist/registry/providers/index.js +2 -2
  250. package/dist/registry/providers/skills-sh.js +91 -72
  251. package/dist/registry/providers/static-index.js +75 -52
  252. package/dist/registry/resolve.js +3 -3
  253. package/dist/runtime.js +242 -0
  254. package/dist/scripts/migrate-storage.js +1594 -673
  255. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +240 -166
  256. package/dist/setup/detect.js +338 -9
  257. package/dist/setup/harness-config-import.js +56 -0
  258. package/dist/setup/registry-stash-loader.js +99 -0
  259. package/dist/setup/setup.js +664 -96
  260. package/dist/sources/include.js +1 -1
  261. package/dist/sources/provider-factory.js +2 -2
  262. package/dist/sources/providers/filesystem.js +3 -3
  263. package/dist/sources/providers/git.js +9 -9
  264. package/dist/sources/providers/index.js +4 -4
  265. package/dist/sources/providers/npm.js +6 -6
  266. package/dist/sources/providers/provider-utils.js +13 -20
  267. package/dist/sources/providers/sync-from-ref.js +5 -5
  268. package/dist/sources/providers/tar-utils.js +2 -2
  269. package/dist/sources/providers/website.js +2 -2
  270. package/dist/sources/resolve.js +5 -5
  271. package/dist/sources/website-ingest.js +5 -5
  272. package/dist/storage/database.js +102 -0
  273. package/dist/storage/engines/sqlite-migrations.js +42 -0
  274. package/dist/storage/locations.js +25 -0
  275. package/dist/storage/repositories/index-db.js +43 -0
  276. package/dist/storage/repositories/workflow-runs-repository.js +141 -0
  277. package/dist/tasks/backends/cron.js +4 -4
  278. package/dist/tasks/backends/exec-utils.js +32 -0
  279. package/dist/tasks/backends/index.js +3 -3
  280. package/dist/tasks/backends/launchd.js +7 -14
  281. package/dist/tasks/backends/schtasks.js +7 -16
  282. package/dist/tasks/embedded.js +71 -0
  283. package/dist/tasks/parser.js +2 -2
  284. package/dist/tasks/resolveAkmBin.js +1 -1
  285. package/dist/tasks/runner.js +28 -15
  286. package/dist/tasks/schedule.js +1 -1
  287. package/dist/tasks/validator.js +7 -7
  288. package/dist/text-import-hook.mjs +51 -0
  289. package/dist/version.js +2 -1
  290. package/dist/wiki/wiki.js +7 -7
  291. package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
  292. package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
  293. package/dist/workflows/cli.js +1 -1
  294. package/dist/workflows/db.js +50 -32
  295. package/dist/workflows/parser.js +4 -4
  296. package/dist/workflows/renderer.js +5 -5
  297. package/dist/workflows/runtime/agent-identity.js +56 -0
  298. package/dist/workflows/runtime/checkin.js +57 -0
  299. package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
  300. package/dist/workflows/validate-summary.js +82 -0
  301. package/docs/README.md +1 -1
  302. package/docs/data-and-telemetry.md +6 -6
  303. package/package.json +16 -8
  304. package/dist/commands/add-cli.js +0 -279
  305. package/dist/commands/env.js +0 -213
  306. package/dist/integrations/agent/sdk-runner.js +0 -126
  307. package/dist/output/shapes/vault-list.js +0 -19
  308. package/dist/output/text/proposal-producer.js +0 -8
  309. package/dist/output/text/proposal.js +0 -12
  310. package/dist/output/text/vault.js +0 -16
  311. /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
  312. /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
  313. /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
  314. /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
  315. /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
  316. /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
  317. /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
  318. /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
@@ -8,7 +8,12 @@
8
8
  * a result object describing what was found.
9
9
  */
10
10
  import fs from "node:fs";
11
+ import os from "node:os";
11
12
  import path from "node:path";
13
+ import { defaultWhich } from "../integrations/agent/detect.js";
14
+ import { SESSION_LOG_HARNESSES } from "../integrations/harnesses/index.js";
15
+ import { spawn } from "../runtime.js";
16
+ import { detectHarnessConfigs } from "./harness-config-import.js";
12
17
  // ── Ollama Detection ────────────────────────────────────────────────────────
13
18
  const OLLAMA_BASE = "http://localhost:11434";
14
19
  /**
@@ -41,7 +46,7 @@ export async function detectOllama() {
41
46
  }
42
47
  // CLI fallback
43
48
  try {
44
- const proc = Bun.spawn(["ollama", "list"], {
49
+ const proc = spawn(["ollama", "list"], {
45
50
  stdout: "pipe",
46
51
  stderr: "pipe",
47
52
  });
@@ -64,15 +69,52 @@ export async function detectOllama() {
64
69
  }
65
70
  return result;
66
71
  }
72
+ const LMSTUDIO_BASE = "http://localhost:1234";
73
+ /**
74
+ * Detect if LM Studio is running and list available models.
75
+ * Probes the OpenAI-compatible /v1/models endpoint.
76
+ */
77
+ export async function detectLMStudio() {
78
+ const result = { available: false, models: [], endpoint: LMSTUDIO_BASE };
79
+ try {
80
+ const response = await fetch(`${LMSTUDIO_BASE}/v1/models`, {
81
+ signal: AbortSignal.timeout(2000),
82
+ });
83
+ if (response.ok) {
84
+ const data = (await response.json());
85
+ if (Array.isArray(data.data)) {
86
+ result.models = data.data
87
+ .map((m) => (typeof m.id === "string" ? m.id : ""))
88
+ .filter(Boolean)
89
+ .sort();
90
+ result.available = true;
91
+ }
92
+ }
93
+ }
94
+ catch {
95
+ // LM Studio not running or not accessible
96
+ }
97
+ return result;
98
+ }
67
99
  // ── Agent Platform Detection ────────────────────────────────────────────────
68
- const AGENT_PLATFORMS = [
69
- { name: "Claude Code", relPath: ".claude" },
70
- { name: "OpenCode", relPath: ".config/opencode" },
71
- { name: "Continue", relPath: ".continue" },
72
- { name: "Codeium / Windsurf", relPath: ".codeium" },
73
- { name: "Cursor", relPath: ".cursor" },
74
- { name: "Codex CLI", relPath: ".codex" },
75
- ];
100
+ /**
101
+ * Setup stash-source candidates, derived from the unified harness registry
102
+ * (#567).
103
+ *
104
+ * BEHAVIOUR FIX: the old hardcoded list named 6 harnesses (Claude Code,
105
+ * OpenCode, Continue, Codeium/Windsurf, Cursor, Codex CLI) but only the first
106
+ * two have a session-log provider. Selecting any of the other four added a
107
+ * filesystem stash source that was never indexed/extracted — a silent no-op
108
+ * "detection trap" (sessions never reached the improve pipeline).
109
+ *
110
+ * The registry is now the single source of which harnesses are real stash
111
+ * sources: a candidate must (a) have `capabilities.sessionLogs === true` AND
112
+ * (b) declare a `setupDetectionDir`. `SESSION_LOG_HARNESSES` already filters by
113
+ * (a), so dropping the four dead options is automatic — they aren't even in the
114
+ * registry. Adding a new session-log harness with a detection dir makes it
115
+ * appear here with no edit to this file.
116
+ */
117
+ const AGENT_PLATFORMS = SESSION_LOG_HARNESSES.filter((h) => h.setupDetectionDir).map((h) => ({ name: h.displayName, relPath: h.setupDetectionDir }));
76
118
  /**
77
119
  * Scan the user's home directory for known agent platform config directories.
78
120
  * Supports both HOME (Unix) and USERPROFILE (Windows).
@@ -94,3 +136,290 @@ export function detectAgentPlatforms() {
94
136
  path: path.join(home, p.relPath),
95
137
  }));
96
138
  }
139
+ /**
140
+ * Map of env var NAME → { provider, kind }. The key NAMES are the only thing
141
+ * this module ever inspects from `process.env`; values are never touched.
142
+ */
143
+ const PROVIDER_ENV_VARS = [
144
+ { name: "ANTHROPIC_API_KEY", provider: "anthropic", kind: "apiKey" },
145
+ { name: "OPENAI_API_KEY", provider: "openai", kind: "apiKey" },
146
+ { name: "GEMINI_API_KEY", provider: "gemini", kind: "apiKey" },
147
+ { name: "GOOGLE_API_KEY", provider: "gemini", kind: "apiKey" },
148
+ { name: "GROQ_API_KEY", provider: "groq", kind: "apiKey" },
149
+ { name: "OLLAMA_HOST", provider: "ollama", kind: "endpoint" },
150
+ { name: "OLLAMA_BASE_URL", provider: "ollama", kind: "endpoint" },
151
+ { name: "LM_STUDIO_BASE_URL", provider: "lmstudio", kind: "endpoint" },
152
+ { name: "LM_STUDIO_API_BASE", provider: "lmstudio", kind: "endpoint" },
153
+ { name: "LMSTUDIO_BASE_URL", provider: "lmstudio", kind: "endpoint" },
154
+ { name: "LMSTUDIO_API_BASE", provider: "lmstudio", kind: "endpoint" },
155
+ { name: "AKM_LLM_API_KEY", provider: "akm-llm", kind: "apiKey" },
156
+ { name: "AKM_LLM_ENDPOINT", provider: "akm-llm", kind: "endpoint" },
157
+ { name: "AKM_LLM_BASE_URL", provider: "akm-llm", kind: "endpoint" },
158
+ ];
159
+ /**
160
+ * Scan `process.env` for the presence of known provider configuration env var
161
+ * NAMES and return inferred providers.
162
+ *
163
+ * Pure function — no network, no filesystem. It reads only whether each known
164
+ * key is *defined and non-empty*; it never reads, returns, or logs the value.
165
+ *
166
+ * @param envSource Env to inspect. Defaults to `process.env`. Tests inject a
167
+ * fake env so a real API key is never required.
168
+ * @returns Inferred providers, each carrying the env var NAME only.
169
+ */
170
+ export function scanProviderEnvVars(envSource = process.env) {
171
+ const results = [];
172
+ for (const entry of PROVIDER_ENV_VARS) {
173
+ const value = envSource[entry.name];
174
+ const present = typeof value === "string" && value.trim().length > 0;
175
+ if (!present)
176
+ continue;
177
+ results.push({ provider: entry.provider, envVar: entry.name, kind: entry.kind });
178
+ }
179
+ return results;
180
+ }
181
+ /** Default endpoints probed in addition to any harness-config base URLs. */
182
+ const DEFAULT_LOCAL_ENDPOINTS = [
183
+ { baseUrl: "http://localhost:11434", label: "Ollama" },
184
+ { baseUrl: "http://localhost:1234", label: "LM Studio" },
185
+ { baseUrl: "http://localhost:8080", label: "Local (8080)" },
186
+ ];
187
+ /**
188
+ * Pick a sensible default model from a list via a name heuristic.
189
+ *
190
+ * Preference order: an explicit "instruct" variant, then the longest name
191
+ * (a rough proxy for the larger / more-capable variant), then the first.
192
+ * Returns `undefined` for an empty list.
193
+ */
194
+ export function pickDefaultModel(models) {
195
+ const cleaned = models.filter((m) => typeof m === "string" && m.trim().length > 0);
196
+ if (cleaned.length === 0)
197
+ return undefined;
198
+ const instruct = cleaned.filter((m) => /instruct/i.test(m));
199
+ const pool = instruct.length > 0 ? instruct : cleaned;
200
+ // Prefer the longest name as a proxy for the larger/most-specific variant,
201
+ // breaking ties by sort order for determinism.
202
+ return [...pool].sort((a, b) => b.length - a.length || a.localeCompare(b))[0];
203
+ }
204
+ /**
205
+ * Probe a single OpenAI-compatible `/v1/models` endpoint.
206
+ *
207
+ * Tolerant of failure: any network/timeout/parse error yields an
208
+ * `available: false` result rather than throwing.
209
+ */
210
+ export async function probeLocalEndpoint(baseUrl, label, timeoutMs = 2000) {
211
+ const result = { baseUrl, label, available: false, models: [] };
212
+ const url = `${baseUrl.replace(/\/$/, "")}/v1/models`;
213
+ try {
214
+ const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
215
+ if (response.ok) {
216
+ const data = (await response.json());
217
+ if (Array.isArray(data.data)) {
218
+ result.models = data.data
219
+ .map((m) => (typeof m.id === "string" ? m.id : ""))
220
+ .filter(Boolean)
221
+ .sort();
222
+ result.available = true;
223
+ result.defaultModel = pickDefaultModel(result.models);
224
+ }
225
+ }
226
+ }
227
+ catch {
228
+ // Endpoint down/unreachable — leave available=false.
229
+ }
230
+ return result;
231
+ }
232
+ /**
233
+ * Probe the default local endpoints (Ollama 11434, LM Studio 1234, generic
234
+ * 8080) plus any base URLs found in imported harness configs.
235
+ *
236
+ * Never throws: every endpoint being down yields a list of unavailable
237
+ * results, not an error.
238
+ *
239
+ * @param harnessBaseUrls Extra base URLs to probe (e.g. from harness configs).
240
+ */
241
+ export async function detectLocalServers(harnessBaseUrls = []) {
242
+ const endpoints = [...DEFAULT_LOCAL_ENDPOINTS];
243
+ for (const raw of harnessBaseUrls) {
244
+ if (typeof raw !== "string" || raw.trim().length === 0)
245
+ continue;
246
+ // Strip a trailing /v1 (and trailing slash) so we probe consistently.
247
+ const baseUrl = raw.replace(/\/$/, "").replace(/\/v1$/, "");
248
+ if (!endpoints.some((e) => e.baseUrl === baseUrl)) {
249
+ endpoints.push({ baseUrl, label: `Harness (${baseUrl})` });
250
+ }
251
+ }
252
+ return Promise.all(endpoints.map((e) => probeLocalEndpoint(e.baseUrl, e.label)));
253
+ }
254
+ /**
255
+ * Suggest stash directories, ranked (lower rank = higher priority).
256
+ *
257
+ * Sources, in priority order:
258
+ * 1. An existing config `stashDir` (always rank 0 — no-op / keep current).
259
+ * 2. A `akm/` or `agent-stash/` directory in the CWD git repo.
260
+ * 3. `~/akm` then `~/.akm` when they already exist.
261
+ *
262
+ * Pure function — filesystem reads only, no network. Tests inject `cwd`/`home`.
263
+ */
264
+ export function detectStashDir(opts) {
265
+ const cwd = opts?.cwd ?? process.cwd();
266
+ const home = opts?.home ?? os.homedir();
267
+ const suggestions = [];
268
+ const seen = new Set();
269
+ const push = (p, reason, rank) => {
270
+ const abs = path.resolve(p);
271
+ if (seen.has(abs))
272
+ return;
273
+ seen.add(abs);
274
+ suggestions.push({ path: abs, reason, rank });
275
+ };
276
+ if (opts?.existingStashDir?.trim()) {
277
+ push(opts.existingStashDir, "existing config stashDir", 0);
278
+ }
279
+ // CWD git repo containing akm/ or agent-stash/
280
+ const repoRoot = findGitRepoRoot(cwd);
281
+ if (repoRoot) {
282
+ for (const dirName of ["akm", "agent-stash"]) {
283
+ const candidate = path.join(repoRoot, dirName);
284
+ try {
285
+ if (fs.statSync(candidate).isDirectory()) {
286
+ push(candidate, `git repo contains ${dirName}/`, 1);
287
+ }
288
+ }
289
+ catch {
290
+ // not present
291
+ }
292
+ }
293
+ }
294
+ // ~/akm then ~/.akm when they exist
295
+ if (home) {
296
+ for (const dirName of ["akm", ".akm"]) {
297
+ const candidate = path.join(home, dirName);
298
+ try {
299
+ if (fs.statSync(candidate).isDirectory()) {
300
+ push(candidate, `${dirName} exists in home`, 2);
301
+ }
302
+ }
303
+ catch {
304
+ // not present
305
+ }
306
+ }
307
+ }
308
+ return suggestions.sort((a, b) => a.rank - b.rank);
309
+ }
310
+ /** Walk up from `start` looking for a directory containing `.git`. */
311
+ function findGitRepoRoot(start) {
312
+ let dir = path.resolve(start);
313
+ for (;;) {
314
+ try {
315
+ if (fs.existsSync(path.join(dir, ".git")))
316
+ return dir;
317
+ }
318
+ catch {
319
+ // ignore
320
+ }
321
+ const parent = path.dirname(dir);
322
+ if (parent === dir)
323
+ return undefined;
324
+ dir = parent;
325
+ }
326
+ }
327
+ /**
328
+ * Detect the best agent harness in priority order:
329
+ * 1. OpenCode SDK resolvable via `import('@opencode-ai/sdk')`.
330
+ * 2. `opencode` binary on PATH.
331
+ * 3. `claude` binary on PATH.
332
+ * 4. none.
333
+ *
334
+ * Pure aside from the dynamic import resolution (which performs no network).
335
+ */
336
+ export async function detectHarness(whichFn = defaultWhich) {
337
+ try {
338
+ await import("@opencode-ai/sdk");
339
+ return "opencode-sdk";
340
+ }
341
+ catch {
342
+ // SDK not installed — fall through to bin probes.
343
+ }
344
+ if (whichFn("opencode"))
345
+ return "opencode";
346
+ if (whichFn("claude"))
347
+ return "claude";
348
+ return "none";
349
+ }
350
+ /**
351
+ * Run the full environment-detection pipeline once and return a single typed
352
+ * result. Orchestrates env-var scan, harness config import, harness selection,
353
+ * local-server probes, and stash-dir suggestions.
354
+ *
355
+ * SAFETY: No API key VALUE is ever read, stored, logged, or returned — only
356
+ * env var NAMES. Tolerant of every detector failing.
357
+ *
358
+ * @param opts.existingStashDir Current config stashDir (no-op suggestion).
359
+ * @param opts.envSource Env to scan. Defaults to `process.env`.
360
+ * @param opts.whichFn Binary lookup. Tests inject a stub.
361
+ */
362
+ export async function detectEnvironment(opts) {
363
+ const envSource = opts?.envSource ?? process.env;
364
+ const whichFn = opts?.whichFn ?? defaultWhich;
365
+ let harnessConfigs = [];
366
+ try {
367
+ harnessConfigs = detectHarnessConfigs();
368
+ }
369
+ catch {
370
+ harnessConfigs = [];
371
+ }
372
+ const harnessBaseUrls = harnessConfigs.map((c) => c.baseUrl).filter((u) => typeof u === "string");
373
+ const [harness, localServers] = await Promise.all([
374
+ detectHarness(whichFn),
375
+ detectLocalServers(harnessBaseUrls).catch(() => []),
376
+ ]);
377
+ let agentPlatforms = [];
378
+ try {
379
+ agentPlatforms = detectAgentPlatforms();
380
+ }
381
+ catch {
382
+ agentPlatforms = [];
383
+ }
384
+ return {
385
+ harness,
386
+ providers: scanProviderEnvVars(envSource),
387
+ harnessConfigs,
388
+ localServers,
389
+ stashSuggestions: detectStashDir({
390
+ existingStashDir: opts?.existingStashDir,
391
+ cwd: opts?.cwd,
392
+ home: opts?.home,
393
+ }),
394
+ agentPlatforms,
395
+ };
396
+ }
397
+ /**
398
+ * Render a compact, human-readable "Detected environment" summary block.
399
+ * Contains env var NAMES only — never any value.
400
+ */
401
+ export function renderDetectionSummary(env) {
402
+ const lines = ["Detected environment:"];
403
+ lines.push(` Harness: ${env.harness}`);
404
+ const liveServers = env.localServers.filter((s) => s.available);
405
+ if (liveServers.length > 0) {
406
+ lines.push(` Local servers: ${liveServers
407
+ .map((s) => `${s.label}${s.defaultModel ? ` (${s.defaultModel})` : ""}`)
408
+ .join(", ")}`);
409
+ }
410
+ else {
411
+ lines.push(" Local servers: none reachable");
412
+ }
413
+ if (env.providers.length > 0) {
414
+ // NAMES only — never values.
415
+ lines.push(` Provider keys: ${env.providers.map((p) => `${p.provider}:${p.envVar}`).join(", ")}`);
416
+ }
417
+ else {
418
+ lines.push(" Provider keys: none in environment");
419
+ }
420
+ const topStash = env.stashSuggestions[0];
421
+ if (topStash) {
422
+ lines.push(` Stash suggest: ${topStash.path} (${topStash.reason})`);
423
+ }
424
+ return lines.join("\n");
425
+ }
@@ -0,0 +1,56 @@
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
+ * Pluggable registry of LLM config importers for supported agent harnesses.
6
+ *
7
+ * Each importer detects whether a harness is installed (filesystem only,
8
+ * no network) and, if so, reads its config to extract LLM connection details.
9
+ * API key VALUES are never stored — only the env var names that hold them.
10
+ *
11
+ * To add a new harness: implement {@link HarnessConfigImporter} and append it
12
+ * to {@link HARNESS_CONFIG_IMPORTERS}.
13
+ *
14
+ * NOTE: The `detect()` method in each importer overlaps intentionally with
15
+ * `detectAgentPlatforms()` in `detect.ts`. That function scans for harness
16
+ * presence to display installed platforms to the user; these importers go
17
+ * further by reading and parsing the harness config. They serve different
18
+ * purposes and should not be deduplicated.
19
+ */
20
+ import { claudeCodeImporter } from "../integrations/harnesses/claude/config-import.js";
21
+ import { openCodeImporter } from "../integrations/harnesses/opencode/config-import.js";
22
+ // The Claude Code importer was migrated to its harness directory in #563
23
+ // (`harnesses/claude/config-import.ts`) and the OpenCode importer in #564
24
+ // (`harnesses/opencode/config-import.ts`). Both are imported back into
25
+ // HARNESS_CONFIG_IMPORTERS below so detection order is unchanged.
26
+ // ── Registry ─────────────────────────────────────────────────────────────────
27
+ /**
28
+ * Registry of all supported harness config importers.
29
+ * To add a new harness: implement {@link HarnessConfigImporter} and append here.
30
+ */
31
+ export const HARNESS_CONFIG_IMPORTERS = [claudeCodeImporter, openCodeImporter];
32
+ /**
33
+ * Run all importers whose `detect()` returns `true` and collect their configs.
34
+ *
35
+ * Pure function — filesystem reads only, no network, no side effects.
36
+ * Individual importer failures are swallowed so one broken harness never
37
+ * blocks the setup wizard.
38
+ *
39
+ * @returns List of detected harness configs (may be empty).
40
+ */
41
+ export function detectHarnessConfigs() {
42
+ const results = [];
43
+ for (const importer of HARNESS_CONFIG_IMPORTERS) {
44
+ try {
45
+ if (!importer.detect())
46
+ continue;
47
+ const config = importer.importConfig();
48
+ if (config)
49
+ results.push(config);
50
+ }
51
+ catch {
52
+ // Never let one importer crash the whole detection
53
+ }
54
+ }
55
+ return results;
56
+ }
@@ -0,0 +1,99 @@
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
+ * Registry-driven stash discovery for the setup wizard.
6
+ *
7
+ * Fetches the list of available stashes from the official AKM registry,
8
+ * using a cached result when available. Falls back to FALLBACK_STASHES
9
+ * when the registry is unreachable or returns no results.
10
+ *
11
+ * Adding a new default-selected stash: append its registry ID to
12
+ * DEFAULT_SELECTED_STASH_IDS below. No other change required.
13
+ */
14
+ // ── Default selections ──────────────────────────────────────────────────────
15
+ /**
16
+ * Registry stash IDs that are pre-selected by default during setup.
17
+ * To add a new default stash: append its registry ID here.
18
+ * IDs must match the `id` field in the official registry index.
19
+ *
20
+ * This is the single source of truth for which stashes are pre-checked
21
+ * in the setup wizard. No other change is required to adjust defaults.
22
+ */
23
+ export const DEFAULT_SELECTED_STASH_IDS = ["itlackey/akm-stash"];
24
+ // ── Fallback list ───────────────────────────────────────────────────────────
25
+ /**
26
+ * Hardcoded stash list used when the registry is unreachable.
27
+ * Mirrors the previous RECOMMENDED_GITHUB_REPOS constant.
28
+ */
29
+ const FALLBACK_STASHES = [
30
+ {
31
+ id: "itlackey/akm-stash",
32
+ name: "itlackey/akm-stash",
33
+ description: "Official AKM onboarding stash",
34
+ url: "https://github.com/itlackey/akm-stash",
35
+ source: "fallback",
36
+ defaultSelected: true,
37
+ },
38
+ {
39
+ id: "andrewyng/context-hub",
40
+ name: "andrewyng/context-hub",
41
+ description: "Optional community prompt and context stash",
42
+ url: "https://github.com/andrewyng/context-hub",
43
+ source: "fallback",
44
+ defaultSelected: false,
45
+ },
46
+ ];
47
+ // ── Loader ──────────────────────────────────────────────────────────────────
48
+ /**
49
+ * Fetch available stashes from the registry and map to SetupStashEntry[].
50
+ *
51
+ * Falls back to FALLBACK_STASHES on network failure, parse error, or
52
+ * empty response — setup never crashes due to a registry outage.
53
+ *
54
+ * @param registryUrl URL of the registry index JSON.
55
+ * @param timeoutMs Fetch timeout in ms (default: 4000).
56
+ */
57
+ export async function loadSetupStashes(registryUrl, timeoutMs = 4000) {
58
+ try {
59
+ const response = await fetch(registryUrl, {
60
+ signal: AbortSignal.timeout(timeoutMs),
61
+ headers: { Accept: "application/json" },
62
+ });
63
+ if (!response.ok)
64
+ return FALLBACK_STASHES;
65
+ const raw = (await response.json());
66
+ if (!Array.isArray(raw.stashes) || raw.stashes.length === 0)
67
+ return FALLBACK_STASHES;
68
+ const entries = raw.stashes.flatMap((item) => {
69
+ if (!item || typeof item !== "object")
70
+ return [];
71
+ const s = item;
72
+ const id = typeof s.id === "string" ? s.id : "";
73
+ const name = typeof s.name === "string" ? s.name : id;
74
+ const description = typeof s.description === "string" ? s.description : "";
75
+ // Prefer github/git source URL built from the ref; fall back to homepage
76
+ const url = (s.source === "github" || s.source === "git") && typeof s.ref === "string"
77
+ ? `https://github.com/${s.ref.replace(/^github:/, "")}`
78
+ : typeof s.homepage === "string"
79
+ ? s.homepage
80
+ : "";
81
+ if (!id || !url)
82
+ return [];
83
+ return [
84
+ {
85
+ id,
86
+ name,
87
+ description,
88
+ url,
89
+ source: "registry",
90
+ defaultSelected: DEFAULT_SELECTED_STASH_IDS.includes(id),
91
+ },
92
+ ];
93
+ });
94
+ return entries.length > 0 ? entries : FALLBACK_STASHES;
95
+ }
96
+ catch {
97
+ return FALLBACK_STASHES;
98
+ }
99
+ }