@iowarp/clio-coder 0.3.9 → 0.4.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 (416) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/NOTICE +33 -0
  3. package/README.md +7 -2
  4. package/dist/{acp-7LOELQFP.js → acp-G5WJBNCT.js} +13 -12
  5. package/dist/{agents-FIBG2SHA.js → agents-FMV2Q5G4.js} +39 -36
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-OI4LIH2I.js → auth-3IDSJEIK.js} +16 -15
  8. package/dist/{builtins-AD25UL3C.js → builtins-XCZWXSC7.js} +3 -3
  9. package/dist/{chunk-IFBNV6H6.js → chunk-2ANTL7MR.js} +3 -3
  10. package/dist/{chunk-56KB5IJP.js → chunk-2OQE55CK.js} +3 -3
  11. package/dist/{chunk-N3PBVRTZ.js → chunk-32KWKNSF.js} +9 -7
  12. package/dist/{chunk-VKBMFOYV.js → chunk-36EJLSQQ.js} +23 -27
  13. package/dist/{chunk-465CC7FK.js → chunk-3BT2XMV4.js} +16 -13
  14. package/dist/{chunk-NQ6UCCOD.js → chunk-3URVFKWK.js} +7 -7
  15. package/dist/{chunk-WIE7ZOSW.js → chunk-3XML7CDN.js} +2 -2
  16. package/dist/chunk-42FMPA75.js +101 -0
  17. package/dist/{chunk-A2NJGIB3.js → chunk-5LXZXPKX.js} +2 -2
  18. package/dist/{chunk-HAY4ZE2P.js → chunk-5PSMVOLM.js} +2 -2
  19. package/dist/{chunk-JEQQR47K.js → chunk-6DB53AJS.js} +168 -9
  20. package/dist/{chunk-KV2AOLDF.js → chunk-76ONBSIA.js} +5 -25
  21. package/dist/{chunk-FALJGAWU.js → chunk-7MCTRUCE.js} +2 -2
  22. package/dist/{chunk-5T7RBWN2.js → chunk-AB44T6BB.js} +5 -5
  23. package/dist/chunk-B7OBL7PK.js +1405 -0
  24. package/dist/{chunk-B5CSFE7B.js → chunk-BBVJUZHB.js} +2 -2
  25. package/dist/{chunk-EQ63NRB7.js → chunk-BBVYXMFO.js} +2 -2
  26. package/dist/{chunk-O6I4CIEU.js → chunk-BKFJHQCA.js} +6 -6
  27. package/dist/chunk-BKFM6EJV.js +462 -0
  28. package/dist/chunk-BMS5RKQY.js +27 -0
  29. package/dist/{chunk-MV2VUEJC.js → chunk-BPKCPIL7.js} +2 -2
  30. package/dist/{chunk-TEO2TLVN.js → chunk-BUMFYQFY.js} +964 -1120
  31. package/dist/{chunk-BNAZZHFG.js → chunk-BYP5D4HI.js} +1 -1
  32. package/dist/chunk-C2LTL2W6.js +2447 -0
  33. package/dist/{chunk-EBOC7MT3.js → chunk-CODPRO7Q.js} +8 -8
  34. package/dist/{chunk-HPCTNZM2.js → chunk-CTJ4RNAA.js} +4 -4
  35. package/dist/{chunk-ODFEOB4F.js → chunk-CY6FY24N.js} +26 -8
  36. package/dist/{chunk-MIX5N5AC.js → chunk-DQITNCXG.js} +394 -154
  37. package/dist/{chunk-S4COXYBG.js → chunk-DYJP44XW.js} +129 -106
  38. package/dist/{chunk-N5XKWMDW.js → chunk-F4CKPOEQ.js} +6 -6
  39. package/dist/{chunk-AMKHQW3C.js → chunk-FEFIFZTL.js} +3 -3
  40. package/dist/{chunk-MV3K5QF2.js → chunk-GCSMB2KY.js} +2 -2
  41. package/dist/{chunk-7C6RYZGQ.js → chunk-GKF55TAZ.js} +3 -3
  42. package/dist/{chunk-B5XRQOLB.js → chunk-GR5G2PVF.js} +9 -8
  43. package/dist/{chunk-XDOQXGFO.js → chunk-GXNLGKAB.js} +64 -8
  44. package/dist/chunk-HHV2GANA.js +88 -0
  45. package/dist/{chunk-774ILSRL.js → chunk-HI63TFOG.js} +4 -3
  46. package/dist/{chunk-R7LNVMCS.js → chunk-HJJTYUHX.js} +64 -72
  47. package/dist/{chunk-RKRLDWD3.js → chunk-HLW2MRKE.js} +1 -1
  48. package/dist/{chunk-CA42X6KT.js → chunk-HWHKMHUA.js} +6 -6
  49. package/dist/chunk-HZHHCK24.js +1631 -0
  50. package/dist/{chunk-HJB5IUKP.js → chunk-I5VEOC6I.js} +12 -12
  51. package/dist/chunk-IBEBSCYA.js +564 -0
  52. package/dist/chunk-IQ7KR472.js +362 -0
  53. package/dist/{chunk-4H6ULJ3H.js → chunk-J4W7KFM7.js} +921 -990
  54. package/dist/{chunk-4LJX2PUC.js → chunk-JDG2WCRO.js} +5 -5
  55. package/dist/{chunk-NZU6YDNV.js → chunk-K5C3NCBD.js} +4 -4
  56. package/dist/{chunk-CGKSTWHD.js → chunk-K6BSR66V.js} +2 -1
  57. package/dist/{chunk-NNNWO6F2.js → chunk-KFZI4NIL.js} +93 -3
  58. package/dist/chunk-KMVISBZR.js +132 -0
  59. package/dist/{chunk-BVDVID7E.js → chunk-LDQ2ZF2M.js} +2 -2
  60. package/dist/{chunk-OEDBCISO.js → chunk-LQ3DZAMX.js} +2 -2
  61. package/dist/{chunk-PZ4I4JE2.js → chunk-LY4S7GJC.js} +124 -116
  62. package/dist/{chunk-3DUR4WUA.js → chunk-MLKNTWH2.js} +19 -19
  63. package/dist/chunk-NEKRRTYW.js +56 -0
  64. package/dist/{chunk-5DQRIYDZ.js → chunk-NHCZP4K7.js} +2 -2
  65. package/dist/chunk-NHLBIGRH.js +1506 -0
  66. package/dist/chunk-NQQH3YT7.js +302 -0
  67. package/dist/chunk-NYS75XW5.js +15 -0
  68. package/dist/{chunk-TD7UE2L5.js → chunk-O4XIVISU.js} +7 -7
  69. package/dist/{chunk-WXCJ7VME.js → chunk-O6TL7WWY.js} +6 -6
  70. package/dist/chunk-OQBA45DZ.js +97 -0
  71. package/dist/{chunk-LU7P4LHA.js → chunk-P3FOHJT4.js} +2 -2
  72. package/dist/chunk-PMZCIOCJ.js +25 -0
  73. package/dist/{chunk-I4HZDVNP.js → chunk-PQEFIJ36.js} +2 -2
  74. package/dist/{chunk-HKO36JWF.js → chunk-QBJA7R7N.js} +31 -3
  75. package/dist/chunk-QDC3K2U3.js +262 -0
  76. package/dist/{chunk-5HFBWUMU.js → chunk-QLFS5GO2.js} +4 -4
  77. package/dist/{chunk-GAYUJ7LE.js → chunk-QLL7ILRG.js} +41 -32
  78. package/dist/{chunk-LXPJXFM5.js → chunk-QREDIESB.js} +7 -7
  79. package/dist/chunk-QSNYB6ZV.js +195 -0
  80. package/dist/chunk-R346GLFC.js +31 -0
  81. package/dist/{chunk-VPTUJU4P.js → chunk-SJ5ZKQ4S.js} +2 -2
  82. package/dist/{chunk-QQ7EKM72.js → chunk-SP2RXXYO.js} +6 -54
  83. package/dist/chunk-SUCTJL45.js +45 -0
  84. package/dist/{chunk-5QKCQQ3E.js → chunk-SUW5DORT.js} +2 -2
  85. package/dist/chunk-T56WDKA5.js +183 -0
  86. package/dist/chunk-TVHHYFHE.js +255 -0
  87. package/dist/{chunk-IHKBWSXF.js → chunk-TZ3SGWZZ.js} +2 -2
  88. package/dist/{chunk-ECUO3KDP.js → chunk-U77AMWDL.js} +47 -7
  89. package/dist/{chunk-3UUY7R3Z.js → chunk-ULC6OTWO.js} +7 -7
  90. package/dist/{chunk-AEYBF3TB.js → chunk-UM7N4G5A.js} +2 -2
  91. package/dist/{chunk-3V5AYSEQ.js → chunk-UXMFQ54G.js} +43 -36
  92. package/dist/{chunk-YW7UVM5V.js → chunk-V5DHCITQ.js} +34 -1
  93. package/dist/{chunk-47CMYGET.js → chunk-VAZSBTKF.js} +2 -2
  94. package/dist/{chunk-3MRC2YSQ.js → chunk-VEO4AP2K.js} +2 -2
  95. package/dist/{chunk-PNY46YEY.js → chunk-VFA6GDY5.js} +47 -3
  96. package/dist/chunk-VO67MWHC.js +75 -0
  97. package/dist/{chunk-XK56QHLX.js → chunk-VPKWYKEY.js} +19 -5
  98. package/dist/{chunk-AD7Y7STJ.js → chunk-VYMXRQI6.js} +36 -22
  99. package/dist/{chunk-T3Z6VAAF.js → chunk-W5VSYASO.js} +19 -17
  100. package/dist/{chunk-5PVQ4SRS.js → chunk-WZR7K7ZX.js} +7 -123
  101. package/dist/{chunk-DG4M6ZUE.js → chunk-X3YGUTOB.js} +4 -4
  102. package/dist/chunk-X75E3D2N.js +686 -0
  103. package/dist/{chunk-P3JGPQFL.js → chunk-YDFRH54B.js} +4 -4
  104. package/dist/chunk-YJX4SHTD.js +40 -0
  105. package/dist/{chunk-ZI647VB5.js → chunk-YPI3QQCF.js} +2 -2
  106. package/dist/{chunk-YSEHGPCT.js → chunk-Z2RR6MAK.js} +3 -3
  107. package/dist/{chunk-HCBCAYZU.js → chunk-Z4TXYIEG.js} +5 -5
  108. package/dist/cli/index.js +47 -36
  109. package/dist/{clio-LT5V7SSZ.js → clio-2JXHBBY5.js} +7 -7
  110. package/dist/{code-nav-LMW275PA.js → code-nav-3YYRMYNF.js} +6 -6
  111. package/dist/{compile-cache-CVJMMODC.js → compile-cache-7FPE6PS3.js} +3 -3
  112. package/dist/{components-ZFA3SAER.js → components-RYZV4JGP.js} +5 -5
  113. package/dist/{config-RXS5T3JT.js → config-QZPCMYSO.js} +58 -50
  114. package/dist/{configure-2WYWSCSD.js → configure-TEGEBYCA.js} +21 -19
  115. package/dist/{context-I3BTOTCS.js → context-AV7OEZ4D.js} +11 -11
  116. package/dist/{context-MVOORGMF.js → context-E6H5RNMC.js} +49 -41
  117. package/dist/{context-PALKKQYL.js → context-GSXUE4CT.js} +25 -23
  118. package/dist/{context-clear-N2WOYZ2K.js → context-clear-SHIBYK6T.js} +48 -40
  119. package/dist/{context-working-set-MIEVECVZ.js → context-working-set-5ZGKPGZQ.js} +11 -10
  120. package/dist/{dispatch-runner-VVA4SRRH.js → dispatch-runner-EFMJT4LD.js} +88 -60
  121. package/dist/{docs-7LQ23DLM.js → docs-23KQS3XK.js} +5 -5
  122. package/dist/doctor-QOA5FNY5.js +313 -0
  123. package/dist/{eval-IJ5VEZDJ.js → eval-TFBYQH4H.js} +66 -64
  124. package/dist/eval-inventory-SXH7PDKX.js +316 -0
  125. package/dist/{evidence-L5APPXNV.js → evidence-ERGESKGN.js} +198 -44
  126. package/dist/{evolve-RGNKFJ52.js → evolve-VDXTSYCJ.js} +46 -38
  127. package/dist/{extensions-7WYWUX5A.js → extensions-7BGBHN57.js} +6 -6
  128. package/dist/{fleet-6CNVBZZP.js → fleet-2RRVDF2V.js} +189 -101
  129. package/dist/{fleet-commands-L2SXSYEI.js → fleet-commands-VJ726XIA.js} +10 -10
  130. package/dist/fleet-decisions-EPAPM3XJ.js +157 -0
  131. package/dist/{fleet-graph-2J3OOIPO.js → fleet-graph-JF5QOATM.js} +16 -15
  132. package/dist/fleet-inspect-VLY4S7QM.js +442 -0
  133. package/dist/{fleet-preflight-CZRJ4JP5.js → fleet-preflight-AIZUEJOY.js} +6 -5
  134. package/dist/{fleet-validate-C5RI6DP7.js → fleet-validate-AJRPDMDV.js} +20 -18
  135. package/dist/fleet-verify-JFEL2L3H.js +175 -0
  136. package/dist/fleet-view-ZCON35AG.js +102 -0
  137. package/dist/{init-VBN2ACVA.js → init-DN2WWLFE.js} +64 -56
  138. package/dist/install-XGLBQY5E.js +13 -0
  139. package/dist/interop-OZBKXAYL.js +114 -0
  140. package/dist/{library-JHGUMLY2.js → library-YWZG7IMW.js} +19 -18
  141. package/dist/{memory-K4OQIYWG.js → memory-I4C4HMLW.js} +48 -40
  142. package/dist/{models-2NCZUWDD.js → models-CEYXJBO6.js} +30 -28
  143. package/dist/{monitor-MMVTJABD.js → monitor-NZ6GCI3P.js} +53 -47
  144. package/dist/{orchestrator-ZKBPCHW6.js → orchestrator-GCGQ4N5I.js} +6311 -7145
  145. package/dist/panes-HMABYVO4.js +58 -0
  146. package/dist/panes-KY6W3V2E.js +103 -0
  147. package/dist/{paths-DBXMZMDU.js → paths-II4K7DNR.js} +5 -5
  148. package/dist/{reset-DD5JGOY3.js → reset-DQ6FGCSH.js} +13 -11
  149. package/dist/resources-BB3MVJMD.js +111 -0
  150. package/dist/{run-QEGNX7FL.js → run-H2GQDUER.js} +113 -82
  151. package/dist/{share-JKD3BQMW.js → share-GTJN6A5O.js} +18 -17
  152. package/dist/{skills-LMQIKDOZ.js → skills-L55TEW6R.js} +31 -24
  153. package/dist/{skills-eval-I7X2774U.js → skills-eval-XVXPH2JI.js} +61 -52
  154. package/dist/skills-inventory-S4MXPJFV.js +126 -0
  155. package/dist/slash-commands-ZSGASKJC.js +77 -0
  156. package/dist/{steer-CF5TDANS.js → steer-RZGSCY4R.js} +3 -3
  157. package/dist/{support-I7LOJLIF.js → support-PKEUNNQL.js} +4 -4
  158. package/dist/{targets-RUSR6B5Z.js → targets-NCPZ644J.js} +31 -29
  159. package/dist/{terminal-lease-QYVORFR4.js → terminal-lease-44SV3YCN.js} +4 -4
  160. package/dist/tools-DAF3DI3C.js +27 -0
  161. package/dist/{trace-ODOQIVIW.js → trace-FYVW2MQA.js} +149 -7
  162. package/dist/tui-primitives-2AKXQNZK.js +13 -0
  163. package/dist/{uninstall-ZJF5H5ZN.js → uninstall-DW2PNOIC.js} +5 -5
  164. package/dist/{upgrade-XANW3FXB.js → upgrade-3XPP6OQL.js} +26 -24
  165. package/dist/{usage-4H7ZRXQT.js → usage-3NLHGTU2.js} +57 -49
  166. package/dist/{verifiers-UZXNBZEB.js → verifiers-SSQONKRT.js} +171 -12
  167. package/dist/{verify-BVKWTNDL.js → verify-3U6J7FZI.js} +9 -9
  168. package/dist/{web-fetch-2YHJ3KTG.js → web-fetch-S7RR6GZ7.js} +3 -3
  169. package/dist/{wiki-generate-MY7WV2QI.js → wiki-generate-CEHYGPGQ.js} +67 -59
  170. package/dist/with-panes-MKB46MPQ.js +782 -0
  171. package/dist/worker/entry.js +101 -66
  172. package/docs/README.md +3 -2
  173. package/docs/acp.md +24 -3
  174. package/docs/alcf-provider.md +1 -1
  175. package/docs/architecture.md +2 -2
  176. package/docs/artifact-versions.md +4 -1
  177. package/docs/built-in-agents.md +1 -1
  178. package/docs/capacity-and-scheduling.md +43 -5
  179. package/docs/commands-and-modes.md +31 -2
  180. package/docs/configuration-and-targets.md +40 -10
  181. package/docs/context-engine.md +1 -1
  182. package/docs/development-pipeline.md +19 -0
  183. package/docs/dispatch-typed-intent.md +385 -0
  184. package/docs/documentation-coverage.md +2 -2
  185. package/docs/documentation-guide.md +1 -1
  186. package/docs/environment-variables.md +1 -0
  187. package/docs/eval-runner.md +1 -1
  188. package/docs/evals-internal.md +1 -1
  189. package/docs/evidence-and-memory.md +2 -2
  190. package/docs/evolution.md +1 -1
  191. package/docs/exit-codes-and-output.md +1 -1
  192. package/docs/extensions-and-sharing.md +25 -1
  193. package/docs/fleet-dispatch.md +22 -1
  194. package/docs/installation-and-lifecycle.md +15 -2
  195. package/docs/middleware-and-components.md +1 -1
  196. package/docs/model-catalog.md +10 -1
  197. package/docs/observability.md +3 -3
  198. package/docs/proactive-memory.md +1 -1
  199. package/docs/prompt-envelope-and-tools.md +1 -1
  200. package/docs/provider-adapter-cookbook.md +1 -1
  201. package/docs/safety-model.md +2 -2
  202. package/docs/scientific-validation.md +3 -3
  203. package/docs/session-lifecycle.md +1 -1
  204. package/docs/skills-marketplace.md +1 -1
  205. package/docs/tool-usage.md +18 -8
  206. package/docs/trace-store.md +1 -1
  207. package/docs/troubleshooting.md +1 -1
  208. package/docs/tui-design.md +1 -1
  209. package/docs/worker-dispatch-mechanics.md +1 -1
  210. package/package.json +4 -2
  211. package/src/cli/acp.ts +6 -2
  212. package/src/cli/argv.ts +25 -0
  213. package/src/cli/configure.ts +23 -21
  214. package/src/cli/doctor-panes.ts +124 -0
  215. package/src/cli/doctor-toolchain.ts +57 -0
  216. package/src/cli/doctor.ts +20 -1
  217. package/src/cli/eval-inventory.ts +436 -0
  218. package/src/cli/eval.ts +13 -0
  219. package/src/cli/evidence-detail.ts +88 -0
  220. package/src/cli/evidence-inventory.ts +183 -0
  221. package/src/cli/evidence.ts +30 -5
  222. package/src/cli/fleet-decisions.ts +69 -0
  223. package/src/cli/fleet-inspect.ts +334 -0
  224. package/src/cli/fleet-verify.ts +133 -0
  225. package/src/cli/fleet-view.ts +810 -0
  226. package/src/cli/fleet.ts +148 -37
  227. package/src/cli/index.ts +14 -2
  228. package/src/cli/interop-inspect.ts +128 -0
  229. package/src/cli/interop.ts +34 -0
  230. package/src/cli/panes.ts +35 -0
  231. package/src/cli/reset.ts +5 -2
  232. package/src/cli/run.ts +58 -0
  233. package/src/cli/skills-inventory.ts +185 -0
  234. package/src/cli/skills.ts +16 -13
  235. package/src/cli/tools.ts +321 -0
  236. package/src/cli/trace-inspect.ts +252 -0
  237. package/src/cli/trace.ts +23 -2
  238. package/src/cli/verifiers-inspect.ts +347 -0
  239. package/src/cli/verifiers.ts +9 -0
  240. package/src/core/bus-events.ts +4 -0
  241. package/src/core/config.ts +49 -0
  242. package/src/core/defaults.ts +93 -7
  243. package/src/core/response-schema.ts +44 -4
  244. package/src/core/theme-token-hex.ts +43 -0
  245. package/src/core/tool-names.ts +2 -1
  246. package/src/core/xdg.ts +1 -1
  247. package/src/domains/agents/fleets/build-review.md +0 -3
  248. package/src/domains/agents/fleets/build-test.md +0 -3
  249. package/src/domains/agents/result-contract-filesystem.ts +32 -0
  250. package/src/domains/agents/result-contract.ts +164 -35
  251. package/src/domains/config/classify.ts +3 -0
  252. package/src/domains/dispatch/admission-error.ts +9 -0
  253. package/src/domains/dispatch/admission.ts +22 -21
  254. package/src/domains/dispatch/capacity-lease.ts +21 -1
  255. package/src/domains/dispatch/council-topology.ts +398 -0
  256. package/src/domains/dispatch/extension.ts +222 -50
  257. package/src/domains/dispatch/fleet-node-prompt.ts +62 -0
  258. package/src/domains/dispatch/fleet-plan.ts +7 -2
  259. package/src/domains/dispatch/fleet-run.ts +64 -1
  260. package/src/domains/dispatch/gate-decisions.ts +11 -1
  261. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  262. package/src/domains/dispatch/gate-topology.ts +289 -0
  263. package/src/domains/dispatch/index.ts +19 -0
  264. package/src/domains/dispatch/intent-compatibility.ts +330 -0
  265. package/src/domains/dispatch/intent.ts +85 -1
  266. package/src/domains/dispatch/reservation-store.ts +24 -4
  267. package/src/domains/dispatch/run-event-journal-bridge.ts +149 -0
  268. package/src/domains/dispatch/run-event-journal.ts +598 -0
  269. package/src/domains/dispatch/state.ts +45 -1
  270. package/src/domains/dispatch/types.ts +13 -0
  271. package/src/domains/dispatch/validation.ts +33 -8
  272. package/src/domains/eval/compare/compare.ts +3 -16
  273. package/src/domains/eval/inventory.ts +113 -0
  274. package/src/domains/eval/schema/serving.ts +31 -0
  275. package/src/domains/evidence/store.ts +6 -0
  276. package/src/domains/memory/index.ts +0 -1
  277. package/src/domains/memory/task-memory-policy.ts +6 -10
  278. package/src/domains/memory/task-memory-telemetry.ts +1 -0
  279. package/src/domains/middleware/memory-intervention.ts +33 -21
  280. package/src/domains/mux/contract.ts +434 -0
  281. package/src/domains/mux/detect.ts +158 -0
  282. package/src/domains/mux/extension.ts +47 -0
  283. package/src/domains/mux/index.ts +96 -0
  284. package/src/domains/mux/manifest.ts +6 -0
  285. package/src/domains/mux/operations.ts +164 -0
  286. package/src/domains/mux/pane-registry.ts +90 -0
  287. package/src/domains/mux/protocol.ts +49 -0
  288. package/src/domains/mux/socket-client.ts +816 -0
  289. package/src/domains/mux/types.ts +222 -0
  290. package/src/domains/mux/viewer-command.ts +59 -0
  291. package/src/domains/mux/yazi/assets/init.lua +2 -0
  292. package/src/domains/mux/yazi/assets/plugins/git.yazi/LICENSE +21 -0
  293. package/src/domains/mux/yazi/assets/plugins/git.yazi/README.md +78 -0
  294. package/src/domains/mux/yazi/assets/plugins/git.yazi/main.lua +255 -0
  295. package/src/domains/mux/yazi/assets/plugins/git.yazi/types.lua +12 -0
  296. package/src/domains/mux/yazi/assets/yazi.toml +17 -0
  297. package/src/domains/mux/yazi/event-stream.ts +180 -0
  298. package/src/domains/mux/yazi/profile.ts +299 -0
  299. package/src/domains/mux/yazi/session.ts +228 -0
  300. package/src/domains/mux/yazi/theme.ts +30 -0
  301. package/src/domains/observability/cost.ts +21 -0
  302. package/src/domains/observability/index.ts +2 -0
  303. package/src/domains/observability/trace-store.ts +42 -0
  304. package/src/domains/providers/endpoint-capacity.ts +154 -22
  305. package/src/domains/providers/endpoint-slots-store.ts +189 -0
  306. package/src/domains/providers/extension.ts +20 -3
  307. package/src/domains/providers/index.ts +22 -0
  308. package/src/domains/providers/model-runtime-capabilities.ts +32 -0
  309. package/src/domains/providers/runtimes/boot-manifest.ts +1 -0
  310. package/src/domains/providers/runtimes/builtins.ts +2 -0
  311. package/src/domains/providers/runtimes/protocol/litellm.ts +375 -0
  312. package/src/domains/providers/support.ts +1 -0
  313. package/src/domains/providers/target-model-cache.ts +124 -0
  314. package/src/domains/resources/index.ts +3 -0
  315. package/src/domains/resources/skills/loader.ts +33 -0
  316. package/src/domains/safety/action-classifier.ts +6 -0
  317. package/src/domains/toolchain/archive.ts +175 -0
  318. package/src/domains/toolchain/contract.ts +28 -0
  319. package/src/domains/toolchain/extension.ts +47 -0
  320. package/src/domains/toolchain/index.ts +39 -0
  321. package/src/domains/toolchain/install.ts +327 -0
  322. package/src/domains/toolchain/manifest.ts +8 -0
  323. package/src/domains/toolchain/paths.ts +34 -0
  324. package/src/domains/toolchain/registry.ts +265 -0
  325. package/src/domains/toolchain/remove.ts +218 -0
  326. package/src/domains/toolchain/resolve.ts +182 -0
  327. package/src/domains/toolchain/types.ts +113 -0
  328. package/src/domains/toolchain/version.ts +88 -0
  329. package/src/engine/acp/server.ts +413 -70
  330. package/src/engine/acp/types.ts +19 -1
  331. package/src/engine/claude/sdk-module.ts +98 -0
  332. package/src/engine/claude/sdk-runtime.ts +19 -11
  333. package/src/engine/tui-primitives.ts +21 -0
  334. package/src/engine/tui.ts +1 -0
  335. package/src/engine/worker-runtime.ts +2 -13
  336. package/src/entry/boot-options.ts +2 -0
  337. package/src/entry/orchestrator.ts +103 -5
  338. package/src/entry/panes-activation.ts +31 -0
  339. package/src/entry/with-panes.ts +20 -0
  340. package/src/interactive/context-overlay.ts +1 -0
  341. package/src/interactive/cost-overlay.ts +2 -0
  342. package/src/interactive/council-grid.ts +1 -3
  343. package/src/interactive/council.ts +11 -0
  344. package/src/interactive/dispatch-board.ts +26 -16
  345. package/src/interactive/interactive-application.ts +153 -6
  346. package/src/interactive/interactive-input-runtime.ts +7 -0
  347. package/src/interactive/interactive-slash-runtime.ts +36 -6
  348. package/src/interactive/memory-overlay.ts +1 -0
  349. package/src/interactive/modal-marker.ts +170 -0
  350. package/src/interactive/mux-bridge.ts +214 -0
  351. package/src/interactive/overlay-frame.ts +58 -2
  352. package/src/interactive/overlay-general-openers.ts +1 -0
  353. package/src/interactive/overlay-key-routing.ts +14 -3
  354. package/src/interactive/overlay-lifecycle.ts +19 -3
  355. package/src/interactive/overlay-model-selectors.ts +40 -3
  356. package/src/interactive/overlay-permission-lifecycle.ts +90 -22
  357. package/src/interactive/overlay-transitions.ts +18 -4
  358. package/src/interactive/overlays/agents.ts +1 -0
  359. package/src/interactive/overlays/ask-user.ts +136 -30
  360. package/src/interactive/overlays/auth-dialog.ts +1 -0
  361. package/src/interactive/overlays/context-reset.ts +1 -0
  362. package/src/interactive/overlays/cwd-fallback.ts +1 -0
  363. package/src/interactive/overlays/decisions.ts +11 -11
  364. package/src/interactive/overlays/extensions.ts +1 -0
  365. package/src/interactive/overlays/fleet-run-approval.ts +1 -0
  366. package/src/interactive/overlays/handoff-review.ts +1 -0
  367. package/src/interactive/overlays/help-reference.ts +2 -0
  368. package/src/interactive/overlays/interop.ts +1 -0
  369. package/src/interactive/overlays/library-install-confirm.ts +1 -0
  370. package/src/interactive/overlays/library-tabs.ts +28 -0
  371. package/src/interactive/overlays/list-overlay.ts +10 -1
  372. package/src/interactive/overlays/message-picker.ts +1 -0
  373. package/src/interactive/overlays/model-scope.ts +86 -0
  374. package/src/interactive/overlays/model-selector.ts +1 -0
  375. package/src/interactive/overlays/prompts.ts +1 -0
  376. package/src/interactive/overlays/session-selector.ts +1 -0
  377. package/src/interactive/overlays/settings-sections.ts +30 -0
  378. package/src/interactive/overlays/settings.ts +579 -44
  379. package/src/interactive/overlays/side-question.ts +1 -0
  380. package/src/interactive/overlays/skills-hub.ts +3 -11
  381. package/src/interactive/overlays/tree-selector.ts +1 -0
  382. package/src/interactive/pane-policy.ts +46 -0
  383. package/src/interactive/panes-runtime.ts +292 -0
  384. package/src/interactive/renderers/compaction-summary.ts +29 -0
  385. package/src/interactive/renderers/worker-entry.ts +122 -14
  386. package/src/interactive/slash-commands.ts +251 -15
  387. package/src/interactive/tasks-overlay.ts +1 -0
  388. package/src/interactive/theme/tokens.ts +3 -14
  389. package/src/interactive/turn-context.ts +47 -2
  390. package/src/interactive/view/artifacts.ts +109 -1
  391. package/src/interactive/view/view-overlay.ts +1 -0
  392. package/src/interactive/watch-pane.ts +152 -0
  393. package/src/interactive/worker-progress.ts +7 -1
  394. package/src/interactive/worker-receipts.ts +19 -1
  395. package/src/interactive/worker-stream.ts +5 -0
  396. package/src/interactive/yazi-bridge.ts +444 -0
  397. package/src/tools/bootstrap.ts +26 -2
  398. package/src/tools/builtin-tool-catalog.ts +15 -0
  399. package/src/tools/compete-worktrees.ts +83 -2
  400. package/src/tools/core-bootstrap.ts +2 -1
  401. package/src/tools/dispatch-admission.ts +14 -3
  402. package/src/tools/dispatch-arguments.ts +20 -20
  403. package/src/tools/dispatch-run-events.ts +134 -19
  404. package/src/tools/dispatch-runner.ts +29 -7
  405. package/src/tools/dispatch-types.ts +15 -3
  406. package/src/tools/dispatch.ts +1 -1
  407. package/src/tools/executables.ts +17 -14
  408. package/src/tools/observation.ts +54 -4
  409. package/src/tools/panes-surface.ts +38 -0
  410. package/src/tools/panes.ts +112 -0
  411. package/src/tools/policy.ts +10 -1
  412. package/src/tools/presentation.ts +1 -0
  413. package/dist/chunk-D73KXYPF.js +0 -37
  414. package/dist/chunk-MLOK6ZOS.js +0 -2888
  415. package/dist/chunk-YKOFT37S.js +0 -279
  416. package/dist/doctor-TWBWFK5V.js +0 -165
