@ashlr/hub 2.2.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 (477) hide show
  1. package/CHANGELOG.md +1158 -0
  2. package/LICENSE +21 -0
  3. package/README.md +833 -0
  4. package/bin/ashlr +8 -0
  5. package/dist/api/core.d.ts +28 -0
  6. package/dist/api/core.js +42 -0
  7. package/dist/api/core.js.map +1 -0
  8. package/dist/api/index.d.ts +9 -0
  9. package/dist/api/index.js +9 -0
  10. package/dist/api/index.js.map +1 -0
  11. package/dist/api/plugin.d.ts +12 -0
  12. package/dist/api/plugin.js +13 -0
  13. package/dist/api/plugin.js.map +1 -0
  14. package/dist/api/types.d.ts +9 -0
  15. package/dist/api/types.js +9 -0
  16. package/dist/api/types.js.map +1 -0
  17. package/dist/cli/args.d.ts +20 -0
  18. package/dist/cli/args.js +23 -0
  19. package/dist/cli/args.js.map +1 -0
  20. package/dist/cli/ask.d.ts +22 -0
  21. package/dist/cli/ask.js +240 -0
  22. package/dist/cli/ask.js.map +1 -0
  23. package/dist/cli/audit.d.ts +26 -0
  24. package/dist/cli/audit.js +199 -0
  25. package/dist/cli/audit.js.map +1 -0
  26. package/dist/cli/backlog.d.ts +26 -0
  27. package/dist/cli/backlog.js +322 -0
  28. package/dist/cli/backlog.js.map +1 -0
  29. package/dist/cli/completions.d.ts +18 -0
  30. package/dist/cli/completions.js +156 -0
  31. package/dist/cli/completions.js.map +1 -0
  32. package/dist/cli/daemon.d.ts +31 -0
  33. package/dist/cli/daemon.js +334 -0
  34. package/dist/cli/daemon.js.map +1 -0
  35. package/dist/cli/demo-sandbox.d.ts +78 -0
  36. package/dist/cli/demo-sandbox.js +227 -0
  37. package/dist/cli/demo-sandbox.js.map +1 -0
  38. package/dist/cli/demo.d.ts +76 -0
  39. package/dist/cli/demo.js +285 -0
  40. package/dist/cli/demo.js.map +1 -0
  41. package/dist/cli/digest.d.ts +46 -0
  42. package/dist/cli/digest.js +352 -0
  43. package/dist/cli/digest.js.map +1 -0
  44. package/dist/cli/doctor-init.d.ts +28 -0
  45. package/dist/cli/doctor-init.js +335 -0
  46. package/dist/cli/doctor-init.js.map +1 -0
  47. package/dist/cli/genome.d.ts +62 -0
  48. package/dist/cli/genome.js +1061 -0
  49. package/dist/cli/genome.js.map +1 -0
  50. package/dist/cli/gh.d.ts +27 -0
  51. package/dist/cli/gh.js +322 -0
  52. package/dist/cli/gh.js.map +1 -0
  53. package/dist/cli/goals.d.ts +55 -0
  54. package/dist/cli/goals.js +667 -0
  55. package/dist/cli/goals.js.map +1 -0
  56. package/dist/cli/health.d.ts +56 -0
  57. package/dist/cli/health.js +407 -0
  58. package/dist/cli/health.js.map +1 -0
  59. package/dist/cli/help.d.ts +51 -0
  60. package/dist/cli/help.js +436 -0
  61. package/dist/cli/help.js.map +1 -0
  62. package/dist/cli/inbox.d.ts +28 -0
  63. package/dist/cli/inbox.js +589 -0
  64. package/dist/cli/inbox.js.map +1 -0
  65. package/dist/cli/index.d.ts +41 -0
  66. package/dist/cli/index.js +1208 -0
  67. package/dist/cli/index.js.map +1 -0
  68. package/dist/cli/knowledge.d.ts +33 -0
  69. package/dist/cli/knowledge.js +575 -0
  70. package/dist/cli/knowledge.js.map +1 -0
  71. package/dist/cli/mcp.d.ts +26 -0
  72. package/dist/cli/mcp.js +487 -0
  73. package/dist/cli/mcp.js.map +1 -0
  74. package/dist/cli/models.d.ts +26 -0
  75. package/dist/cli/models.js +435 -0
  76. package/dist/cli/models.js.map +1 -0
  77. package/dist/cli/new.d.ts +32 -0
  78. package/dist/cli/new.js +565 -0
  79. package/dist/cli/new.js.map +1 -0
  80. package/dist/cli/notify.d.ts +20 -0
  81. package/dist/cli/notify.js +188 -0
  82. package/dist/cli/notify.js.map +1 -0
  83. package/dist/cli/onboard.d.ts +85 -0
  84. package/dist/cli/onboard.js +305 -0
  85. package/dist/cli/onboard.js.map +1 -0
  86. package/dist/cli/open.d.ts +38 -0
  87. package/dist/cli/open.js +86 -0
  88. package/dist/cli/open.js.map +1 -0
  89. package/dist/cli/orient.d.ts +14 -0
  90. package/dist/cli/orient.js +110 -0
  91. package/dist/cli/orient.js.map +1 -0
  92. package/dist/cli/picker.d.ts +21 -0
  93. package/dist/cli/picker.js +177 -0
  94. package/dist/cli/picker.js.map +1 -0
  95. package/dist/cli/plugins.d.ts +21 -0
  96. package/dist/cli/plugins.js +394 -0
  97. package/dist/cli/plugins.js.map +1 -0
  98. package/dist/cli/preflight.d.ts +29 -0
  99. package/dist/cli/preflight.js +131 -0
  100. package/dist/cli/preflight.js.map +1 -0
  101. package/dist/cli/pulse.d.ts +26 -0
  102. package/dist/cli/pulse.js +482 -0
  103. package/dist/cli/pulse.js.map +1 -0
  104. package/dist/cli/reflect.d.ts +42 -0
  105. package/dist/cli/reflect.js +381 -0
  106. package/dist/cli/reflect.js.map +1 -0
  107. package/dist/cli/run.d.ts +29 -0
  108. package/dist/cli/run.js +687 -0
  109. package/dist/cli/run.js.map +1 -0
  110. package/dist/cli/sandbox.d.ts +20 -0
  111. package/dist/cli/sandbox.js +345 -0
  112. package/dist/cli/sandbox.js.map +1 -0
  113. package/dist/cli/seams.d.ts +41 -0
  114. package/dist/cli/seams.js +167 -0
  115. package/dist/cli/seams.js.map +1 -0
  116. package/dist/cli/serve.d.ts +25 -0
  117. package/dist/cli/serve.js +249 -0
  118. package/dist/cli/serve.js.map +1 -0
  119. package/dist/cli/ship.d.ts +33 -0
  120. package/dist/cli/ship.js +433 -0
  121. package/dist/cli/ship.js.map +1 -0
  122. package/dist/cli/spec.d.ts +19 -0
  123. package/dist/cli/spec.js +478 -0
  124. package/dist/cli/spec.js.map +1 -0
  125. package/dist/cli/swarm.d.ts +42 -0
  126. package/dist/cli/swarm.js +1365 -0
  127. package/dist/cli/swarm.js.map +1 -0
  128. package/dist/cli/telemetry.d.ts +26 -0
  129. package/dist/cli/telemetry.js +364 -0
  130. package/dist/cli/telemetry.js.map +1 -0
  131. package/dist/cli/tui.d.ts +17 -0
  132. package/dist/cli/tui.js +52 -0
  133. package/dist/cli/tui.js.map +1 -0
  134. package/dist/cli/ui.d.ts +53 -0
  135. package/dist/cli/ui.js +66 -0
  136. package/dist/cli/ui.js.map +1 -0
  137. package/dist/cli/update.d.ts +38 -0
  138. package/dist/cli/update.js +682 -0
  139. package/dist/cli/update.js.map +1 -0
  140. package/dist/cli/vercel.d.ts +20 -0
  141. package/dist/cli/vercel.js +193 -0
  142. package/dist/cli/vercel.js.map +1 -0
  143. package/dist/cli/verify-safety.d.ts +96 -0
  144. package/dist/cli/verify-safety.js +509 -0
  145. package/dist/cli/verify-safety.js.map +1 -0
  146. package/dist/cli/wire.d.ts +22 -0
  147. package/dist/cli/wire.js +205 -0
  148. package/dist/cli/wire.js.map +1 -0
  149. package/dist/core/classify.d.ts +51 -0
  150. package/dist/core/classify.js +450 -0
  151. package/dist/core/classify.js.map +1 -0
  152. package/dist/core/config.d.ts +63 -0
  153. package/dist/core/config.js +474 -0
  154. package/dist/core/config.js.map +1 -0
  155. package/dist/core/daemon/loop.d.ts +74 -0
  156. package/dist/core/daemon/loop.js +618 -0
  157. package/dist/core/daemon/loop.js.map +1 -0
  158. package/dist/core/daemon/state.d.ts +66 -0
  159. package/dist/core/daemon/state.js +197 -0
  160. package/dist/core/daemon/state.js.map +1 -0
  161. package/dist/core/dashboard.d.ts +40 -0
  162. package/dist/core/dashboard.js +463 -0
  163. package/dist/core/dashboard.js.map +1 -0
  164. package/dist/core/digest/build.d.ts +51 -0
  165. package/dist/core/digest/build.js +230 -0
  166. package/dist/core/digest/build.js.map +1 -0
  167. package/dist/core/digest/deliver.d.ts +47 -0
  168. package/dist/core/digest/deliver.js +230 -0
  169. package/dist/core/digest/deliver.js.map +1 -0
  170. package/dist/core/digest/store.d.ts +57 -0
  171. package/dist/core/digest/store.js +223 -0
  172. package/dist/core/digest/store.js.map +1 -0
  173. package/dist/core/doctor-fix.d.ts +21 -0
  174. package/dist/core/doctor-fix.js +440 -0
  175. package/dist/core/doctor-fix.js.map +1 -0
  176. package/dist/core/doctor.d.ts +18 -0
  177. package/dist/core/doctor.js +806 -0
  178. package/dist/core/doctor.js.map +1 -0
  179. package/dist/core/env-bridge.d.ts +64 -0
  180. package/dist/core/env-bridge.js +103 -0
  181. package/dist/core/env-bridge.js.map +1 -0
  182. package/dist/core/genome/capture.d.ts +49 -0
  183. package/dist/core/genome/capture.js +352 -0
  184. package/dist/core/genome/capture.js.map +1 -0
  185. package/dist/core/genome/consolidate.d.ts +38 -0
  186. package/dist/core/genome/consolidate.js +426 -0
  187. package/dist/core/genome/consolidate.js.map +1 -0
  188. package/dist/core/genome/export.d.ts +29 -0
  189. package/dist/core/genome/export.js +102 -0
  190. package/dist/core/genome/export.js.map +1 -0
  191. package/dist/core/genome/playbook.d.ts +33 -0
  192. package/dist/core/genome/playbook.js +320 -0
  193. package/dist/core/genome/playbook.js.map +1 -0
  194. package/dist/core/genome/recall.d.ts +45 -0
  195. package/dist/core/genome/recall.js +293 -0
  196. package/dist/core/genome/recall.js.map +1 -0
  197. package/dist/core/genome/store.d.ts +62 -0
  198. package/dist/core/genome/store.js +711 -0
  199. package/dist/core/genome/store.js.map +1 -0
  200. package/dist/core/git.d.ts +34 -0
  201. package/dist/core/git.js +115 -0
  202. package/dist/core/git.js.map +1 -0
  203. package/dist/core/goals/advance.d.ts +82 -0
  204. package/dist/core/goals/advance.js +263 -0
  205. package/dist/core/goals/advance.js.map +1 -0
  206. package/dist/core/goals/planner.d.ts +58 -0
  207. package/dist/core/goals/planner.js +233 -0
  208. package/dist/core/goals/planner.js.map +1 -0
  209. package/dist/core/goals/store.d.ts +128 -0
  210. package/dist/core/goals/store.js +442 -0
  211. package/dist/core/goals/store.js.map +1 -0
  212. package/dist/core/inbox/apply.d.ts +36 -0
  213. package/dist/core/inbox/apply.js +407 -0
  214. package/dist/core/inbox/apply.js.map +1 -0
  215. package/dist/core/inbox/notify-proposal.d.ts +13 -0
  216. package/dist/core/inbox/notify-proposal.js +21 -0
  217. package/dist/core/inbox/notify-proposal.js.map +1 -0
  218. package/dist/core/inbox/store.d.ts +67 -0
  219. package/dist/core/inbox/store.js +258 -0
  220. package/dist/core/inbox/store.js.map +1 -0
  221. package/dist/core/index-engine.d.ts +62 -0
  222. package/dist/core/index-engine.js +486 -0
  223. package/dist/core/index-engine.js.map +1 -0
  224. package/dist/core/integrations/desktop-notify.d.ts +18 -0
  225. package/dist/core/integrations/desktop-notify.js +42 -0
  226. package/dist/core/integrations/desktop-notify.js.map +1 -0
  227. package/dist/core/integrations/editors.d.ts +50 -0
  228. package/dist/core/integrations/editors.js +216 -0
  229. package/dist/core/integrations/editors.js.map +1 -0
  230. package/dist/core/integrations/github.d.ts +66 -0
  231. package/dist/core/integrations/github.js +332 -0
  232. package/dist/core/integrations/github.js.map +1 -0
  233. package/dist/core/integrations/identity.d.ts +29 -0
  234. package/dist/core/integrations/identity.js +359 -0
  235. package/dist/core/integrations/identity.js.map +1 -0
  236. package/dist/core/integrations/notify.d.ts +23 -0
  237. package/dist/core/integrations/notify.js +96 -0
  238. package/dist/core/integrations/notify.js.map +1 -0
  239. package/dist/core/integrations/vercel.d.ts +37 -0
  240. package/dist/core/integrations/vercel.js +192 -0
  241. package/dist/core/integrations/vercel.js.map +1 -0
  242. package/dist/core/knowledge/ask.d.ts +34 -0
  243. package/dist/core/knowledge/ask.js +325 -0
  244. package/dist/core/knowledge/ask.js.map +1 -0
  245. package/dist/core/knowledge/graph.d.ts +51 -0
  246. package/dist/core/knowledge/graph.js +564 -0
  247. package/dist/core/knowledge/graph.js.map +1 -0
  248. package/dist/core/knowledge/index.d.ts +74 -0
  249. package/dist/core/knowledge/index.js +586 -0
  250. package/dist/core/knowledge/index.js.map +1 -0
  251. package/dist/core/learn/playbooks.d.ts +84 -0
  252. package/dist/core/learn/playbooks.js +241 -0
  253. package/dist/core/learn/playbooks.js.map +1 -0
  254. package/dist/core/learn/reflect.d.ts +87 -0
  255. package/dist/core/learn/reflect.js +435 -0
  256. package/dist/core/learn/reflect.js.map +1 -0
  257. package/dist/core/learn/store.d.ts +51 -0
  258. package/dist/core/learn/store.js +165 -0
  259. package/dist/core/learn/store.js.map +1 -0
  260. package/dist/core/learn/tuning.d.ts +48 -0
  261. package/dist/core/learn/tuning.js +201 -0
  262. package/dist/core/learn/tuning.js.map +1 -0
  263. package/dist/core/lifecycle/scaffold.d.ts +43 -0
  264. package/dist/core/lifecycle/scaffold.js +260 -0
  265. package/dist/core/lifecycle/scaffold.js.map +1 -0
  266. package/dist/core/lifecycle/ship.d.ts +48 -0
  267. package/dist/core/lifecycle/ship.js +513 -0
  268. package/dist/core/lifecycle/ship.js.map +1 -0
  269. package/dist/core/lifecycle/templates.d.ts +20 -0
  270. package/dist/core/lifecycle/templates.js +605 -0
  271. package/dist/core/lifecycle/templates.js.map +1 -0
  272. package/dist/core/mcp-gateway.d.ts +59 -0
  273. package/dist/core/mcp-gateway.js +385 -0
  274. package/dist/core/mcp-gateway.js.map +1 -0
  275. package/dist/core/mcp-native.d.ts +53 -0
  276. package/dist/core/mcp-native.js +507 -0
  277. package/dist/core/mcp-native.js.map +1 -0
  278. package/dist/core/mcp-registry.d.ts +36 -0
  279. package/dist/core/mcp-registry.js +180 -0
  280. package/dist/core/mcp-registry.js.map +1 -0
  281. package/dist/core/observability/budget-alert.d.ts +18 -0
  282. package/dist/core/observability/budget-alert.js +90 -0
  283. package/dist/core/observability/budget-alert.js.map +1 -0
  284. package/dist/core/observability/estimate.d.ts +27 -0
  285. package/dist/core/observability/estimate.js +188 -0
  286. package/dist/core/observability/estimate.js.map +1 -0
  287. package/dist/core/observability/forecast.d.ts +19 -0
  288. package/dist/core/observability/forecast.js +101 -0
  289. package/dist/core/observability/forecast.js.map +1 -0
  290. package/dist/core/observability/governance.d.ts +28 -0
  291. package/dist/core/observability/governance.js +101 -0
  292. package/dist/core/observability/governance.js.map +1 -0
  293. package/dist/core/observability/otlp.d.ts +88 -0
  294. package/dist/core/observability/otlp.js +217 -0
  295. package/dist/core/observability/otlp.js.map +1 -0
  296. package/dist/core/observability/rollup.d.ts +35 -0
  297. package/dist/core/observability/rollup.js +311 -0
  298. package/dist/core/observability/rollup.js.map +1 -0
  299. package/dist/core/observability/telemetry-sink.d.ts +63 -0
  300. package/dist/core/observability/telemetry-sink.js +350 -0
  301. package/dist/core/observability/telemetry-sink.js.map +1 -0
  302. package/dist/core/observability/usage-source.d.ts +60 -0
  303. package/dist/core/observability/usage-source.js +347 -0
  304. package/dist/core/observability/usage-source.js.map +1 -0
  305. package/dist/core/onboard.d.ts +27 -0
  306. package/dist/core/onboard.js +288 -0
  307. package/dist/core/onboard.js.map +1 -0
  308. package/dist/core/orient.d.ts +23 -0
  309. package/dist/core/orient.js +135 -0
  310. package/dist/core/orient.js.map +1 -0
  311. package/dist/core/phantom.d.ts +23 -0
  312. package/dist/core/phantom.js +279 -0
  313. package/dist/core/phantom.js.map +1 -0
  314. package/dist/core/plugins/host-api.d.ts +29 -0
  315. package/dist/core/plugins/host-api.js +113 -0
  316. package/dist/core/plugins/host-api.js.map +1 -0
  317. package/dist/core/plugins/integrity.d.ts +36 -0
  318. package/dist/core/plugins/integrity.js +73 -0
  319. package/dist/core/plugins/integrity.js.map +1 -0
  320. package/dist/core/plugins/manifest.d.ts +31 -0
  321. package/dist/core/plugins/manifest.js +316 -0
  322. package/dist/core/plugins/manifest.js.map +1 -0
  323. package/dist/core/plugins/registry.d.ts +87 -0
  324. package/dist/core/plugins/registry.js +415 -0
  325. package/dist/core/plugins/registry.js.map +1 -0
  326. package/dist/core/plugins/types.d.ts +182 -0
  327. package/dist/core/plugins/types.js +40 -0
  328. package/dist/core/plugins/types.js.map +1 -0
  329. package/dist/core/plugins/wrappers.d.ts +40 -0
  330. package/dist/core/plugins/wrappers.js +229 -0
  331. package/dist/core/plugins/wrappers.js.map +1 -0
  332. package/dist/core/portfolio/backlog.d.ts +40 -0
  333. package/dist/core/portfolio/backlog.js +177 -0
  334. package/dist/core/portfolio/backlog.js.map +1 -0
  335. package/dist/core/portfolio/scanners.d.ts +21 -0
  336. package/dist/core/portfolio/scanners.js +600 -0
  337. package/dist/core/portfolio/scanners.js.map +1 -0
  338. package/dist/core/providers.d.ts +34 -0
  339. package/dist/core/providers.js +250 -0
  340. package/dist/core/providers.js.map +1 -0
  341. package/dist/core/quality/conventions.d.ts +35 -0
  342. package/dist/core/quality/conventions.js +267 -0
  343. package/dist/core/quality/conventions.js.map +1 -0
  344. package/dist/core/quality/fixes.d.ts +56 -0
  345. package/dist/core/quality/fixes.js +209 -0
  346. package/dist/core/quality/fixes.js.map +1 -0
  347. package/dist/core/quality/health.d.ts +69 -0
  348. package/dist/core/quality/health.js +350 -0
  349. package/dist/core/quality/health.js.map +1 -0
  350. package/dist/core/quality/store.d.ts +56 -0
  351. package/dist/core/quality/store.js +195 -0
  352. package/dist/core/quality/store.js.map +1 -0
  353. package/dist/core/readiness.d.ts +112 -0
  354. package/dist/core/readiness.js +431 -0
  355. package/dist/core/readiness.js.map +1 -0
  356. package/dist/core/run/agent-loop.d.ts +40 -0
  357. package/dist/core/run/agent-loop.js +291 -0
  358. package/dist/core/run/agent-loop.js.map +1 -0
  359. package/dist/core/run/budget.d.ts +47 -0
  360. package/dist/core/run/budget.js +113 -0
  361. package/dist/core/run/budget.js.map +1 -0
  362. package/dist/core/run/engines.d.ts +75 -0
  363. package/dist/core/run/engines.js +199 -0
  364. package/dist/core/run/engines.js.map +1 -0
  365. package/dist/core/run/model-manager.d.ts +64 -0
  366. package/dist/core/run/model-manager.js +339 -0
  367. package/dist/core/run/model-manager.js.map +1 -0
  368. package/dist/core/run/orchestrator.d.ts +100 -0
  369. package/dist/core/run/orchestrator.js +1515 -0
  370. package/dist/core/run/orchestrator.js.map +1 -0
  371. package/dist/core/run/provider-client.d.ts +46 -0
  372. package/dist/core/run/provider-client.js +796 -0
  373. package/dist/core/run/provider-client.js.map +1 -0
  374. package/dist/core/run/retry.d.ts +19 -0
  375. package/dist/core/run/retry.js +68 -0
  376. package/dist/core/run/retry.js.map +1 -0
  377. package/dist/core/run/router.d.ts +50 -0
  378. package/dist/core/run/router.js +257 -0
  379. package/dist/core/run/router.js.map +1 -0
  380. package/dist/core/run/self-heal.d.ts +52 -0
  381. package/dist/core/run/self-heal.js +181 -0
  382. package/dist/core/run/self-heal.js.map +1 -0
  383. package/dist/core/run/streaming.d.ts +31 -0
  384. package/dist/core/run/streaming.js +122 -0
  385. package/dist/core/run/streaming.js.map +1 -0
  386. package/dist/core/run/verify.d.ts +30 -0
  387. package/dist/core/run/verify.js +204 -0
  388. package/dist/core/run/verify.js.map +1 -0
  389. package/dist/core/sandbox/audit.d.ts +31 -0
  390. package/dist/core/sandbox/audit.js +169 -0
  391. package/dist/core/sandbox/audit.js.map +1 -0
  392. package/dist/core/sandbox/policy.d.ts +61 -0
  393. package/dist/core/sandbox/policy.js +211 -0
  394. package/dist/core/sandbox/policy.js.map +1 -0
  395. package/dist/core/sandbox/worktree.d.ts +175 -0
  396. package/dist/core/sandbox/worktree.js +673 -0
  397. package/dist/core/sandbox/worktree.js.map +1 -0
  398. package/dist/core/seams/backlog.d.ts +47 -0
  399. package/dist/core/seams/backlog.js +47 -0
  400. package/dist/core/seams/backlog.js.map +1 -0
  401. package/dist/core/seams/daemon-coordinator.d.ts +72 -0
  402. package/dist/core/seams/daemon-coordinator.js +76 -0
  403. package/dist/core/seams/daemon-coordinator.js.map +1 -0
  404. package/dist/core/seams/genome.d.ts +45 -0
  405. package/dist/core/seams/genome.js +53 -0
  406. package/dist/core/seams/genome.js.map +1 -0
  407. package/dist/core/seams/identity.d.ts +40 -0
  408. package/dist/core/seams/identity.js +44 -0
  409. package/dist/core/seams/identity.js.map +1 -0
  410. package/dist/core/seams/inbox.d.ts +60 -0
  411. package/dist/core/seams/inbox.js +66 -0
  412. package/dist/core/seams/inbox.js.map +1 -0
  413. package/dist/core/seams/index.d.ts +20 -0
  414. package/dist/core/seams/index.js +21 -0
  415. package/dist/core/seams/index.js.map +1 -0
  416. package/dist/core/seams/portfolio.d.ts +50 -0
  417. package/dist/core/seams/portfolio.js +61 -0
  418. package/dist/core/seams/portfolio.js.map +1 -0
  419. package/dist/core/seams/registry.d.ts +42 -0
  420. package/dist/core/seams/registry.js +128 -0
  421. package/dist/core/seams/registry.js.map +1 -0
  422. package/dist/core/seams/run-swarm.d.ts +66 -0
  423. package/dist/core/seams/run-swarm.js +74 -0
  424. package/dist/core/seams/run-swarm.js.map +1 -0
  425. package/dist/core/seams/types.d.ts +123 -0
  426. package/dist/core/seams/types.js +35 -0
  427. package/dist/core/seams/types.js.map +1 -0
  428. package/dist/core/spec/spec-store.d.ts +62 -0
  429. package/dist/core/spec/spec-store.js +359 -0
  430. package/dist/core/spec/spec-store.js.map +1 -0
  431. package/dist/core/swarm/gate.d.ts +45 -0
  432. package/dist/core/swarm/gate.js +114 -0
  433. package/dist/core/swarm/gate.js.map +1 -0
  434. package/dist/core/swarm/planner.d.ts +32 -0
  435. package/dist/core/swarm/planner.js +293 -0
  436. package/dist/core/swarm/planner.js.map +1 -0
  437. package/dist/core/swarm/rollback.d.ts +56 -0
  438. package/dist/core/swarm/rollback.js +266 -0
  439. package/dist/core/swarm/rollback.js.map +1 -0
  440. package/dist/core/swarm/runner.d.ts +62 -0
  441. package/dist/core/swarm/runner.js +1263 -0
  442. package/dist/core/swarm/runner.js.map +1 -0
  443. package/dist/core/swarm/sign.d.ts +71 -0
  444. package/dist/core/swarm/sign.js +362 -0
  445. package/dist/core/swarm/sign.js.map +1 -0
  446. package/dist/core/swarm/store.d.ts +52 -0
  447. package/dist/core/swarm/store.js +195 -0
  448. package/dist/core/swarm/store.js.map +1 -0
  449. package/dist/core/tidy.d.ts +32 -0
  450. package/dist/core/tidy.js +354 -0
  451. package/dist/core/tidy.js.map +1 -0
  452. package/dist/core/tools-registry.d.ts +16 -0
  453. package/dist/core/tools-registry.js +308 -0
  454. package/dist/core/tools-registry.js.map +1 -0
  455. package/dist/core/types.d.ts +2545 -0
  456. package/dist/core/types.js +9 -0
  457. package/dist/core/types.js.map +1 -0
  458. package/dist/core/web/api.d.ts +53 -0
  459. package/dist/core/web/api.js +698 -0
  460. package/dist/core/web/api.js.map +1 -0
  461. package/dist/core/web/public/app.js +1906 -0
  462. package/dist/core/web/public/index.html +721 -0
  463. package/dist/core/web/public/styles.css +2007 -0
  464. package/dist/core/web/server.d.ts +18 -0
  465. package/dist/core/web/server.js +122 -0
  466. package/dist/core/web/server.js.map +1 -0
  467. package/dist/core/web/static.d.ts +18 -0
  468. package/dist/core/web/static.js +122 -0
  469. package/dist/core/web/static.js.map +1 -0
  470. package/dist/tui/app.d.ts +33 -0
  471. package/dist/tui/app.js +350 -0
  472. package/dist/tui/app.js.map +1 -0
  473. package/dist/tui/render.d.ts +20 -0
  474. package/dist/tui/render.js +558 -0
  475. package/dist/tui/render.js.map +1 -0
  476. package/package.json +80 -0
  477. package/schema/config.schema.json +223 -0
