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
@@ -0,0 +1,219 @@
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
+ * Stash-lifecycle command cluster — the create/index/ingest/inspect verbs for
6
+ * the working stash and its index database: `akm init` (create the stash +
7
+ * persist stashDir), `akm index` (build/refresh the search index), `akm import`
8
+ * (ingest a knowledge doc/URL), `akm db` (+ nested `backups` — inspect the
9
+ * SQLite data dir), and `akm info` (system capabilities + index stats).
10
+ * Extracted verbatim from src/cli.ts (WS6) so the God Module shrinks; the
11
+ * `main.subCommands.{init,index,import,db,info}` keys and every subcommand's
12
+ * args/output shape stay byte-identical.
13
+ *
14
+ * These share no private helper with any command still inline in cli.ts — every
15
+ * dependency is already exported from a shared module (core/paths, core/warn,
16
+ * core/errors, core/events, output/context, cli/shared, cli/parse-args, plus the
17
+ * per-command implementations in ./init, ./indexer, ./info, ./db-cli, ./knowledge,
18
+ * ./core/asset-create, ./core/common), so the cluster moves with zero hoisting.
19
+ *
20
+ * The leaf handlers whose body is a plain `runWithJsonErrors(...) + output(...)`
21
+ * (`init`, `import`, `info`, `db`, `db backups`) are migrated onto
22
+ * `defineJsonCommand`, which emits the same JSON envelope (stdout/stderr/
23
+ * exit-code) as the inline form. `index` keeps a plain `defineCommand` wrapping
24
+ * `runWithJsonErrors` because its body owns a spinner, an AbortController, and
25
+ * SIGINT/SIGTERM handlers in a try/finally — left byte-for-byte untouched.
26
+ */
27
+ import path from "node:path";
28
+ import * as p from "@clack/prompts";
29
+ import { defineCommand } from "citty";
30
+ import { hasSubcommand } from "../../cli/parse-args.js";
31
+ import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
32
+ import { assertFlatAssetName } from "../../core/asset/asset-create.js";
33
+ import { isHttpUrl } from "../../core/common.js";
34
+ import { UsageError } from "../../core/errors.js";
35
+ import { appendEvent } from "../../core/events.js";
36
+ import { getCacheDir } from "../../core/paths.js";
37
+ import { clearLogFile, info, isVerbose, setLogFile } from "../../core/warn.js";
38
+ import { akmIndex } from "../../indexer/indexer.js";
39
+ import { getHyphenatedBoolean, getOutputMode, parseFlagValue } from "../../output/context.js";
40
+ import { akmDbBackups } from "../db-cli.js";
41
+ import { readKnowledgeInput, writeMarkdownAsset } from "../read/knowledge.js";
42
+ import { assembleInfo } from "./info.js";
43
+ import { akmInit } from "./init.js";
44
+ export const initCommand = defineJsonCommand({
45
+ meta: {
46
+ name: "init",
47
+ description: "Initialize akm's working stash directory and persist stashDir in config",
48
+ },
49
+ args: {
50
+ dir: { type: "string", description: "Custom stash directory path (default: ~/akm)" },
51
+ },
52
+ async run({ args }) {
53
+ // Accept both historical spellings for backwards compatibility with
54
+ // older docs/scripts that used `--stashDir`.
55
+ const legacyDir = parseFlagValue(process.argv, "--stashDir") ?? parseFlagValue(process.argv, "--stash-dir");
56
+ const result = await akmInit({ dir: args.dir ?? legacyDir });
57
+ output("init", result);
58
+ },
59
+ });
60
+ export const indexCommand = defineCommand({
61
+ meta: { name: "index", description: "Build search index (incremental by default; --full forces full reindex)" },
62
+ args: {
63
+ full: { type: "boolean", description: "Force full reindex", default: false },
64
+ clean: {
65
+ type: "boolean",
66
+ description: "After indexing, remove any entries whose source file no longer exists on disk.",
67
+ default: false,
68
+ },
69
+ "dry-run": {
70
+ type: "boolean",
71
+ description: "When combined with --clean, report stale entries without deleting them.",
72
+ default: false,
73
+ },
74
+ },
75
+ async run({ args }) {
76
+ await runWithJsonErrors(async () => {
77
+ if (getHyphenatedBoolean(args, "enrich") || parseFlagValue(process.argv, "--enrich") !== undefined) {
78
+ throw new UsageError("`akm index --enrich` has been removed. Plain `akm index` now performs metadata enrichment by default.");
79
+ }
80
+ if (getHyphenatedBoolean(args, "re-enrich") || parseFlagValue(process.argv, "--re-enrich") !== undefined) {
81
+ throw new UsageError("`akm index --re-enrich` has been removed. Re-enrichment of index-time LLM passes is not exposed in this slice.");
82
+ }
83
+ const outputMode = getOutputMode();
84
+ const controller = new AbortController();
85
+ const abort = () => controller.abort(new Error("index interrupted"));
86
+ process.once("SIGINT", abort);
87
+ process.once("SIGTERM", abort);
88
+ const indexLogFile = path.join(getCacheDir(), "logs", "index", `${new Date().toISOString().replace(/[:.]/g, "-")}.log`);
89
+ setLogFile(indexLogFile);
90
+ const verbose = isVerbose();
91
+ const spin = !verbose && outputMode.format === "text" ? p.spinner() : null;
92
+ if (spin) {
93
+ spin.start(`Building search index${args.full ? " (full rebuild)" : ""}...`);
94
+ }
95
+ let latestMessage = "";
96
+ try {
97
+ const result = await akmIndex({
98
+ full: args.full,
99
+ clean: args.clean,
100
+ dryRun: args["dry-run"],
101
+ onProgress: ({ phase, message, processed, total }) => {
102
+ latestMessage = message;
103
+ const progressPrefix = processed !== undefined && total !== undefined ? `[${processed}/${total}] ` : "";
104
+ if (verbose) {
105
+ info(`[index:${phase}] ${progressPrefix}${message}`);
106
+ }
107
+ else if (spin) {
108
+ spin.stop(`${progressPrefix}${message}`);
109
+ spin.start(`${progressPrefix}${message}`);
110
+ }
111
+ },
112
+ signal: controller.signal,
113
+ });
114
+ if (spin) {
115
+ spin.stop(`Indexed ${result.totalEntries} assets.`);
116
+ }
117
+ output("index", result);
118
+ }
119
+ catch (error) {
120
+ if (spin) {
121
+ spin.stop(latestMessage ? `Indexing failed after: ${latestMessage}` : "Indexing failed.");
122
+ }
123
+ throw error;
124
+ }
125
+ finally {
126
+ clearLogFile();
127
+ process.off("SIGINT", abort);
128
+ process.off("SIGTERM", abort);
129
+ }
130
+ });
131
+ },
132
+ });
133
+ export const infoCommand = defineJsonCommand({
134
+ meta: { name: "info", description: "Show system capabilities, configuration, and index stats" },
135
+ run() {
136
+ const result = assembleInfo();
137
+ output("info", result);
138
+ },
139
+ });
140
+ // MVP DB administration. Currently only `akm db backups`; restore is manual —
141
+ // stop akm and run `scripts/migrations/restore-data-dir.sh <backup>`.
142
+ // Single source of truth: the routing set is derived from the subCommands keys
143
+ // (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
144
+ const dbSubCommands = {
145
+ backups: defineJsonCommand({
146
+ meta: {
147
+ name: "backups",
148
+ description: "List pre-upgrade snapshots of the data directory (newest first). Backups are created automatically before destructive DB version upgrades unless AKM_DB_BACKUP=0.",
149
+ },
150
+ run() {
151
+ output("db-backups", akmDbBackups());
152
+ },
153
+ }),
154
+ };
155
+ const DB_SUBCOMMAND_SET = new Set(Object.keys(dbSubCommands));
156
+ export const dbCommand = defineJsonCommand({
157
+ meta: {
158
+ name: "db",
159
+ description: "Inspect the AKM SQLite data directory. Currently exposes `backups`; to restore from a snapshot, stop akm and run scripts/migrations/restore-data-dir.sh against the chosen backup.",
160
+ },
161
+ subCommands: dbSubCommands,
162
+ run({ args }) {
163
+ if (hasSubcommand(args, DB_SUBCOMMAND_SET))
164
+ return;
165
+ // Default action: list backups.
166
+ output("db-backups", akmDbBackups());
167
+ },
168
+ });
169
+ export const importKnowledgeCommand = defineJsonCommand({
170
+ meta: {
171
+ name: "import",
172
+ description: "Import a knowledge document or URL into the default stash",
173
+ },
174
+ args: {
175
+ source: {
176
+ type: "positional",
177
+ description: 'Source file path, URL, or "-" to read from stdin',
178
+ required: true,
179
+ },
180
+ name: {
181
+ type: "string",
182
+ description: "Knowledge name (flat, no '/'; defaults to the source filename or content slug). Use --path for a subdirectory.",
183
+ },
184
+ path: {
185
+ type: "string",
186
+ description: "Relative subdirectory under knowledge/ to place the document in (e.g. 'projects/example'). The filename still comes from --name or the source slug.",
187
+ },
188
+ force: {
189
+ type: "boolean",
190
+ description: "Overwrite an existing knowledge document with the same name",
191
+ default: false,
192
+ },
193
+ target: {
194
+ type: "string",
195
+ description: "Override the write destination. Accepts a source name from your config; falls back to defaultWriteTarget then the working stash.",
196
+ },
197
+ },
198
+ async run({ args }) {
199
+ // `--name` is a flat name; subdirectory placement is `--path`'s job.
200
+ assertFlatAssetName(args.name);
201
+ const { content, preferredName } = await readKnowledgeInput(args.source);
202
+ const result = await writeMarkdownAsset({
203
+ type: "knowledge",
204
+ content,
205
+ name: args.name ?? (isHttpUrl(args.source) ? preferredName : undefined),
206
+ fallbackPrefix: "knowledge",
207
+ preferredName,
208
+ force: args.force,
209
+ target: args.target,
210
+ path: args.path,
211
+ });
212
+ appendEvent({
213
+ eventType: "import",
214
+ ref: result.ref,
215
+ metadata: { source: args.source, path: result.path, force: args.force === true },
216
+ });
217
+ output("import", { ok: true, source: args.source, ...result });
218
+ },
219
+ });
@@ -0,0 +1,79 @@
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 fs from "node:fs";
5
+ import path from "node:path";
6
+ import { getDirname } from "../../runtime.js";
7
+ const SKELETON_DIR = path.join(getDirname(import.meta.url), "../../assets/stash-skeleton");
8
+ /**
9
+ * Copy the default stash skeleton into a newly created stash directory.
10
+ *
11
+ * Each file in src/assets/stash-skeleton/ is written to the stash root only
12
+ * if the destination does not already exist — existing files are never
13
+ * overwritten. Non-fatal: if the skeleton directory is missing or a copy
14
+ * fails the caller continues normally.
15
+ */
16
+ export function copyStashSkeleton(stashDir) {
17
+ let entries;
18
+ try {
19
+ entries = fs.readdirSync(SKELETON_DIR);
20
+ }
21
+ catch {
22
+ return;
23
+ }
24
+ for (const entry of entries) {
25
+ const src = path.join(SKELETON_DIR, entry);
26
+ const dest = path.join(stashDir, entry);
27
+ if (fs.existsSync(dest))
28
+ continue;
29
+ try {
30
+ fs.copyFileSync(src, dest);
31
+ }
32
+ catch {
33
+ // Non-fatal — stash is usable without skeleton files
34
+ }
35
+ }
36
+ }
37
+ /**
38
+ * Scaffold the optional `.meta/index.md` orientation doc for the stash
39
+ * `.meta/` convention. Written only when absent — an existing `.meta/index.md`
40
+ * is never overwritten. Non-fatal: a stash works fine without it.
41
+ *
42
+ * `.meta/` is a dot-directory, so the indexer skips it; the template is
43
+ * written here (rather than shipped under `src/assets/`) because the
44
+ * build-time asset glob excludes dotfiles.
45
+ */
46
+ export function scaffoldStashMeta(stashDir) {
47
+ const metaDir = path.join(stashDir, ".meta");
48
+ const indexPath = path.join(metaDir, "index.md");
49
+ if (fs.existsSync(indexPath))
50
+ return;
51
+ try {
52
+ fs.mkdirSync(metaDir, { recursive: true });
53
+ fs.writeFileSync(indexPath, STASH_META_INDEX_TEMPLATE);
54
+ }
55
+ catch {
56
+ // Non-fatal — stash is usable without the .meta orientation doc
57
+ }
58
+ }
59
+ const STASH_META_INDEX_TEMPLATE = `---
60
+ # Optional, human-authored orientation for this stash. Not indexed; surfaced
61
+ # on demand via \`akm show meta\` (this file) or \`akm show <stash>//meta\`.
62
+ # Every field is optional — delete what you don't need.
63
+ purpose:
64
+ - Describe what this stash is for.
65
+ entry_points:
66
+ # Refs an agent should start from, e.g. skill:code-review, workflow:ship-release
67
+ conventions:
68
+ # House rules an agent should follow when working in this stash.
69
+ maintainer:
70
+ ---
71
+ # About this stash
72
+
73
+ Replace this with a short orientation for agents and humans: what lives here,
74
+ where to start, and the conventions to follow.
75
+
76
+ Extend the \`.meta/\` directory with more docs as needed — \`.meta/about.md\`,
77
+ \`.meta/conventions.md\`, \`.meta/license\` — and read any of them with
78
+ \`akm show meta:<name>\` (or \`akm show <stash>//meta:<name>\`).
79
+ `;
@@ -0,0 +1,173 @@
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
+ * Default scheduled-task set for the improve pipeline (issue #552).
6
+ *
7
+ * Ships a well-tuned multi-cadence task set so a typical single-developer
8
+ * install is correct with zero manual config. Registration is **idempotent**:
9
+ * running `akm setup` / `akm tasks init` twice yields the same task set with no
10
+ * duplicates. Each default task is a shell-command task (`akm improve --profile
11
+ * <name>`), so the profile is overridable in `config.json` without editing the
12
+ * task definition.
13
+ *
14
+ * The OS-scheduler-touching primitives (`add` / `setEnabled` / `list`) are
15
+ * injected via {@link RegisterDefaultTasksDeps} so tests can supply fakes and
16
+ * never mutate the host scheduler or a real stash — mirroring the
17
+ * dependency-injection pattern used by `stepScheduledTasks` in setup.ts.
18
+ */
19
+ import fs from "node:fs";
20
+ import os from "node:os";
21
+ import { akmTasksAdd, akmTasksList } from "./tasks.js";
22
+ /**
23
+ * The canonical default improve task set. `update-stashes` is deliberately NOT
24
+ * listed here — it ships as an embedded core template and is left unchanged.
25
+ */
26
+ export const DEFAULT_IMPROVE_TASKS = [
27
+ {
28
+ id: "akm-improve-frequent",
29
+ profile: "frequent",
30
+ command: "akm improve --profile frequent --auto-accept safe",
31
+ schedule: "0 * * * *",
32
+ description: "Frequent extract + inference pass (every 60 min)",
33
+ enableMode: "always",
34
+ },
35
+ {
36
+ id: "akm-improve-consolidate",
37
+ profile: "consolidate",
38
+ command: "akm improve --profile consolidate --auto-accept safe",
39
+ schedule: "0 */4 * * *",
40
+ description: "Consolidation-only pass (every 4h)",
41
+ enableMode: "always",
42
+ },
43
+ {
44
+ id: "akm-improve-nightly",
45
+ profile: "thorough",
46
+ command: "akm improve --profile thorough --auto-accept safe",
47
+ schedule: "0 2 * * *",
48
+ description: "Full nightly quality sweep (daily 2am)",
49
+ enableMode: "server",
50
+ },
51
+ {
52
+ id: "akm-improve-catchup",
53
+ profile: "catchup",
54
+ command: "akm improve --profile catchup --auto-accept safe",
55
+ schedule: null,
56
+ description: "Manual recovery — consolidation + triage drain (run on demand)",
57
+ enableMode: "manual",
58
+ },
59
+ {
60
+ id: "akm-graph-refresh-weekly",
61
+ profile: "graph-refresh",
62
+ command: "akm improve --profile graph-refresh --auto-accept safe",
63
+ schedule: "0 3 * * 0",
64
+ description: "Full-corpus graph rebuild (weekly Sunday 3am)",
65
+ enableMode: "always",
66
+ },
67
+ ];
68
+ /**
69
+ * A schedule for the manual catch-up task. The scheduler requires a valid
70
+ * cron expression even when the task is registered disabled, so we give the
71
+ * unscheduled task a nominal far-future-ish cadence and leave it disabled —
72
+ * the documented entry point is `akm tasks run akm-improve-catchup`.
73
+ */
74
+ const MANUAL_TASK_NOMINAL_SCHEDULE = "0 4 1 1 *"; // 04:00 on Jan 1 — never effectively fires
75
+ const DEFAULT_DEPS = {
76
+ list: akmTasksList,
77
+ add: akmTasksAdd,
78
+ };
79
+ /**
80
+ * Decide whether `akm setup` is running in a CI environment, where it must
81
+ * register NO scheduled tasks. Mirrors the common `CI=true` convention used by
82
+ * GitHub Actions, GitLab CI, CircleCI, etc.
83
+ */
84
+ export function isCiEnvironment(env = process.env) {
85
+ const ci = env.CI;
86
+ if (ci === undefined || ci === null)
87
+ return false;
88
+ const v = String(ci).trim().toLowerCase();
89
+ return v !== "" && v !== "0" && v !== "false";
90
+ }
91
+ /**
92
+ * Platform-appropriate default for the "Is this a server install?" prompt:
93
+ * - Linux without a battery → `true` (server).
94
+ * - macOS / any host with a battery (laptop) → `false`.
95
+ * Used as the default when setup is non-interactive (no TTY / --yes / CI).
96
+ */
97
+ export function detectServerDefault() {
98
+ if (os.platform() !== "linux")
99
+ return false;
100
+ // A laptop exposes a battery under /sys/class/power_supply/BAT*. Absence of
101
+ // any battery is our heuristic for "server / desktop".
102
+ try {
103
+ const entries = fs.readdirSync("/sys/class/power_supply");
104
+ const hasBattery = entries.some((e) => /^BAT/i.test(e));
105
+ return !hasBattery;
106
+ }
107
+ catch {
108
+ // If we cannot read power-supply info, prefer the safe server default on
109
+ // Linux (the nightly sweep is low-impact and re-runnable).
110
+ return true;
111
+ }
112
+ }
113
+ /**
114
+ * Idempotently register the default improve task set.
115
+ *
116
+ * - Skips entirely when `CI` is set (returns `{ skipped: true, reason: "ci" }`).
117
+ * - Creates any missing task; never duplicates an existing one.
118
+ * - Re-aligns the enabled-state of existing tasks to the desired default only
119
+ * when the install is fresh-creating them (existing tasks the user may have
120
+ * toggled are left untouched — we only toggle tasks we just created, plus we
121
+ * never re-disable a user-enabled task).
122
+ */
123
+ export async function registerDefaultTasks(options = {}) {
124
+ if (isCiEnvironment()) {
125
+ return { skipped: true, reason: "ci", created: [], existing: [], toggled: [] };
126
+ }
127
+ const deps = options.deps ?? DEFAULT_DEPS;
128
+ const serverInstall = options.serverInstall ?? detectServerDefault();
129
+ let installed = [];
130
+ try {
131
+ installed = (await deps.list()).tasks;
132
+ }
133
+ catch {
134
+ installed = [];
135
+ }
136
+ const existingIds = new Set(installed.map((t) => t.id));
137
+ const created = [];
138
+ const existing = [];
139
+ const toggled = [];
140
+ for (const spec of DEFAULT_IMPROVE_TASKS) {
141
+ const desiredEnabled = resolveDesiredEnabled(spec, serverInstall);
142
+ if (existingIds.has(spec.id)) {
143
+ // Idempotent: never re-create. Leave the existing task in place.
144
+ existing.push(spec.id);
145
+ continue;
146
+ }
147
+ const schedule = spec.schedule ?? MANUAL_TASK_NOMINAL_SCHEDULE;
148
+ const addInput = {
149
+ id: spec.id,
150
+ schedule,
151
+ command: spec.command,
152
+ description: spec.description,
153
+ // Manual + non-server-enabled tasks are written disabled so the
154
+ // scheduler entry is inert until the user opts in / runs it manually.
155
+ disabled: !desiredEnabled,
156
+ };
157
+ await deps.add(addInput);
158
+ created.push(spec.id);
159
+ if (desiredEnabled)
160
+ toggled.push(spec.id);
161
+ }
162
+ return { skipped: false, created, existing, toggled };
163
+ }
164
+ function resolveDesiredEnabled(spec, serverInstall) {
165
+ switch (spec.enableMode) {
166
+ case "always":
167
+ return true;
168
+ case "server":
169
+ return serverInstall;
170
+ case "manual":
171
+ return false;
172
+ }
173
+ }
@@ -0,0 +1,210 @@
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
+ * `akm tasks` command family. Extracted verbatim from src/cli.ts (WS6) so the
6
+ * God Module shrinks; the `main.subCommands.tasks` key and every subcommand's
7
+ * args/output shape are byte-identical. Handlers whose body is a plain
8
+ * `runWithJsonErrors(...) + output(...)` are migrated to `defineJsonCommand`,
9
+ * which emits the same JSON envelope (stdout/stderr/exit-code) as the inline
10
+ * form. `tasks run` keeps a plain `defineCommand` because it forwards the
11
+ * task's own exit code via `process.exit`. The private helper
12
+ * `makeTasksToggleCommand` and the `TASKS_SUBCOMMAND_SET` routing constant move
13
+ * with the family.
14
+ */
15
+ import { defineCommand } from "citty";
16
+ import { hasSubcommand, parsePositiveIntFlag } from "../../cli/parse-args.js";
17
+ import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
18
+ import { getHyphenatedArg } from "../../output/context.js";
19
+ import { detectServerDefault, registerDefaultTasks } from "./default-tasks.js";
20
+ import { akmTasksAdd, akmTasksDoctor, akmTasksHistory, akmTasksList, akmTasksRemove, akmTasksRun, akmTasksSetEnabled, akmTasksShow, akmTasksSync, parseTaskRef, } from "./tasks.js";
21
+ const tasksAddCommand = defineJsonCommand({
22
+ meta: { name: "add", description: "Register a new scheduled task and install it in the OS scheduler" },
23
+ args: {
24
+ id: { type: "positional", description: "Task id (used as filename and scheduler entry)", required: true },
25
+ schedule: { type: "string", description: 'Cron-style schedule, e.g. "0 9 * * *" or "@daily"', required: true },
26
+ workflow: { type: "string", description: "Workflow ref to invoke (e.g. workflow:my-flow)" },
27
+ prompt: {
28
+ type: "string",
29
+ description: "Prompt for the configured agent harness — inline text, an asset ref like agent:foo, or ./path.md",
30
+ },
31
+ command: {
32
+ type: "string",
33
+ description: 'Shell command to run on the schedule (no AI agent), e.g. "akm improve --auto-accept safe". Split on whitespace; quote the whole flag value.',
34
+ },
35
+ profile: { type: "string", description: "Agent profile to use for prompt targets (default: defaults.agent)" },
36
+ params: { type: "string", description: "Workflow params as a JSON object" },
37
+ name: { type: "string", description: "Human-readable name for the task" },
38
+ "when-to-use": { type: "string", description: "Guidance on when this task runs or should be used" },
39
+ description: { type: "string", description: "Human-readable description" },
40
+ tags: { type: "string", description: "Comma-separated tags" },
41
+ disabled: { type: "boolean", description: "Register but leave disabled in the OS scheduler", default: false },
42
+ force: { type: "boolean", description: "Overwrite an existing task with the same id", default: false },
43
+ },
44
+ async run({ args }) {
45
+ const result = await akmTasksAdd({
46
+ id: args.id,
47
+ schedule: args.schedule,
48
+ workflow: args.workflow,
49
+ prompt: args.prompt,
50
+ command: args.command,
51
+ profile: args.profile,
52
+ params: args.params,
53
+ name: args.name,
54
+ when_to_use: getHyphenatedArg(args, "when-to-use"),
55
+ description: args.description,
56
+ tags: args.tags
57
+ ? args.tags
58
+ .split(/[\s,]+/)
59
+ .map((s) => s.trim())
60
+ .filter(Boolean)
61
+ : undefined,
62
+ disabled: args.disabled === true,
63
+ force: args.force === true,
64
+ });
65
+ output("tasks-add", result);
66
+ },
67
+ });
68
+ const tasksInitCommand = defineJsonCommand({
69
+ meta: {
70
+ name: "init",
71
+ description: "Idempotently register the default improve task set (skips when CI=true)",
72
+ },
73
+ args: {
74
+ server: {
75
+ type: "boolean",
76
+ description: "Treat this as a server install (enables the nightly sweep). Defaults to platform detection.",
77
+ },
78
+ laptop: {
79
+ type: "boolean",
80
+ description: "Treat this as a laptop install (leaves the nightly sweep disabled).",
81
+ },
82
+ },
83
+ async run({ args }) {
84
+ const serverInstall = args.server === true ? true : args.laptop === true ? false : detectServerDefault();
85
+ const result = await registerDefaultTasks({ serverInstall });
86
+ output("tasks-init", result);
87
+ },
88
+ });
89
+ const tasksListCommand = defineJsonCommand({
90
+ meta: { name: "list", description: "List scheduled tasks in the stash" },
91
+ async run() {
92
+ const result = await akmTasksList();
93
+ output("tasks-list", result);
94
+ },
95
+ });
96
+ const tasksShowCommand = defineJsonCommand({
97
+ meta: { name: "show", description: "Show a parsed task definition" },
98
+ args: { id: { type: "positional", description: "Task id or task:<id>", required: true } },
99
+ async run({ args }) {
100
+ const { id } = parseTaskRef(args.id);
101
+ const result = await akmTasksShow(id);
102
+ output("tasks-show", result);
103
+ },
104
+ });
105
+ const tasksRemoveCommand = defineJsonCommand({
106
+ meta: { name: "remove", description: "Delete a task file and uninstall it from the OS scheduler" },
107
+ args: { id: { type: "positional", description: "Task id", required: true } },
108
+ async run({ args }) {
109
+ const { id } = parseTaskRef(args.id);
110
+ const result = await akmTasksRemove(id);
111
+ output("tasks-remove", result);
112
+ },
113
+ });
114
+ function makeTasksToggleCommand(enabled) {
115
+ const verb = enabled ? "enable" : "disable";
116
+ const description = enabled
117
+ ? "Enable a previously-disabled task"
118
+ : "Disable a task in the OS scheduler without removing the file";
119
+ return defineJsonCommand({
120
+ meta: { name: verb, description },
121
+ args: { id: { type: "positional", description: "Task id", required: true } },
122
+ async run({ args }) {
123
+ const { id } = parseTaskRef(args.id);
124
+ const result = await akmTasksSetEnabled(id, enabled);
125
+ output(`tasks-${verb}`, result);
126
+ },
127
+ });
128
+ }
129
+ const tasksEnableCommand = makeTasksToggleCommand(true);
130
+ const tasksDisableCommand = makeTasksToggleCommand(false);
131
+ const tasksRunCommand = defineCommand({
132
+ meta: {
133
+ name: "run",
134
+ description: "Execute a task now (this is what cron / launchd / schtasks invoke at the scheduled time)",
135
+ },
136
+ args: { id: { type: "positional", description: "Task id", required: true } },
137
+ async run({ args }) {
138
+ await runWithJsonErrors(async () => {
139
+ const { id } = parseTaskRef(args.id);
140
+ const envelope = await akmTasksRun(id);
141
+ output("tasks-run", envelope);
142
+ if (envelope.exitCode !== 0)
143
+ process.exit(envelope.exitCode);
144
+ });
145
+ },
146
+ });
147
+ const tasksHistoryCommand = defineJsonCommand({
148
+ meta: { name: "history", description: "Show recent task run history" },
149
+ args: {
150
+ id: { type: "string", description: "Filter to one task id" },
151
+ limit: { type: "string", description: "Maximum rows to return (default 50)" },
152
+ },
153
+ async run({ args }) {
154
+ const limit = parsePositiveIntFlag(args.limit ?? undefined);
155
+ const result = await akmTasksHistory({ id: args.id, limit });
156
+ output("tasks-history", result);
157
+ },
158
+ });
159
+ const tasksSyncCommand = defineJsonCommand({
160
+ meta: {
161
+ name: "sync",
162
+ description: "Reconcile the on-disk task files with the OS scheduler",
163
+ },
164
+ async run() {
165
+ const result = await akmTasksSync();
166
+ output("tasks-sync", result);
167
+ },
168
+ });
169
+ const tasksDoctorCommand = defineJsonCommand({
170
+ meta: {
171
+ name: "doctor",
172
+ description: "Report the active scheduler backend, akm bin path, log dir, and supported schedule subset",
173
+ },
174
+ async run() {
175
+ const result = await akmTasksDoctor();
176
+ output("tasks-doctor", result);
177
+ },
178
+ });
179
+ // Single source of truth: the routing set is derived from the subCommands keys
180
+ // (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
181
+ const tasksSubCommands = {
182
+ add: tasksAddCommand,
183
+ init: tasksInitCommand,
184
+ list: tasksListCommand,
185
+ show: tasksShowCommand,
186
+ remove: tasksRemoveCommand,
187
+ enable: tasksEnableCommand,
188
+ disable: tasksDisableCommand,
189
+ run: tasksRunCommand,
190
+ history: tasksHistoryCommand,
191
+ sync: tasksSyncCommand,
192
+ doctor: tasksDoctorCommand,
193
+ };
194
+ const TASKS_SUBCOMMAND_SET = new Set(Object.keys(tasksSubCommands));
195
+ export const tasksCommand = defineCommand({
196
+ meta: {
197
+ name: "tasks",
198
+ alias: "task",
199
+ description: "Schedule workflows or prompts via the OS-native scheduler (cron / launchd / schtasks)",
200
+ },
201
+ subCommands: tasksSubCommands,
202
+ run({ args }) {
203
+ return runWithJsonErrors(async () => {
204
+ if (hasSubcommand(args, TASKS_SUBCOMMAND_SET))
205
+ return;
206
+ const result = await akmTasksList();
207
+ output("tasks-list", result);
208
+ });
209
+ },
210
+ });