package/src/cli/reset.ts CHANGED
@@ -15,7 +15,7 @@ Each level clears exactly the root (or file) it names and nothing else.
15
15
  Levels (combinable except --all):
16
16
  --state state root only (default). Holds every session transcript, so a
17
17
  reset is the end of resume, /view, and the audit behind them.
18
- --data data root only: memory, evidence, evals (durable products)
18
+ --data data root only: memory, evidence, evals, vendored tools (durable products)
19
19
  --cache cache root only
20
20
  --auth credentials.yaml only
21
21
  --config settings.yaml only
@@ -42,7 +42,10 @@ Safety:
42
42
  const ROOT_NOTES: Readonly<Record<string, string>> = {
43
43
  state:
44
44
  "the state root holds every session transcript and the audit trail beside it; resume and /view lose their history",
45
- data: "the data root holds durable products (memory, evidence, evals)",
45
+ // Vendored tools are named because they are the one durable product a reset
46
+ // cannot regenerate from anything local: getting them back is a download.
47
+ data:
48
+ "the data root holds durable products (memory, evidence, evals, and any vendored external tools, which a reinstall re-downloads)",
46
49
  };
47
50
 
48
51
  interface ParsedResetArgs {
package/src/cli/run.ts CHANGED
@@ -11,6 +11,7 @@ import { createContextDomainModule } from "../domains/context/runtime.js";
11
11
  import type { DispatchContract, DispatchRequest } from "../domains/dispatch/contract.js";
12
12
  import { agentRoleFactsResolver, requestExecutionRole } from "../domains/dispatch/execution-role.js";
13
13
  import { createDispatchDomainModule } from "../domains/dispatch/index.js";
14
+ import { configureRunEventJournal } from "../domains/dispatch/run-event-journal.js";
14
15
  import type { RunReceipt } from "../domains/dispatch/types.js";
15
16
  import { ensureClioState, LifecycleDomainModule } from "../domains/lifecycle/index.js";
16
17
  import {
@@ -158,6 +159,45 @@ export function explicitSkillPathErrors(skillPaths: ReadonlyArray<string>): stri
158
159
  return errors;
159
160
  }
160
161
 
162
+ /**
163
+ * The TUI's verdict for a command-shaped token the registry does not own,
164
+ * applied to a headless task. Returns the refusal message, or null when the
165
+ * task is not an unknown command.
166
+ *
167
+ * `parseSlashCommand` is the canonical shape test and the canonical registry
168
+ * walk, so a token that is one word of letters, digits, hyphens, or colons and
169
+ * names no command lands here exactly as it does in the editor. Prose keeps
170
+ * reaching the model: an absolute path such as `/home/user/notes` carries a
171
+ * separator and is not one word, and `\/tmp is full` is the same escape the
172
+ * editor honors.
173
+ *
174
+ * A prompt template claims the token before the refusal does. Only membership
175
+ * is checked, not expansion: a template that exists but refuses (an untrusted
176
+ * project root, an unreadable body) has its own message, which the headless
177
+ * boot path already prints from the expansion itself. Deciding that here would
178
+ * duplicate the trust rules and put two refusals on one token.
179
+ *
180
+ * Both modules are imported lazily, so a task that does not start with a slash
181
+ * pays for neither. The laziness is a startup-cost choice and nothing more: for
182
+ * bundle partitioning a dynamic import is worse than a static one, because a
183
+ * dynamically imported module becomes a chunk split point by itself. What keeps
184
+ * this edge safe is the other end. `slash-commands.ts` is a declared CLI seam
185
+ * whose own imports stay off the interactive render graph, so reaching it does
186
+ * not make this file a second reacher of the chunk the instant shell's Stage 0
187
+ * closure sits on. Boundaries rule6 in `tests/boundaries/check-boundaries.ts`
188
+ * holds both halves of that.
189
+ */
190
+ export async function unknownSlashCommandRefusal(task: string): Promise<string | null> {
191
+ if (!task.trim().startsWith("/")) return null;
192
+ const { parseSlashCommand } = await import("../interactive/slash-commands.js");
193
+ const command = parseSlashCommand(task);
194
+ if (command.kind !== "unknown-command") return null;
195
+ const { loadPromptTemplates } = await import("../domains/resources/index.js");
196
+ const templates = loadPromptTemplates({ cwd: process.cwd() });
197
+ if (templates.items.some((template) => template.name === command.token)) return null;
198
+ return `/${command.token} is not a command. Type /help for the list.`;
199
+ }
200
+
161
201
  export async function runClioRun(
162
202
  args: ReadonlyArray<string>,
163
203
  options: { apiKey?: string; noContextFiles?: boolean; noSkills?: boolean; skillPaths?: ReadonlyArray<string> } = {},
@@ -218,6 +258,15 @@ export async function runClioRun(
218
258
  if (!assembled) return 2;
219
259
 
220
260
  if (parsed.agentId === undefined) {
261
+ // A mistyped slash command is a usage error, not a task. Refusing it
262
+ // here keeps it from booting a session and spending a model turn on a
263
+ // command that was never run, which is what the TUI has always done
264
+ // and what docs/extensions-and-sharing.md documents for both surfaces.
265
+ const slashRefusal = await unknownSlashCommandRefusal(assembled.prompt);
266
+ if (slashRefusal !== null) {
267
+ process.stderr.write(`clio-coder run: ${slashRefusal}\n`);
268
+ return 2;
269
+ }
221
270
  // An explicit --target override is a one-run target; a missing id is an
222
271
  // operator config error, not an assistant response. Reject it before the
223
272
  // headless turn so the resolver diagnostic never streams to stdout as a
@@ -314,11 +363,20 @@ async function runDispatch(
314
363
  createDispatchDomainModule({
315
364
  getSettings: () => effectiveSettings,
316
365
  autonomyOverride: parsed.autonomy !== undefined,
366
+ // This path iterates `handle.events` itself and builds no dispatch
367
+ // event registry, so the domain is the only thing that can write the
368
+ // run's durable transcript.
369
+ journalRunEvents: true,
317
370
  }),
318
371
  LifecycleDomainModule,
319
372
  ]);
320
373
  const config = loaded.getContract<ConfigContract>("config");
321
374
  const baseSettings = config?.get() ?? readSettings();
375
+ // Install `panes.journal` the same way the interactive composition root
376
+ // does. The journal never reads settings.yaml itself: it sits on the
377
+ // dispatch event path, where a settings read would be both a cost and a
378
+ // throw site.
379
+ configureRunEventJournal(baseSettings.panes.journal);
322
380
  effectiveSettings =
323
381
  parsed.autonomy === undefined ? baseSettings : { ...structuredClone(baseSettings), autonomy: parsed.autonomy };
324
382
  const dispatch = loaded.getContract<DispatchContract>("dispatch");
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Fixed machine-readable projection of this installation's installed skills.
3
+ *
4
+ * `skills list --json` already emits a machine-readable listing, and a GUI host
5
+ * reading it gets two things wrong. Without `--all` it sees only the skills the
6
+ * model may load, which is `trusted && !disableModelInvocation`, so a surface
7
+ * rendering trust and invocability off that payload prints "trusted" and
8
+ * "allowed" on every row and structurally cannot print anything else. And the
9
+ * rows it does get are the full loaded records, carrying the SKILL.md body, the
10
+ * file path, the base directory, and two content hashes, so every consumer has
11
+ * to re-derive the same redaction.
12
+ *
13
+ * This command answers both. It loads once, reports every installed skill
14
+ * whether or not the model can reach it, says which ones it can, and carries
15
+ * only fields that are safe by construction. It is a separate subcommand rather
16
+ * than a flag on `skills inspect`, because that one names a skill and a fixed
17
+ * read a host may run must take no identifier at all.
18
+ *
19
+ * Held back deliberately: the skill body, its file path, its base directory,
20
+ * both hashes, the install and registry URLs, and the free-text loader
21
+ * diagnostics. Whether a skill has an upstream to update from crosses; where
22
+ * that upstream is does not.
23
+ *
24
+ * Marketplace discovery is deliberately not here. `library list --json` already
25
+ * folds `discoverMarketplaceSkills` into its catalog, so every installable
26
+ * skill already crosses through that read, and a second one would report the
27
+ * same rows under a different name.
28
+ */
29
+
30
+ import {
31
+ loadSkills,
32
+ type ResourceDiagnostic,
33
+ type Skill,
34
+ type SkillCatalogInvalidReason,
35
+ skillCatalogValidity,
36
+ } from "../domains/resources/index.js";
37
+
38
+ /** Wire bound on the emitted skill list. */
39
+ const MAX_SKILLS_INVENTORY_SKILLS = 64;
40
+ /** Wire bound on each skill's declared tool policy. */
41
+ const MAX_SKILLS_INVENTORY_TOOLS = 32;
42
+
43
+ const TOOL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
44
+
45
+ type SkillAuditState = "pass" | "warn" | "fail" | "unknown" | "not-reported";
46
+
47
+ interface SkillsInventoryDiagnosticCounts {
48
+ readonly errors: number;
49
+ readonly warnings: number;
50
+ readonly collisions: number;
51
+ }
52
+
53
+ interface SkillsInventorySkill {
54
+ readonly name: string;
55
+ readonly description: string;
56
+ readonly scope: string;
57
+ readonly source: string;
58
+ /** Whether the skill's root is trusted enough for the model to see it at all. */
59
+ readonly trusted: boolean;
60
+ /** Whether the skill's own frontmatter permits model invocation. */
61
+ readonly modelInvocable: boolean;
62
+ /**
63
+ * The effective answer, which is both of the above.
64
+ *
65
+ * Reported rather than left to be inferred because it is the fact an
66
+ * operator is actually asking about, and because a listing filtered to it
67
+ * cannot state it.
68
+ */
69
+ readonly modelVisible: boolean;
70
+ readonly precedence: number;
71
+ readonly diagnostics: SkillsInventoryDiagnosticCounts;
72
+ /** Tools the skill narrows itself to. The harness intersects these with host policy. */
73
+ readonly allowedTools: readonly string[];
74
+ readonly disallowedTools: readonly string[];
75
+ /** True when a dispatched worker installed this, rather than the operator. */
76
+ readonly installedByWorker: boolean;
77
+ /** True when provenance records an upstream, so `skills update` has something to fetch. */
78
+ readonly updatable: boolean;
79
+ readonly audit: SkillAuditState;
80
+ readonly installedAt: string | null;
81
+ readonly updatedAt: string | null;
82
+ }
83
+
84
+ interface SkillsInventorySnapshot {
85
+ readonly version: 1;
86
+ readonly generatedAt: string;
87
+ /** The verdict `skills validate` reaches, from the rule both callers share. */
88
+ readonly valid: boolean;
89
+ readonly invalidReason: SkillCatalogInvalidReason | null;
90
+ /** Every installed skill the loader resolved, before the wire bound. */
91
+ readonly total: number;
92
+ /** How many of those the model may actually load by name. */
93
+ readonly modelVisible: number;
94
+ readonly diagnostics: SkillsInventoryDiagnosticCounts;
95
+ readonly skills: readonly SkillsInventorySkill[];
96
+ readonly skillsTruncated: boolean;
97
+ }
98
+
99
+ const AUDIT_STATES: ReadonlySet<string> = new Set(["pass", "warn", "fail", "unknown"]);
100
+
101
+ function countDiagnostics(diagnostics: ReadonlyArray<ResourceDiagnostic>): SkillsInventoryDiagnosticCounts {
102
+ let errors = 0;
103
+ let warnings = 0;
104
+ let collisions = 0;
105
+ for (const diag of diagnostics) {
106
+ if (diag.type === "error") errors += 1;
107
+ else if (diag.type === "collision") collisions += 1;
108
+ else warnings += 1;
109
+ }
110
+ return { errors, warnings, collisions };
111
+ }
112
+
113
+ /**
114
+ * Tool names are already-crossing identifiers: the agent catalog carries the
115
+ * same class. They are re-enforced structurally here anyway, because a skill's
116
+ * frontmatter is operator-authored and the loader does not hold it to a shape.
117
+ */
118
+ function toolPolicy(value: ReadonlyArray<string> | undefined): string[] {
119
+ if (value === undefined) return [];
120
+ const tools: string[] = [];
121
+ for (const tool of value) {
122
+ if (tools.length >= MAX_SKILLS_INVENTORY_TOOLS) break;
123
+ if (typeof tool !== "string" || tool.length > 64 || !TOOL_PATTERN.test(tool)) continue;
124
+ if (!tools.includes(tool)) tools.push(tool);
125
+ }
126
+ return tools;
127
+ }
128
+
129
+ function timestamp(value: string | undefined): string | null {
130
+ if (value === undefined) return null;
131
+ const parsed = Date.parse(value);
132
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
133
+ }
134
+
135
+ function projectSkill(skill: Skill): SkillsInventorySkill {
136
+ const provenance = skill.provenance;
137
+ const audit = provenance?.audit;
138
+ return {
139
+ name: skill.name,
140
+ description: skill.description,
141
+ scope: skill.scope,
142
+ source: skill.source,
143
+ trusted: skill.trusted,
144
+ modelInvocable: !skill.disableModelInvocation,
145
+ modelVisible: skill.trusted && !skill.disableModelInvocation,
146
+ precedence: skill.precedence,
147
+ diagnostics: countDiagnostics(skill.diagnostics),
148
+ allowedTools: toolPolicy(skill.allowedTools),
149
+ disallowedTools: toolPolicy(skill.disallowedTools),
150
+ installedByWorker: provenance?.installedBy === "worker",
151
+ // The URL itself is a host-side fact; that one exists is what tells the
152
+ // operator whether an update has anywhere to come from.
153
+ updatable: provenance?.installUrl !== undefined || provenance?.registryUrl !== undefined,
154
+ audit: audit !== undefined && AUDIT_STATES.has(audit) ? (audit as SkillAuditState) : "not-reported",
155
+ installedAt: timestamp(provenance?.installedAt),
156
+ updatedAt: timestamp(provenance?.updatedAt),
157
+ };
158
+ }
159
+
160
+ function skillsInventorySnapshot(now: () => number = Date.now, cwd: string = process.cwd()): SkillsInventorySnapshot {
161
+ const list = loadSkills({ cwd });
162
+ const validity = skillCatalogValidity(list);
163
+ const projected = list.items.map(projectSkill);
164
+ const skills = projected.slice(0, MAX_SKILLS_INVENTORY_SKILLS);
165
+ return {
166
+ version: 1,
167
+ generatedAt: new Date(now()).toISOString(),
168
+ valid: validity.ok,
169
+ invalidReason: validity.reason,
170
+ total: projected.length,
171
+ modelVisible: projected.filter((skill) => skill.modelVisible).length,
172
+ diagnostics: countDiagnostics(list.diagnostics),
173
+ skills,
174
+ skillsTruncated: skills.length < projected.length,
175
+ };
176
+ }
177
+
178
+ export function runSkillsInventory(args: ReadonlyArray<string>): number {
179
+ if (args.length !== 1 || args[0] !== "--json") {
180
+ process.stderr.write("clio-coder skills inventory: usage: clio-coder skills inventory --json\n");
181
+ return 2;
182
+ }
183
+ process.stdout.write(`${JSON.stringify(skillsInventorySnapshot(), null, 2)}\n`);
184
+ return 0;
185
+ }
package/src/cli/skills.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  type ResourceDiagnostic,
9
9
  type Skill,
10
10
  type SkillUpdateReport,
11
+ skillCatalogValidity,
11
12
  updateSkills,
12
13
  } from "../domains/resources/index.js";
13
14
  import { formatColumns, printError, printOk } from "./shared.js";
@@ -18,6 +19,7 @@ Manage local Clio and Agent Skills-compatible skills.
18
19
 
19
20
  Commands:
20
21
  clio-coder skills list [--json] [--all]
22
+ clio-coder skills inventory --json
21
23
  clio-coder skills search <query> [--json]
22
24
  clio-coder skills inspect <name> [--json]
23
25
  clio-coder skills validate [path] [--json]
@@ -27,6 +29,10 @@ Commands:
27
29
  clio-coder skills sync [--force]
28
30
  clio-coder skills eval <name|path> [--scenario <id>] [--target <id>] [--workspace <path>] [--timeout <seconds>] [--trust-fixtures] [--allow-network] [--json]
29
31
 
32
+ inventory is the fixed machine-readable read a GUI host may run. Unlike a bare
33
+ list it reports every installed skill, not only the ones the model may load, and
34
+ says which are which; it carries no body, path, hash, or install URL.
35
+
30
36
  search covers installed skills plus the local marketplace: CLIO_CODER_SKILL_CATALOG_DIR,
31
37
  a repo skills/ catalog, or the catalog and skill-marketplace.json index the
32
38
  installed clio-coder package carries. Installing from the shipped catalog copies
@@ -249,6 +255,12 @@ function validationLoad(pathArg: string | undefined): ReturnType<typeof loadSkil
249
255
  }
250
256
 
251
257
  export async function runSkillsCommand(argv: ReadonlyArray<string>): Promise<number> {
258
+ // Routed before the shared parser so the fixed read stays exactly fixed: no
259
+ // skills flag can reach it, and no flag it does not name can be spent on it.
260
+ if (argv[0] === "inventory") {
261
+ const { runSkillsInventory } = await import("./skills-inventory.js");
262
+ return runSkillsInventory(argv.slice(1));
263
+ }
252
264
  let parsed: Parsed;
253
265
  try {
254
266
  parsed = parse(argv);
@@ -365,19 +377,10 @@ export async function runSkillsCommand(argv: ReadonlyArray<string>): Promise<num
365
377
  return 2;
366
378
  }
367
379
  const list = validationLoad(pathArg);
368
- // A scanned file that produced no loaded skill is malformed, and a
369
- // collision drops a skill: both make the catalog invalid, as does any
370
- // hard error. Benign warnings (name/path mismatch, name format,
371
- // description length) attach to a file that still loaded, so their path
372
- // is among the loaded skills and they do not fail validation.
373
- const loadedPaths = new Set(list.items.map((skill) => skill.filePath));
374
- const hasInvalidDiagnostic = list.diagnostics.some(
375
- (diag) =>
376
- diag.type === "error" ||
377
- diag.type === "collision" ||
378
- (diag.type === "warning" && diag.path !== undefined && !loadedPaths.has(diag.path)),
379
- );
380
- const ok = list.items.length > 0 && !hasInvalidDiagnostic;
380
+ // The rule itself lives in the loader, because `skills inspect --json`
381
+ // reports the same verdict to a GUI host and a verdict that drifts
382
+ // between the terminal and the browser is worse than no verdict.
383
+ const { ok } = skillCatalogValidity(list);
381
384
  if (parsed.json) {
382
385
  process.stdout.write(`${JSON.stringify({ ok, skills: list.items, diagnostics: list.diagnostics }, null, 2)}\n`);
383
386
  } else {
@@ -0,0 +1,321 @@
1
+ import { describeYaziProfile, inspectCurrentYaziProfile, resetYaziProfile } from "../domains/mux/index.js";
2
+ import {
3
+ describeFloorRejection,
4
+ describeResolution,
5
+ findPinnedTool,
6
+ installedToolVersions,
7
+ installRemedy,
8
+ installTool,
9
+ PINNED_TOOLS,
10
+ removeTool,
11
+ type ToolStatus,
12
+ toolStatus,
13
+ toolStatuses,
14
+ } from "../domains/toolchain/index.js";
15
+ import { formatColumns, printError, printOk } from "./shared.js";
16
+
17
+ const HELP = `clio-coder tools <command>
18
+
19
+ Manage the external terminal programs Clio can drive. Every one of them is
20
+ optional: Clio works without them and only uses one when it is present. Each is
21
+ pinned to an exact upstream version and checksum, and a copy already on PATH
22
+ wins over Clio's own as long as it clears the pinned minimum.
23
+
24
+ Commands:
25
+ clio-coder tools list [--json] the pinned table and where each resolves
26
+ clio-coder tools status <id> [--json] [--reset-profile] one tool in detail
27
+ clio-coder tools install <id> [--force] [--json] download, verify, and vendor a tool
28
+ clio-coder tools remove <id>|--all [--json] delete every vendored version of a tool
29
+
30
+ Downloads happen only here. Nothing on a startup path fetches anything.
31
+
32
+ An install keeps one version directory per tool: once the pinned version is in
33
+ place, versions it superseded are pruned, along with any staging directory left
34
+ by an install that was killed. Removal touches nothing outside <data>/tools/<id>
35
+ and needs no confirmation, since every byte under it is re-downloadable from the
36
+ checksum-pinned registry. "remove --all" sweeps every tool in the table.
37
+ `;
38
+
39
+ interface Parsed {
40
+ command?: string;
41
+ positional: string[];
42
+ json: boolean;
43
+ force: boolean;
44
+ all: boolean;
45
+ resetProfile: boolean;
46
+ help: boolean;
47
+ }
48
+
49
+ export async function runToolsCommand(argv: ReadonlyArray<string> = []): Promise<number> {
50
+ let parsed: Parsed;
51
+ try {
52
+ parsed = parse(argv);
53
+ } catch (error) {
54
+ printError(error instanceof Error ? error.message : String(error));
55
+ process.stderr.write(HELP);
56
+ return 2;
57
+ }
58
+ if (parsed.help || parsed.command === undefined) {
59
+ process.stdout.write(HELP);
60
+ return parsed.help ? 0 : 2;
61
+ }
62
+
63
+ // `--all` belongs to `remove` alone. Silently ignoring it elsewhere would let
64
+ // `tools install --all` read as a request that was honored.
65
+ if (parsed.all && parsed.command !== "remove") {
66
+ printError("--all is only valid with `tools remove`");
67
+ return 2;
68
+ }
69
+
70
+ switch (parsed.command) {
71
+ case "list":
72
+ if (parsed.resetProfile) return invalidResetProfile();
73
+ return listTools(parsed.json);
74
+ case "status":
75
+ return statusTool(parsed.positional[0], parsed.json, parsed.resetProfile);
76
+ case "install":
77
+ if (parsed.resetProfile) return invalidResetProfile();
78
+ return installOne(parsed.positional[0], parsed.force, parsed.json);
79
+ case "remove":
80
+ if (parsed.resetProfile) return invalidResetProfile();
81
+ return parsed.all ? removeAll(parsed.positional[0], parsed.json) : removeOne(parsed.positional[0], parsed.json);
82
+ default:
83
+ printError(`unknown tools command: ${parsed.command}`);
84
+ process.stderr.write(HELP);
85
+ return 2;
86
+ }
87
+ }
88
+
89
+ function parse(argv: ReadonlyArray<string>): Parsed {
90
+ const out: Parsed = { positional: [], json: false, force: false, all: false, resetProfile: false, help: false };
91
+ for (const arg of argv) {
92
+ if (!out.command && !arg.startsWith("-")) {
93
+ out.command = arg;
94
+ continue;
95
+ }
96
+ switch (arg) {
97
+ case "--json":
98
+ out.json = true;
99
+ break;
100
+ case "--force":
101
+ case "-f":
102
+ out.force = true;
103
+ break;
104
+ case "--all":
105
+ out.all = true;
106
+ break;
107
+ case "--reset-profile":
108
+ out.resetProfile = true;
109
+ break;
110
+ case "--help":
111
+ case "-h":
112
+ out.help = true;
113
+ break;
114
+ default:
115
+ if (arg.startsWith("-")) throw new Error(`unknown flag: ${arg}`);
116
+ out.positional.push(arg);
117
+ }
118
+ }
119
+ return out;
120
+ }
121
+
122
+ function listTools(json: boolean): number {
123
+ const statuses = toolStatuses();
124
+ if (json) {
125
+ process.stdout.write(`${JSON.stringify(statuses.map(jsonShape), null, 2)}\n`);
126
+ return 0;
127
+ }
128
+ const rows = statuses.map((status) => [
129
+ status.id,
130
+ status.version,
131
+ status.license,
132
+ status.resolution.source,
133
+ describeResolution(status),
134
+ ]);
135
+ process.stdout.write(formatColumns([["TOOL", "PIN", "LICENSE", "SOURCE", "RESOLVED"], ...rows]));
136
+ return 0;
137
+ }
138
+
139
+ function invalidResetProfile(): number {
140
+ printError("--reset-profile is only valid with `tools status yazi`");
141
+ return 2;
142
+ }
143
+
144
+ /**
145
+ * The one refusal an unknown id gets, from every verb.
146
+ *
147
+ * It names the tools that do exist, because the whole error an operator makes
148
+ * here is a name: `tools remove herd` and `tools remove tmux` want different
149
+ * answers, and the registry list gives both of them one.
150
+ */
151
+ function unknownTool(id: string): number {
152
+ printError(`unknown tool: ${id} (known: ${PINNED_TOOLS.map((row) => row.id).join(", ")})`);
153
+ return 2;
154
+ }
155
+
156
+ function statusTool(id: string | undefined, json: boolean, resetProfile: boolean): number {
157
+ if (id === undefined) {
158
+ printError("usage: clio-coder tools status <id>");
159
+ return 2;
160
+ }
161
+ const entry = findPinnedTool(id);
162
+ if (entry === null) {
163
+ return unknownTool(id);
164
+ }
165
+ if (resetProfile && id !== "yazi") return invalidResetProfile();
166
+ if (resetProfile) resetYaziProfile();
167
+ const status = toolStatus(entry);
168
+ const profile = id === "yazi" ? inspectCurrentYaziProfile() : null;
169
+ if (json) {
170
+ process.stdout.write(
171
+ `${JSON.stringify({ ...jsonShape(status), ...(profile ? { profile, profileReset: resetProfile } : {}) }, null, 2)}\n`,
172
+ );
173
+ return 0;
174
+ }
175
+ const rejection = describeFloorRejection(status);
176
+ const installedVersions = installedToolVersions(entry.id);
177
+ const lines = [
178
+ `${entry.id} ${entry.version} (${entry.license})`,
179
+ ` ${entry.summary}`,
180
+ ` homepage ${entry.homepage}`,
181
+ ` binaries ${entry.binaries.join(", ")}`,
182
+ ` PATH floor ${entry.minimumVersion}`,
183
+ ` platform ${status.platform ?? `${process.platform}-${process.arch}`}${status.supported ? "" : " (no pinned asset)"}`,
184
+ ` vendor dir ${status.installDir}${status.installed ? "" : " (not installed)"}`,
185
+ ` vendored ${installedVersions.length === 0 ? "nothing" : installedVersions.join(", ")}`,
186
+ ` resolves to ${describeResolution(status)}`,
187
+ // Spelled out on its own line as well as inside the resolution sentence,
188
+ // because a rejected PATH copy is the one thing an operator reads this
189
+ // command to understand and the sentence above may be about something else.
190
+ ...(rejection === null
191
+ ? []
192
+ : [
193
+ ` floor rejected: ${rejection}`,
194
+ ...(status.resolution.source === "none" && status.supported ? [` remedy ${installRemedy(entry.id)}`] : []),
195
+ ]),
196
+ ...(profile
197
+ ? [
198
+ ` profile ${describeYaziProfile(profile)}`,
199
+ ...(resetProfile ? [" profile reset; next open regenerates it"] : []),
200
+ ]
201
+ : []),
202
+ ];
203
+ process.stdout.write(`${lines.join("\n")}\n`);
204
+ return 0;
205
+ }
206
+
207
+ async function installOne(id: string | undefined, force: boolean, json: boolean): Promise<number> {
208
+ if (id === undefined) {
209
+ printError("usage: clio-coder tools install <id>");
210
+ return 2;
211
+ }
212
+ if (findPinnedTool(id) === null) {
213
+ return unknownTool(id);
214
+ }
215
+ const result = await installTool(id, {
216
+ force,
217
+ ...(json ? {} : { onProgress: (message: string) => process.stderr.write(` ${message}\n`) }),
218
+ });
219
+ if (json) {
220
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
221
+ return result.ok ? 0 : 1;
222
+ }
223
+ if (!result.ok) {
224
+ printError(result.message);
225
+ return 1;
226
+ }
227
+ printOk(result.message);
228
+ for (const path of result.documents) process.stdout.write(` license ${path}\n`);
229
+ for (const version of result.pruned) process.stdout.write(` pruned ${id} ${version}\n`);
230
+ return 0;
231
+ }
232
+
233
+ /**
234
+ * Delete a tool's vendored install.
235
+ *
236
+ * The two answers an operator can act on are "there was nothing there" and
237
+ * "these versions are gone", and both are exit 0: the state they asked for
238
+ * holds either way. Only an id the registry does not know is a usage error,
239
+ * because that is the one case where nothing was inspected at all.
240
+ */
241
+ function removeOne(id: string | undefined, json: boolean): number {
242
+ if (id === undefined) {
243
+ printError("usage: clio-coder tools remove <id>");
244
+ return 2;
245
+ }
246
+ if (findPinnedTool(id) === null) return unknownTool(id);
247
+ const result = removeTool(id);
248
+ if (json) {
249
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
250
+ return result.ok ? 0 : 1;
251
+ }
252
+ if (!result.ok) {
253
+ printError(result.message);
254
+ return 1;
255
+ }
256
+ printOk(result.message);
257
+ if (result.removed.length > 0) {
258
+ process.stdout.write(` reinstall with \`${installRemedy(id)}\`\n`);
259
+ }
260
+ return 0;
261
+ }
262
+
263
+ /**
264
+ * Delete every pinned tool's vendored install.
265
+ *
266
+ * Each tool is swept independently and the exit code is the worst of them, so
267
+ * one directory that resists deletion does not hide the two that came off
268
+ * cleanly. Tools that had nothing installed are counted rather than listed:
269
+ * on the machine this verb is for, most of the registry is usually absent, and
270
+ * three lines of "nothing to remove" would bury the one line that matters.
271
+ */
272
+ function removeAll(id: string | undefined, json: boolean): number {
273
+ if (id !== undefined) {
274
+ printError("`tools remove --all` takes no tool id; drop the id or drop --all");
275
+ return 2;
276
+ }
277
+ const results = PINNED_TOOLS.map((entry) => removeTool(entry.id));
278
+ if (json) {
279
+ process.stdout.write(`${JSON.stringify(results, null, 2)}\n`);
280
+ return results.every((result) => result.ok) ? 0 : 1;
281
+ }
282
+ for (const result of results) {
283
+ if (!result.ok) printError(result.message);
284
+ else if (result.removed.length > 0 || result.staleStaging.length > 0) printOk(result.message);
285
+ }
286
+ const touched = results.filter((result) => result.ok && result.removed.length > 0);
287
+ const untouched = results.filter((result) => result.ok && result.removed.length === 0);
288
+ if (touched.length > 0) {
289
+ process.stdout.write(` reinstall with \`clio-coder tools install <id>\`\n`);
290
+ }
291
+ if (untouched.length > 0) {
292
+ process.stdout.write(` ${untouched.length} of ${results.length} had nothing vendored\n`);
293
+ }
294
+ return results.every((result) => result.ok) ? 0 : 1;
295
+ }
296
+
297
+ /** The JSON surface, flattened so a script does not have to walk the resolution. */
298
+ function jsonShape(status: ToolStatus): Record<string, unknown> {
299
+ return {
300
+ id: status.id,
301
+ version: status.version,
302
+ license: status.license,
303
+ platform: status.platform,
304
+ supported: status.supported,
305
+ installed: status.installed,
306
+ installDir: status.installDir,
307
+ source: status.resolution.source,
308
+ binaryPath: status.resolution.binaryPath,
309
+ foundVersion: status.resolution.version,
310
+ minimumVersion: status.resolution.entry?.minimumVersion ?? null,
311
+ pathCandidate: status.resolution.pathCandidate,
312
+ // A script deciding whether to warn about a floor rejection should not have
313
+ // to parse `detail` for it, so the rejection and the command that answers
314
+ // it are their own fields. `remedy` is null exactly where installing would
315
+ // change nothing: already vendored, or no asset for this platform.
316
+ floorRejection: describeFloorRejection(status),
317
+ remedy: status.resolution.source === "none" && status.supported ? installRemedy(status.id) : null,
318
+ installedVersions: installedToolVersions(status.id),
319
+ detail: describeResolution(status),
320
+ };
321
+ }