@@ -0,0 +1,1208 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ashlr CLI — main entry point.
4
+ *
5
+ * Commands:
6
+ * index [--refresh] Rebuild the desktop index; print counts by category.
7
+ * go [query] [--open|--cd] Find an item; open in editor, cd, or just print.
8
+ * status Attention board: dirty/stale repos, aligned table.
9
+ * ls [category] List all indexed items, optionally filtered.
10
+ * open [query] Open best match in editor; no query → picker.
11
+ * tidy [--apply] [--json] Plan (dry-run) or apply tidy moves; --json emits machine output.
12
+ * config [get|set|path] Read/write config values.
13
+ * doctor One-glance health check (config, phantom, providers).
14
+ * init [--yes] Idempotent onboarding; NON-TTY safe with --yes.
15
+ * mcp [list|doctor|install] MCP aggregation gateway and registry management.
16
+ * run "<goal>" [opts] Local-first agent orchestrator; decompose & execute a goal.
17
+ * run show <id> Print a past run in detail.
18
+ * runs [--json] List past runs.
19
+ * pulse [--json] [--window] Local observability dashboard: tokens, cost, activity.
20
+ * new <name> [opts] Scaffold a new project and register it in the index.
21
+ * ship [path] [opts] Run pre-ship gate (lint/test/build/deps) + optional deploy.
22
+ * recall "<query>" Search shared genome memory; return top relevant entries.
23
+ * learn "<text>" [opts] Append a note to shared genome memory.
24
+ * genome Genome status/health: entry count, projects, staleness.
25
+ * update [--check] [--json] Safe self-update: git pull --ff-only + rebuild; --check reports only.
26
+ * spec new "<goal>" [opts] Author a versioned end-state spec artifact.
27
+ * spec list/show/refine Manage spec artifacts.
28
+ * swarm "<goal>"|<specId> Decompose a spec into a contracts-first agent swarm and run it.
29
+ * swarms [--json] List past swarm runs.
30
+ * tui [--once] Interactive terminal dashboard (alias: dash).
31
+ * serve [--port N] [--open] Local web dashboard + JSON API on 127.0.0.1 (default port 7777).
32
+ * gh <pr|issue|ci> Read GitHub PRs / issues / CI status (read-only via gh CLI).
33
+ * gh pr create Create a PR (explicit + confirm-gated mutation).
34
+ * vercel <ls|logs> Read Vercel deployments / latest logs (read-only via vercel CLI).
35
+ * wire [claude|codex|cursor|all] Wire ashlr MCP gateway into editor config(s).
36
+ * notify test Send a test ping to the configured webhook (no-op if unconfigured).
37
+ * help Show this help.
38
+ *
39
+ * Exit codes: 0 success, 1 error/not-found, 2 bad usage.
40
+ */
41
+ import { loadConfig, saveConfig, CONFIG_PATH } from '../core/config.js';
42
+ import { buildIndex, loadIndex, writeIndex } from '../core/index-engine.js';
43
+ import { planTidy, applyTidy } from '../core/tidy.js';
44
+ import { openInEditor } from './open.js';
45
+ import { pick } from './picker.js';
46
+ import { cmdDoctor, cmdInit } from './doctor-init.js';
47
+ import { pad, makeColors } from './ui.js';
48
+ /**
49
+ * Build a lazy loader for a CLI command export.
50
+ * @param importer dynamic import of the command module
51
+ * @param pick selects the command function from the loaded module
52
+ * @param label human command name + source path for the fallback message
53
+ * (e.g. "mcp command requires src/cli/mcp.ts (M3 module not yet built).")
54
+ */
55
+ function lazyCmd(importer, pick, label) {
56
+ let cached;
57
+ return async () => {
58
+ if (cached === undefined) {
59
+ try {
60
+ cached = pick((await importer()));
61
+ }
62
+ catch {
63
+ cached = null;
64
+ }
65
+ }
66
+ if (cached === null) {
67
+ return async (_args) => {
68
+ console.error(red('error: ') + label);
69
+ return 1;
70
+ };
71
+ }
72
+ return cached;
73
+ };
74
+ }
75
+ const loadMcpCmd = lazyCmd(() => import('./mcp.js'), (m) => m.cmdMcp, 'mcp command requires src/cli/mcp.ts (M3 module not yet built).');
76
+ // Cache slots — undefined = not yet attempted; null = attempted & failed
77
+ let _getToolsRegistry = undefined;
78
+ let _discoverMcpServers = undefined;
79
+ async function tryGetToolsRegistry() {
80
+ if (_getToolsRegistry === undefined) {
81
+ try {
82
+ const mod = await import('../core/tools-registry.js');
83
+ _getToolsRegistry = mod.getToolsRegistry;
84
+ }
85
+ catch {
86
+ _getToolsRegistry = null;
87
+ }
88
+ }
89
+ return _getToolsRegistry ?? null;
90
+ }
91
+ async function tryDiscoverMcpServers() {
92
+ if (_discoverMcpServers === undefined) {
93
+ try {
94
+ const mod = await import('../core/mcp-registry.js');
95
+ _discoverMcpServers = mod.discoverMcpServers;
96
+ }
97
+ catch {
98
+ _discoverMcpServers = null;
99
+ }
100
+ }
101
+ return _discoverMcpServers ?? null;
102
+ }
103
+ // ─── M4 command loaders ────────────────────────────────────────────
104
+ const loadRunCmd = lazyCmd(() => import('./run.js'), (m) => m.cmdRun, 'run command requires src/cli/run.ts (M4 module not yet built).');
105
+ const loadRunsCmd = lazyCmd(() => import('./run.js'), (m) => m.cmdRuns, 'runs command requires src/cli/run.ts (M4 module not yet built).');
106
+ let _buildRollup = undefined;
107
+ const loadPulseCmd = lazyCmd(() => import('./pulse.js'), (m) => m.cmdPulse, 'pulse command requires src/cli/pulse.ts (M5 module not yet built).');
108
+ async function tryBuildRollup() {
109
+ if (_buildRollup === undefined) {
110
+ try {
111
+ const mod = (await import('../core/observability/rollup.js'));
112
+ _buildRollup = mod.buildRollup;
113
+ }
114
+ catch {
115
+ _buildRollup = null;
116
+ }
117
+ }
118
+ return _buildRollup ?? null;
119
+ }
120
+ // ─── M6 command loaders ────────────────────────────────────────────
121
+ const loadNewCmd = lazyCmd(() => import('./new.js'), (m) => m.cmdNew, 'new command requires src/cli/new.ts (M6 module not yet built).');
122
+ const loadShipCmd = lazyCmd(() => import('./ship.js'), (m) => m.cmdShip, 'ship command requires src/cli/ship.ts (M6 module not yet built).');
123
+ // ─── M7 command loaders ────────────────────────────────────────────
124
+ const loadRecallCmd = lazyCmd(() => import('./genome.js'), (m) => m.cmdRecall, 'recall command requires src/cli/genome.ts (M7 module not yet built).');
125
+ const loadLearnCmd = lazyCmd(() => import('./genome.js'), (m) => m.cmdLearn, 'learn command requires src/cli/genome.ts (M7 module not yet built).');
126
+ const loadGenomeCmd = lazyCmd(() => import('./genome.js'), (m) => m.cmdGenome, 'genome command requires src/cli/genome.ts (M7 module not yet built).');
127
+ // ─── M9 command loader ───────────────────────────────────────────
128
+ const loadUpdateCmd = lazyCmd(() => import('./update.js'), (m) => m.cmdUpdate, 'update command requires src/cli/update.ts (M9 module not yet built).');
129
+ // ─── M12 command loaders ────────────────────────────────────────────
130
+ const loadSpecCmd = lazyCmd(() => import('./spec.js'), (m) => m.cmdSpec, 'spec command requires src/cli/spec.ts (M12 module not yet built).');
131
+ const loadSwarmCmd = lazyCmd(() => import('./swarm.js'), (m) => m.cmdSwarm, 'swarm command requires src/cli/swarm.ts (M12 module not yet built).');
132
+ const loadSwarmsCmd = lazyCmd(() => import('./swarm.js'), (m) => m.cmdSwarms, 'swarms command requires src/cli/swarm.ts (M12 module not yet built).');
133
+ // ─── M13 command loader ────────────────────────────────────────────
134
+ const loadTuiCmd = lazyCmd(() => import('./tui.js'), (m) => m.cmdTui, 'tui command requires src/cli/tui.ts (M13 module not yet built).');
135
+ // ─── M14 command loader ────────────────────────────────────────────
136
+ const loadServeCmd = lazyCmd(() => import('./serve.js'), (m) => m.cmdServe, 'serve command requires src/cli/serve.ts (M14 module not yet built).');
137
+ const loadModelsCmd = lazyCmd(() => import('./models.js'), (m) => m.cmdModels, 'models command requires src/cli/models.ts (M15 module not yet built).');
138
+ // ─── M18 command loaders ───────────────────────────────────────────
139
+ const loadGhCmd = lazyCmd(() => import('./gh.js'), (m) => m.cmdGh, 'gh command requires src/cli/gh.ts (M18 module not yet built).');
140
+ const loadVercelCmd = lazyCmd(() => import('./vercel.js'), (m) => m.cmdVercel, 'vercel command requires src/cli/vercel.ts (M18 module not yet built).');
141
+ const loadWireCmd = lazyCmd(() => import('./wire.js'), (m) => m.cmdWire, 'wire command requires src/cli/wire.ts (M18 module not yet built).');
142
+ const loadNotifyCmd = lazyCmd(() => import('./notify.js'), (m) => m.cmdNotify, 'notify command requires src/cli/notify.ts (M18 module not yet built).');
143
+ // ─── M19 command loader ────────────────────────────────────────────
144
+ const loadTelemetryCmd = lazyCmd(() => import('./telemetry.js'), (m) => m.cmdTelemetry, 'telemetry command requires src/cli/telemetry.ts (M19 module not yet built).');
145
+ // ─── M21 command loaders ────────────────────────────────────────────
146
+ const loadSandboxCmd = lazyCmd(() => import('./sandbox.js'), (m) => m.cmdSandbox, 'sandbox command requires src/cli/sandbox.ts (M21 module not yet built).');
147
+ // H6 (PART A): the `ashlr audit` viewer moved to its own ./audit.ts module
148
+ // (read-only; adds --action/--result/--since filters). Re-pointed here from the
149
+ // old sandbox.ts#cmdAudit, matching the reflect/health/seams/verify-safety
150
+ // loaders — see docs/contracts/CONTRACT-H6.md §A.3.
151
+ const loadAuditCmd = lazyCmd(() => import('./audit.js'), (m) => m.cmdAudit, 'audit command requires src/cli/audit.ts (H6 module not yet built).');
152
+ const loadEnrollCmd = lazyCmd(() => import('./sandbox.js'), (m) => m.cmdEnroll, 'enroll command requires src/cli/sandbox.ts (M21 module not yet built).');
153
+ // ─── M22 command loaders ────────────────────────────────────────────
154
+ const loadBacklogCmd = lazyCmd(() => import('./backlog.js'), (m) => m.cmdBacklog, 'backlog command requires src/cli/backlog.ts (M22 module not yet built).');
155
+ // ─── M23 command loaders ────────────────────────────────────────────
156
+ const loadInboxCmd = lazyCmd(() => import('./inbox.js'), (m) => m.cmdInbox, 'inbox command requires src/cli/inbox.ts (M23 module not yet built).');
157
+ // ─── M24 command loaders ────────────────────────────────────────────
158
+ const loadDaemonCmd = lazyCmd(() => import('./daemon.js'), (m) => m.cmdDaemon, 'daemon command requires src/cli/daemon.ts (M24 module not yet built).');
159
+ // ─── M25 command loaders ────────────────────────────────────────────
160
+ const loadAskCmd = lazyCmd(() => import('./ask.js'), (m) => m.cmdAsk, 'ask command requires src/cli/ask.ts (M25 module not yet built).');
161
+ const loadKnowledgeCmd = lazyCmd(() => import('./knowledge.js'), (m) => m.cmdKnowledge, 'knowledge command requires src/cli/knowledge.ts (M25 module not yet built).');
162
+ // ─── M26 command loaders ────────────────────────────────────────────
163
+ const loadReflectCmd = lazyCmd(() => import('./reflect.js'), (m) => m.cmdReflect, 'reflect command requires src/cli/reflect.ts (M26 module not yet built).');
164
+ // ─── M27 command loaders ────────────────────────────────────────────
165
+ const loadHealthCmd = lazyCmd(() => import('./health.js'), (m) => m.cmdHealth, 'health command requires src/cli/health.ts (M27 module not yet built).');
166
+ // ─── M28 command loaders ────────────────────────────────────────────
167
+ const loadGoalsCmd = lazyCmd(() => import('./goals.js'), (m) => m.cmdGoals, 'goals command requires src/cli/goals.ts (M28 module not yet built).');
168
+ // ─── M29 command loaders ────────────────────────────────────────────
169
+ const loadDigestCmd = lazyCmd(() => import('./digest.js'), (m) => m.cmdDigest, 'digest command requires src/cli/digest.ts (M29 module not yet built).');
170
+ // ─── M30 command loaders ────────────────────────────────────────────
171
+ const loadSeamsCmd = lazyCmd(() => import('./seams.js'), (m) => m.cmdSeams, 'seams command requires src/cli/seams.ts (M30 module not yet built).');
172
+ // ─── M31 command loaders ──────────────────────────────────────────────────────────────
173
+ const loadOrientCmd = lazyCmd(() => import('./orient.js'), (m) => m.cmdOrient, 'orient command requires src/cli/orient.ts (M31 module not yet built).');
174
+ const loadDocsCmd = lazyCmd(() => import('./help.js'), (m) => m.cmdDocs, 'docs command requires src/cli/help.ts (M31 module not yet built).');
175
+ const loadCompletionsCmd = lazyCmd(() => import('./completions.js'), (m) => m.cmdCompletions, 'completions command requires src/cli/completions.ts (M31 module not yet built).');
176
+ // ─── M33 command loaders ──────────────────────────────────────────────────────────────
177
+ const loadPluginsCmd = lazyCmd(() => import('./plugins.js'), (m) => m.cmdPlugins, 'plugins command requires src/cli/plugins.ts (M33 module not yet built).');
178
+ const loadXCmd = lazyCmd(() => import('./plugins.js'), (m) => m.cmdX, 'x command requires src/cli/plugins.ts (M33 module not yet built).');
179
+ // ─── H4 command loaders ─────────────────────────────────────────────
180
+ const loadVerifySafetyCmd = lazyCmd(() => import('./verify-safety.js'), (m) => m.cmdVerifySafety, 'verify-safety command requires src/cli/verify-safety.ts (H4 module not yet built).');
181
+ // ─── H7 command loaders ─────────────────────────────────────────────
182
+ // preflight + onboard, matching the reflect/health/seams/verify-safety/audit
183
+ // loaders — see docs/contracts/CONTRACT-H7.md.
184
+ const loadPreflightCmd = lazyCmd(() => import('./preflight.js'), (m) => m.cmdPreflight, 'preflight command requires src/cli/preflight.ts (H7 module not yet built).');
185
+ const loadOnboardCmd = lazyCmd(() => import('./onboard.js'), (m) => m.cmdOnboard, 'onboard command requires src/cli/onboard.ts (H7 module not yet built).');
186
+ // ─── H8 command loader ──────────────────────────────────────────────
187
+ // `ashlr demo` — a watchable, reproducible run of the FULL autonomous chain on a
188
+ // DISPOSABLE repo in an ISOLATED tmp context; proposal-only; auto-cleans. The
189
+ // ONE new runtime surface in v2.1 (see docs/contracts/CONTRACT-H8.md).
190
+ const loadDemoCmd = lazyCmd(() => import('./demo.js'), (m) => m.cmdDemo, 'demo command requires src/cli/demo.ts (H8 module not yet built).');
191
+ let _githubStatus = undefined;
192
+ let _vercelStatus = undefined;
193
+ let _getIdentity = undefined;
194
+ async function tryGithubStatus() {
195
+ if (_githubStatus === undefined) {
196
+ try {
197
+ const mod = (await import('../core/integrations/github.js'));
198
+ _githubStatus = mod.githubStatus;
199
+ }
200
+ catch {
201
+ _githubStatus = null;
202
+ }
203
+ }
204
+ return _githubStatus ?? null;
205
+ }
206
+ async function tryVercelStatus() {
207
+ if (_vercelStatus === undefined) {
208
+ try {
209
+ const mod = (await import('../core/integrations/vercel.js'));
210
+ _vercelStatus = mod.vercelStatus;
211
+ }
212
+ catch {
213
+ _vercelStatus = null;
214
+ }
215
+ }
216
+ return _vercelStatus ?? null;
217
+ }
218
+ async function tryGetIdentity() {
219
+ if (_getIdentity === undefined) {
220
+ try {
221
+ const mod = (await import('../core/integrations/identity.js'));
222
+ _getIdentity = mod.getIdentity;
223
+ }
224
+ catch {
225
+ _getIdentity = null;
226
+ }
227
+ }
228
+ return _getIdentity ?? null;
229
+ }
230
+ // ─── ANSI helpers ──────────────────────────────────────────────────────────────
231
+ // index.ts colorizes unconditionally (output is always color-coded regardless
232
+ // of TTY), so we bind the shared colorizers with tty=true.
233
+ const { bold, dim, red, green, yellow, cyan, gray, magenta } = makeColors(true);
234
+ // ─── Utility ──────────────────────────────────────────────────────────────────
235
+ function die(msg, code = 1) {
236
+ console.error(red('error: ') + msg);
237
+ process.exit(code);
238
+ }
239
+ /** Ensure the index exists; auto-build if missing. Returns the index. */
240
+ function requireIndex(cfg) {
241
+ let idx = loadIndex();
242
+ if (!idx) {
243
+ // Informational chatter goes to stderr so stdout stays clean for capture
244
+ // (e.g. `cd $(ashlr go foo --cd)`).
245
+ console.error(dim('No index found — building now…'));
246
+ idx = buildIndex(cfg);
247
+ writeIndex(idx);
248
+ }
249
+ return idx;
250
+ }
251
+ /** Score an item against a query string (higher = better match). */
252
+ function scoreItem(item, query) {
253
+ const q = query.toLowerCase();
254
+ const name = item.name.toLowerCase();
255
+ const cat = (item.category ?? '').toLowerCase();
256
+ const desc = (item.description ?? '').toLowerCase();
257
+ // Exact name match
258
+ if (name === q)
259
+ return 100;
260
+ // Name starts with query
261
+ if (name.startsWith(q))
262
+ return 80;
263
+ // Name contains query
264
+ if (name.includes(q))
265
+ return 60;
266
+ // Category + name
267
+ if (cat.includes(q))
268
+ return 40;
269
+ // Description
270
+ if (desc.includes(q))
271
+ return 20;
272
+ return 0;
273
+ }
274
+ /** Filter and rank items by query. Returns sorted descending by score. */
275
+ function filterItems(items, query) {
276
+ if (!query.trim())
277
+ return items;
278
+ return items
279
+ .map(item => ({ item, score: scoreItem(item, query) }))
280
+ .filter(({ score }) => score > 0)
281
+ .sort((a, b) => b.score - a.score)
282
+ .map(({ item }) => item);
283
+ }
284
+ /** Days since a date string. */
285
+ function daysSince(iso) {
286
+ return (Date.now() - new Date(iso).getTime()) / (1000 * 60 * 60 * 24);
287
+ }
288
+ /** Format a short relative time. */
289
+ function relativeTime(iso) {
290
+ if (!iso)
291
+ return gray('—');
292
+ const days = daysSince(iso);
293
+ if (days < 1)
294
+ return green('today');
295
+ if (days < 2)
296
+ return green('yesterday');
297
+ if (days < 7)
298
+ return green(`${Math.floor(days)}d ago`);
299
+ if (days < 30)
300
+ return yellow(`${Math.floor(days)}d ago`);
301
+ if (days < 365)
302
+ return `${Math.floor(days / 30)}mo ago`;
303
+ return red(`${Math.floor(days / 365)}y ago`);
304
+ }
305
+ /**
306
+ * Config key segments that would let `config set` walk into an object's
307
+ * prototype chain (prototype pollution). Any dot-path containing one of these
308
+ * is rejected before we walk/assign, since the result is persisted to
309
+ * ~/.ashlr/config.json and re-applied via deepMerge on the next load.
310
+ */
311
+ const DANGEROUS_KEY_SEGMENTS = new Set(['__proto__', 'prototype', 'constructor']);
312
+ /** Reject a config dot-path whose any segment is a prototype-polluting prop. */
313
+ function assertSafeConfigKey(key) {
314
+ for (const segment of key.split('.')) {
315
+ if (DANGEROUS_KEY_SEGMENTS.has(segment)) {
316
+ die(`Illegal config key segment: "${segment}"`, 2);
317
+ }
318
+ }
319
+ }
320
+ /** Get a nested config value by dot-path key. */
321
+ function getConfigValue(cfg, key) {
322
+ const parts = key.split('.');
323
+ let val = cfg;
324
+ for (const part of parts) {
325
+ if (val === null || typeof val !== 'object')
326
+ return undefined;
327
+ val = val[part];
328
+ }
329
+ return val;
330
+ }
331
+ /**
332
+ * True when a config dot-path leaf looks sensitive (token/key/secret/etc).
333
+ * Used to redact values in `config get`/`config set` output so a manually
334
+ * stashed credential never lands in terminal scrollback or shell history.
335
+ */
336
+ const SENSITIVE_KEY_RE = /token|key|secret|password|passwd|auth|credential|api[_-]?key/i;
337
+ function isSensitiveKey(key) {
338
+ const leaf = key.split('.').pop() ?? key;
339
+ return SENSITIVE_KEY_RE.test(leaf);
340
+ }
341
+ /**
342
+ * Assign `value` to a dot-path leaf in `cfg`, creating intermediate objects as
343
+ * needed. Caller MUST run assertSafeConfigKey(key) first (prototype-pollution
344
+ * guard) — setByPath itself does not re-check.
345
+ */
346
+ function setByPath(cfg, key, value) {
347
+ const parts = key.split('.');
348
+ let obj = cfg;
349
+ for (let i = 0; i < parts.length - 1; i++) {
350
+ const hasOwn = Object.prototype.hasOwnProperty.call(obj, parts[i]);
351
+ const next = hasOwn ? obj[parts[i]] : undefined;
352
+ if (!hasOwn || next === null || typeof next !== 'object') {
353
+ obj[parts[i]] = {};
354
+ }
355
+ obj = obj[parts[i]];
356
+ }
357
+ obj[parts[parts.length - 1]] = value;
358
+ }
359
+ /** Set a nested config value by dot-path key (mutates cfg). */
360
+ function setConfigValue(cfg, key, rawValue) {
361
+ assertSafeConfigKey(key);
362
+ // Coerce to number or boolean when applicable
363
+ let value = rawValue;
364
+ if (rawValue === 'true')
365
+ value = true;
366
+ else if (rawValue === 'false')
367
+ value = false;
368
+ else {
369
+ const num = Number(rawValue);
370
+ if (!isNaN(num) && rawValue.trim() !== '')
371
+ value = num;
372
+ }
373
+ setByPath(cfg, key, value);
374
+ }
375
+ // ─── Command: index ───────────────────────────────────────────────────────────
376
+ function cmdIndex(args) {
377
+ const refresh = args.includes('--refresh');
378
+ const cfg = loadConfig();
379
+ let idx = loadIndex();
380
+ if (!idx || refresh) {
381
+ if (refresh && idx) {
382
+ console.error(dim('Refreshing index…'));
383
+ }
384
+ else {
385
+ console.error(dim('Building index…'));
386
+ }
387
+ idx = buildIndex(cfg);
388
+ writeIndex(idx);
389
+ }
390
+ const { items } = idx;
391
+ // Count by category
392
+ const byCat = new Map();
393
+ for (const item of items) {
394
+ const cat = item.category ?? '(uncategorized)';
395
+ byCat.set(cat, (byCat.get(cat) ?? 0) + 1);
396
+ }
397
+ const sortedCats = [...byCat.entries()].sort((a, b) => b[1] - a[1]);
398
+ const maxCat = Math.max(...sortedCats.map(([c]) => c.length), 12);
399
+ console.log('');
400
+ console.log(bold(' ashlr index') + gray(` — ${items.length} items, built ${new Date(idx.generatedAt).toLocaleString()}`));
401
+ console.log('');
402
+ console.log(` ${bold(pad('Category', maxCat))} ${bold(pad('Items', 6, 'right'))}`);
403
+ console.log(` ${'─'.repeat(maxCat)} ${'─'.repeat(6)}`);
404
+ for (const [cat, count] of sortedCats) {
405
+ console.log(` ${cyan(pad(cat, maxCat))} ${pad(String(count), 6, 'right')}`);
406
+ }
407
+ console.log('');
408
+ console.log(` ${bold('Total:')} ${items.length} items across ${byCat.size} categories`);
409
+ const repos = items.filter(i => i.kind === 'repo');
410
+ const active = repos.filter(i => i.active);
411
+ console.log(` ${dim(`${repos.length} repos (${active.length} active)`)}`);
412
+ console.log('');
413
+ }
414
+ // ─── Command: go ─────────────────────────────────────────────────────────────
415
+ async function cmdGo(args) {
416
+ const openFlag = args.includes('--open');
417
+ const cdFlag = args.includes('--cd');
418
+ const queryArgs = args.filter(a => !a.startsWith('--'));
419
+ const query = queryArgs.join(' ');
420
+ const cfg = loadConfig();
421
+ const idx = requireIndex(cfg);
422
+ const candidates = filterItems(idx.items, query);
423
+ let chosen = null;
424
+ if (candidates.length === 0 && query) {
425
+ die(`No items match "${query}".`);
426
+ }
427
+ else if (candidates.length === 1) {
428
+ chosen = candidates[0];
429
+ }
430
+ else {
431
+ // Let user pick (fzf or readline)
432
+ chosen = await pick(candidates.length > 0 ? candidates : idx.items);
433
+ }
434
+ if (!chosen) {
435
+ die('Nothing selected.', 1);
436
+ }
437
+ if (openFlag) {
438
+ openInEditor(chosen.path, cfg);
439
+ console.log(green('opened: ') + chosen.path);
440
+ }
441
+ else if (cdFlag) {
442
+ // Print path only — caller shell function can eval $(ashlr go ... --cd)
443
+ process.stdout.write(chosen.path + '\n');
444
+ }
445
+ else {
446
+ // Print a summary
447
+ printItemSummary(chosen);
448
+ }
449
+ }
450
+ function printItemSummary(item) {
451
+ console.log('');
452
+ console.log(` ${bold(item.name)} ${dim(item.kind)} ${item.category ? cyan(item.category) : ''}`);
453
+ console.log(` ${gray(item.path)}`);
454
+ if (item.description) {
455
+ console.log(` ${item.description}`);
456
+ }
457
+ if (item.git) {
458
+ const g = item.git;
459
+ const dirtyStr = g.dirty > 0 ? red(`${g.dirty} dirty`) : green('clean');
460
+ const syncStr = g.ahead > 0 || g.behind > 0
461
+ ? yellow(`↑${g.ahead} ↓${g.behind}`)
462
+ : green('in sync');
463
+ console.log(` git: ${cyan(g.branch)} ${dirtyStr} ${syncStr} last commit: ${relativeTime(g.lastCommit)}`);
464
+ }
465
+ if (item.linkTarget) {
466
+ console.log(` ${dim('→')} ${item.linkTarget}`);
467
+ }
468
+ console.log(` last modified: ${relativeTime(item.lastModified)}`);
469
+ console.log('');
470
+ }
471
+ // ─── Command: status ─────────────────────────────────────────────────────────
472
+ async function cmdStatus(_args) {
473
+ const cfg = loadConfig();
474
+ const idx = requireIndex(cfg);
475
+ const repos = idx.items.filter(i => i.kind === 'repo' && i.git);
476
+ const dirty = repos.filter(i => (i.git?.dirty ?? 0) > 0);
477
+ const offSync = repos.filter(i => !dirty.includes(i) && ((i.git?.ahead ?? 0) > 0 || (i.git?.behind ?? 0) > 0));
478
+ const stale = repos.filter(i => !dirty.includes(i) &&
479
+ !offSync.includes(i) &&
480
+ daysSince(i.lastModified) > cfg.staleDays);
481
+ const nameW = 30;
482
+ const branchW = 18;
483
+ const gitW = 16;
484
+ const timeW = 14;
485
+ function printRepoTable(items) {
486
+ if (items.length === 0) {
487
+ console.log(` ${dim('(none)')}`);
488
+ return;
489
+ }
490
+ console.log(` ${bold(pad('Repo', nameW))} ` +
491
+ `${bold(pad('Branch', branchW))} ` +
492
+ `${bold(pad('Git', gitW))} ` +
493
+ `${bold(pad('Last commit', timeW))}`);
494
+ console.log(` ${'─'.repeat(nameW)} ${'─'.repeat(branchW)} ${'─'.repeat(gitW)} ${'─'.repeat(timeW)}`);
495
+ for (const item of items) {
496
+ const g = item.git;
497
+ const dirtyStr = g.dirty > 0
498
+ ? red(`${g.dirty}`)
499
+ : '';
500
+ const syncStr = g.ahead > 0 || g.behind > 0
501
+ ? yellow(`↑${g.ahead} ↓${g.behind}`)
502
+ : '';
503
+ const gitCol = [dirtyStr, syncStr].filter(Boolean).join(' ') || green('ok');
504
+ console.log(` ${pad(cyan(item.name), nameW)} ` +
505
+ `${pad(g.branch, branchW)} ` +
506
+ `${pad(gitCol, gitW)} ` +
507
+ `${relativeTime(g.lastCommit)}`);
508
+ }
509
+ }
510
+ console.log('');
511
+ console.log(bold(' ashlr status') + gray(` — ${repos.length} repos tracked`));
512
+ console.log('');
513
+ if (dirty.length > 0) {
514
+ console.log(bold(red(` Dirty (${dirty.length})`)));
515
+ printRepoTable(dirty);
516
+ console.log('');
517
+ }
518
+ if (offSync.length > 0) {
519
+ console.log(bold(yellow(` Off-sync (${offSync.length})`)));
520
+ printRepoTable(offSync);
521
+ console.log('');
522
+ }
523
+ if (stale.length > 0) {
524
+ console.log(bold(gray(` Stale — > ${cfg.staleDays}d (${stale.length})`)));
525
+ printRepoTable(stale);
526
+ console.log('');
527
+ }
528
+ const cleanActive = repos.filter(i => !dirty.includes(i) && !offSync.includes(i) && !stale.includes(i));
529
+ console.log(` ${green(`${cleanActive.length} clean`)} ` +
530
+ `${red(`${dirty.length} dirty`)} ` +
531
+ `${yellow(`${offSync.length} off-sync`)} ` +
532
+ `${gray(`${stale.length} stale`)}`);
533
+ console.log('');
534
+ // ── M3 Ecosystem summary (graceful degradation if modules not yet built) ──
535
+ const [getToolsRegistryFn, discoverMcpServersFn] = await Promise.all([
536
+ tryGetToolsRegistry(),
537
+ tryDiscoverMcpServers(),
538
+ ]);
539
+ const ecosystemParts = [];
540
+ if (getToolsRegistryFn) {
541
+ try {
542
+ const reg = getToolsRegistryFn();
543
+ const installedNames = reg.tools
544
+ .filter(t => t.installed)
545
+ .map(t => t.id)
546
+ .slice(0, 5); // show up to 5 names; remainder implied by count
547
+ const moreCount = reg.installedCount - installedNames.length;
548
+ const nameList = installedNames.join(', ') + (moreCount > 0 ? `, +${moreCount}` : '');
549
+ ecosystemParts.push(`${bold('Tools:')} ${cyan(`${reg.installedCount}/${reg.tools.length} installed`)}` +
550
+ (installedNames.length > 0 ? ` ${dim('—')} ${gray(nameList)}` : ''));
551
+ }
552
+ catch {
553
+ // silently skip on error
554
+ }
555
+ }
556
+ if (discoverMcpServersFn) {
557
+ try {
558
+ const mcpReg = discoverMcpServersFn();
559
+ ecosystemParts.push(`${bold('MCP servers:')} ${cyan(`${mcpReg.servers.length} discovered`)}`);
560
+ }
561
+ catch {
562
+ // silently skip on error
563
+ }
564
+ }
565
+ if (ecosystemParts.length > 0) {
566
+ console.log(` ${ecosystemParts.join(` ${dim('·')} `)}`);
567
+ console.log('');
568
+ }
569
+ // ── M5 Activity summary (best-effort; silently skipped on error) ──────────
570
+ try {
571
+ const buildRollupFn = await tryBuildRollup();
572
+ if (buildRollupFn) {
573
+ const rollup = buildRollupFn('7d', cfg);
574
+ const t = rollup.totals;
575
+ const tokK = ((t.tokensIn + t.tokensOut) / 1000).toFixed(1);
576
+ const cost = t.estCostUsd.toFixed(2);
577
+ const BUDGET_SUFFIX = {
578
+ over: ` ${red('● over budget')}`,
579
+ warn: ` ${yellow('● near cap')}`,
580
+ };
581
+ const budgetSuffix = BUDGET_SUFFIX[rollup.budget.level] ?? '';
582
+ console.log(` ${bold('Activity (7d):')} ${cyan(`${t.sessions} sessions`)} ${dim('·')} ` +
583
+ `${cyan(`${tokK}k tokens`)} ${dim('·')} ` +
584
+ `${cyan(`$${cost}`)} ${dim('·')} ` +
585
+ `${cyan(`${t.commits} commits`)}` +
586
+ budgetSuffix);
587
+ console.log('');
588
+ }
589
+ }
590
+ catch {
591
+ // silently omit — never break status
592
+ }
593
+ // ── M7 Genome summary (best-effort; silently omitted on error) ────────────
594
+ try {
595
+ const genomeMod = await import('../core/genome/store.js');
596
+ const health = genomeMod.genomeHealth(cfg);
597
+ console.log(` ${bold('Memory:')} ${cyan(`${health.totalEntries} genome entries`)} across ` +
598
+ `${cyan(`${health.projects} project${health.projects !== 1 ? 's' : ''}`)}` +
599
+ (health.embeddingsAvailable ? ` ${dim('·')} ${gray('embeddings available')}` : ''));
600
+ console.log('');
601
+ }
602
+ catch {
603
+ // silently omit — genome may not be built yet; never break status
604
+ }
605
+ // ── M18 GitHub / Vercel / Identity one-liners (best-effort; never break status) ──
606
+ // All reads are bounded (reuse installed CLIs; they own auth) and silently
607
+ // omitted when not applicable (not a gh repo, no vercel link, not logged in).
608
+ const cwd = process.cwd();
609
+ // GitHub: only surface when cwd is a gh repo
610
+ try {
611
+ const ghStatusFn = await tryGithubStatus();
612
+ if (ghStatusFn) {
613
+ const gs = ghStatusFn(cwd);
614
+ if (gs.isRepo) {
615
+ const ciStr = gs.ci === 'passing'
616
+ ? green('CI passing')
617
+ : gs.ci === 'failing'
618
+ ? red('CI failing')
619
+ : gs.ci === 'pending'
620
+ ? yellow('CI pending')
621
+ : dim('no CI');
622
+ const repoLabel = gs.repo ? gray(` (${gs.repo})`) : '';
623
+ console.log(` ${bold('GitHub:')} ${cyan(`${gs.openPrs} open PR${gs.openPrs !== 1 ? 's' : ''}`)}` +
624
+ ` ${dim('·')} ${cyan(`${gs.openIssues} open issue${gs.openIssues !== 1 ? 's' : ''}`)}` +
625
+ ` ${dim('·')} ${ciStr}${repoLabel}`);
626
+ console.log('');
627
+ }
628
+ }
629
+ }
630
+ catch {
631
+ // silently omit — never break status
632
+ }
633
+ // Vercel: only surface when a project is linked
634
+ try {
635
+ const vcStatusFn = await tryVercelStatus();
636
+ if (vcStatusFn) {
637
+ const vs = vcStatusFn(cwd);
638
+ if (vs.linked) {
639
+ const stateStr = vs.latestState
640
+ ? (vs.latestState === 'READY' ? green(vs.latestState) : yellow(vs.latestState))
641
+ : dim('unknown');
642
+ const urlStr = vs.url ? ` ${dim('·')} ${cyan(vs.url)}` : '';
643
+ console.log(` ${bold('Vercel:')} ${stateStr}${urlStr}`);
644
+ console.log('');
645
+ }
646
+ }
647
+ }
648
+ catch {
649
+ // silently omit — never break status
650
+ }
651
+ // Identity: only surface when phantom is logged in
652
+ try {
653
+ const identityFn = await tryGetIdentity();
654
+ if (identityFn) {
655
+ const id = identityFn();
656
+ if (id.loggedIn && id.user) {
657
+ const tierStr = id.tier ? ` ${dim('·')} tier ${cyan(id.tier)}` : '';
658
+ const teamStr = id.team ? ` ${dim('·')} team ${cyan(id.team)}` : '';
659
+ console.log(` ${bold('You:')} ${cyan(id.user)}${tierStr}${teamStr}`);
660
+ console.log('');
661
+ }
662
+ }
663
+ }
664
+ catch {
665
+ // silently omit — never break status
666
+ }
667
+ }
668
+ // ─── Command: ls ─────────────────────────────────────────────────────────────
669
+ function cmdLs(args) {
670
+ const catFilter = args.filter(a => !a.startsWith('--'))[0] ?? null;
671
+ const cfg = loadConfig();
672
+ const idx = requireIndex(cfg);
673
+ let items = idx.items;
674
+ if (catFilter) {
675
+ const q = catFilter.toLowerCase();
676
+ items = items.filter(i => (i.category ?? '').toLowerCase() === q);
677
+ if (items.length === 0) {
678
+ // Partial match fallback
679
+ items = idx.items.filter(i => (i.category ?? '').toLowerCase().includes(q));
680
+ }
681
+ if (items.length === 0) {
682
+ die(`No items found in category "${catFilter}".`);
683
+ }
684
+ }
685
+ const nameW = Math.min(40, Math.max(10, ...items.map(i => i.name.length)));
686
+ const kindW = 10;
687
+ const catW = 16;
688
+ console.log('');
689
+ console.log(` ${bold(pad('Name', nameW))} ${bold(pad('Kind', kindW))} ${bold(pad('Category', catW))} ${bold('Description')}`);
690
+ console.log(` ${'─'.repeat(nameW)} ${'─'.repeat(kindW)} ${'─'.repeat(catW)} ${'─'.repeat(30)}`);
691
+ // Sort: repos first, then by category, then by name
692
+ const sorted = [...items].sort((a, b) => {
693
+ if (a.kind === 'repo' && b.kind !== 'repo')
694
+ return -1;
695
+ if (a.kind !== 'repo' && b.kind === 'repo')
696
+ return 1;
697
+ const catCmp = (a.category ?? '').localeCompare(b.category ?? '');
698
+ if (catCmp !== 0)
699
+ return catCmp;
700
+ return a.name.localeCompare(b.name);
701
+ });
702
+ for (const item of sorted) {
703
+ const kindColor = item.kind === 'repo' ? cyan : item.kind === 'symlink' ? magenta : gray;
704
+ const activeMarker = item.kind === 'repo' && item.active ? green('●') : dim('○');
705
+ const desc = item.description ? dim(item.description.slice(0, 50)) : '';
706
+ console.log(` ${activeMarker} ${pad(item.name, nameW - 2)} ` +
707
+ `${pad(kindColor(item.kind), kindW)} ` +
708
+ `${pad(item.category ?? dim('—'), catW)} ` +
709
+ desc);
710
+ }
711
+ console.log('');
712
+ console.log(` ${dim(`${sorted.length} items${catFilter ? ` in "${catFilter}"` : ''}`)}`);
713
+ console.log('');
714
+ }
715
+ // ─── Command: open ────────────────────────────────────────────────────────────
716
+ async function cmdOpen(args) {
717
+ const query = args.filter(a => !a.startsWith('--')).join(' ');
718
+ const cfg = loadConfig();
719
+ const idx = requireIndex(cfg);
720
+ let chosen = null;
721
+ if (query) {
722
+ const candidates = filterItems(idx.items, query);
723
+ if (candidates.length === 0) {
724
+ die(`No items match "${query}".`);
725
+ }
726
+ // Take top match directly if score is unambiguous, else pick
727
+ chosen = candidates[0];
728
+ }
729
+ else {
730
+ chosen = await pick(idx.items);
731
+ }
732
+ if (!chosen) {
733
+ die('Nothing selected.', 1);
734
+ }
735
+ openInEditor(chosen.path, cfg);
736
+ console.log(green('opened: ') + chosen.path);
737
+ }
738
+ // ─── Command: tidy ────────────────────────────────────────────────────────────
739
+ function cmdTidy(args) {
740
+ const apply = args.includes('--apply');
741
+ const json = args.includes('--json');
742
+ const cfg = loadConfig();
743
+ const plan = planTidy(cfg);
744
+ // ── Machine output ──────────────────────────────────────────────────────
745
+ // With --json, emit ONLY the TidyPlan as JSON on stdout with zero color or
746
+ // log noise. The Raycast "Tidy Desktop" command relies on this contract.
747
+ // Supports `--json --apply` to apply first, then emit the post-apply plan.
748
+ if (json) {
749
+ if (apply) {
750
+ applyTidy(plan);
751
+ // Recompute so the emitted plan reflects the new (tidied) state.
752
+ const after = planTidy(cfg);
753
+ process.stdout.write(JSON.stringify(after));
754
+ }
755
+ else {
756
+ process.stdout.write(JSON.stringify(plan));
757
+ }
758
+ return;
759
+ }
760
+ const fromW = 50;
761
+ const toW = 50;
762
+ if (plan.moves.length === 0 && plan.skipped.length === 0) {
763
+ console.log('');
764
+ console.log(` ${green('Nothing to tidy.')} Desktop is already organized.`);
765
+ console.log('');
766
+ return;
767
+ }
768
+ if (plan.moves.length > 0) {
769
+ console.log('');
770
+ console.log(bold(apply ? ' Applying tidy moves:' : ' Tidy dry-run (use --apply to execute):'));
771
+ console.log('');
772
+ console.log(` ${bold(pad('From', fromW))} ${bold(pad('To', toW))} ${bold('Rule')}`);
773
+ console.log(` ${'─'.repeat(fromW)} ${'─'.repeat(toW)} ${'─'.repeat(20)}`);
774
+ for (const move of plan.moves) {
775
+ // Show relative-ish paths
776
+ const fromShort = move.from.replace(process.env['HOME'] ?? '', '~');
777
+ const toShort = move.to.replace(process.env['HOME'] ?? '', '~');
778
+ console.log(` ${pad(fromShort, fromW)} ${cyan(pad(toShort, toW))} ${dim(move.rule)}`);
779
+ }
780
+ console.log('');
781
+ }
782
+ if (plan.skipped.length > 0) {
783
+ console.log(` ${dim(`Skipped ${plan.skipped.length} path(s):`)}`);
784
+ for (const s of plan.skipped) {
785
+ const shortPath = s.path.replace(process.env['HOME'] ?? '', '~');
786
+ console.log(` ${dim(pad(shortPath, fromW))} ${gray(s.reason)}`);
787
+ }
788
+ console.log('');
789
+ }
790
+ if (apply) {
791
+ applyTidy(plan);
792
+ console.log(green(` Applied ${plan.moves.length} move(s).`));
793
+ console.log('');
794
+ }
795
+ else if (plan.moves.length > 0) {
796
+ console.log(` ${yellow(`${plan.moves.length} move(s) planned.`)} Run with ${bold('--apply')} to execute.`);
797
+ console.log('');
798
+ }
799
+ }
800
+ // ─── Command: config ─────────────────────────────────────────────────────────
801
+ function cmdConfig(args) {
802
+ const subCmd = args[0];
803
+ const cfg = loadConfig();
804
+ if (!subCmd || subCmd === 'path') {
805
+ console.log(CONFIG_PATH);
806
+ return;
807
+ }
808
+ if (subCmd === 'get') {
809
+ const key = args[1];
810
+ if (!key)
811
+ die('Usage: ashlr config get <key>', 2);
812
+ const val = getConfigValue(cfg, key);
813
+ if (val === undefined) {
814
+ die(`Key "${key}" not found in config.`);
815
+ }
816
+ if (isSensitiveKey(key)) {
817
+ console.log('<redacted>');
818
+ }
819
+ else {
820
+ console.log(typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val));
821
+ }
822
+ return;
823
+ }
824
+ if (subCmd === 'set') {
825
+ const key = args[1];
826
+ // --json flag: the token immediately after --json is the JSON value
827
+ const jsonFlagIdx = args.indexOf('--json');
828
+ const hasJsonFlag = jsonFlagIdx !== -1;
829
+ const jsonValue = hasJsonFlag ? args[jsonFlagIdx + 1] : undefined;
830
+ // Plain scalar value: args[2] when --json is not present
831
+ const rawValue = hasJsonFlag ? undefined : args[2];
832
+ if (!key)
833
+ die('Usage: ashlr config set <key> <value>', 2);
834
+ assertSafeConfigKey(key);
835
+ const topKey = key.split('.')[0];
836
+ // Keys that must never be overwritten with a coerced scalar string.
837
+ const STRUCTURED_KEYS = new Set([
838
+ 'keepers', 'tidyRules', 'roots', 'categories', 'models', 'telemetry', 'tools', 'phantom',
839
+ ]);
840
+ const existing = getConfigValue(cfg, key);
841
+ const isStructured = Array.isArray(existing) || (existing !== null && typeof existing === 'object');
842
+ if (STRUCTURED_KEYS.has(topKey) && isStructured) {
843
+ // Structured path — require --json with a value
844
+ if (!hasJsonFlag || jsonValue === undefined) {
845
+ die(`Key "${key}" holds a structured value (array/object) and cannot be ` +
846
+ `set with a plain scalar.\n` +
847
+ `Use --json to set it:\n` +
848
+ ` ashlr config set ${key} --json '${JSON.stringify(existing)}'`, 2);
849
+ }
850
+ let parsed;
851
+ try {
852
+ parsed = JSON.parse(jsonValue);
853
+ }
854
+ catch {
855
+ die(`Invalid JSON for "${key}": ${jsonValue}`, 2);
856
+ }
857
+ // Walk to the parent object and set the leaf key
858
+ setByPath(cfg, key, parsed);
859
+ saveConfig(cfg);
860
+ const shown = isSensitiveKey(key) ? '<redacted>' : JSON.stringify(parsed);
861
+ console.log(`${green('set')} ${key} = ${shown}`);
862
+ return;
863
+ }
864
+ // Scalar path: --json is allowed but not required
865
+ if (hasJsonFlag && jsonValue !== undefined) {
866
+ let parsed;
867
+ try {
868
+ parsed = JSON.parse(jsonValue);
869
+ }
870
+ catch {
871
+ die(`Invalid JSON for "${key}": ${jsonValue}`, 2);
872
+ }
873
+ setByPath(cfg, key, parsed);
874
+ saveConfig(cfg);
875
+ const shown = isSensitiveKey(key) ? '<redacted>' : JSON.stringify(parsed);
876
+ console.log(`${green('set')} ${key} = ${shown}`);
877
+ return;
878
+ }
879
+ if (rawValue === undefined)
880
+ die(`Usage: ashlr config set <key> <value>`, 2);
881
+ setConfigValue(cfg, key, rawValue);
882
+ saveConfig(cfg);
883
+ const shown = isSensitiveKey(key) ? '<redacted>' : rawValue;
884
+ console.log(`${green('set')} ${key} = ${shown}`);
885
+ return;
886
+ }
887
+ // No sub-command: print a formatted summary
888
+ console.log('');
889
+ console.log(bold(' ashlr config') + gray(` — ${CONFIG_PATH}`));
890
+ console.log('');
891
+ console.log(` ${bold(pad('editor', 20))} ${cyan(cfg.editor)}`);
892
+ console.log(` ${bold(pad('staleDays', 20))} ${cfg.staleDays}`);
893
+ console.log(` ${bold(pad('roots', 20))} ${cfg.roots.map(r => r.replace(process.env['HOME'] ?? '', '~')).join(', ')}`);
894
+ console.log(` ${bold(pad('categories', 20))} ${Object.keys(cfg.categories).join(', ')}`);
895
+ console.log(` ${bold(pad('keepers', 20))} ${cfg.keepers.length} entries`);
896
+ console.log(` ${bold(pad('tidyRules', 20))} ${cfg.tidyRules.length} rules`);
897
+ console.log('');
898
+ }
899
+ // ─── Command: help ────────────────────────────────────────────────────────────
900
+ /**
901
+ * M32: help moved to src/cli/help.ts (topic-grouped, searchable, --all).
902
+ * This thin wrapper keeps `ashlr help` working even if the module is missing.
903
+ */
904
+ async function cmdHelp(rest = []) {
905
+ try {
906
+ const mod = await import('./help.js');
907
+ await mod.cmdHelp(rest);
908
+ }
909
+ catch {
910
+ console.log('ashlr — local-first command center. Run a command or see the README.');
911
+ }
912
+ }
913
+ // ─── Top-level dispatch ───────────────────────────────────────────────────────
914
+ async function main() {
915
+ const argv = process.argv.slice(2);
916
+ // ── Internal background-worker entry point ──────────────────────────────
917
+ // When `cmdSwarm --background` spawns a detached worker it re-invokes the
918
+ // CLI as: `ashlr --_worker swarm --resume <id> [...]`
919
+ // Strip the flag and route as if the user typed `ashlr swarm --resume <id>`.
920
+ const workerFlagIdx = argv.indexOf('--_worker');
921
+ if (workerFlagIdx !== -1) {
922
+ argv.splice(workerFlagIdx, 1); // remove --_worker in-place
923
+ }
924
+ const cmd = argv[0] ?? 'help';
925
+ const rest = argv.slice(1);
926
+ try {
927
+ switch (cmd) {
928
+ case 'index':
929
+ cmdIndex(rest);
930
+ break;
931
+ case 'go':
932
+ await cmdGo(rest);
933
+ break;
934
+ case 'status':
935
+ await cmdStatus(rest);
936
+ break;
937
+ case 'ls':
938
+ cmdLs(rest);
939
+ break;
940
+ case 'open':
941
+ await cmdOpen(rest);
942
+ break;
943
+ case 'tidy':
944
+ cmdTidy(rest);
945
+ break;
946
+ case 'config':
947
+ cmdConfig(rest);
948
+ break;
949
+ case 'doctor':
950
+ process.exitCode = await cmdDoctor(rest);
951
+ break;
952
+ case 'init':
953
+ process.exitCode = await cmdInit(rest);
954
+ break;
955
+ case 'mcp': {
956
+ const cmdMcp = await loadMcpCmd();
957
+ process.exitCode = await cmdMcp(rest);
958
+ break;
959
+ }
960
+ case 'run': {
961
+ // --no-memory is passed through to cmdRun as-is; run.ts parses it
962
+ // into RunOptions.noMemory for the orchestrator's genome injection gate.
963
+ const cmdRun = await loadRunCmd();
964
+ process.exitCode = await cmdRun(rest);
965
+ break;
966
+ }
967
+ case 'runs': {
968
+ const cmdRuns = await loadRunsCmd();
969
+ process.exitCode = await cmdRuns(rest);
970
+ break;
971
+ }
972
+ case 'pulse': {
973
+ const cmdPulse = await loadPulseCmd();
974
+ process.exitCode = await cmdPulse(rest);
975
+ break;
976
+ }
977
+ case 'new': {
978
+ const cmdNew = await loadNewCmd();
979
+ process.exitCode = await cmdNew(rest);
980
+ break;
981
+ }
982
+ case 'ship': {
983
+ const cmdShip = await loadShipCmd();
984
+ process.exitCode = await cmdShip(rest);
985
+ break;
986
+ }
987
+ case 'recall': {
988
+ const cmdRecall = await loadRecallCmd();
989
+ process.exitCode = await cmdRecall(rest);
990
+ break;
991
+ }
992
+ case 'learn': {
993
+ const cmdLearn = await loadLearnCmd();
994
+ process.exitCode = await cmdLearn(rest);
995
+ break;
996
+ }
997
+ case 'genome': {
998
+ const cmdGenome = await loadGenomeCmd();
999
+ process.exitCode = await cmdGenome(rest);
1000
+ break;
1001
+ }
1002
+ case 'update': {
1003
+ const cmdUpdate = await loadUpdateCmd();
1004
+ process.exitCode = await cmdUpdate(rest);
1005
+ break;
1006
+ }
1007
+ case 'spec': {
1008
+ const cmdSpec = await loadSpecCmd();
1009
+ process.exitCode = await cmdSpec(rest);
1010
+ break;
1011
+ }
1012
+ case 'swarm': {
1013
+ // Internal --_worker flag: a detached background process spawned by
1014
+ // cmdSwarm --background re-invokes the CLI with this flag to resume
1015
+ // and drive the swarm without a TTY. Route directly to cmdSwarm which
1016
+ // handles the --resume path (the worker argv is passed through as-is).
1017
+ const cmdSwarm = await loadSwarmCmd();
1018
+ process.exitCode = await cmdSwarm(rest);
1019
+ break;
1020
+ }
1021
+ case 'swarms': {
1022
+ const cmdSwarms = await loadSwarmsCmd();
1023
+ process.exitCode = await cmdSwarms(rest);
1024
+ break;
1025
+ }
1026
+ case 'tui':
1027
+ case 'dash': {
1028
+ const cmdTui = await loadTuiCmd();
1029
+ process.exitCode = await cmdTui(rest);
1030
+ break;
1031
+ }
1032
+ case 'serve': {
1033
+ const cmdServe = await loadServeCmd();
1034
+ process.exitCode = await cmdServe(rest);
1035
+ break;
1036
+ }
1037
+ case 'models': {
1038
+ const cmdModels = await loadModelsCmd();
1039
+ process.exitCode = await cmdModels(rest);
1040
+ break;
1041
+ }
1042
+ case 'gh': {
1043
+ const cmdGh = await loadGhCmd();
1044
+ process.exitCode = await cmdGh(rest);
1045
+ break;
1046
+ }
1047
+ case 'vercel': {
1048
+ const cmdVercel = await loadVercelCmd();
1049
+ process.exitCode = await cmdVercel(rest);
1050
+ break;
1051
+ }
1052
+ case 'wire': {
1053
+ const cmdWire = await loadWireCmd();
1054
+ process.exitCode = await cmdWire(rest);
1055
+ break;
1056
+ }
1057
+ case 'notify': {
1058
+ const cmdNotify = await loadNotifyCmd();
1059
+ process.exitCode = await cmdNotify(rest);
1060
+ break;
1061
+ }
1062
+ case 'telemetry': {
1063
+ const cmdTelemetry = await loadTelemetryCmd();
1064
+ process.exitCode = await cmdTelemetry(rest);
1065
+ break;
1066
+ }
1067
+ case 'sandbox': {
1068
+ const cmdSandbox = await loadSandboxCmd();
1069
+ process.exitCode = await cmdSandbox(rest);
1070
+ break;
1071
+ }
1072
+ case 'audit': {
1073
+ const cmdAudit = await loadAuditCmd();
1074
+ process.exitCode = await cmdAudit(rest);
1075
+ break;
1076
+ }
1077
+ case 'enroll': {
1078
+ const cmdEnroll = await loadEnrollCmd();
1079
+ process.exitCode = await cmdEnroll(rest);
1080
+ break;
1081
+ }
1082
+ case 'backlog': {
1083
+ const cmdBacklog = await loadBacklogCmd();
1084
+ process.exitCode = await cmdBacklog(rest);
1085
+ break;
1086
+ }
1087
+ case 'inbox': {
1088
+ const cmdInbox = await loadInboxCmd();
1089
+ process.exitCode = await cmdInbox(rest);
1090
+ break;
1091
+ }
1092
+ case 'daemon': {
1093
+ const cmdDaemon = await loadDaemonCmd();
1094
+ process.exitCode = await cmdDaemon(rest);
1095
+ break;
1096
+ }
1097
+ case 'ask': {
1098
+ const cmdAsk = await loadAskCmd();
1099
+ process.exitCode = await cmdAsk(rest);
1100
+ break;
1101
+ }
1102
+ case 'knowledge': {
1103
+ const cmdKnowledge = await loadKnowledgeCmd();
1104
+ process.exitCode = await cmdKnowledge(rest);
1105
+ break;
1106
+ }
1107
+ case 'reflect': {
1108
+ const cmdReflect = await loadReflectCmd();
1109
+ process.exitCode = await cmdReflect(rest);
1110
+ break;
1111
+ }
1112
+ case 'health': {
1113
+ const cmdHealth = await loadHealthCmd();
1114
+ process.exitCode = await cmdHealth(rest);
1115
+ break;
1116
+ }
1117
+ case 'goals': {
1118
+ const cmdGoals = await loadGoalsCmd();
1119
+ process.exitCode = await cmdGoals(rest);
1120
+ break;
1121
+ }
1122
+ case 'digest': {
1123
+ const cmdDigest = await loadDigestCmd();
1124
+ process.exitCode = await cmdDigest(rest);
1125
+ break;
1126
+ }
1127
+ case 'seams': {
1128
+ const cmdSeams = await loadSeamsCmd();
1129
+ process.exitCode = await cmdSeams(rest);
1130
+ break;
1131
+ }
1132
+ case 'verify-safety': {
1133
+ const cmdVerifySafety = await loadVerifySafetyCmd();
1134
+ process.exitCode = await cmdVerifySafety(rest);
1135
+ break;
1136
+ }
1137
+ case 'preflight': {
1138
+ const cmdPreflight = await loadPreflightCmd();
1139
+ process.exitCode = await cmdPreflight(rest);
1140
+ break;
1141
+ }
1142
+ case 'onboard': {
1143
+ const cmdOnboard = await loadOnboardCmd();
1144
+ process.exitCode = await cmdOnboard(rest);
1145
+ break;
1146
+ }
1147
+ case 'demo': {
1148
+ const cmdDemo = await loadDemoCmd();
1149
+ process.exitCode = await cmdDemo(rest);
1150
+ break;
1151
+ }
1152
+ case 'orient': {
1153
+ const cmdOrient = await loadOrientCmd();
1154
+ process.exitCode = await cmdOrient(rest);
1155
+ break;
1156
+ }
1157
+ case 'docs': {
1158
+ const cmdDocs = await loadDocsCmd();
1159
+ process.exitCode = await cmdDocs(rest);
1160
+ break;
1161
+ }
1162
+ case 'completions': {
1163
+ const cmdCompletions = await loadCompletionsCmd();
1164
+ process.exitCode = await cmdCompletions(rest);
1165
+ break;
1166
+ }
1167
+ case 'plugins': {
1168
+ const cmdPlugins = await loadPluginsCmd();
1169
+ process.exitCode = await cmdPlugins(rest);
1170
+ break;
1171
+ }
1172
+ case 'x': {
1173
+ const cmdX = await loadXCmd();
1174
+ process.exitCode = await cmdX(rest);
1175
+ break;
1176
+ }
1177
+ case 'help':
1178
+ case '--help':
1179
+ case '-h':
1180
+ await cmdHelp(rest);
1181
+ break;
1182
+ default: {
1183
+ console.error(red(`Unknown command: ${cmd}`));
1184
+ // M31: "did you mean" — best-effort, never blocks the error path.
1185
+ try {
1186
+ const { didYouMean } = await import('./completions.js');
1187
+ const suggestion = didYouMean(cmd);
1188
+ if (suggestion)
1189
+ console.error(`Did you mean ${cyan(`ashlr ${suggestion}`)}?`);
1190
+ }
1191
+ catch { /* suggestion is best-effort */ }
1192
+ console.error(dim('Run `ashlr help` for usage.'));
1193
+ process.exit(2);
1194
+ }
1195
+ }
1196
+ }
1197
+ catch (err) {
1198
+ const msg = err instanceof Error ? err.message : String(err);
1199
+ // Distinguish "not implemented" stubs from real errors
1200
+ if (msg.includes('not implemented')) {
1201
+ die(`Module not yet implemented: ${msg}\nThis command depends on a core module that hasn't been built yet.`);
1202
+ }
1203
+ die(msg);
1204
+ }
1205
+ }
1206
+ // Run — top-level await is fine in ESM with Node 22
1207
+ await main();
1208
+ //# sourceMappingURL=index.js.map