akm-cli 0.9.1 → 0.9.2-alpha.2

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 (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -0,0 +1,308 @@
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 { ConfigError, UsageError } from "../../core/errors.js";
5
+ import { cloneExecutionJsonObject } from "../../execution/json.js";
6
+ import { createInlineResolvedCommand, createResolvedCommand, createResolvedPersona, } from "../../execution/resolved-request.js";
7
+ import { isPortableExecutionAgentSelector, } from "../../execution/source.js";
8
+ import { recordIndexedShowUsage } from "../../indexer/usage/show-usage.js";
9
+ import { resolveUsageEventSource } from "../../indexer/usage/usage-events.js";
10
+ import { fallbackAnnouncement, NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, } from "../../integrations/agent/engine-fallback.js";
11
+ import { requireAuthorizedExecutionPlan, } from "../../integrations/agent/execution-cascade.js";
12
+ import { dispatchLoweredExecutionRequest, lowerResolvedExecutionRequest, } from "../../integrations/agent/execution-lowering.js";
13
+ import { prepareResolvedExecution } from "../../integrations/agent/execution-preparation.js";
14
+ import { parseBuiltinCommandAction } from "./builtin-action.js";
15
+ import { loadAdapterExecutionSource, } from "./execution-source-loader.js";
16
+ import { applyPortableCommandArguments } from "./portable-template.js";
17
+ function own(value, key) {
18
+ return Object.hasOwn(value, key);
19
+ }
20
+ function selectAgent(command, invocationDefaults, current) {
21
+ let selected = {
22
+ present: false,
23
+ source: "command",
24
+ };
25
+ for (const [source, values] of [
26
+ ["command", command],
27
+ ["invocation", invocationDefaults],
28
+ ["current", current],
29
+ ]) {
30
+ if (values && own(values, "agent"))
31
+ selected = { present: true, value: values.agent, source };
32
+ }
33
+ return selected;
34
+ }
35
+ function defaultSourceLoader(ref, kind, options) {
36
+ return kind === "command"
37
+ ? loadAdapterExecutionSource(ref, "command", options)
38
+ : loadAdapterExecutionSource(ref, "persona", options);
39
+ }
40
+ function qualifyCommandSelectedPersona(selector, command) {
41
+ if (!command || selector.includes("//"))
42
+ return selector;
43
+ return `${command.identity.bundle}//${selector}`;
44
+ }
45
+ function snapshotCommandConfig(config, path = "command config") {
46
+ return cloneExecutionJsonObject(config, path);
47
+ }
48
+ export async function prepareCommandInvocation(options) {
49
+ const inputConfig = snapshotCommandConfig(options.config, "command input config");
50
+ const action = parseBuiltinCommandAction(options.action);
51
+ const sourceLoader = options.sourceLoader ?? defaultSourceLoader;
52
+ let renderedCommand;
53
+ let commandDefaults = Object.freeze({});
54
+ let command;
55
+ if (action.kind === "stored") {
56
+ const rendered = await sourceLoader(action.ref, "command", {
57
+ config: inputConfig,
58
+ ...(options.sourceLookup ? { lookup: options.sourceLookup } : {}),
59
+ });
60
+ if (rendered.kind !== "command")
61
+ throw new TypeError("command source loader returned a non-command source");
62
+ renderedCommand = rendered;
63
+ commandDefaults = rendered.defaults;
64
+ const applied = applyPortableCommandArguments(rendered.content, action.arguments, rendered.identity.ref);
65
+ command = createResolvedCommand({
66
+ source: rendered,
67
+ ...(own(applied, "argumentInput") ? { argumentInput: applied.argumentInput } : {}),
68
+ content: applied.content,
69
+ });
70
+ }
71
+ else {
72
+ if (options.inlineContentMode === "literal") {
73
+ if (action.arguments !== undefined) {
74
+ throw new UsageError("Literal inline command content cannot declare portable arguments.", "INVALID_FLAG_VALUE");
75
+ }
76
+ command = createInlineResolvedCommand({ template: action.content, content: action.content });
77
+ }
78
+ else {
79
+ const applied = applyPortableCommandArguments(action.content, action.arguments, "inline command");
80
+ command = createInlineResolvedCommand({
81
+ template: applied.template,
82
+ ...(own(applied, "argumentInput") ? { argumentInput: applied.argumentInput } : {}),
83
+ content: applied.content,
84
+ });
85
+ }
86
+ }
87
+ const selectedAgent = selectAgent(commandDefaults, options.invocationDefaults, options.current);
88
+ let renderedPersona;
89
+ if (selectedAgent.present &&
90
+ typeof selectedAgent.value === "string" &&
91
+ isPortableExecutionAgentSelector(selectedAgent.value)) {
92
+ const lookupRef = selectedAgent.source === "command"
93
+ ? qualifyCommandSelectedPersona(selectedAgent.value, renderedCommand)
94
+ : selectedAgent.value;
95
+ const rendered = await sourceLoader(lookupRef, "persona", {
96
+ config: inputConfig,
97
+ ...(options.sourceLookup ? { lookup: options.sourceLookup } : {}),
98
+ });
99
+ if (rendered.kind !== "persona")
100
+ throw new TypeError("persona source loader returned a non-persona source");
101
+ renderedPersona = rendered;
102
+ }
103
+ const persona = renderedPersona ? createResolvedPersona(renderedPersona) : selectedAgent.present ? null : undefined;
104
+ return prepareResolvedExecution({
105
+ command,
106
+ config: inputConfig,
107
+ invocationKind: options.invocationKind ?? "direct",
108
+ ...(persona !== undefined ? { persona } : {}),
109
+ ...(renderedPersona ? { agentLayer: { id: renderedPersona.identity.ref, values: renderedPersona.defaults } } : {}),
110
+ commandLayer: {
111
+ id: renderedCommand?.identity.ref ?? "inline-command",
112
+ values: commandDefaults,
113
+ },
114
+ ...(options.invocationDefaults ? { invocationDefaults: options.invocationDefaults } : {}),
115
+ ...(options.current ? { current: options.current } : {}),
116
+ ...(options.modelMap ? { modelMap: options.modelMap } : {}),
117
+ ...(options.authorizeTools ? { authorizeTools: options.authorizeTools } : {}),
118
+ });
119
+ }
120
+ function compareText(left, right) {
121
+ return left < right ? -1 : left > right ? 1 : 0;
122
+ }
123
+ const DIAGNOSTIC_PROVENANCE_FIELDS = new Set([
124
+ "/inference",
125
+ "agent",
126
+ "authorization",
127
+ "command",
128
+ "engine",
129
+ "engine.requested",
130
+ "model",
131
+ "outputSchema",
132
+ "persona",
133
+ "runtime.environment",
134
+ "runtime.settings",
135
+ "runtime.timeoutMs",
136
+ "runtime.workspace",
137
+ "tools",
138
+ ]);
139
+ const DIAGNOSTIC_NOTICE_FIELDS = new Set([
140
+ "agent",
141
+ "command.content",
142
+ "command.extensions",
143
+ "conversation",
144
+ "engine",
145
+ "engine.extensions",
146
+ "extensions",
147
+ "inference",
148
+ "model",
149
+ "outputSchema",
150
+ "persona",
151
+ "persona.extensions",
152
+ "runtime.environment",
153
+ "runtime.extensions",
154
+ "runtime.settings",
155
+ "runtime.timeoutMs",
156
+ "runtime.workspace",
157
+ "tools",
158
+ ]);
159
+ function canonicalDiagnosticProvenanceField(field) {
160
+ if (field.startsWith("/inference/"))
161
+ return "/inference/*";
162
+ return DIAGNOSTIC_PROVENANCE_FIELDS.has(field) ? field : undefined;
163
+ }
164
+ function canonicalDiagnosticNoticeField(field) {
165
+ if (typeof field !== "string")
166
+ return undefined;
167
+ if (field.startsWith("inference."))
168
+ return "inference.*";
169
+ return DIAGNOSTIC_NOTICE_FIELDS.has(field) ? field : undefined;
170
+ }
171
+ function diagnosticProvenance(provenance) {
172
+ const canonical = new Map();
173
+ for (const [field, source] of Object.entries(provenance)) {
174
+ const safeField = canonicalDiagnosticProvenanceField(field);
175
+ if (!safeField)
176
+ continue;
177
+ const candidate = Object.freeze({ field: safeField, layer: source.layer, kind: source.kind, via: source.via });
178
+ const previous = canonical.get(safeField);
179
+ if (!previous || compareText(JSON.stringify(candidate), JSON.stringify(previous)) < 0) {
180
+ canonical.set(safeField, candidate);
181
+ }
182
+ }
183
+ return Object.freeze([...canonical.values()].sort((left, right) => compareText(left.field, right.field)));
184
+ }
185
+ function safeDiagnosticToken(value) {
186
+ return value.length <= 128 && /^[a-z][a-z0-9-]*$/.test(value);
187
+ }
188
+ /** Rebuild a known lowerer notice instead of forwarding its optional details or arbitrary message bytes. */
189
+ function safeDiagnosticNotice(notice) {
190
+ if (!safeDiagnosticToken(notice.adapter))
191
+ return undefined;
192
+ switch (notice.code) {
193
+ case "engine-fallback":
194
+ return Object.freeze({
195
+ code: "engine-fallback",
196
+ severity: "info",
197
+ adapter: "akm",
198
+ field: "engine",
199
+ message: "No engine was selected; using the fixed opencode-sdk fallback.",
200
+ });
201
+ case "unrecognized-request-notice":
202
+ return Object.freeze({
203
+ code: "unrecognized-request-notice",
204
+ severity: "warning",
205
+ adapter: "akm",
206
+ message: "An unrecognized durable execution notice was omitted at the engine lowering boundary.",
207
+ });
208
+ case "untranslated-field": {
209
+ const field = canonicalDiagnosticNoticeField(notice.field);
210
+ if (!field)
211
+ return undefined;
212
+ return Object.freeze({
213
+ code: "untranslated-field",
214
+ severity: "warning",
215
+ adapter: notice.adapter,
216
+ field,
217
+ message: `The ${notice.adapter} lowerer does not translate resolved field ${field}; dispatch will continue optimistically.`,
218
+ });
219
+ }
220
+ case "conversation-prompt-composed":
221
+ return Object.freeze({
222
+ code: "conversation-prompt-composed",
223
+ severity: "warning",
224
+ adapter: notice.adapter,
225
+ field: "conversation",
226
+ message: `The ${notice.adapter} transport has no native multi-message channel; AKM composed the conversation prefix into one deterministic JSON prompt block.`,
227
+ });
228
+ case "persona-prompt-composed":
229
+ return Object.freeze({
230
+ code: "persona-prompt-composed",
231
+ severity: "info",
232
+ adapter: notice.adapter,
233
+ field: "persona",
234
+ message: "The selected engine has no native persona channel; AKM composed the persona into the prompt.",
235
+ });
236
+ default:
237
+ return undefined;
238
+ }
239
+ }
240
+ function diagnosticNotices(notices) {
241
+ const canonical = new Map();
242
+ for (const notice of notices) {
243
+ const safe = safeDiagnosticNotice(notice);
244
+ if (safe)
245
+ canonical.set(JSON.stringify(safe), safe);
246
+ }
247
+ return Object.freeze([...canonical.values()].sort((left, right) => compareText(JSON.stringify(left), JSON.stringify(right))));
248
+ }
249
+ /**
250
+ * Authorize and lower a prepared invocation, then project only safe structural
251
+ * diagnostics. It never acquires a dispatch lease, materializes credentials,
252
+ * records usage, or exposes request/plan values.
253
+ */
254
+ export function inspectPreparedCommandInvocation(prepared) {
255
+ const request = requireAuthorizedExecutionPlan(prepared.plan);
256
+ const lowered = lowerResolvedExecutionRequest(request, prepared.config);
257
+ const selectedEngine = request.engine.name;
258
+ if (!selectedEngine) {
259
+ throw new ConfigError(`command ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "INVALID_CONFIG_FILE");
260
+ }
261
+ return Object.freeze({
262
+ schemaVersion: 1,
263
+ shape: "command-dry-run",
264
+ ok: true,
265
+ dryRun: true,
266
+ engine: selectedEngine,
267
+ provenance: diagnosticProvenance(prepared.plan.provenance),
268
+ notices: diagnosticNotices(lowered.notices),
269
+ });
270
+ }
271
+ function resultEnvelope(result, engine, warnings, notices) {
272
+ return {
273
+ schemaVersion: 2,
274
+ ok: result.ok,
275
+ shape: "agent-result",
276
+ engine,
277
+ exitCode: result.exitCode,
278
+ stdout: result.stdout ?? "",
279
+ stderr: result.stderr ?? "",
280
+ durationMs: result.durationMs,
281
+ ...(result.error !== undefined ? { error: result.error } : {}),
282
+ ...(result.reason !== undefined ? { reason: result.reason } : {}),
283
+ ...(warnings.length > 0 ? { warnings } : {}),
284
+ ...(notices.length > 0 ? { notices } : {}),
285
+ };
286
+ }
287
+ export async function dispatchPreparedCommandInvocation(prepared, options = {}) {
288
+ const request = requireAuthorizedExecutionPlan(prepared.plan);
289
+ const lowered = lowerResolvedExecutionRequest(request, prepared.config);
290
+ const selectedEngine = request.engine.name;
291
+ if (!selectedEngine) {
292
+ throw new ConfigError(`command ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "INVALID_CONFIG_FILE");
293
+ }
294
+ const result = await dispatchLoweredExecutionRequest(lowered, options);
295
+ const consumedRefs = new Set();
296
+ if (request.command.source)
297
+ consumedRefs.add(request.command.source.ref);
298
+ if (request.persona)
299
+ consumedRefs.add(request.persona.source.ref);
300
+ const eventSource = resolveUsageEventSource();
301
+ for (const ref of consumedRefs)
302
+ recordIndexedShowUsage(ref, eventSource);
303
+ const announcement = fallbackAnnouncement(prepared.fallbackEngineName, selectedEngine);
304
+ return resultEnvelope(result, selectedEngine, announcement ? [announcement] : [], lowered.notices);
305
+ }
306
+ export async function executeCommandInvocation(options, dispatchOptions = {}) {
307
+ return dispatchPreparedCommandInvocation(await prepareCommandInvocation(options), dispatchOptions);
308
+ }
@@ -0,0 +1,176 @@
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 { createHash } from "node:crypto";
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { adapterForId } from "../../core/adapter/registry.js";
8
+ import { parseBundleRef } from "../../core/asset/asset-ref.js";
9
+ import { isWithin } from "../../core/common.js";
10
+ import { loadConfig } from "../../core/config/config.js";
11
+ import { bundleComponentConfig, bundlesToSourceEntries } from "../../core/config/config-sources.js";
12
+ import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
13
+ import { lookupBundleRef } from "../../indexer/indexer.js";
14
+ import { deriveInstallations } from "../../indexer/installations.js";
15
+ import { resolveEntryContentDir, resolveSourceEntries } from "../../indexer/search/search-source.js";
16
+ import { buildFileContext } from "../../indexer/walk/file-context.js";
17
+ async function defaultLookup(ref) {
18
+ return lookupBundleRef(ref);
19
+ }
20
+ async function effectiveConfig(config) {
21
+ if (config)
22
+ return config;
23
+ return loadConfig();
24
+ }
25
+ function realDirectory(value, label) {
26
+ let real;
27
+ let stat;
28
+ try {
29
+ real = fs.realpathSync(value);
30
+ stat = fs.statSync(real);
31
+ }
32
+ catch {
33
+ throw new NotFoundError(`${label} is missing or unreadable. Rebuild the index and retry.`, "FILE_NOT_FOUND");
34
+ }
35
+ if (!stat.isDirectory())
36
+ throw new ConfigError(`${label} is not a directory.`, "INVALID_CONFIG_FILE");
37
+ return real;
38
+ }
39
+ function realRegularFile(value, label) {
40
+ let real;
41
+ let stat;
42
+ try {
43
+ real = fs.realpathSync(value);
44
+ stat = fs.statSync(real);
45
+ }
46
+ catch {
47
+ throw new NotFoundError(`${label} is stale, missing, or unreadable. Rebuild the index and retry.`, "FILE_NOT_FOUND");
48
+ }
49
+ if (!stat.isFile())
50
+ throw new NotFoundError(`${label} is not a regular file.`, "FILE_NOT_FOUND");
51
+ return real;
52
+ }
53
+ function isLexicallyWithin(candidate, root) {
54
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
55
+ return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
56
+ }
57
+ function implicitComponentForEntry(entry, config, realRoot) {
58
+ const sources = resolveSourceEntries(undefined, config);
59
+ const installations = deriveInstallations(sources);
60
+ for (const [index, installation] of installations.entries()) {
61
+ if (installation.id !== entry.bundleId)
62
+ continue;
63
+ const source = sources[index];
64
+ const component = installation.components[0];
65
+ if (!source || !component)
66
+ continue;
67
+ const canonicalRoot = realDirectory(component.root, `Canonical implicit source root for ${entry.bundleId}`);
68
+ if (canonicalRoot !== realRoot) {
69
+ throw new ConfigError(`Canonical implicit source root drift for ${JSON.stringify(entry.itemRef)}; the indexed root no longer matches the working bundle source.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing AKM_BUNDLE_DIR or the default bundle directory.");
70
+ }
71
+ if (component.adapter !== entry.adapterId) {
72
+ throw new ConfigError(`Implicit adapter drift for ${JSON.stringify(entry.itemRef)}: the index records ${JSON.stringify(entry.adapterId)} but the canonical working source selects ${JSON.stringify(component.adapter)}.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing the working source adapter.");
73
+ }
74
+ return Object.freeze({
75
+ id: installation.id,
76
+ adapter: component.adapter,
77
+ root: canonicalRoot,
78
+ writable: component.writable,
79
+ });
80
+ }
81
+ return undefined;
82
+ }
83
+ function componentForEntry(entry, config, realRoot) {
84
+ const configured = config.bundles?.[entry.bundleId];
85
+ if (!configured) {
86
+ const implicit = implicitComponentForEntry(entry, config, realRoot);
87
+ if (implicit)
88
+ return implicit;
89
+ throw new ConfigError(`Indexed execution source ${JSON.stringify(entry.itemRef)} belongs to unconfigured bundle ${JSON.stringify(entry.bundleId)}.`, "INVALID_CONFIG_FILE", "Refresh the bundle configuration and rebuild the index before dispatching this asset.");
90
+ }
91
+ const component = bundleComponentConfig(configured);
92
+ const configuredAdapter = component?.adapter;
93
+ if (configuredAdapter && configuredAdapter !== entry.adapterId) {
94
+ throw new ConfigError(`Configured adapter drift for ${JSON.stringify(entry.itemRef)}: the index records ${JSON.stringify(entry.adapterId)} but the bundle config selects ${JSON.stringify(configuredAdapter)}.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing a bundle adapter.");
95
+ }
96
+ const configuredSource = bundlesToSourceEntries(config)?.find((source) => source.name === entry.bundleId);
97
+ const configuredContentRoot = configuredSource ? resolveEntryContentDir(configuredSource) : undefined;
98
+ if (!configuredSource || !configuredContentRoot) {
99
+ throw new ConfigError(`Configured source for ${JSON.stringify(entry.itemRef)} no longer resolves to materialized content.`, "INVALID_CONFIG_FILE", "Restore or update the bundle, then run `akm index --full` before dispatching this asset.");
100
+ }
101
+ const lexicalSourceRoot = path.resolve(configuredContentRoot);
102
+ const lexicalConfiguredRoot = path.resolve(configuredContentRoot, component?.root ?? ".");
103
+ if (!isLexicallyWithin(lexicalConfiguredRoot, lexicalSourceRoot)) {
104
+ throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Keep component roots inside their owning bundle source, then run `akm index --full`.");
105
+ }
106
+ const configuredSourceRoot = realDirectory(lexicalSourceRoot, `Configured source root for ${entry.bundleId}`);
107
+ let configuredRoot;
108
+ try {
109
+ configuredRoot = realDirectory(lexicalConfiguredRoot, `Configured component root for ${entry.bundleId}`);
110
+ }
111
+ catch {
112
+ throw new ConfigError(`Configured ${JSON.stringify(configuredSource.type)} source for ${JSON.stringify(entry.itemRef)} is not materialized at its current component root.`, "INVALID_CONFIG_FILE", "Restore or update the bundle, then run `akm index --full` before dispatching this asset.");
113
+ }
114
+ if (!isWithin(configuredRoot, configuredSourceRoot)) {
115
+ throw new ConfigError(`Configured component root for ${JSON.stringify(entry.itemRef)} resolves outside its materialized bundle source.`, "INVALID_CONFIG_FILE", "Remove the escaping symlink or component path, then run `akm index --full`.");
116
+ }
117
+ if (configuredRoot !== realRoot) {
118
+ throw new ConfigError(`Configured source or component root drift for ${JSON.stringify(entry.itemRef)}; the indexed root no longer matches the ${JSON.stringify(configuredSource.type)} bundle source.`, "INVALID_CONFIG_FILE", "Run `akm index --full` after changing a bundle source or component root.");
119
+ }
120
+ const writable = component?.writable ?? configured.writable ?? configured.path !== undefined;
121
+ return Object.freeze({
122
+ id: entry.bundleId,
123
+ adapter: entry.adapterId,
124
+ root: realRoot,
125
+ writable,
126
+ });
127
+ }
128
+ function expectedIndexedType(kind) {
129
+ return kind === "command" ? "command" : "agent";
130
+ }
131
+ function assertRenderedIdentity(source, entry, realRoot, realFile, content) {
132
+ const expectedFile = path.relative(realRoot, realFile).replace(/\\/g, "/");
133
+ const expectedHash = createHash("sha256").update(content, "utf8").digest("hex");
134
+ if (source.identity.ref !== entry.itemRef ||
135
+ source.identity.bundle !== entry.bundleId ||
136
+ source.identity.adapter !== entry.adapterId ||
137
+ source.identity.file !== expectedFile ||
138
+ source.identity.hash !== expectedHash) {
139
+ throw new ConfigError(`Execution renderer identity drift for ${JSON.stringify(entry.itemRef)}.`, "INVALID_CONFIG_FILE", "Run `akm index --full`; if the mismatch remains, repair the owning bundle adapter.");
140
+ }
141
+ }
142
+ export async function loadAdapterExecutionSource(refInput, expectedKind, options = {}) {
143
+ const parsed = parseBundleRef(refInput);
144
+ if (parsed.fragment !== undefined) {
145
+ throw new UsageError(`Execution source ${JSON.stringify(refInput)} cannot select a fragment.`, "INVALID_FLAG_VALUE");
146
+ }
147
+ const entry = await (options.lookup ?? defaultLookup)(parsed);
148
+ if (!entry) {
149
+ throw new NotFoundError(`Execution source ${JSON.stringify(refInput)} was not found in the local index.`, "ASSET_NOT_FOUND", "Run `akm index --full`, then retry the command ref.");
150
+ }
151
+ const indexedType = expectedIndexedType(expectedKind);
152
+ if (entry.type !== indexedType) {
153
+ throw new UsageError(`Execution source ${JSON.stringify(refInput)} resolves to type ${JSON.stringify(entry.type)}, expected ${JSON.stringify(indexedType)}.`, "INVALID_FLAG_VALUE");
154
+ }
155
+ const config = await effectiveConfig(options.config);
156
+ const realRoot = realDirectory(entry.stashDir, `Indexed component root for ${entry.itemRef}`);
157
+ const realFile = realRegularFile(entry.filePath, `Indexed execution source ${entry.itemRef}`);
158
+ if (!isWithin(realFile, realRoot) || realFile === realRoot) {
159
+ throw new ConfigError(`Indexed execution source ${JSON.stringify(entry.itemRef)} resolves outside its component root.`, "INVALID_CONFIG_FILE", "Remove the escaping path and rebuild the index.");
160
+ }
161
+ const component = componentForEntry(entry, config, realRoot);
162
+ const adapter = (options.adapterFor ?? adapterForId)(entry.adapterId);
163
+ if (!adapter) {
164
+ throw new ConfigError(`Execution source adapter ${JSON.stringify(entry.adapterId)} is not registered.`, "INVALID_CONFIG_FILE");
165
+ }
166
+ if (!adapter.renderExecutionSource) {
167
+ throw new UsageError(`Bundle adapter ${JSON.stringify(entry.adapterId)} does not support command or persona execution.`, "INVALID_FLAG_VALUE");
168
+ }
169
+ const fileContext = (options.fileContext ?? buildFileContext)(entry.stashDir, entry.filePath);
170
+ const rendered = adapter.renderExecutionSource(component, fileContext);
171
+ if (!rendered || rendered.kind !== expectedKind) {
172
+ throw new ConfigError(`Bundle adapter ${JSON.stringify(entry.adapterId)} did not render ${JSON.stringify(entry.itemRef)} as a ${expectedKind}.`, "INVALID_CONFIG_FILE", "Rebuild the index; if the mismatch remains, repair the source file or owning adapter.");
173
+ }
174
+ assertRenderedIdentity(rendered, entry, realRoot, realFile, fileContext.content());
175
+ return rendered;
176
+ }
@@ -0,0 +1,60 @@
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 { UsageError } from "../../core/errors.js";
5
+ export const PORTABLE_ARGUMENTS_PLACEHOLDER = "$ARGUMENTS";
6
+ const UNSUPPORTED_TEMPLATE_CONSTRUCTS = Object.freeze([
7
+ { label: "$ARGUMENTS[N]", pattern: /\$ARGUMENTS\s*\[/u },
8
+ { label: "```! ... ```", pattern: /(?:^|\r?\n)[\t ]*```!/u },
9
+ { label: "!`...`", pattern: /!`/u },
10
+ { label: "@file", pattern: /(?<![A-Za-z0-9._%+-])@(?:\.{0,2}\/)?[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*/u },
11
+ { label: "$" + "{...}", pattern: /\$\{/u },
12
+ { label: "$(...)", pattern: /\$\(/u },
13
+ { label: "$N", pattern: /\$\d/u },
14
+ { label: "$NAME", pattern: /\$[A-Za-z_][A-Za-z0-9_]*/u },
15
+ { label: "{{...}}", pattern: /\{\{|\}\}/u },
16
+ ]);
17
+ function unsupportedTemplate(source, label) {
18
+ return new UsageError(`Command ${JSON.stringify(source)} uses unsupported portable template construct ${label}.`, "INVALID_FLAG_VALUE", `AKM command execution supports only the literal ${PORTABLE_ARGUMENTS_PLACEHOLDER} placeholder. Invoke native-only templates through their owning tool instead.`);
19
+ }
20
+ /**
21
+ * Validate the deliberately small portable command-template language.
22
+ *
23
+ * The source identifier is safe to surface; template and argument bytes never
24
+ * enter the error because they may contain user or secret material.
25
+ */
26
+ export function validatePortableCommandTemplate(template, source) {
27
+ if (typeof template !== "string")
28
+ throw new TypeError("command template must be a string");
29
+ if (typeof source !== "string" || source.length === 0)
30
+ throw new TypeError("command source must be a string");
31
+ // Check native extensions of the portable spelling before masking the one
32
+ // supported token. AKM never interprets indexed native placeholders.
33
+ const indexedArguments = UNSUPPORTED_TEMPLATE_CONSTRUCTS[0];
34
+ if (indexedArguments?.pattern.test(template))
35
+ throw unsupportedTemplate(source, indexedArguments.label);
36
+ // Mask only the exact portable token. Everything else remains visible to the
37
+ // unsupported-construct detectors, including `$ARGUMENTS_SUFFIX`.
38
+ const portableMasked = template.replace(/\$ARGUMENTS(?![A-Za-z0-9_])/gu, "");
39
+ for (const construct of UNSUPPORTED_TEMPLATE_CONSTRUCTS.slice(1)) {
40
+ if (construct.pattern.test(portableMasked))
41
+ throw unsupportedTemplate(source, construct.label);
42
+ }
43
+ }
44
+ /**
45
+ * Apply literal `$ARGUMENTS` replacement once. `split`/`join` is intentional:
46
+ * unlike `String.replace`, caller bytes such as `$&` have no replacement-string
47
+ * semantics, and inserted `$ARGUMENTS` text is never rescanned.
48
+ */
49
+ export function applyPortableCommandArguments(template, argumentInput, source) {
50
+ validatePortableCommandTemplate(template, source);
51
+ if (argumentInput !== undefined && typeof argumentInput !== "string") {
52
+ throw new TypeError("command argument input must be a string or omitted");
53
+ }
54
+ const content = template.split(PORTABLE_ARGUMENTS_PLACEHOLDER).join(argumentInput ?? "");
55
+ return Object.freeze({
56
+ template,
57
+ ...(argumentInput === undefined ? {} : { argumentInput }),
58
+ content,
59
+ });
60
+ }
@@ -17,9 +17,10 @@ import { resolveStashDir } from "../core/common.js";
17
17
  import { DEFAULT_CONFIG, loadConfig, mutateConfig } from "../core/config/config.js";
18
18
  import { configGet, configSet, configUnset, unknownKeyHint } from "../core/config/config-walker.js";
19
19
  import { getCacheDir, getConfigPath, getDbPath, getDefaultStashDir } from "../core/paths.js";
20
+ import { formatRegistryUrl } from "../core/registry-url.js";
20
21
  // ── Public API ──────────────────────────────────────────────────────────────
21
22
  export function getConfigValue(config, key) {
22
- return redactConfigValue(configGet(config, key));
23
+ return redactConfigValue(configGet(config, key), key.split("."));
23
24
  }
24
25
  export function setConfigValue(config, key, rawValue) {
25
26
  return configSet(config, key, rawValue);
@@ -32,20 +33,25 @@ export function listConfig(config) {
32
33
  // retired top-level `sources[]` array is no longer surfaced.
33
34
  return redactConfigValue({ ...DEFAULT_CONFIG, ...config });
34
35
  }
35
- function redactConfigValue(value) {
36
+ function redactConfigValue(value, path = []) {
37
+ if (typeof value === "string" && isRegistryUrlPath(path))
38
+ return formatRegistryUrl(value);
36
39
  if (Array.isArray(value))
37
- return value.map(redactConfigValue);
40
+ return value.map((child, index) => redactConfigValue(child, [...path, String(index)]));
38
41
  if (!value || typeof value !== "object")
39
42
  return value;
40
43
  const result = {};
41
44
  for (const [key, child] of Object.entries(value)) {
42
45
  if (key !== "apiKey" ||
43
46
  (typeof child === "string" && /^\$(?:[A-Za-z_][A-Za-z0-9_]*|\{[A-Za-z_][A-Za-z0-9_]*\})$/.test(child))) {
44
- result[key] = redactConfigValue(child);
47
+ result[key] = redactConfigValue(child, [...path, key]);
45
48
  }
46
49
  }
47
50
  return result;
48
51
  }
52
+ function isRegistryUrlPath(path) {
53
+ return path[0] === "registries" && path.at(-1) === "url";
54
+ }
49
55
  export { unknownKeyHint };
50
56
  // ── `akm config` command surface ────────────────────────────────────────────
51
57
  // Extracted verbatim from src/cli.ts (WS6). The `main.subCommands.config` key
@@ -53,7 +53,7 @@ import { sensitiveMarkerPath } from "./marker-path.js";
53
53
  /** Matches a KEY=value assignment line, capturing only the key. */
54
54
  const ASSIGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
55
55
  /** Scan lines and return KEY names in file order, without duplicates. */
56
- function scanKeys(text) {
56
+ export function scanEnvKeyNames(text) {
57
57
  const keys = [];
58
58
  const seen = new Set();
59
59
  for (const line of text.split(/\r?\n/)) {
@@ -61,6 +61,8 @@ function scanKeys(text) {
61
61
  if (!m)
62
62
  continue;
63
63
  const key = m[1];
64
+ if (!key)
65
+ continue;
64
66
  if (seen.has(key))
65
67
  continue;
66
68
  seen.add(key);
@@ -81,7 +83,7 @@ export function listKeys(envPath) {
81
83
  if (!fs.existsSync(envPath))
82
84
  return { keys: [] };
83
85
  const text = fs.readFileSync(envPath, "utf8");
84
- return { keys: scanKeys(text) };
86
+ return { keys: scanEnvKeyNames(text) };
85
87
  }
86
88
  /**
87
89
  * Read all KEY=value pairs from an env file. Intended for programmatic callers
@@ -315,7 +315,7 @@ export const feedbackCommand = defineJsonCommand({
315
315
  throw new UsageError(`Source "${parsedRef.bundle}" is not configured.`, "INVALID_FLAG_VALUE");
316
316
  }
317
317
  const lookupRef = makeBundleRef(parsedRef.bundle, parsedRef.conceptId);
318
- const entryId = findEntryIdByRef(db, lookupRef, requestedSource?.path);
318
+ const entryId = findEntryIdByRef(db, lookupRef, parsedRef.bundle);
319
319
  if (entryId === undefined) {
320
320
  // NotFoundError (exit 1), not UsageError (exit 2): the flags parsed
321
321
  // fine, the asset just isn't there. The documented exit-code table