@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
@@ -0,0 +1,265 @@
1
+ import type { PinnedTool, ToolPlatform } from "./types.js";
2
+
3
+ /**
4
+ * The pinned table.
5
+ *
6
+ * Every checksum here was computed from the asset as downloaded from the URL
7
+ * beside it, not copied from a release note. Where upstream publishes its own
8
+ * checksum file the two were compared and agree; where upstream publishes no
9
+ * checksums, only the platform whose asset was actually fetched and hashed is
10
+ * listed, because a hash nobody verified is worse than a missing platform: it
11
+ * turns an honest "no asset for your platform" into a failed install that
12
+ * looks like tampering.
13
+ *
14
+ * Bumping a version means re-downloading each listed asset and re-computing
15
+ * each hash. The registry-shape contract test refuses an entry that carries a
16
+ * platform without a checksum. A bump also supersedes whatever version the
17
+ * machine already has, and the installer prunes it, so a tool holds exactly one
18
+ * version directory.
19
+ *
20
+ * `minimumVersion` is the floor a copy already on PATH has to clear. A floor
21
+ * moves on evidence and on nothing else: name the older release, say which of
22
+ * its surfaces you exercised and how, and put that in the comment beside the
23
+ * number. A rejection that felt noisy is not evidence. Where no such
24
+ * measurement exists the floor sits at the pin, because a release Clio was
25
+ * never run against is not something to discover through a failure that reads
26
+ * as a bug in the feature.
27
+ *
28
+ * The cost of a floor lands on an operator whose own copy is a release or two
29
+ * behind, so `describeResolution` in `resolve.ts` is required to name what it
30
+ * found, the floor it missed, and the command that fixes it.
31
+ */
32
+ export const PINNED_TOOLS: ReadonlyArray<PinnedTool> = [
33
+ {
34
+ id: "herdr",
35
+ version: "0.8.2",
36
+ summary: "terminal multiplexer with an agent-aware socket API; powers Clio panes",
37
+ homepage: "https://herdr.dev",
38
+ license: "Apache-2.0",
39
+ binaries: ["herdr"],
40
+ primaryBinary: "herdr",
41
+ // Lowered from the pin on evidence, which is the bar this file sets for
42
+ // moving a floor. `herdr api schema --json` was read from 0.7.5
43
+ // (protocol 17) and 0.8.2 (protocol 20): every method the mux domain
44
+ // sends exists in both, and the only two 0.8.2 adds, `workspace.move_block`
45
+ // and `workspace.reordered`, are ones Clio never sends. The two methods
46
+ // that are not universal are already gated at runtime by protocol number
47
+ // in `src/domains/mux/protocol.ts`, whose own floor is 17, so an operator's
48
+ // 0.7.5 takes the documented fallback rather than failing. 0.7.5 is the
49
+ // oldest release actually checked, not the oldest that might work.
50
+ minimumVersion: "0.7.5",
51
+ versionArgs: ["--version"],
52
+ downloads: {
53
+ "linux-x64": {
54
+ url: "https://github.com/herdrdev/herdr/releases/download/v0.8.2/herdr-linux-x86_64",
55
+ sha256: "976150a14d490c94b243ea2e1a7eb2dfb67f12e36b182db90936f6728e6aecf4",
56
+ archive: "raw",
57
+ binaryMembers: { herdr: "" },
58
+ documentMembers: [],
59
+ },
60
+ "linux-arm64": {
61
+ url: "https://github.com/herdrdev/herdr/releases/download/v0.8.2/herdr-linux-aarch64",
62
+ sha256: "f55610658e1c2e0d2aaef730b4b2ab885f7f8ba00285ab372bfb14f2e3d5b40d",
63
+ archive: "raw",
64
+ binaryMembers: { herdr: "" },
65
+ documentMembers: [],
66
+ },
67
+ "darwin-x64": {
68
+ url: "https://github.com/herdrdev/herdr/releases/download/v0.8.2/herdr-macos-x86_64",
69
+ sha256: "ab50262c8190cd7aa9056d249d255c08c328c3e8716de9cfa29db4f131b8e2c1",
70
+ archive: "raw",
71
+ binaryMembers: { herdr: "" },
72
+ documentMembers: [],
73
+ },
74
+ "darwin-arm64": {
75
+ url: "https://github.com/herdrdev/herdr/releases/download/v0.8.2/herdr-macos-aarch64",
76
+ sha256: "a5d4f4d504d8b309c91f811050559300faba31258425f53c50852fc96f6ae574",
77
+ archive: "raw",
78
+ binaryMembers: { herdr: "" },
79
+ documentMembers: [],
80
+ },
81
+ },
82
+ // No `win32-x64` entry, and not for want of an asset. herdr publishes
83
+ // `herdr-windows-x86_64.zip` for this tag and it was downloaded and read:
84
+ // alongside `herdr.exe` it carries a ConPTY runtime, `conpty/conpty.dll`
85
+ // and `conpty/x64/OpenConsole.exe`, that has to sit in a subdirectory
86
+ // beside the executable. The installer places every declared member flat
87
+ // under its basename, so declaring this asset would install a `herdr.exe`
88
+ // with its runtime scattered next to it, which is a broken install that
89
+ // checksums and unpacks cleanly. Windows herdr waits on the installer
90
+ // learning to preserve a member's relative path.
91
+ //
92
+ // The release asset is a bare executable, so the Apache-2.0 text comes
93
+ // from the repository at the pinned tag.
94
+ documents: [
95
+ {
96
+ name: "LICENSE",
97
+ url: "https://raw.githubusercontent.com/herdrdev/herdr/v0.8.2/LICENSE",
98
+ sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
99
+ },
100
+ ],
101
+ },
102
+ {
103
+ id: "yazi",
104
+ version: "26.8.15",
105
+ summary: "terminal file manager; the file-picker pane preset",
106
+ homepage: "https://yazi-rs.github.io",
107
+ license: "MIT",
108
+ binaries: ["yazi", "ya"],
109
+ primaryBinary: "yazi",
110
+ // Stays at the pin, now for a measured reason rather than caution. On
111
+ // 26.1.22 the one command Clio sends, `ya emit-to <receiver> cd <path>`,
112
+ // has the same signature it has at the pin. What was never exercised
113
+ // there is the rest of the surface: the managed profile Clio generates
114
+ // (`yazi.toml`, `keymap.toml`, `init.lua`) and the DDS payload shapes a
115
+ // pick comes back in. A profile schema mismatch degrades quietly into a
116
+ // file manager that opens and does the wrong thing, which is worse than
117
+ // vendoring a second copy. Lower this once a pick round trip has been
118
+ // driven end to end on the older release.
119
+ minimumVersion: "26.8.15",
120
+ versionArgs: ["--version"],
121
+ downloads: {
122
+ "linux-x64": {
123
+ url: "https://github.com/sxyazi/yazi/releases/download/v26.8.15/yazi-x86_64-unknown-linux-gnu.zip",
124
+ sha256: "cc67eb7991550c2f9407cda52d3f5af0937627aa6884e7de99a04fcf059807e0",
125
+ archive: "zip",
126
+ binaryMembers: {
127
+ yazi: "yazi-x86_64-unknown-linux-gnu/yazi",
128
+ ya: "yazi-x86_64-unknown-linux-gnu/ya",
129
+ },
130
+ documentMembers: ["yazi-x86_64-unknown-linux-gnu/LICENSE"],
131
+ },
132
+ "linux-arm64": {
133
+ url: "https://github.com/sxyazi/yazi/releases/download/v26.8.15/yazi-aarch64-unknown-linux-gnu.zip",
134
+ sha256: "f5a85771f06bb0e8c488136ae0aedaec8d341a7cee995549df391d7d852fe8d1",
135
+ archive: "zip",
136
+ binaryMembers: {
137
+ yazi: "yazi-aarch64-unknown-linux-gnu/yazi",
138
+ ya: "yazi-aarch64-unknown-linux-gnu/ya",
139
+ },
140
+ documentMembers: ["yazi-aarch64-unknown-linux-gnu/LICENSE"],
141
+ },
142
+ "darwin-x64": {
143
+ url: "https://github.com/sxyazi/yazi/releases/download/v26.8.15/yazi-x86_64-apple-darwin.zip",
144
+ sha256: "70bb2bcf57d8af862a54e2d12f2fddceefb9aa4ba3783e9a4dcbf2a8e64aacb3",
145
+ archive: "zip",
146
+ binaryMembers: {
147
+ yazi: "yazi-x86_64-apple-darwin/yazi",
148
+ ya: "yazi-x86_64-apple-darwin/ya",
149
+ },
150
+ documentMembers: ["yazi-x86_64-apple-darwin/LICENSE"],
151
+ },
152
+ "darwin-arm64": {
153
+ url: "https://github.com/sxyazi/yazi/releases/download/v26.8.15/yazi-aarch64-apple-darwin.zip",
154
+ sha256: "3f54907ea08abe96506f4b22239340ed8923a6aeaeae78f33d59bce57daca4cd",
155
+ archive: "zip",
156
+ binaryMembers: {
157
+ yazi: "yazi-aarch64-apple-darwin/yazi",
158
+ ya: "yazi-aarch64-apple-darwin/ya",
159
+ },
160
+ documentMembers: ["yazi-aarch64-apple-darwin/LICENSE"],
161
+ },
162
+ // Same shape as the other three: two executables and a LICENSE at
163
+ // known member paths, so the installer needs nothing new to place it.
164
+ "win32-x64": {
165
+ url: "https://github.com/sxyazi/yazi/releases/download/v26.8.15/yazi-x86_64-pc-windows-msvc.zip",
166
+ sha256: "451f6770999fa8f9b08e6c9f94a688c263b6d3007b0944c4407f1ae335eace30",
167
+ archive: "zip",
168
+ binaryMembers: {
169
+ yazi: "yazi-x86_64-pc-windows-msvc/yazi.exe",
170
+ ya: "yazi-x86_64-pc-windows-msvc/ya.exe",
171
+ },
172
+ documentMembers: ["yazi-x86_64-pc-windows-msvc/LICENSE"],
173
+ },
174
+ },
175
+ documents: [],
176
+ },
177
+ {
178
+ id: "croc",
179
+ version: "11.3.6",
180
+ summary: "relay file transfer between machines; the transfer primitive's first backend",
181
+ homepage: "https://schollz.com/software/croc6",
182
+ license: "MIT",
183
+ binaries: ["croc"],
184
+ primaryBinary: "croc",
185
+ // Croc negotiates its relay protocol by major version, so any 11.x on
186
+ // PATH speaks to a pinned 11.x relay.
187
+ minimumVersion: "11.0.0",
188
+ versionArgs: ["--version"],
189
+ downloads: {
190
+ "linux-x64": {
191
+ url: "https://github.com/schollz/croc/releases/download/v11.3.6/croc_v11.3.6_Linux-64bit.tar.gz",
192
+ sha256: "bd18e01024f5ccc8e101c08c8233d4cffbfda4ff59acad80eaa1fc2963efc0b2",
193
+ archive: "tar.gz",
194
+ binaryMembers: { croc: "croc" },
195
+ documentMembers: ["LICENSE", "THIRD_PARTY_NOTICES.md"],
196
+ },
197
+ "linux-arm64": {
198
+ url: "https://github.com/schollz/croc/releases/download/v11.3.6/croc_v11.3.6_Linux-ARM64.tar.gz",
199
+ sha256: "c26ac67207301ed75ae0ece63796ec8a2a002b1cf64f4e3d3d8e7bcee508b5b3",
200
+ archive: "tar.gz",
201
+ binaryMembers: { croc: "croc" },
202
+ documentMembers: ["LICENSE", "THIRD_PARTY_NOTICES.md"],
203
+ },
204
+ "darwin-x64": {
205
+ url: "https://github.com/schollz/croc/releases/download/v11.3.6/croc_v11.3.6_macOS-64bit.tar.gz",
206
+ sha256: "701817a20f4d2bb4312f3234e6d328e0bdd68d6d1311db3a4ab4daf10906c5b2",
207
+ archive: "tar.gz",
208
+ binaryMembers: { croc: "croc" },
209
+ documentMembers: ["LICENSE", "THIRD_PARTY_NOTICES.md"],
210
+ },
211
+ "darwin-arm64": {
212
+ url: "https://github.com/schollz/croc/releases/download/v11.3.6/croc_v11.3.6_macOS-ARM64.tar.gz",
213
+ sha256: "96c4ef67751b4387e3d44a7a559aafe54094f0089f34a6c71dfb5361bd48d368",
214
+ archive: "tar.gz",
215
+ binaryMembers: { croc: "croc" },
216
+ documentMembers: ["LICENSE", "THIRD_PARTY_NOTICES.md"],
217
+ },
218
+ // A zip rather than a tarball on Windows, and the only entry in the
219
+ // table whose hash is confirmed by upstream's own checksums file
220
+ // rather than only by the download this repository made.
221
+ "win32-x64": {
222
+ url: "https://github.com/schollz/croc/releases/download/v11.3.6/croc_v11.3.6_Windows-64bit.zip",
223
+ sha256: "ed22552d371d55a9e3c3b612b982484fa00adaff8fb32c3f19f36dbf8e248bbf",
224
+ archive: "zip",
225
+ binaryMembers: { croc: "croc.exe" },
226
+ documentMembers: ["LICENSE", "THIRD_PARTY_NOTICES.md"],
227
+ },
228
+ },
229
+ documents: [],
230
+ },
231
+ ];
232
+
233
+ /** The registry's platform key for the running process, or null when unmapped. */
234
+ export function currentToolPlatform(): ToolPlatform | null {
235
+ const platform = process.platform;
236
+ const arch = process.arch;
237
+ if (platform === "linux") {
238
+ if (arch === "x64") return "linux-x64";
239
+ if (arch === "arm64") return "linux-arm64";
240
+ return null;
241
+ }
242
+ if (platform === "darwin") {
243
+ if (arch === "x64") return "darwin-x64";
244
+ if (arch === "arm64") return "darwin-arm64";
245
+ return null;
246
+ }
247
+ if (platform === "win32" && arch === "x64") return "win32-x64";
248
+ return null;
249
+ }
250
+
251
+ /** The row with this id. */
252
+ export function findPinnedTool(id: string): PinnedTool | null {
253
+ return PINNED_TOOLS.find((entry) => entry.id === id) ?? null;
254
+ }
255
+
256
+ /**
257
+ * The row that owns this executable name.
258
+ *
259
+ * Resolution is asked for by binary name (`ya` belongs to yazi), so the lookup
260
+ * has to cover every name an entry installs, not just the tool id.
261
+ */
262
+ export function findPinnedToolByBinary(name: string): PinnedTool | null {
263
+ const bare = name.endsWith(".exe") ? name.slice(0, -4) : name;
264
+ return PINNED_TOOLS.find((entry) => entry.binaries.includes(bare)) ?? null;
265
+ }
@@ -0,0 +1,218 @@
1
+ import { readdirSync, rmdirSync, rmSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { toolchainRoot } from "./paths.js";
4
+ import { findPinnedTool } from "./registry.js";
5
+
6
+ /**
7
+ * Deleting vendored installs, and pruning the versions a pin bump superseded.
8
+ *
9
+ * Everything here only ever unlinks inside `<data>/tools/<id>/`, a directory
10
+ * Clio created by downloading it, so nothing an operator wrote is reachable
11
+ * from this file. That is what makes removal safe without a confirmation
12
+ * prompt: the worst outcome is a re-download of bytes the registry pins by
13
+ * checksum anyway.
14
+ *
15
+ * Version directories are the only thing considered. A name starting with `.`
16
+ * is an installer's staging or retired directory (`.<version>.incomplete-`,
17
+ * `.<version>.replaced-`), and a prune that raced an install and deleted one
18
+ * would corrupt that install. A full remove is different: it takes the whole
19
+ * `<id>` tree because no install of that tool is meant to survive it.
20
+ *
21
+ * A staging directory old enough to be dead is swept anyway. A killed install
22
+ * used to leave one behind permanently, invisible to every verb and reachable
23
+ * only by an operator who went reading under the data root.
24
+ */
25
+
26
+ export interface ToolRemoveOptions {
27
+ /** Vendor root override. Defaults to the data root's `tools` directory. */
28
+ root?: string;
29
+ /**
30
+ * How old a staging directory must be before it counts as abandoned.
31
+ * Defaults to `STALE_STAGING_MS`. Tests set it; nothing else should need to.
32
+ */
33
+ staleStagingMs?: number;
34
+ }
35
+
36
+ /**
37
+ * How long a staging directory may sit before a sweep treats it as dead.
38
+ *
39
+ * The window is far shorter than it looks. `installPinnedTool` downloads every
40
+ * asset and verifies every checksum *before* it creates the staging directory,
41
+ * so that directory's whole life is a handful of local file writes and two
42
+ * renames: sub-second normally, seconds on a struggling disk. An hour is twelve
43
+ * times the installer's own per-asset download timeout and orders of magnitude
44
+ * past the writes, which leaves no plausible live install inside it.
45
+ *
46
+ * The pid in the name is deliberately not consulted. Pids are reused, a pid
47
+ * belonging to another user answers a liveness probe with EPERM rather than an
48
+ * honest yes or no, and a data root shared over a network filesystem makes the
49
+ * number meaningless. Age is the one signal that means the same thing
50
+ * everywhere.
51
+ */
52
+ export const STALE_STAGING_MS = 3_600_000;
53
+
54
+ /** What one sweep of a tool's directory deleted, and what it could not. */
55
+ export interface ToolRemoveResult {
56
+ ok: boolean;
57
+ id: string;
58
+ /** `<root>/<id>`, whether or not it exists. */
59
+ dir: string;
60
+ /** Version directories this sweep deleted, oldest name first. */
61
+ removed: string[];
62
+ /** Version directories that survived a delete attempt, with the reason. */
63
+ failed: ReadonlyArray<{ version: string; error: string }>;
64
+ /** Abandoned staging directories this sweep collected, by name. */
65
+ staleStaging: string[];
66
+ message: string;
67
+ }
68
+
69
+ /** Version directories currently installed for a tool, sorted. */
70
+ export function installedToolVersions(id: string, options: ToolRemoveOptions = {}): string[] {
71
+ const dir = join(options.root ?? toolchainRoot(), id);
72
+ let names: string[];
73
+ try {
74
+ names = readdirSync(dir);
75
+ } catch {
76
+ // A tool nobody ever installed has no directory, which is not an error:
77
+ // the answer to "which versions are installed" is none.
78
+ return [];
79
+ }
80
+ return names
81
+ .filter((name) => !name.startsWith(".") && isDirectory(join(dir, name)))
82
+ .sort((a, b) => a.localeCompare(b));
83
+ }
84
+
85
+ /**
86
+ * Delete every vendored version of a tool, then the tool's own directory.
87
+ *
88
+ * A tool with nothing installed is a no-op that reports success: the operator
89
+ * asked for a state ("this tool is not vendored") that already holds, and
90
+ * failing them for it would make the verb unusable in a script.
91
+ */
92
+ export function removeTool(id: string, options: ToolRemoveOptions = {}): ToolRemoveResult {
93
+ if (findPinnedTool(id) === null) {
94
+ const dir = join(options.root ?? toolchainRoot(), id);
95
+ return { ok: false, id, dir, removed: [], failed: [], staleStaging: [], message: `unknown tool: ${id}` };
96
+ }
97
+ return removeVersions(id, null, options);
98
+ }
99
+
100
+ /**
101
+ * Delete every vendored version of a tool except `keep`.
102
+ *
103
+ * Called by the installer once a version is in place, so a pin bump leaves one
104
+ * version directory behind rather than one per version the machine ever ran.
105
+ * The kept version is not required to exist: pruning to a version that is not
106
+ * installed empties the tool, which is what `--force` recovery wants.
107
+ */
108
+ export function pruneSupersededVersions(id: string, keep: string, options: ToolRemoveOptions = {}): ToolRemoveResult {
109
+ return removeVersions(id, keep, options);
110
+ }
111
+
112
+ function removeVersions(id: string, keep: string | null, options: ToolRemoveOptions): ToolRemoveResult {
113
+ const root = options.root ?? toolchainRoot();
114
+ const dir = join(root, id);
115
+ const versions = installedToolVersions(id, { root }).filter((version) => version !== keep);
116
+ const removed: string[] = [];
117
+ const failed: { version: string; error: string }[] = [];
118
+ for (const version of versions) {
119
+ try {
120
+ rmSync(join(dir, version), { recursive: true, force: true });
121
+ removed.push(version);
122
+ } catch (error) {
123
+ failed.push({ version, error: error instanceof Error ? error.message : String(error) });
124
+ }
125
+ }
126
+ // Before the rmdir below, so an install killed weeks ago cannot keep the tool
127
+ // directory alive after every version of it is gone.
128
+ const staleStaging = sweepStaleStaging(dir, options.staleStagingMs ?? STALE_STAGING_MS);
129
+ if (keep === null && failed.length === 0) {
130
+ // The tool is gone, so its directory is litter. `rmdir` takes it only when
131
+ // it is empty, so a staging directory young enough to belong to a running
132
+ // installer makes this fail instead of destroying that install.
133
+ try {
134
+ rmdirSync(dir);
135
+ } catch {
136
+ // Either it was never there or something still lives in it. Neither
137
+ // changes what this call promised: no version of the tool remains.
138
+ }
139
+ }
140
+ return {
141
+ ok: failed.length === 0,
142
+ id,
143
+ dir,
144
+ removed,
145
+ failed,
146
+ staleStaging,
147
+ message: describeRemoval(id, dir, keep, removed, failed, staleStaging),
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Delete the abandoned staging directories under one tool.
153
+ *
154
+ * Only names the installer produces are considered, so an operator who parked
155
+ * something of their own under `<data>/tools/<id>` with a leading dot keeps it.
156
+ * A directory whose age cannot be read is left alone: an unreadable stat is not
157
+ * evidence of death.
158
+ */
159
+ function sweepStaleStaging(dir: string, staleMs: number): string[] {
160
+ let names: string[];
161
+ try {
162
+ names = readdirSync(dir);
163
+ } catch {
164
+ return [];
165
+ }
166
+ const cutoff = Date.now() - staleMs;
167
+ const swept: string[] = [];
168
+ for (const name of names.sort((a, b) => a.localeCompare(b))) {
169
+ if (!STAGING_NAME.test(name)) continue;
170
+ const stat = statSync(join(dir, name), { throwIfNoEntry: false });
171
+ if (stat === undefined || !stat.isDirectory() || stat.mtimeMs > cutoff) continue;
172
+ try {
173
+ rmSync(join(dir, name), { recursive: true, force: true });
174
+ swept.push(name);
175
+ } catch {
176
+ // A staging directory that resists deletion is litter, not a failure:
177
+ // nothing resolves it, and reporting it would turn a successful install
178
+ // into a failed one over a directory the ladder never looks at.
179
+ }
180
+ }
181
+ return swept;
182
+ }
183
+
184
+ /** `.<version>.incomplete-<pid>-<t>` and `.<version>.replaced-<pid>-<t>`. */
185
+ const STAGING_NAME = /^\.[^/]+\.(?:incomplete|replaced)-\d+-[a-z0-9]+$/;
186
+
187
+ function describeRemoval(
188
+ id: string,
189
+ dir: string,
190
+ keep: string | null,
191
+ removed: ReadonlyArray<string>,
192
+ failed: ReadonlyArray<{ version: string; error: string }>,
193
+ staleStaging: ReadonlyArray<string>,
194
+ ): string {
195
+ // Reported as a suffix rather than as the headline: an abandoned staging
196
+ // directory is housekeeping, and the operator asked about versions.
197
+ const swept =
198
+ staleStaging.length === 0
199
+ ? ""
200
+ : `; swept ${staleStaging.length} abandoned install ${staleStaging.length === 1 ? "directory" : "directories"}`;
201
+ const trouble = failed.map((entry) => `${entry.version} (${entry.error})`).join(", ");
202
+ if (failed.length > 0) {
203
+ const done = removed.length > 0 ? `removed ${removed.join(", ")}; ` : "";
204
+ return `${done}could not remove ${id} ${trouble} under ${dir}${swept}`;
205
+ }
206
+ if (removed.length === 0) {
207
+ return keep === null
208
+ ? `${id} is not installed under ${dir}; nothing to remove${swept}`
209
+ : `${id} has no superseded versions under ${dir}${swept}`;
210
+ }
211
+ const what = `${id} ${removed.join(", ")}`;
212
+ const headline = keep === null ? `removed ${what} from ${dir}` : `pruned ${what} from ${dir}, keeping ${keep}`;
213
+ return `${headline}${swept}`;
214
+ }
215
+
216
+ function isDirectory(path: string): boolean {
217
+ return statSync(path, { throwIfNoEntry: false })?.isDirectory() === true;
218
+ }
@@ -0,0 +1,182 @@
1
+ import { statSync } from "node:fs";
2
+ import { delimiter, join } from "node:path";
3
+ import { toolVersionDir, vendoredBinaryPath } from "./paths.js";
4
+ import { currentToolPlatform, findPinnedTool, findPinnedToolByBinary, PINNED_TOOLS } from "./registry.js";
5
+ import type { PinnedTool, ToolPathCandidate, ToolResolution, ToolStatus } from "./types.js";
6
+ import { probeBinaryVersion, satisfiesMinimum } from "./version.js";
7
+
8
+ /**
9
+ * The shared resolution ladder: PATH, then the vendored pin, then nothing.
10
+ *
11
+ * PATH comes first because a tool the operator installed themselves is the one
12
+ * they maintain, and Clio downloading a second copy of a program already on the
13
+ * machine is a surprise. It only wins when its version clears the registry's
14
+ * floor, because the alternative is Clio driving a surface that release does not
15
+ * have and reporting the resulting failure as a bug in the feature.
16
+ *
17
+ * Nothing here downloads, spawns a server, or writes to disk. It is safe on the
18
+ * interactive startup path and inside doctor, both of which promise to create
19
+ * nothing.
20
+ */
21
+
22
+ /** First executable named `name` on PATH, or null. */
23
+ export function findExecutableOnPath(name: string): string | null {
24
+ const pathEnv = process.env.PATH;
25
+ if (!pathEnv) return null;
26
+ const candidates = process.platform === "win32" ? [`${name}.exe`, name] : [name];
27
+ for (const dir of pathEnv.split(delimiter)) {
28
+ if (!dir) continue;
29
+ for (const candidate of candidates) {
30
+ const full = join(dir, candidate);
31
+ const stat = statSync(full, { throwIfNoEntry: false });
32
+ if (stat?.isFile() && (process.platform === "win32" || (stat.mode & 0o111) !== 0)) return full;
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Resolve one executable name through the ladder.
40
+ *
41
+ * A name that is not in the registry gets a plain PATH lookup: there is no pin
42
+ * to compare against and nothing vendored to fall back to, so the answer is
43
+ * whatever PATH says.
44
+ */
45
+ export function resolveToolBinary(name: string): ToolResolution {
46
+ const entry = findPinnedToolByBinary(name) ?? findPinnedTool(name);
47
+ if (entry === null) {
48
+ const found = findExecutableOnPath(name);
49
+ return {
50
+ source: found === null ? "none" : "path",
51
+ binaryPath: found,
52
+ version: null,
53
+ entry: null,
54
+ pathCandidate: found === null ? null : { path: found, version: null, satisfiesMinimum: true },
55
+ vendoredPath: null,
56
+ };
57
+ }
58
+ // An entry asked for by tool id resolves its primary binary; asked for by
59
+ // binary name it resolves that binary, so `ya` does not silently become `yazi`.
60
+ const binary = entry.binaries.includes(name) ? name : entry.primaryBinary;
61
+ return resolveEntryBinary(entry, binary);
62
+ }
63
+
64
+ /** The ladder for a known registry row and one of its binaries. */
65
+ export function resolveEntryBinary(entry: PinnedTool, binary: string): ToolResolution {
66
+ const pathCandidate = probePathCandidate(entry, binary);
67
+ const vendored = vendoredBinaryPath(entry.id, entry.version, binary);
68
+ const vendoredStat = statSync(vendored, { throwIfNoEntry: false });
69
+ const vendoredPath = vendoredStat?.isFile() ? vendored : null;
70
+
71
+ if (pathCandidate?.satisfiesMinimum) {
72
+ return {
73
+ source: "path",
74
+ binaryPath: pathCandidate.path,
75
+ version: pathCandidate.version,
76
+ entry,
77
+ pathCandidate,
78
+ vendoredPath,
79
+ };
80
+ }
81
+ if (vendoredPath !== null) {
82
+ return {
83
+ source: "vendored",
84
+ binaryPath: vendoredPath,
85
+ version: entry.version,
86
+ entry,
87
+ pathCandidate,
88
+ vendoredPath,
89
+ };
90
+ }
91
+ return { source: "none", binaryPath: null, version: null, entry, pathCandidate, vendoredPath: null };
92
+ }
93
+
94
+ /** Per-entry state for the CLI and doctor. */
95
+ export function toolStatus(entry: PinnedTool): ToolStatus {
96
+ const platform = currentToolPlatform();
97
+ const supported = platform !== null && entry.downloads[platform] !== undefined;
98
+ const resolution = resolveEntryBinary(entry, entry.primaryBinary);
99
+ return {
100
+ id: entry.id,
101
+ version: entry.version,
102
+ license: entry.license,
103
+ platform,
104
+ supported,
105
+ installed: resolution.vendoredPath !== null,
106
+ installDir: toolVersionDir(entry.id, entry.version),
107
+ resolution,
108
+ };
109
+ }
110
+
111
+ /** Per-entry state for every row, in registry order. */
112
+ export function toolStatuses(): ToolStatus[] {
113
+ return PINNED_TOOLS.map((entry) => toolStatus(entry));
114
+ }
115
+
116
+ /** The one command that fixes a tool Clio cannot resolve. */
117
+ export function installRemedy(id: string): string {
118
+ return `clio-coder tools install ${id}`;
119
+ }
120
+
121
+ /**
122
+ * What a rejected PATH copy was, and which floor it failed. Null when there is
123
+ * no PATH copy or when the one there was accepted.
124
+ *
125
+ * The floors are deliberately conservative: they sit at the pin for herdr and
126
+ * yazi because Clio drives socket and plugin surfaces verified against only
127
+ * that release. That is a defensible choice and a costly one, since it rejects
128
+ * binaries an operator installed themselves and that mostly work. The cost is
129
+ * only payable if the rejection is legible, so every renderer of a rejection
130
+ * names the path, the version found, and the floor it missed, and never reports
131
+ * a rejected copy as an absent one.
132
+ */
133
+ export function describeFloorRejection(status: ToolStatus): string | null {
134
+ const candidate = status.resolution.pathCandidate;
135
+ if (candidate === null || candidate.satisfiesMinimum) return null;
136
+ const floor = status.resolution.entry?.minimumVersion ?? status.version;
137
+ const found = candidate.version === null ? "an unreadable version" : candidate.version;
138
+ return `PATH copy ${candidate.path} is ${found}, below the ${floor} floor`;
139
+ }
140
+
141
+ /**
142
+ * One sentence describing where a tool resolved and how its version compares to
143
+ * the pin. Shared by `clio-coder tools status` and the doctor rows so the two
144
+ * cannot drift into describing the same machine differently.
145
+ */
146
+ export function describeResolution(status: ToolStatus): string {
147
+ const { resolution } = status;
148
+ const rejection = describeFloorRejection(status);
149
+ if (resolution.source === "path") {
150
+ const version = resolution.version ?? "unreadable version";
151
+ return `PATH ${resolution.binaryPath} (${version}, pin ${status.version})`;
152
+ }
153
+ if (resolution.source === "vendored") {
154
+ // The remedy is deliberately absent here. The rejection cost the operator
155
+ // nothing: Clio has a copy at the pin and is running it, and printing an
156
+ // install command that would change nothing is its own kind of dishonesty.
157
+ const rejected = rejection === null ? "" : `; ${rejection}, so Clio runs the vendored copy`;
158
+ return `vendored ${resolution.binaryPath} (${status.version})${rejected}`;
159
+ }
160
+ if (!status.supported) {
161
+ const platform = status.platform ?? `${process.platform}-${process.arch}`;
162
+ // No remedy either: there is no asset to install on this machine, so the
163
+ // rejection is the whole story and naming a command would be a dead end.
164
+ const rejected = rejection === null ? "" : `; ${rejection}`;
165
+ return `not installed and no pinned asset for this platform (${platform})${rejected}`;
166
+ }
167
+ if (rejection !== null) {
168
+ return `${rejection}, and nothing is vendored (install with \`${installRemedy(status.id)}\`)`;
169
+ }
170
+ return `not found (install with \`${installRemedy(status.id)}\`)`;
171
+ }
172
+
173
+ function probePathCandidate(entry: PinnedTool, binary: string): ToolPathCandidate | null {
174
+ const found = findExecutableOnPath(binary);
175
+ if (found === null) return null;
176
+ // The floor is a property of the tool, and only the primary binary is asked
177
+ // for its version, so a secondary binary inherits the primary's verdict from
178
+ // the same install root rather than being probed with flags it may not have.
179
+ const probeTarget = binary === entry.primaryBinary ? found : (findExecutableOnPath(entry.primaryBinary) ?? found);
180
+ const version = probeBinaryVersion(probeTarget, entry.versionArgs);
181
+ return { path: found, version, satisfiesMinimum: satisfiesMinimum(version, entry.minimumVersion) };
182
+ }