@iowarp/clio-coder 0.4.2 → 0.4.4

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 (523) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/CONTRIBUTING.md +86 -19
  3. package/README.md +35 -6
  4. package/dist/{acp-TMDQZDIG.js → acp-WNAYYF4F.js} +12 -13
  5. package/dist/{agents-5N5NG3XG.js → agents-3OKXHLOI.js} +60 -57
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-Z5CCBXKQ.js → auth-VKNNMGPU.js} +21 -19
  8. package/dist/{builtins-K6TNDT24.js → builtins-WGALA46I.js} +9 -4
  9. package/dist/{chunk-XE3PCIXH.js → chunk-23L32XTI.js} +12 -9
  10. package/dist/{chunk-I64IFBLB.js → chunk-25QBEXRS.js} +18 -11
  11. package/dist/{chunk-CDNVLKUX.js → chunk-26QSH3EJ.js} +13 -7
  12. package/dist/{chunk-QQLGQY2A.js → chunk-2ASED4PZ.js} +22 -22
  13. package/dist/{chunk-MCEPRMZW.js → chunk-2CU2H6KE.js} +2 -2
  14. package/dist/chunk-2DSOYNFC.js +108 -0
  15. package/dist/{chunk-72GZI5EV.js → chunk-2JH2WHGE.js} +2 -2
  16. package/dist/{chunk-I66EAJFY.js → chunk-2WZ546HR.js} +267 -232
  17. package/dist/{chunk-O3YUNJZ2.js → chunk-2ZSONWVL.js} +82 -25
  18. package/dist/{chunk-2NHR3NAY.js → chunk-36CT5VVL.js} +331 -42
  19. package/dist/{chunk-2X4RYJTJ.js → chunk-3GY4F45V.js} +3 -3
  20. package/dist/{chunk-ZW55JB7N.js → chunk-3ODX73FK.js} +4 -6
  21. package/dist/{chunk-PBP4B7XR.js → chunk-3UNOLWNZ.js} +3 -3
  22. package/dist/{chunk-4JDLP6ZS.js → chunk-3UUXNFEX.js} +14 -10
  23. package/dist/{chunk-RKSR6VSF.js → chunk-4IUZQIJ3.js} +29 -1
  24. package/dist/{chunk-ZW4HH5JJ.js → chunk-4M6Z5QVF.js} +6 -6
  25. package/dist/{chunk-K6BSR66V.js → chunk-4NSRCOYP.js} +4 -1
  26. package/dist/{chunk-M2DAX4F6.js → chunk-4WR7VSYB.js} +2 -2
  27. package/dist/{chunk-FSP7CMNU.js → chunk-54X7T7DK.js} +61 -6
  28. package/dist/{chunk-54ODD65L.js → chunk-5636DCO5.js} +4 -4
  29. package/dist/chunk-57XXR6DR.js +3763 -0
  30. package/dist/{chunk-3KIPBMUA.js → chunk-5ICU3EUH.js} +2 -2
  31. package/dist/{chunk-77QIVUZB.js → chunk-5MEZN6CB.js} +4 -4
  32. package/dist/{chunk-O42A54GG.js → chunk-5OIVVPHF.js} +2 -2
  33. package/dist/{chunk-YJISEZKC.js → chunk-5TUB6SLS.js} +6 -6
  34. package/dist/{chunk-IMXMHHMQ.js → chunk-6OSVSQL5.js} +341 -57
  35. package/dist/{chunk-Q4XWMHX6.js → chunk-6PAZTBPA.js} +14 -2
  36. package/dist/{chunk-FVDGR2ZL.js → chunk-6Q3CYFD3.js} +112 -39
  37. package/dist/{chunk-IDNA72AH.js → chunk-6QOTUPRG.js} +155 -36
  38. package/dist/{chunk-X7IARSHT.js → chunk-6UINWWS6.js} +16 -10
  39. package/dist/{chunk-CYZW7JHJ.js → chunk-72YIHOZQ.js} +9 -9
  40. package/dist/{chunk-IKSLQ4XV.js → chunk-75W7L2E2.js} +752 -861
  41. package/dist/{chunk-CRFOIAX3.js → chunk-7UGL4MB5.js} +6 -6
  42. package/dist/{chunk-HIICAHCJ.js → chunk-AUPNRN7C.js} +2 -2
  43. package/dist/{chunk-7BHIY2MW.js → chunk-BJVFZO5U.js} +8 -50
  44. package/dist/{chunk-B74PXLU7.js → chunk-CUSRQKPU.js} +65 -3
  45. package/dist/chunk-DQOVN6KV.js +386 -0
  46. package/dist/{chunk-E7GT7O5N.js → chunk-DT3LWJOB.js} +7 -4
  47. package/dist/chunk-DXKJURES.js +671 -0
  48. package/dist/{chunk-JBCS7CRR.js → chunk-EL24TAU4.js} +10 -10
  49. package/dist/{chunk-TPEQIQIE.js → chunk-ELWDPP3Y.js} +8 -8
  50. package/dist/{chunk-NDINPTJ4.js → chunk-ELZVTCGV.js} +5 -4
  51. package/dist/chunk-EXLD33WO.js +381 -0
  52. package/dist/chunk-FEAXX7B6.js +101 -0
  53. package/dist/{chunk-RLYRBIYQ.js → chunk-FFUPXJC4.js} +90 -331
  54. package/dist/{chunk-5PFYMY2V.js → chunk-FTMGRKEF.js} +2 -2
  55. package/dist/{chunk-34BHNEE3.js → chunk-GHS5EBTQ.js} +58 -7
  56. package/dist/{chunk-DYHAXKHD.js → chunk-GWZNEVM2.js} +12 -8
  57. package/dist/chunk-GX5WYQO4.js +59 -0
  58. package/dist/chunk-GYV6VZOC.js +26 -0
  59. package/dist/{chunk-MQXIVJ35.js → chunk-HAXOFFRH.js} +5 -5
  60. package/dist/chunk-I2DWJ4GM.js +390 -0
  61. package/dist/{chunk-TXOTCRLG.js → chunk-I5FWO7L5.js} +5 -5
  62. package/dist/{chunk-WNP7O5WZ.js → chunk-ID64D7PE.js} +4 -4
  63. package/dist/{chunk-XQRY4DTA.js → chunk-IGLP3ODT.js} +10 -10
  64. package/dist/chunk-IRXAATOX.js +539 -0
  65. package/dist/chunk-IXIY2H4R.js +44 -0
  66. package/dist/{chunk-SSEYRH53.js → chunk-IZXGRF7P.js} +92 -147
  67. package/dist/{chunk-5TSRNF4G.js → chunk-JCI2ROMZ.js} +164 -6
  68. package/dist/{chunk-JWJGP5DQ.js → chunk-JEIYHLOR.js} +7 -7
  69. package/dist/{chunk-F2I26BDK.js → chunk-JQLNNIKT.js} +4 -4
  70. package/dist/{chunk-BYMNWQ7O.js → chunk-JSD46VO2.js} +315 -63
  71. package/dist/{chunk-AK5XEFVZ.js → chunk-JT2RFCC5.js} +64 -14
  72. package/dist/{chunk-MCMZMDAC.js → chunk-K6T2ZAMZ.js} +168 -6
  73. package/dist/{chunk-PGF63K6I.js → chunk-KFV5L5SK.js} +73 -4
  74. package/dist/{chunk-IHXBNWMM.js → chunk-KXDSS5WJ.js} +7 -3
  75. package/dist/{chunk-PJX3WQUQ.js → chunk-LLXSDWXS.js} +3 -3
  76. package/dist/{chunk-DZAW46HP.js → chunk-LTIKRKFL.js} +3 -3
  77. package/dist/{chunk-DZEK6CJN.js → chunk-N56KALIC.js} +21 -21
  78. package/dist/{chunk-B7HM5Z7T.js → chunk-NAI6ZFCY.js} +9 -5
  79. package/dist/{chunk-I66ZTYNP.js → chunk-NRO2BJRH.js} +2656 -2213
  80. package/dist/{chunk-ZGNYYXQ6.js → chunk-NXIMQY5W.js} +3 -3
  81. package/dist/{chunk-IKOZFYBN.js → chunk-NXYCB2VD.js} +149 -106
  82. package/dist/{chunk-CWVRRIEI.js → chunk-NZMNUPZZ.js} +2 -2
  83. package/dist/{chunk-462T4EGZ.js → chunk-O5CVSAG5.js} +2 -2
  84. package/dist/chunk-ODGTEFFI.js +50 -0
  85. package/dist/{chunk-3F7VUY77.js → chunk-OEJSLEPW.js} +2 -2
  86. package/dist/{chunk-KKOJXO6R.js → chunk-OMQNJVKW.js} +4 -2
  87. package/dist/{chunk-5KW52TEP.js → chunk-Q4WO54TA.js} +132 -77
  88. package/dist/{chunk-W6NIE6OW.js → chunk-QUFRYSWI.js} +13 -7
  89. package/dist/{chunk-42FMPA75.js → chunk-QZWQA4DE.js} +2 -2
  90. package/dist/chunk-R6Q67RJH.js +134 -0
  91. package/dist/{chunk-W4YEMFBX.js → chunk-RAY4OVGZ.js} +3 -3
  92. package/dist/{chunk-ZNT2M6TG.js → chunk-RQCKCSRL.js} +17 -17
  93. package/dist/{chunk-LJID3DYZ.js → chunk-RXTN6AKH.js} +3 -3
  94. package/dist/{chunk-P75RZCJW.js → chunk-RZDWV63N.js} +3 -3
  95. package/dist/{chunk-UH632ZYL.js → chunk-S6PYF2XF.js} +2 -2
  96. package/dist/{chunk-HJWWJ6IL.js → chunk-TOIVGRUX.js} +17 -5
  97. package/dist/{chunk-HLAFFSEK.js → chunk-TQAHXW6Y.js} +2 -2
  98. package/dist/{chunk-JIEGK6UF.js → chunk-U6TMQNSI.js} +48 -4
  99. package/dist/{chunk-2HFQNRV3.js → chunk-UEPWCCTY.js} +12 -12
  100. package/dist/chunk-UOIZ7DA4.js +41 -0
  101. package/dist/{chunk-UH347SHR.js → chunk-USR47QNF.js} +11 -11
  102. package/dist/{chunk-AZ4WMN4W.js → chunk-V6HJFQZE.js} +2 -2
  103. package/dist/chunk-V76WTFTW.js +318 -0
  104. package/dist/{chunk-NMJXSHBJ.js → chunk-W54I7H25.js} +2 -2
  105. package/dist/{chunk-KPXDY6QF.js → chunk-XRZT5WY5.js} +2 -2
  106. package/dist/{chunk-UBRFI4HS.js → chunk-XULDXHTN.js} +142 -50
  107. package/dist/chunk-XXYSBZIQ.js +283 -0
  108. package/dist/{chunk-HKMD33FO.js → chunk-Y55JBDO5.js} +405 -122
  109. package/dist/{chunk-XOXV5GKE.js → chunk-YD5GIKET.js} +17 -8
  110. package/dist/{chunk-XGDPUNND.js → chunk-YECAMM3D.js} +2 -2
  111. package/dist/{chunk-BO7Y52RY.js → chunk-YNFKXPEC.js} +7 -7
  112. package/dist/{chunk-VXMFAE2W.js → chunk-YPC6ZR5L.js} +19 -6
  113. package/dist/{chunk-M2WXEHER.js → chunk-ZA4VCIGV.js} +2 -2
  114. package/dist/cli/index.js +42 -40
  115. package/dist/{clio-7VB377CC.js → clio-QLICPCF5.js} +7 -7
  116. package/dist/{code-nav-YVLCYA7V.js → code-nav-IJR2DBPR.js} +9 -9
  117. package/dist/{components-UBWCQSRW.js → components-2TGAI2RC.js} +5 -6
  118. package/dist/{config-4HVOS65E.js → config-IUA6OYNS.js} +88 -81
  119. package/dist/{configure-PIWO7B24.js → configure-VEPX4NMX.js} +26 -25
  120. package/dist/{context-KQYIWPWT.js → context-2DKHWH2T.js} +60 -45
  121. package/dist/{context-IYEHL3WQ.js → context-4MPR7WKB.js} +78 -69
  122. package/dist/{context-N6ZE3LGJ.js → context-BOYF5EJM.js} +15 -11
  123. package/dist/{context-clear-G4OGZJDS.js → context-clear-S4ZJCQUX.js} +73 -65
  124. package/dist/context-map-COB37XXN.js +505 -0
  125. package/dist/{context-working-set-BWLF6LJP.js → context-working-set-3I3FYX6Y.js} +18 -17
  126. package/dist/detail-A7JAVSIG.js +98 -0
  127. package/dist/{dispatch-runner-2QQAITS3.js → dispatch-runner-RJ5I2F2O.js} +99 -75
  128. package/dist/{docs-PD3EXDKU.js → docs-SPOV3BAN.js} +3 -5
  129. package/dist/{doctor-LHBD36VU.js → doctor-DKICC2SN.js} +71 -48
  130. package/dist/{eval-C45FYRJ6.js → eval-OQOQUDHK.js} +308 -146
  131. package/dist/{eval-inventory-6DEJPLBF.js → eval-inventory-Y6QRFOH5.js} +4 -4
  132. package/dist/{evidence-6SHONYAF.js → evidence-4DQ25GUQ.js} +79 -175
  133. package/dist/evidence-4F5USFKH.js +208 -0
  134. package/dist/{evolve-KRKMV72X.js → evolve-GSS52E5J.js} +71 -67
  135. package/dist/{extensions-KPZ2UHBB.js → extensions-G7MFLYHT.js} +8 -9
  136. package/dist/{fleet-IVTCKDHT.js → fleet-Q37YHHAQ.js} +126 -118
  137. package/dist/{fleet-commands-EDWL3IT7.js → fleet-commands-G7E4N7SM.js} +16 -13
  138. package/dist/{fleet-decisions-YP3YEFGK.js → fleet-decisions-O7M6QBA2.js} +9 -8
  139. package/dist/{fleet-graph-ZFWKHY2M.js → fleet-graph-TOUBW6OW.js} +21 -20
  140. package/dist/{fleet-inspect-FVUNCBML.js → fleet-inspect-SW33JJNI.js} +65 -60
  141. package/dist/{fleet-preflight-UN5XED4R.js → fleet-preflight-CV2655TW.js} +4 -5
  142. package/dist/{fleet-validate-XOWC4HSX.js → fleet-validate-KESZX2YH.js} +25 -24
  143. package/dist/{fleet-verify-UN3SODEL.js → fleet-verify-UQPMTVE3.js} +66 -61
  144. package/dist/{fleet-view-TWHJKCN6.js → fleet-view-MN2VG4MR.js} +65 -60
  145. package/dist/{init-T2QORQ3Y.js → init-PXEXQSBF.js} +90 -82
  146. package/dist/{interop-IN5I2A66.js → interop-ZG5T62U3.js} +12 -13
  147. package/dist/inventory-C26CFDRR.js +101 -0
  148. package/dist/{library-LSCATDLZ.js → library-B2W4N74O.js} +29 -29
  149. package/dist/{memory-HYOKAGGJ.js → memory-YCANYS5A.js} +73 -69
  150. package/dist/{models-2GPMFYCM.js → models-GERTU3YI.js} +51 -47
  151. package/dist/{monitor-E4ASVUJH.js → monitor-CPNIUULB.js} +74 -67
  152. package/dist/{orchestrator-DDMPR3PY.js → orchestrator-J4BSH4WQ.js} +1288 -1606
  153. package/dist/{panes-E3RUXOW5.js → panes-BOHAEGYC.js} +4 -4
  154. package/dist/{panes-IXKLOKA2.js → panes-NXSLDQZ2.js} +10 -11
  155. package/dist/{paths-L7LGY6RN.js → paths-VSUWNC22.js} +6 -7
  156. package/dist/reset-TNWTB5LU.js +343 -0
  157. package/dist/{resources-OTRSN34L.js → resources-4PXNMD5G.js} +29 -22
  158. package/dist/{run-5DEYH5QK.js → run-D6XJ34CN.js} +132 -132
  159. package/dist/{share-IHWTLO3M.js → share-2NWMJJEE.js} +27 -27
  160. package/dist/{skills-IYMXMKW4.js → skills-KR7WON5G.js} +40 -33
  161. package/dist/{skills-eval-DROHSJAR.js → skills-eval-O2ZNOLDS.js} +81 -77
  162. package/dist/{skills-inventory-D7X4L4ZX.js → skills-inventory-ZZOUBK7O.js} +23 -22
  163. package/dist/{slash-commands-QBM7UZ3B.js → slash-commands-ZXPJD64J.js} +47 -37
  164. package/dist/{steer-Z5DO23FJ.js → steer-XA25PSCS.js} +4 -4
  165. package/dist/{support-U7QOWY26.js → support-7EMVWYG2.js} +6 -6
  166. package/dist/{targets-P2FUC4IL.js → targets-OMH2XCSN.js} +50 -49
  167. package/dist/tasks-IPAGMEIX.js +36 -0
  168. package/dist/{terminal-lease-YREJ3JX2.js → terminal-lease-C2J3JYRE.js} +4 -4
  169. package/dist/{tools-5B7RO6MV.js → tools-EFFEAIDP.js} +8 -9
  170. package/dist/{trace-YMGMUM6A.js → trace-FXMXUZUF.js} +7 -7
  171. package/dist/uninstall-HALS6BLF.js +407 -0
  172. package/dist/upgrade-MS72RJEP.js +306 -0
  173. package/dist/{usage-ME5MPXGX.js → usage-NHG6MCJM.js} +162 -108
  174. package/dist/{verifiers-BVZ7IWOO.js → verifiers-7AUNVXDY.js} +155 -22
  175. package/dist/{verify-5K7ZKQFC.js → verify-FWYGPKMR.js} +14 -12
  176. package/dist/{web-fetch-MPARV2K7.js → web-fetch-V4FKSDAV.js} +4 -4
  177. package/dist/{wiki-generate-F5W5QTYY.js → wiki-generate-743CIGJW.js} +99 -90
  178. package/dist/{with-panes-BYOJCLAM.js → with-panes-BDQEWBRT.js} +10 -10
  179. package/dist/worker/entry.js +72 -68
  180. package/docs/README.md +3 -2
  181. package/docs/architecture/acp.md +17 -0
  182. package/docs/architecture/artifact-placement.md +1 -0
  183. package/docs/architecture/artifact-versions.md +2 -2
  184. package/docs/architecture/context-engine.md +4 -0
  185. package/docs/architecture/dispatch-typed-intent.md +1 -1
  186. package/docs/architecture/evidence-and-memory.md +1 -1
  187. package/docs/architecture/middleware-and-components.md +1 -1
  188. package/docs/architecture/model-catalog.md +21 -10
  189. package/docs/architecture/observability.md +19 -2
  190. package/docs/architecture/prompt-envelope-and-tools.md +17 -5
  191. package/docs/architecture/provider-adapter-cookbook.md +63 -0
  192. package/docs/architecture/safety-model.md +25 -22
  193. package/docs/architecture/tui-design.md +1 -1
  194. package/docs/guide/built-in-agents.md +25 -11
  195. package/docs/guide/commands-and-modes.md +18 -3
  196. package/docs/guide/configuration-and-targets.md +100 -10
  197. package/docs/guide/configuration-reference.md +17 -7
  198. package/docs/guide/environment-variables.md +4 -2
  199. package/docs/guide/installation-and-lifecycle.md +37 -4
  200. package/docs/guide/proactive-memory.md +66 -55
  201. package/docs/guide/skills-marketplace.md +18 -0
  202. package/docs/guide/tool-usage.md +78 -3
  203. package/docs/history/config-knobs-audit.md +2 -2
  204. package/docs/process/development-pipeline.md +40 -2
  205. package/docs/process/eval-runner.md +67 -3
  206. package/docs/process/git-commit-provenance.md +15 -0
  207. package/docs/process/release-cut-checklist.md +207 -0
  208. package/docs/process/scientific-validation.md +18 -17
  209. package/evals/behavioral-machinery-support.ts +1 -0
  210. package/evals/behavioral-machinery.yaml +1 -1
  211. package/evals/behavioral-model.yaml +3 -2
  212. package/package.json +2 -2
  213. package/skills/README.md +7 -5
  214. package/skills/coding/ast-grep/SKILL.md +101 -30
  215. package/skills/coding/ast-grep/evals.md +26 -0
  216. package/skills/coding/coding-standards/SKILL.md +47 -2
  217. package/skills/coding/coding-standards/evals.md +23 -0
  218. package/skills/coding/prototype/SKILL.md +87 -28
  219. package/skills/coding/prototype/evals.md +19 -0
  220. package/skills/coding/tdd/SKILL.md +80 -53
  221. package/skills/coding/tdd/evals.md +20 -0
  222. package/skills/context/context-handoff/SKILL.md +43 -2
  223. package/skills/context/context-handoff/evals.md +44 -0
  224. package/skills/context/context-prime/SKILL.md +45 -15
  225. package/skills/context/context-prime/evals.md +45 -0
  226. package/skills/git/branch-closeout/SKILL.md +132 -0
  227. package/skills/git/branch-closeout/evals.md +133 -0
  228. package/skills/git/branch-closeout/references/closeout-checklist.md +81 -0
  229. package/skills/git/file-ticket/SKILL.md +77 -63
  230. package/skills/git/file-ticket/assets/issue-template.md +22 -0
  231. package/skills/git/file-ticket/evals.md +31 -26
  232. package/skills/git/file-ticket/references/issue-discovery.md +49 -0
  233. package/skills/git/fix-issue/SKILL.md +87 -64
  234. package/skills/git/fix-issue/evals.md +35 -31
  235. package/skills/git/fix-issue/references/diagnosis-and-rca.md +46 -0
  236. package/skills/git/resolve-merge-conflicts/SKILL.md +100 -51
  237. package/skills/git/resolve-merge-conflicts/evals.md +52 -25
  238. package/skills/git/resolve-merge-conflicts/references/conflict-matrix.md +126 -0
  239. package/skills/git/ship/SKILL.md +103 -67
  240. package/skills/git/ship/assets/pr-template.md +21 -0
  241. package/skills/git/ship/evals.md +44 -28
  242. package/skills/git/ship/references/remote-and-branch-policy.md +62 -0
  243. package/skills/git/worktree-create/SKILL.md +80 -50
  244. package/skills/git/worktree-create/evals.md +40 -33
  245. package/skills/git/worktree-create/references/worktree-setup.md +62 -66
  246. package/skills/git/worktree-merge/SKILL.md +112 -65
  247. package/skills/git/worktree-merge/evals.md +42 -34
  248. package/skills/git/worktree-merge/references/merge-strategies.md +52 -0
  249. package/skills/planning/archify/SKILL.md +196 -0
  250. package/skills/planning/archify/evals.md +65 -0
  251. package/skills/planning/architecture/SKILL.md +61 -12
  252. package/skills/planning/architecture/evals.md +65 -0
  253. package/skills/planning/backlog/SKILL.md +130 -14
  254. package/skills/planning/backlog/evals.md +142 -0
  255. package/skills/planning/prd/SKILL.md +47 -6
  256. package/skills/planning/prd/evals.md +54 -0
  257. package/skills/planning/product-intent/SKILL.md +57 -2
  258. package/skills/planning/product-intent/evals.md +70 -0
  259. package/skills/planning/tech-spec/SKILL.md +53 -2
  260. package/skills/planning/tech-spec/evals.md +73 -0
  261. package/skills/registry.yaml +58 -50
  262. package/skills/remote.yaml +13 -0
  263. package/skills/research/arxiv-literature/SKILL.md +76 -18
  264. package/skills/research/arxiv-literature/evals.md +50 -0
  265. package/skills/research/experiment-protocol/SKILL.md +20 -1
  266. package/skills/research/experiment-protocol/evals.md +23 -0
  267. package/skills/research/scientific-debugging/SKILL.md +24 -1
  268. package/skills/research/scientific-debugging/evals.md +18 -0
  269. package/skills/research/scientific-modernization/SKILL.md +26 -1
  270. package/skills/research/scientific-modernization/evals.md +27 -0
  271. package/skills/skill-marketplace.json +63 -28
  272. package/skills/workflow/cut-it/SKILL.md +64 -5
  273. package/skills/workflow/cut-it/evals.md +101 -0
  274. package/skills/workflow/design-council/SKILL.md +112 -27
  275. package/skills/workflow/design-council/evals.md +161 -0
  276. package/skills/workflow/grill-me/SKILL.md +85 -10
  277. package/skills/workflow/grill-me/evals.md +153 -0
  278. package/skills/workflow/workflow-distiller/SKILL.md +76 -17
  279. package/skills/workflow/workflow-distiller/evals.md +118 -0
  280. package/src/cli/args.ts +0 -8
  281. package/src/cli/configure-interop.ts +105 -13
  282. package/src/cli/configure-oauth.ts +57 -0
  283. package/src/cli/configure-onboarding.ts +980 -0
  284. package/src/cli/configure-target.ts +594 -0
  285. package/src/cli/configure.ts +1084 -529
  286. package/src/cli/context-map.ts +114 -0
  287. package/src/cli/context.ts +4 -0
  288. package/src/cli/doctor-state-size.ts +1 -12
  289. package/src/cli/doctor-validation-contract.ts +28 -0
  290. package/src/cli/doctor.ts +5 -0
  291. package/src/cli/evidence-detail.ts +1 -75
  292. package/src/cli/evidence-inventory.ts +1 -167
  293. package/src/cli/index.ts +3 -0
  294. package/src/cli/lifecycle-presenter.ts +436 -0
  295. package/src/cli/models.ts +10 -2
  296. package/src/cli/modes/print.ts +5 -1
  297. package/src/cli/reset.ts +228 -106
  298. package/src/cli/run.ts +7 -4
  299. package/src/cli/select.ts +664 -0
  300. package/src/cli/skills.ts +9 -2
  301. package/src/cli/targets.ts +3 -0
  302. package/src/cli/tasks.ts +84 -0
  303. package/src/cli/uninstall.ts +233 -165
  304. package/src/cli/upgrade.ts +210 -150
  305. package/src/cli/usage.ts +92 -27
  306. package/src/cli/validate-model.ts +3 -3
  307. package/src/cli/verifiers.ts +147 -1
  308. package/src/cli/wiki-generate.ts +1 -0
  309. package/src/core/commit-attribution.ts +41 -1
  310. package/src/core/config.ts +56 -0
  311. package/src/core/external-diagnostic.ts +44 -0
  312. package/src/core/gateway-routing.ts +157 -0
  313. package/src/core/git-commit-attribution.ts +46 -3
  314. package/src/core/run-overrides.ts +0 -5
  315. package/src/core/safe-exec.ts +17 -2
  316. package/src/core/skill-activation.ts +92 -2
  317. package/src/core/tool-names.ts +5 -2
  318. package/src/domains/agents/builtins/architect.md +1 -1
  319. package/src/domains/agents/builtins/coder.md +1 -1
  320. package/src/domains/agents/builtins/documenter.md +1 -1
  321. package/src/domains/agents/builtins/git-master.md +1 -1
  322. package/src/domains/agents/builtins/provenance.md +7 -7
  323. package/src/domains/agents/builtins/tester.md +1 -1
  324. package/src/domains/agents/builtins/verifier.md +2 -2
  325. package/src/domains/agents/builtins/wiki-writer.md +4 -3
  326. package/src/domains/agents/builtins/world-knowledge.md +31 -0
  327. package/src/domains/agents/catalog.ts +1 -1
  328. package/src/domains/agents/result-contract.ts +70 -0
  329. package/src/domains/context/extension.ts +31 -7
  330. package/src/domains/context/refresh.ts +3 -0
  331. package/src/domains/context/wiki/frontmatter.ts +5 -2
  332. package/src/domains/context/wiki/generate.ts +6 -0
  333. package/src/domains/context/wiki/map-seed.ts +589 -0
  334. package/src/domains/context/wiki/plan.ts +2 -2
  335. package/src/domains/context/wiki/prompts.ts +43 -0
  336. package/src/domains/dispatch/active-route-planner.ts +4 -0
  337. package/src/domains/dispatch/admission.ts +29 -0
  338. package/src/domains/dispatch/agent-candidates.ts +10 -0
  339. package/src/domains/dispatch/budget-envelope.ts +86 -1
  340. package/src/domains/dispatch/capability-match.ts +1 -0
  341. package/src/domains/dispatch/capacity-lease.ts +17 -0
  342. package/src/domains/dispatch/code-step.ts +11 -4
  343. package/src/domains/dispatch/contract.ts +42 -8
  344. package/src/domains/dispatch/execution-scheduler.ts +2 -0
  345. package/src/domains/dispatch/extension.ts +184 -56
  346. package/src/domains/dispatch/fleet-commit-attribution.ts +5 -0
  347. package/src/domains/dispatch/fleet-run.ts +1 -0
  348. package/src/domains/dispatch/host-verification.ts +114 -13
  349. package/src/domains/dispatch/intent.ts +28 -18
  350. package/src/domains/dispatch/orphan-recovery.ts +2 -0
  351. package/src/domains/dispatch/receipt-integrity.ts +4 -0
  352. package/src/domains/dispatch/reservation-store.ts +5 -3
  353. package/src/domains/dispatch/state.ts +15 -2
  354. package/src/domains/dispatch/types.ts +20 -2
  355. package/src/domains/dispatch/worker-model-metadata.ts +38 -0
  356. package/src/domains/eval/metrics/call-ledger-stream.ts +34 -11
  357. package/src/domains/eval/metrics/token-stream.ts +201 -31
  358. package/src/domains/eval/metrics/tracked.ts +40 -4
  359. package/src/domains/eval/runners/clio-run.ts +17 -11
  360. package/src/domains/eval/runners/context-index.ts +2 -7
  361. package/src/domains/eval/runners/context-init.ts +3 -6
  362. package/src/domains/eval/runners/external-command.ts +29 -11
  363. package/src/domains/eval/schema/suite.ts +28 -0
  364. package/src/domains/eval/schema/verdict.ts +2 -2
  365. package/src/domains/eval/suites/resolve.ts +13 -1
  366. package/src/domains/eval/suites/run.ts +24 -3
  367. package/src/domains/evidence/build.ts +102 -15
  368. package/src/domains/evidence/detail.ts +69 -0
  369. package/src/domains/evidence/eval.ts +13 -1
  370. package/src/domains/evidence/finish-contract-map.ts +5 -1
  371. package/src/domains/evidence/inventory.ts +167 -0
  372. package/src/domains/evidence/store.ts +16 -0
  373. package/src/domains/evidence/types.ts +12 -0
  374. package/src/domains/extensions/resources.ts +7 -0
  375. package/src/domains/interop/registry.ts +6 -2
  376. package/src/domains/interop/types.ts +4 -0
  377. package/src/domains/lifecycle/migrations/index.ts +4 -0
  378. package/src/domains/memory/task-memory-policy.ts +70 -26
  379. package/src/domains/memory/task-memory-telemetry.ts +1 -0
  380. package/src/domains/middleware/index.ts +0 -1
  381. package/src/domains/middleware/marketplace-offer.ts +22 -35
  382. package/src/domains/middleware/memory-intervention.ts +127 -32
  383. package/src/domains/middleware/memory-step-endpoint.ts +3 -2
  384. package/src/domains/middleware/runtime.ts +7 -3
  385. package/src/domains/middleware/skills-reminder.ts +31 -2
  386. package/src/domains/mux/detect.ts +3 -6
  387. package/src/domains/observability/accountability.ts +15 -1
  388. package/src/domains/observability/compaction-usage.ts +118 -0
  389. package/src/domains/observability/contract.ts +52 -7
  390. package/src/domains/observability/cost.ts +1 -1
  391. package/src/domains/observability/evidence-index.ts +10 -0
  392. package/src/domains/observability/extension.ts +15 -6
  393. package/src/domains/observability/out-of-turn-usage.ts +52 -21
  394. package/src/domains/observability/projection.ts +394 -45
  395. package/src/{interactive → domains/observability}/worker-progress.ts +3 -3
  396. package/src/domains/prompts/fragments/operating/contract.md +2 -0
  397. package/src/domains/prompts/fragments/wiki/page.md +8 -0
  398. package/src/domains/providers/contract.ts +4 -1
  399. package/src/domains/providers/extension.ts +40 -9
  400. package/src/domains/providers/model-capabilities.ts +9 -0
  401. package/src/domains/providers/model-discovery.ts +3 -4
  402. package/src/domains/providers/model-runtime-capabilities.ts +15 -5
  403. package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +48 -26
  404. package/src/domains/providers/runtimes/antigravity/antigravity-code.ts +225 -45
  405. package/src/domains/providers/runtimes/claude/claude-code.ts +9 -0
  406. package/src/domains/providers/runtimes/common/lmstudio-http.ts +6 -2
  407. package/src/domains/providers/runtimes/common/local-synth.ts +2 -0
  408. package/src/domains/providers/runtimes/protocol/litellm.ts +119 -29
  409. package/src/domains/providers/support.ts +11 -5
  410. package/src/domains/providers/target-model-cache.ts +25 -2
  411. package/src/domains/providers/types/capability-flags.ts +2 -0
  412. package/src/domains/providers/types/runtime-descriptor.ts +20 -1
  413. package/src/domains/providers/types/target-descriptor.ts +19 -0
  414. package/src/domains/resources/index.ts +3 -0
  415. package/src/domains/resources/skills/install.ts +72 -7
  416. package/src/domains/resources/skills/loader.ts +7 -0
  417. package/src/domains/resources/skills/marketplace.ts +63 -11
  418. package/src/domains/safety/action-classifier.ts +7 -0
  419. package/src/domains/safety/autonomy.ts +15 -0
  420. package/src/domains/safety/default-path-policy.ts +2 -0
  421. package/src/domains/safety/finish-contract-registration.ts +29 -14
  422. package/src/domains/safety/finish-contract.ts +252 -40
  423. package/src/domains/safety/index.ts +21 -1
  424. package/src/domains/safety/path-policy.ts +1 -1
  425. package/src/domains/safety/policy-engine.ts +60 -17
  426. package/src/domains/safety/protected-artifacts.ts +191 -88
  427. package/src/domains/safety/rigor.ts +53 -39
  428. package/src/domains/safety/run-effects.ts +2 -22
  429. package/src/domains/safety/skill-authority.ts +55 -0
  430. package/src/domains/safety/validation-contract.ts +388 -0
  431. package/src/domains/session/archive-readers.ts +10 -1
  432. package/src/domains/session/compaction/compact.ts +72 -22
  433. package/src/domains/session/decision-board.ts +101 -2
  434. package/src/domains/session/entries.ts +50 -7
  435. package/src/domains/session/extension.ts +4 -4
  436. package/src/domains/session/handoff.ts +2 -1
  437. package/src/domains/session/manager.ts +2 -3
  438. package/src/domains/session/task-board.ts +14 -1
  439. package/src/domains/session/tree/fork.ts +1 -2
  440. package/src/domains/session/tree/navigator.ts +1 -1
  441. package/src/domains/session/usage.ts +3 -3
  442. package/src/domains/user-tasks/acceptance.ts +56 -0
  443. package/src/domains/user-tasks/active-acceptance.ts +40 -0
  444. package/src/domains/user-tasks/store.ts +34 -3
  445. package/src/engine/acp/adapter.ts +24 -6
  446. package/src/engine/acp/server.ts +21 -4
  447. package/src/engine/acp/transport.ts +53 -8
  448. package/src/engine/acp/types.ts +4 -0
  449. package/src/engine/agent.ts +13 -3
  450. package/src/engine/ai.ts +26 -8
  451. package/src/engine/antigravity/subprocess-runtime.ts +386 -120
  452. package/src/engine/api-registry.ts +3 -0
  453. package/src/engine/apis/ollama-native.ts +15 -0
  454. package/src/engine/apis/openai-completions.ts +117 -14
  455. package/src/engine/claude/subprocess-runtime.ts +107 -60
  456. package/src/engine/external-subprocess.ts +122 -6
  457. package/src/entry/background-model-metadata.ts +18 -0
  458. package/src/entry/compaction-prompt.ts +57 -0
  459. package/src/entry/orchestrator.ts +416 -218
  460. package/src/entry/task-memory-lifecycle.ts +35 -0
  461. package/src/interactive/chat-loop-messages.ts +13 -4
  462. package/src/interactive/chat-loop.ts +65 -2
  463. package/src/interactive/chat-renderer.ts +1 -0
  464. package/src/interactive/cost-overlay.ts +26 -2
  465. package/src/interactive/dispatch-board.ts +46 -717
  466. package/src/interactive/fleet-run-preview.ts +2 -1
  467. package/src/interactive/interactive-application.ts +3 -2
  468. package/src/interactive/interactive-presentation.ts +55 -12
  469. package/src/interactive/interactive-slash-runtime.ts +4 -2
  470. package/src/interactive/oracle.ts +5 -2
  471. package/src/interactive/overlays/fleet-run-approval.ts +3 -2
  472. package/src/interactive/overlays/message-picker.ts +2 -2
  473. package/src/interactive/overlays/settings.ts +2 -2
  474. package/src/interactive/overlays/tree-selector.ts +2 -2
  475. package/src/interactive/renderers/branch-summary.ts +1 -1
  476. package/src/interactive/renderers/worker-entry.ts +32 -0
  477. package/src/interactive/slash-autocomplete.ts +4 -6
  478. package/src/interactive/slash-commands.ts +49 -45
  479. package/src/interactive/slash-spec.ts +28 -0
  480. package/src/interactive/theme/labels.ts +19 -13
  481. package/src/interactive/turn-context.ts +9 -5
  482. package/src/interactive/turn-recovery.ts +8 -0
  483. package/src/interactive/turn-runtime.ts +27 -11
  484. package/src/interactive/turn-state.ts +7 -0
  485. package/src/interactive/view/artifacts.ts +2 -0
  486. package/src/interactive/worker-receipts.ts +1 -0
  487. package/src/interactive/worker-stream.ts +13 -4
  488. package/src/tools/bootstrap.ts +4 -0
  489. package/src/tools/builtin-tool-catalog.ts +31 -0
  490. package/src/tools/compete-worktrees.ts +7 -1
  491. package/src/tools/context/index.ts +30 -9
  492. package/src/tools/core-bootstrap.ts +16 -0
  493. package/src/tools/decide.ts +136 -0
  494. package/src/tools/dispatch-admission.ts +21 -0
  495. package/src/tools/dispatch-arguments.ts +1 -0
  496. package/src/tools/dispatch-event-text.ts +10 -0
  497. package/src/tools/dispatch-plan.ts +6 -2
  498. package/src/tools/dispatch-runner.ts +27 -1
  499. package/src/tools/dispatch-types.ts +6 -0
  500. package/src/tools/evidence.ts +96 -0
  501. package/src/tools/limitation.ts +76 -0
  502. package/src/tools/policy.ts +9 -0
  503. package/src/tools/presentation.ts +3 -0
  504. package/src/tools/registry.ts +11 -5
  505. package/src/tools/result-shaping.ts +17 -5
  506. package/src/tools/task-worktree.ts +13 -3
  507. package/src/tools/tasks.ts +10 -1
  508. package/src/tools/verify/authoring.ts +170 -83
  509. package/src/tools/verify/catalog.ts +122 -5
  510. package/src/tools/verify/index.ts +2 -1
  511. package/src/tools/verify/numeric.ts +298 -0
  512. package/src/tools/verify/perf.ts +143 -0
  513. package/src/tools/verify/scripts.ts +229 -2
  514. package/src/tools/worker-evidence.ts +3 -1
  515. package/src/worker/spec-contract.ts +4 -0
  516. package/dist/chunk-2Z2IKEXI.js +0 -1554
  517. package/dist/chunk-RVG5JXAL.js +0 -41
  518. package/dist/chunk-T56WDKA5.js +0 -183
  519. package/dist/chunk-VN3SHNBN.js +0 -313
  520. package/dist/chunk-VPKWYKEY.js +0 -169
  521. package/dist/reset-OAQP3W4O.js +0 -230
  522. package/dist/uninstall-N34PCTGJ.js +0 -331
  523. package/dist/upgrade-PXK3S2YM.js +0 -325
@@ -1,40 +1,45 @@
1
+ import { existsSync, readFileSync } from "node:fs";
1
2
  import { stdin as input, stdout as output } from "node:process";
2
3
  import { createInterface } from "node:readline/promises";
3
4
  import {
5
+ bindAgentProfileInSettings,
4
6
  type ClioSettings,
5
7
  readSettings,
6
8
  removeTargetFromSettings,
7
9
  settingsPath,
8
10
  updateSettings,
9
11
  } from "../core/config.js";
12
+ import {
13
+ type AutonomyLevel,
14
+ type OutputVerbosity,
15
+ type PanesSettings,
16
+ type SmoothStreaming,
17
+ THINKING_LEVELS,
18
+ type ThinkingLevel,
19
+ type TuiMode,
20
+ type WorkerPermissionMode,
21
+ } from "../core/defaults.js";
10
22
  import { initializeClioHome } from "../core/init.js";
11
- import { openAuthStorage, resolveAuthTarget, targetRequiresAuth } from "../domains/providers/auth/index.js";
12
- import { getCatalogModelForRuntime } from "../domains/providers/catalog.js";
13
- import { credentialsPresent } from "../domains/providers/credentials.js";
23
+ import { warnLegacyNaming } from "../core/naming-compat.js";
24
+ import { resolveClioDirs } from "../core/xdg.js";
25
+ import { getVersionInfo } from "../domains/lifecycle/version.js";
26
+ import { openAuthStorage, targetRequiresAuth } from "../domains/providers/auth/index.js";
14
27
  import {
15
28
  buildProviderSupportEntry,
16
29
  configuredTargetsForRuntime,
17
30
  describeRuntimeModels,
18
31
  isOrchestratorEligibleRuntime,
19
- listKnownModelsForRuntime,
20
32
  listProviderSupportEntries,
21
33
  type ProviderSupportEntry,
22
- readTargetModelSnapshot,
23
34
  recordTargetModelSnapshot,
24
35
  resolveRuntimeAuthTarget,
25
36
  supportGroupLabel,
26
37
  } from "../domains/providers/index.js";
27
- import { probeCapabilitiesForModel } from "../domains/providers/model-capabilities.js";
28
38
  import { fingerprintNativeRuntime } from "../domains/providers/probe/fingerprint.js";
29
39
  import { getRuntimeRegistry } from "../domains/providers/registry.js";
30
40
  import { registerBuiltinRuntimes } from "../domains/providers/runtimes/builtins.js";
31
41
  import { greetLmStudio } from "../domains/providers/runtimes/common/lmstudio-http.js";
32
- import type {
33
- ProbeContext,
34
- ProbeModelStatus,
35
- ProbeResult,
36
- RuntimeDescriptor,
37
- } from "../domains/providers/types/runtime-descriptor.js";
42
+ import type { ProbeResult, RuntimeDescriptor } from "../domains/providers/types/runtime-descriptor.js";
38
43
  import type { TargetDescriptor } from "../domains/providers/types/target-descriptor.js";
39
44
  import { registerClioOAuthProviders } from "../engine/oauth.js";
40
45
  import { ask, askYesNo } from "./ask.js";
@@ -47,22 +52,57 @@ import {
47
52
  matchCategoryChoice,
48
53
  type RuntimeListRow,
49
54
  } from "./configure-layout.js";
50
- import { createDelayedManualCodeInput } from "./oauth-manual-input.js";
51
- import { promptOAuthSelection } from "./oauth-select.js";
55
+ import { loginOAuthRuntime } from "./configure-oauth.js";
56
+ import { canRunOnboarding, runOnboardingWizard } from "./configure-onboarding.js";
57
+ import {
58
+ applyTarget,
59
+ assertOrchestratorReplacementEligible,
60
+ buildDescriptor,
61
+ defaultUrlFor,
62
+ deriveTargetId,
63
+ describeAuthStatus,
64
+ normalizeUrl,
65
+ PROTOCOL_COMPAT_RUNTIME_IDS,
66
+ probeLines,
67
+ railPrefix,
68
+ resolveSupportedWireModels,
69
+ runtimeProbe,
70
+ runtimesForCategory,
71
+ setBackgroundPointer,
72
+ setOrchestratorPointer,
73
+ setWorkerDefaultPointer,
74
+ setWorkerProfilePointer,
75
+ validateContextWindowOverride,
76
+ validateResolvedModel,
77
+ type WireModelInventory,
78
+ warnUndiscoveredContextWindow,
79
+ } from "./configure-target.js";
80
+ import { createLifecyclePresenter, type LifecyclePresenter, shortenPath } from "./lifecycle-presenter.js";
81
+ import { canSelect, promptSelect } from "./select.js";
52
82
  import { credentialWriteFailed, printError, printOk, printPlaintextCredentialWarning } from "./shared.js";
53
83
  import { terminalColumns, wrapPlain } from "./text-layout.js";
54
- import { type LiveModelInventory, validateModelChoice } from "./validate-model.js";
55
84
 
56
85
  const HELP = `clio-coder configure
57
86
 
58
- Configure model targets for chat and fleet-agent dispatch.
87
+ Configure model targets and runtime settings for chat and fleet dispatch.
59
88
 
60
89
  Usage:
61
- clio-coder configure interactive wizard
90
+ clio-coder configure interactive configuration wizard
91
+ clio-coder configure --section <name> open one section directly:
92
+ targets, models, chat, fleet,
93
+ permissions, panes, skills, diagnostics
94
+ Without a terminal this prints the
95
+ section's values and exits.
96
+ clio-coder configure --json emit the effective settings as JSON
62
97
  clio-coder configure --interop review detected coding agents as delegation peers
63
98
  clio-coder configure --list list target runtimes (user-facing only)
64
99
  clio-coder configure --list --all list every registered runtime including aliases
65
100
  clio-coder configure --id <targetId> [flags] --runtime <runtimeId>
101
+ clio-coder configure --help, -h show this message
102
+
103
+ In the interactive menus: arrow keys move, enter opens, escape goes back, q
104
+ quits. Where the terminal cannot support that, the same menus are numbered and
105
+ answered by typing a number, b for back, or q to quit.
66
106
 
67
107
  Non-interactive flags:
68
108
  --id <targetId> target id to register (required when non-interactive)
@@ -75,6 +115,7 @@ Non-interactive flags:
75
115
  (mutually exclusive with --agent-profile)
76
116
  --agent-profile <name> save this target as a named fleet profile
77
117
  --agent-profile-model <id> model to use for --agent-profile
118
+ --bind-agent <agentId> bind this --agent-profile to an agent
78
119
  --api-key-env <VAR> read API key from this env var at call time
79
120
  --api-key <literal> store API key in credentials.yaml
80
121
  --force save a model outside the runtime catalog, or one the
@@ -90,30 +131,14 @@ Non-interactive flags:
90
131
  --reasoning <true|false> capability override
91
132
  `;
92
133
 
93
- const DEFAULT_PORTS: Record<string, number> = {
94
- llamacpp: 8080,
95
- "llamacpp-anthropic": 8080,
96
- "llamacpp-completion": 8080,
97
- "llamacpp-embed": 8080,
98
- "llamacpp-rerank": 8080,
99
- "lmstudio-native": 1234,
100
- lmstudio: 1234,
101
- "ollama-native": 11434,
102
- vllm: 8000,
103
- sglang: 30000,
104
- "lemonade-anthropic": 8000,
105
- lemonade: 8000,
106
- "anthropic-compat": 8000,
107
- "openai-compat": 8000,
108
- litellm: 4000,
109
- };
110
-
111
134
  interface ParsedArgs {
112
135
  positional: string[];
113
136
  help: boolean;
114
137
  list: boolean;
115
138
  all: boolean;
116
139
  interop: boolean;
140
+ json: boolean;
141
+ section?: string;
117
142
  remove?: string;
118
143
  renameOld?: string;
119
144
  renameNew?: string;
@@ -126,6 +151,7 @@ interface ParsedArgs {
126
151
  workerModel?: string;
127
152
  workerProfile?: string;
128
153
  workerProfileModel?: string;
154
+ bindAgent?: string;
129
155
  apiKeyEnv?: string;
130
156
  apiKey?: string;
131
157
  force: boolean;
@@ -146,6 +172,7 @@ function parseSetupArgs(argv: ReadonlyArray<string>): ParsedArgs {
146
172
  list: false,
147
173
  all: false,
148
174
  interop: false,
175
+ json: false,
149
176
  force: false,
150
177
  gateway: false,
151
178
  setOrchestrator: false,
@@ -165,6 +192,12 @@ function parseSetupArgs(argv: ReadonlyArray<string>): ParsedArgs {
165
192
  case "-h":
166
193
  out.help = true;
167
194
  break;
195
+ case "--json":
196
+ out.json = true;
197
+ break;
198
+ case "--section":
199
+ out.section = need();
200
+ break;
168
201
  case "--list":
169
202
  out.list = true;
170
203
  break;
@@ -208,6 +241,9 @@ function parseSetupArgs(argv: ReadonlyArray<string>): ParsedArgs {
208
241
  case "--agent-profile-model":
209
242
  out.workerProfileModel = need();
210
243
  break;
244
+ case "--bind-agent":
245
+ out.bindAgent = need();
246
+ break;
211
247
  case "--api-key-env":
212
248
  out.apiKeyEnv = need();
213
249
  break;
@@ -269,32 +305,6 @@ function ensureRegistryPopulated(): void {
269
305
  if (registry.list().length === 0) registerBuiltinRuntimes(registry);
270
306
  }
271
307
 
272
- function trimTrailing(url: string): string {
273
- return url.endsWith("/") && url.length > 1 ? url.slice(0, -1) : url;
274
- }
275
-
276
- function normalizeUrl(input: string, runtimeId: string): string {
277
- const trimmed = input.trim();
278
- if (trimmed.length === 0) return defaultUrlFor(runtimeId);
279
- const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
280
- let withScheme = hasScheme ? trimmed : `http://${trimmed}`;
281
- if (runtimeId === "lmstudio") withScheme = withScheme.replace(/^ws:/u, "http:").replace(/^wss:/u, "https:");
282
- try {
283
- const parsed = new URL(withScheme);
284
- if (!hasScheme && !parsed.port && DEFAULT_PORTS[runtimeId]) {
285
- parsed.port = String(DEFAULT_PORTS[runtimeId]);
286
- }
287
- return trimTrailing(parsed.toString());
288
- } catch {
289
- return trimTrailing(withScheme);
290
- }
291
- }
292
-
293
- function defaultUrlFor(runtimeId: string): string {
294
- const port = DEFAULT_PORTS[runtimeId];
295
- return port ? `http://127.0.0.1:${port}` : "http://127.0.0.1:8080";
296
- }
297
-
298
308
  const RUNTIME_LIST_CAPTION =
299
309
  "every registered runtime. run `clio-coder auth list` for the ones clio authenticates itself; the rest authenticate through their own tool (claude-cli, aws-sdk) or need no credential.";
300
310
 
@@ -342,256 +352,6 @@ function printRuntimeList(includeHidden: boolean): void {
342
352
  }
343
353
  }
344
354
 
345
- function deriveTargetId(runtimeId: string, existing: ReadonlyArray<TargetDescriptor>): string {
346
- const base = runtimeId;
347
- const taken = new Set(existing.map((e) => e.id));
348
- if (!taken.has(base)) return base;
349
- for (let i = 2; i < 1000; i++) {
350
- const candidate = `${base}-${i}`;
351
- if (!taken.has(candidate)) return candidate;
352
- }
353
- return `${base}-${Date.now()}`;
354
- }
355
-
356
- async function buildProbeContext(runtime?: RuntimeDescriptor, target?: TargetDescriptor): Promise<ProbeContext> {
357
- const probeCtx: ProbeContext = {
358
- credentialsPresent: credentialsPresent(),
359
- httpTimeoutMs: 5000,
360
- };
361
- if (!runtime || !target || !targetRequiresAuth(target, runtime)) return probeCtx;
362
- try {
363
- const resolution = await openAuthStorage().resolveForTarget(resolveAuthTarget(target, runtime), {
364
- includeFallback: false,
365
- });
366
- if (resolution.apiKey) probeCtx.authToken = resolution.apiKey;
367
- } catch {
368
- // Let the runtime's probe report its own missing-auth diagnostic.
369
- }
370
- return probeCtx;
371
- }
372
-
373
- function runtimeKnownModelsFor(runtimeId: string): ReadonlyArray<string> {
374
- return listKnownModelsForRuntime(runtimeId);
375
- }
376
-
377
- function catalogContextWindowFor(runtime: RuntimeDescriptor, modelId: string): number | null {
378
- const catalogModel = getCatalogModelForRuntime(runtime.id, modelId);
379
- if (typeof catalogModel?.contextWindow === "number" && catalogModel.contextWindow > 0) {
380
- return catalogModel.contextWindow;
381
- }
382
- if (runtimeKnownModelsFor(runtime.id).includes(modelId) && runtime.defaultCapabilities.contextWindow > 0) {
383
- return runtime.defaultCapabilities.contextWindow;
384
- }
385
- return null;
386
- }
387
-
388
- /**
389
- * Check the resolved model against the catalog where there is one and against
390
- * the target's own list where there is not. A runtime with no catalog used to
391
- * pass any string here while the same command had a live list that ruled it
392
- * out, so the target saved as `ok` and failed on its first
393
- * turn. Without a catalog or a live list there is nothing to check against, and
394
- * the note says so rather than implying the id was verified.
395
- */
396
- function validateResolvedModel(
397
- runtime: RuntimeDescriptor,
398
- target: Pick<TargetDescriptor, "id" | "url">,
399
- modelId: string | undefined,
400
- force: boolean,
401
- inventory: WireModelInventory,
402
- ): boolean {
403
- if (!modelId) return true;
404
- const knownModels = runtimeKnownModelsFor(runtime.id);
405
- const live = inventory.source === "probe" ? liveInventoryFor(target, inventory) : undefined;
406
- const validation = validateModelChoice({ runtimeId: runtime.id, modelId, knownModels, live, force });
407
- if (!validation.ok) {
408
- process.stderr.write(`error: ${validation.reason}\n`);
409
- return false;
410
- }
411
- if (validation.warning) process.stderr.write(`warning: ${validation.warning}\n`);
412
- if (knownModels.length === 0 && live === undefined && runtime.kind === "http") {
413
- process.stderr.write(
414
- `warning: could not verify model '${modelId}': ${target.url ?? `target '${target.id}'`} returned no model list; it is saved as written\n`,
415
- );
416
- }
417
- return true;
418
- }
419
-
420
- function validateContextWindowOverride(
421
- runtime: RuntimeDescriptor,
422
- modelId: string | undefined,
423
- contextWindow: number | undefined,
424
- force: boolean,
425
- ): boolean {
426
- if (contextWindow === undefined || !modelId) return true;
427
- const catalogMax = catalogContextWindowFor(runtime, modelId);
428
- if (catalogMax === null || contextWindow <= catalogMax) return true;
429
- const message = `--context-window ${contextWindow} exceeds catalog max ${catalogMax} for model '${modelId}'`;
430
- if (!force) {
431
- process.stderr.write(`error: ${message}. Use --force to override.\n`);
432
- return false;
433
- }
434
- process.stderr.write(`warning: ${message}\n`);
435
- return true;
436
- }
437
-
438
- async function runtimeProbe(
439
- runtime: RuntimeDescriptor,
440
- target: TargetDescriptor,
441
- authToken?: string,
442
- ): Promise<ProbeResult | null> {
443
- if (typeof runtime.probe !== "function") return null;
444
- try {
445
- const context = await buildProbeContext(runtime, target);
446
- return await runtime.probe(target, authToken === undefined ? context : { ...context, authToken });
447
- } catch (err) {
448
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
449
- }
450
- }
451
-
452
- async function runtimeProbeModels(
453
- runtime: RuntimeDescriptor,
454
- target: TargetDescriptor,
455
- authToken?: string,
456
- ): Promise<string[]> {
457
- if (typeof runtime.probeModels !== "function") return [];
458
- try {
459
- const context = await buildProbeContext(runtime, target);
460
- return await runtime.probeModels(target, authToken === undefined ? context : { ...context, authToken });
461
- } catch {
462
- return [];
463
- }
464
- }
465
-
466
- function applyTarget(settings: ClioSettings, descriptor: TargetDescriptor): void {
467
- const idx = settings.targets.findIndex((e) => e.id === descriptor.id);
468
- if (idx >= 0) settings.targets[idx] = descriptor;
469
- else settings.targets.push(descriptor);
470
- }
471
-
472
- function setOrchestratorPointer(settings: ClioSettings, descriptor: TargetDescriptor, model?: string | null): void {
473
- const runtime = getRuntimeRegistry().get(descriptor.runtime);
474
- if (!runtime) {
475
- throw new Error(
476
- `cannot use target '${descriptor.id}' as orchestrator target because runtime '${descriptor.runtime}' is not registered`,
477
- );
478
- }
479
- if (!isOrchestratorEligibleRuntime(runtime)) {
480
- throw new Error(
481
- `cannot use target '${descriptor.id}' as orchestrator target because runtime '${runtime.id}' is not an HTTP/native runtime`,
482
- );
483
- }
484
- settings.chat.target = descriptor.id;
485
- settings.chat.model = model ?? descriptor.defaultModel ?? null;
486
- }
487
-
488
- function setWorkerDefaultPointer(settings: ClioSettings, descriptor: TargetDescriptor, model?: string | null): void {
489
- settings.fleet.default.target = descriptor.id;
490
- settings.fleet.default.model = model ?? descriptor.defaultModel ?? null;
491
- }
492
-
493
- function setBackgroundPointer(settings: ClioSettings, descriptor: TargetDescriptor, model?: string | null): void {
494
- const runtime = getRuntimeRegistry().get(descriptor.runtime);
495
- if (!runtime || !isOrchestratorEligibleRuntime(runtime)) {
496
- throw new Error(
497
- `cannot use target '${descriptor.id}' as background target because runtime '${descriptor.runtime}' is not an HTTP/native runtime`,
498
- );
499
- }
500
- settings.context.memory.target = descriptor.id;
501
- settings.context.memory.model = model ?? descriptor.defaultModel ?? null;
502
- }
503
-
504
- function assertOrchestratorReplacementEligible(settings: ClioSettings, descriptor: TargetDescriptor): void {
505
- if (settings.chat.target !== descriptor.id && settings.context.memory.target !== descriptor.id) return;
506
- const role = settings.chat.target === descriptor.id ? "orchestrator" : "background";
507
- const runtime = getRuntimeRegistry().get(descriptor.runtime);
508
- if (!runtime) {
509
- throw new Error(
510
- `cannot update ${role} target '${descriptor.id}' because runtime '${descriptor.runtime}' is not registered`,
511
- );
512
- }
513
- if (!isOrchestratorEligibleRuntime(runtime)) {
514
- throw new Error(`cannot update ${role} target '${descriptor.id}' to non-HTTP/native runtime '${runtime.id}'`);
515
- }
516
- }
517
-
518
- function setWorkerProfilePointer(
519
- settings: ClioSettings,
520
- name: string,
521
- descriptor: TargetDescriptor,
522
- model?: string | null,
523
- ): void {
524
- const trimmed = name.trim();
525
- if (trimmed.length === 0) throw new Error("fleet profile name must be non-empty");
526
- settings.fleet.profiles[trimmed] = {
527
- target: descriptor.id,
528
- model: model ?? descriptor.defaultModel ?? null,
529
- thinkingLevel: "off",
530
- };
531
- }
532
-
533
- /**
534
- * A target that was probed successfully and still reported no context window
535
- * will run against the runtime descriptor's placeholder. Say so at the moment
536
- * the target is written, because the alternative is a plausible-looking number
537
- * the operator never chose and the server never claimed.
538
- */
539
- function warnUndiscoveredContextWindow(descriptor: TargetDescriptor, probe: ProbeResult | null): void {
540
- if (!probe?.ok) return;
541
- if (typeof descriptor.capabilities?.contextWindow === "number" && descriptor.capabilities.contextWindow > 0) return;
542
- const discovered = probeCapabilitiesForModel(
543
- {
544
- target: descriptor,
545
- probeCapabilities: probe.discoveredCapabilities ?? null,
546
- probeModelCapabilities: probe.modelCapabilities ?? null,
547
- probeModelId: probe.capabilityModelId ?? null,
548
- },
549
- descriptor.defaultModel,
550
- );
551
- if (typeof discovered?.contextWindow === "number" && discovered.contextWindow > 0) return;
552
- process.stdout.write(
553
- " warning: the target reported no context window; Clio will use the runtime default as a guess. Set one with --context-window.\n",
554
- );
555
- }
556
-
557
- /**
558
- * What the probe read, as opposed to whether it connected.
559
- *
560
- * A health check can pass while every enrichment read behind it returns
561
- * nothing, and reporting that as `probe ok` makes it indistinguishable from a
562
- * full read. That is how a target gets blessed at configure time and then
563
- * fails on the first turn. Naming the gap is the difference between a user who
564
- * knows to look now and one who finds out from a raw 404 later.
565
- */
566
- function probeReadings(probe: ProbeResult): string[] {
567
- const readings: string[] = [];
568
- if (probe.models && probe.models.length > 0) readings.push(`${probe.models.length} models`);
569
- if (probe.serverVersion) readings.push(probe.serverVersion);
570
- if (probe.discoveredCapabilities && Object.keys(probe.discoveredCapabilities).length > 0) {
571
- readings.push("capabilities");
572
- }
573
- return readings;
574
- }
575
-
576
- /** The probe result as the lines a user reads, cause and next step included. */
577
- function probeLines(descriptor: TargetDescriptor, probe: ProbeResult): string[] {
578
- const latency = probe.latencyMs !== undefined ? ` (${probe.latencyMs}ms)` : "";
579
- if (!probe.ok) {
580
- const where = descriptor.url ? ` ${descriptor.url}` : "";
581
- return [
582
- `probe failed${latency}:${where} ${probe.error ?? "unknown"}`,
583
- ` check the server is running and reachable, then re-run: clio-coder configure --id ${descriptor.id} --url <url>`,
584
- ];
585
- }
586
- const readings = probeReadings(probe);
587
- if (readings.length > 0) return [`probe ok${latency} ${readings.join(", ")}`];
588
- return [
589
- `probe reachable${latency}, but the target answered no model list and no version`,
590
- " Clio cannot verify the model id or the context window from here and will send both as written.",
591
- " re-read them once the server serves them with: clio-coder targets --probe",
592
- ];
593
- }
594
-
595
355
  function printSummary(settings: ClioSettings, descriptor: TargetDescriptor, probe: ProbeResult | null): void {
596
356
  process.stdout.write(`\nsaved target ${descriptor.id} (runtime=${descriptor.runtime})\n`);
597
357
  if (descriptor.url) process.stdout.write(` url ${descriptor.url}\n`);
@@ -612,166 +372,6 @@ function printSummary(settings: ClioSettings, descriptor: TargetDescriptor, prob
612
372
  process.stdout.write(`\nsettings written to ${settingsPath()}\n`);
613
373
  }
614
374
 
615
- function buildDescriptor(
616
- runtime: RuntimeDescriptor,
617
- id: string,
618
- parts: {
619
- url?: string;
620
- model?: string;
621
- apiKeyEnv?: string;
622
- apiKeyRef?: string;
623
- oauthProfile?: string;
624
- gateway?: boolean;
625
- lifecycle?: TargetDescriptor["lifecycle"];
626
- contextWindow?: number;
627
- maxTokens?: number;
628
- reasoning?: boolean;
629
- lmstudio?: TargetDescriptor["lmstudio"];
630
- },
631
- ): TargetDescriptor {
632
- const descriptor: TargetDescriptor = { id, runtime: runtime.id };
633
- if (parts.url) descriptor.url = parts.url;
634
- if (parts.model) descriptor.defaultModel = parts.model;
635
- const auth: NonNullable<TargetDescriptor["auth"]> = {};
636
- if (parts.apiKeyEnv) auth.apiKeyEnvVar = parts.apiKeyEnv;
637
- if (parts.apiKeyRef) auth.apiKeyRef = parts.apiKeyRef;
638
- if (parts.oauthProfile) auth.oauthProfile = parts.oauthProfile;
639
- if (Object.keys(auth).length > 0) descriptor.auth = auth;
640
- if (parts.gateway) descriptor.gateway = true;
641
- if (parts.lifecycle) descriptor.lifecycle = parts.lifecycle;
642
- const caps: NonNullable<TargetDescriptor["capabilities"]> = {};
643
- if (parts.contextWindow !== undefined) caps.contextWindow = parts.contextWindow;
644
- if (parts.maxTokens !== undefined) caps.maxTokens = parts.maxTokens;
645
- if (parts.reasoning !== undefined) caps.reasoning = parts.reasoning;
646
- if (Object.keys(caps).length > 0) descriptor.capabilities = caps;
647
- if (parts.lmstudio) descriptor.lmstudio = parts.lmstudio;
648
- return descriptor;
649
- }
650
-
651
- function describeAuthStatus(runtime: RuntimeDescriptor): string {
652
- const status = openAuthStorage().statusForTarget(resolveRuntimeAuthTarget(runtime), { includeFallback: false });
653
- // This describes a credential, not a network state. It is printed before the
654
- // wizard has asked for a URL, so `not connected` read as a connection that
655
- // had already been tried and failed.
656
- if (!status.available) return "none stored";
657
- if (status.source === "environment") return `environment${status.detail ? ` (${status.detail})` : ""}`;
658
- if (status.source === "stored-api-key") return "stored api key";
659
- if (status.source === "stored-oauth") return "stored oauth";
660
- return status.source;
661
- }
662
-
663
- async function loginOAuthRuntime(rl: ReturnType<typeof createInterface>, runtime: RuntimeDescriptor): Promise<boolean> {
664
- const auth = openAuthStorage();
665
- if (runtime.authNotice) process.stdout.write(`note: ${runtime.authNotice}\n`);
666
- const manualCodeInput = createDelayedManualCodeInput(
667
- rl,
668
- "Paste verification code if browser callback does not complete automatically: ",
669
- );
670
- try {
671
- await auth.login(runtime.oauthProviderId ?? runtime.id, {
672
- onAuth: ({ url, instructions }) => {
673
- process.stdout.write(`\nOpen: ${url}\n`);
674
- if (instructions) process.stdout.write(`${instructions}\n`);
675
- process.stdout.write("Waiting for the browser callback. A manual code prompt will appear if needed.\n");
676
- },
677
- onDeviceCode: ({ verificationUri, userCode }) => {
678
- process.stdout.write(`\nOpen: ${verificationUri}\n`);
679
- process.stdout.write(`Enter code: ${userCode}\n`);
680
- },
681
- onPrompt: async (prompt) => {
682
- const answer = await rl.question(`${prompt.message}${prompt.allowEmpty ? " " : ": "}`);
683
- return prompt.allowEmpty ? answer : answer.trim();
684
- },
685
- onSelect: (prompt) => promptOAuthSelection(rl, prompt),
686
- onManualCodeInput: manualCodeInput.onManualCodeInput,
687
- onProgress: (message) => {
688
- process.stderr.write(`${message}\n`);
689
- },
690
- });
691
- if (credentialWriteFailed(auth, `credential for ${runtime.id} was not stored`)) return false;
692
- printOk(`authenticated ${runtime.id}`);
693
- return true;
694
- } catch (error) {
695
- printError(error instanceof Error ? error.message : String(error));
696
- return false;
697
- } finally {
698
- manualCodeInput.cancel();
699
- }
700
- }
701
-
702
- /**
703
- * The model ids a target can be configured with, and where they came from.
704
- * `probe` is the only source that says anything about the server in front of
705
- * us right now; `catalog` is static provider knowledge, and `existing` is the
706
- * list a previous configure recorded, which the server may no longer match.
707
- */
708
- interface WireModelInventory {
709
- models: string[];
710
- source: "catalog" | "probe" | "cache" | "legacy" | "none";
711
- /** Per-model load state when the probe reported it. */
712
- modelStates?: Readonly<Record<string, ProbeModelStatus>> | undefined;
713
- }
714
-
715
- /**
716
- * Every id the runtime would resolve. LM Studio lists a loaded model under its
717
- * instance id and keeps the model key only in the state map, and the request
718
- * path accepts either, so both count as advertised.
719
- */
720
- function advertisedModelIds(inventory: Pick<WireModelInventory, "models" | "modelStates">): string[] {
721
- const ids = [...inventory.models];
722
- for (const id of Object.keys(inventory.modelStates ?? {})) if (!ids.includes(id)) ids.push(id);
723
- return ids;
724
- }
725
-
726
- function residentModelIds(modelStates: WireModelInventory["modelStates"]): string[] {
727
- return Object.entries(modelStates ?? {})
728
- .filter(([, status]) => status.state === "loaded" || status.state === "loading")
729
- .map(([id]) => id);
730
- }
731
-
732
- function liveInventoryFor(
733
- target: Pick<TargetDescriptor, "id" | "url">,
734
- inventory: WireModelInventory,
735
- ): LiveModelInventory {
736
- return {
737
- targetId: target.id,
738
- url: target.url,
739
- models: advertisedModelIds(inventory),
740
- resident: residentModelIds(inventory.modelStates),
741
- };
742
- }
743
-
744
- async function resolveSupportedWireModels(
745
- runtime: RuntimeDescriptor,
746
- target: TargetDescriptor,
747
- existing?: TargetDescriptor,
748
- authToken?: string,
749
- ): Promise<WireModelInventory> {
750
- const known = listKnownModelsForRuntime(runtime.id);
751
- if (known.length > 0) return { models: known, source: "catalog" };
752
- if (runtime.kind === "http") {
753
- // The full probe carries load state alongside the ids; a runtime that
754
- // lists models only through probeModels still gets its ids checked.
755
- const probe = await runtimeProbe(runtime, target, authToken);
756
- if (probe?.ok && probe.models && probe.models.length > 0) {
757
- recordTargetModelSnapshot(target, probe.models);
758
- return { models: [...probe.models], source: "probe", modelStates: probe.modelStates };
759
- }
760
- const discovered = await runtimeProbeModels(runtime, target, authToken);
761
- if (discovered.length > 0) {
762
- recordTargetModelSnapshot(target, discovered);
763
- return { models: discovered, source: "probe" };
764
- }
765
- }
766
- const cached = readTargetModelSnapshot(target);
767
- if (cached && cached.models.length > 0) return { models: [...cached.models], source: "cache" };
768
- if (existing?.wireModels && existing.wireModels.length > 0) {
769
- recordTargetModelSnapshot(target, existing.wireModels);
770
- return { models: [...existing.wireModels], source: "legacy" };
771
- }
772
- return { models: [], source: "none" };
773
- }
774
-
775
375
  function resolveModelChoice(
776
376
  answer: string,
777
377
  wireModels: ReadonlyArray<string>,
@@ -819,6 +419,10 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
819
419
  printError("--agent-profile-model requires --agent-profile");
820
420
  return 2;
821
421
  }
422
+ if (args.bindAgent !== undefined && args.workerProfile === undefined) {
423
+ printError("--bind-agent requires --agent-profile");
424
+ return 2;
425
+ }
822
426
  if (args.workerProfile !== undefined && args.workerProfile.trim().length === 0) {
823
427
  printError("--agent-profile must be non-empty");
824
428
  return 2;
@@ -889,13 +493,14 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
889
493
  ...(args.maxTokens !== undefined ? { maxTokens: args.maxTokens } : {}),
890
494
  ...(args.reasoning !== undefined ? { reasoning: args.reasoning } : {}),
891
495
  ...(existing?.lmstudio ? { lmstudio: existing.lmstudio } : {}),
496
+ ...(existing?.litellm ? { litellm: existing.litellm } : {}),
892
497
  });
893
498
  const inventory = await resolveSupportedWireModels(runtime, seed, existing, args.apiKey);
894
499
  const wireModels = inventory.models;
895
500
  const model =
896
501
  args.model ??
897
502
  existing?.defaultModel ??
898
- support.defaultModel ??
503
+ (inventory.source === "probe" ? wireModels[0] : support.defaultModel) ??
899
504
  (support.modelSource === "catalog" ? undefined : wireModels[0]);
900
505
  if (model === undefined && support.modelSource === "catalog") {
901
506
  refuseCatalogSeededModel(runtime, support);
@@ -919,6 +524,7 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
919
524
  ...(args.maxTokens !== undefined ? { maxTokens: args.maxTokens } : {}),
920
525
  ...(args.reasoning !== undefined ? { reasoning: args.reasoning } : {}),
921
526
  ...(existing?.lmstudio ? { lmstudio: existing.lmstudio } : {}),
527
+ ...(existing?.litellm ? { litellm: existing.litellm } : {}),
922
528
  });
923
529
  const setOrchestrator = args.setOrchestrator || args.orchestratorModel !== undefined;
924
530
  const setBackground = args.setBackground || args.backgroundModel !== undefined;
@@ -958,6 +564,7 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
958
564
  descriptor,
959
565
  args.workerProfileModel ?? descriptor.defaultModel ?? null,
960
566
  );
567
+ if (args.bindAgent !== undefined) bindAgentProfileInSettings(target, args.bindAgent, args.workerProfile);
961
568
  }
962
569
  };
963
570
  // Apply to the local snapshot for the summary below, then persist the same
@@ -966,7 +573,9 @@ async function runNonInteractive(runtime: RuntimeDescriptor, args: ParsedArgs):
966
573
  applyConfiguration(settings);
967
574
  updateSettings(applyConfiguration);
968
575
  const probe = await runtimeProbe(runtime, descriptor);
969
- if (probe?.ok && probe.models) recordTargetModelSnapshot(descriptor, probe.models);
576
+ if (probe?.ok && probe.models) {
577
+ recordTargetModelSnapshot(descriptor, probe.models, probe.modelLabels ? { modelLabels: probe.modelLabels } : {});
578
+ }
970
579
  printSummary(settings, descriptor, probe);
971
580
  if (
972
581
  runtime.auth === "oauth" &&
@@ -1037,45 +646,6 @@ async function pickRuntimeFromEntries(
1037
646
  }
1038
647
  }
1039
648
 
1040
- const LOCAL_APP_RUNTIME_IDS: ReadonlySet<string> = new Set(["ollama-native", "lmstudio"]);
1041
-
1042
- // Generic protocol-compatible runtimes. They are classified local-http (they
1043
- // carry a probe and no cloud catalog entry), but a hosted endpoint such as
1044
- // Inception/Mercury is exactly an OpenAI-compatible cloud API, so the wizard
1045
- // also surfaces them under the Cloud API path.
1046
- const PROTOCOL_COMPAT_RUNTIME_IDS: ReadonlySet<string> = new Set(["openai-compat", "anthropic-compat"]);
1047
-
1048
- function runtimesForCategory(
1049
- entries: ReadonlyArray<ProviderSupportEntry>,
1050
- category: ConfigureCategory,
1051
- ): ProviderSupportEntry[] {
1052
- switch (category) {
1053
- case "local-app":
1054
- return entries.filter((entry) => LOCAL_APP_RUNTIME_IDS.has(entry.runtimeId));
1055
- case "local-http":
1056
- return entries.filter((entry) => entry.group === "local-http" && !LOCAL_APP_RUNTIME_IDS.has(entry.runtimeId));
1057
- case "chatgpt":
1058
- return entries.filter((entry) => entry.runtimeId === "openai-codex");
1059
- case "cloud-api": {
1060
- const byLabel = (a: ProviderSupportEntry, b: ProviderSupportEntry) =>
1061
- a.label.localeCompare(b.label) || a.runtimeId.localeCompare(b.runtimeId);
1062
- const named = entries
1063
- .filter((entry) => entry.group === "cloud-api")
1064
- .slice()
1065
- .sort(byLabel);
1066
- // Surface the generic OpenAI/Anthropic-compatible runtimes here too,
1067
- // shown last and under the cloud heading rather than their native group.
1068
- const compat = entries
1069
- .filter((entry) => PROTOCOL_COMPAT_RUNTIME_IDS.has(entry.runtimeId))
1070
- .map((entry) => ({ ...entry, group: "cloud-api" as const }))
1071
- .sort(byLabel);
1072
- return [...named, ...compat];
1073
- }
1074
- case "all":
1075
- return entries.slice();
1076
- }
1077
- }
1078
-
1079
649
  async function pickCategory(rl: ReturnType<typeof createInterface>): Promise<ConfigureCategory | null> {
1080
650
  process.stdout.write("\nHow will you connect Clio to a model?\n\n");
1081
651
  for (const line of formatCategoryMenu(terminalColumns())) process.stdout.write(`${line}\n`);
@@ -1143,16 +713,11 @@ async function maybeSteerToNativeRuntime(
1143
713
  return native;
1144
714
  }
1145
715
 
1146
- async function runInteractive(
716
+ async function runTargetSetupInteractive(
1147
717
  rl: ReturnType<typeof createInterface>,
1148
- preselectedRuntime: RuntimeDescriptor | null,
718
+ runtime: RuntimeDescriptor,
1149
719
  defaults: ParsedArgs,
1150
720
  ): Promise<number> {
1151
- let runtime = preselectedRuntime ?? (await pickRuntimeViaCategory(rl));
1152
- if (!runtime) {
1153
- printError("configuration cancelled");
1154
- return 130;
1155
- }
1156
721
  const auth = openAuthStorage();
1157
722
  const settings = readSettings();
1158
723
  let support = buildProviderSupportEntry(runtime);
@@ -1289,9 +854,10 @@ async function runInteractive(
1289
854
  ...(defaults.maxTokens !== undefined ? { maxTokens: defaults.maxTokens } : {}),
1290
855
  ...(defaults.reasoning !== undefined ? { reasoning: defaults.reasoning } : {}),
1291
856
  ...(existing?.lmstudio ? { lmstudio: existing.lmstudio } : {}),
857
+ ...(existing?.litellm ? { litellm: existing.litellm } : {}),
1292
858
  });
1293
859
 
1294
- if (runtime.kind === "http") {
860
+ if (runtime.kind === "http" || runtime.externalAgentLoop?.modelCatalog === "live-authoritative") {
1295
861
  inventory = await resolveSupportedWireModels(runtime, tentative, existing ?? undefined);
1296
862
  }
1297
863
  const wireModels = inventory.models;
@@ -1299,12 +865,21 @@ async function runInteractive(
1299
865
  // --model here the way the non-interactive path does. It does need to stop
1300
866
  // offering the alphabetically first id as though it were the recommended one.
1301
867
  const catalogOrdered = support.modelSource === "catalog";
1302
- model = model ?? existing?.defaultModel ?? support.defaultModel ?? (catalogOrdered ? undefined : wireModels[0]);
868
+ model =
869
+ model ??
870
+ existing?.defaultModel ??
871
+ (inventory.source === "probe" ? wireModels[0] : support.defaultModel) ??
872
+ (catalogOrdered ? undefined : wireModels[0]);
1303
873
  if (wireModels.length > 0) {
1304
874
  process.stdout.write("\nSelectable models:\n");
1305
875
  for (const [index, wireModel] of wireModels.entries()) {
1306
- process.stdout.write(` ${index + 1}. ${wireModel}${wireModel === model ? " [default]" : ""}\n`);
876
+ const label = inventory.labels?.[wireModel];
877
+ process.stdout.write(
878
+ ` ${index + 1}. ${wireModel}${label && label !== wireModel ? ` — ${label}` : ""}${wireModel === model ? " [default]" : ""}\n`,
879
+ );
1307
880
  }
881
+ if (inventory.source === "cache") process.stdout.write(" cached model snapshot (not verified in this run)\n");
882
+ if (inventory.probeError) process.stdout.write(` live probe unavailable: ${inventory.probeError}\n`);
1308
883
  if (!model && catalogOrdered) {
1309
884
  process.stdout.write(` listed in provider catalog order, which recommends none of them; pick one.\n`);
1310
885
  }
@@ -1375,6 +950,7 @@ async function runInteractive(
1375
950
  ...(defaults.maxTokens !== undefined ? { maxTokens: defaults.maxTokens } : {}),
1376
951
  ...(reasoningChoice !== undefined ? { reasoning: reasoningChoice } : {}),
1377
952
  ...(existing?.lmstudio ? { lmstudio: existing.lmstudio } : {}),
953
+ ...(existing?.litellm ? { litellm: existing.litellm } : {}),
1378
954
  });
1379
955
  try {
1380
956
  assertOrchestratorReplacementEligible(settings, descriptor);
@@ -1460,7 +1036,7 @@ async function runInteractive(
1460
1036
 
1461
1037
  printSummary(settings, descriptor, probe);
1462
1038
  printOk(`target ${targetId} saved`);
1463
- await runInteropReview(rl);
1039
+ await runInteropReview({ rl });
1464
1040
  return 0;
1465
1041
  }
1466
1042
 
@@ -1510,7 +1086,980 @@ export function runTargetRename(oldId: string, newId: string): number {
1510
1086
  return 0;
1511
1087
  }
1512
1088
 
1513
- export async function runConfigureCommand(argv: ReadonlyArray<string>): Promise<number> {
1089
+ /**
1090
+ * The runtime configuration screens.
1091
+ *
1092
+ * Every screen is one `SectionSpec`: a title, the values it shows, and the
1093
+ * actions that change them. Rendering goes through the lifecycle presenter, so
1094
+ * `configure` degrades on a pipe and under NO_COLOR exactly as `reset`,
1095
+ * `upgrade`, and `uninstall` do. Navigation goes through `pickEntry`, which
1096
+ * uses the arrow-key selector on a terminal and the numbered readline prompt
1097
+ * everywhere else, so Escape leaves a screen without the screen having to teach
1098
+ * a token for it, and a scripted run still has a way to answer.
1099
+ */
1100
+
1101
+ interface SectionIo {
1102
+ rl: ReturnType<typeof createInterface>;
1103
+ out: NodeJS.WritableStream;
1104
+ /** Both ends of this run, for an action that opens a picker of its own. */
1105
+ streams: ConfigureStreams;
1106
+ /** Confirm one change on the transcript, in the presenter's voice. */
1107
+ ok: (text: string) => void;
1108
+ }
1109
+
1110
+ interface SectionAction {
1111
+ label: string;
1112
+ hint?: string;
1113
+ run: (io: SectionIo) => Promise<void>;
1114
+ }
1115
+
1116
+ interface SectionSpec {
1117
+ /** Canonical `--section` name. */
1118
+ id: string;
1119
+ title: string;
1120
+ /** What the main menu says this screen covers. */
1121
+ summary: string;
1122
+ /** Other spellings `--section` accepts. */
1123
+ aliases?: ReadonlyArray<string>;
1124
+ fields: () => Array<readonly [string, string]>;
1125
+ actions: ReadonlyArray<SectionAction>;
1126
+ }
1127
+
1128
+ const NONE = "(none)";
1129
+ const onOff = (value: boolean): string => (value ? "enabled" : "disabled");
1130
+ const listOr = (values: ReadonlyArray<string>, fallback = NONE): string =>
1131
+ values.length > 0 ? values.join(", ") : fallback;
1132
+
1133
+ /** Read a bounded integer, leaving the setting alone when the answer is not one. */
1134
+ async function askInteger(
1135
+ io: SectionIo,
1136
+ label: string,
1137
+ current: number,
1138
+ apply: (value: number) => void,
1139
+ min = 0,
1140
+ ): Promise<void> {
1141
+ const answer = await ask(io.rl, label, String(current));
1142
+ if (answer === null) return;
1143
+ const parsed = Number(answer);
1144
+ if (!Number.isFinite(parsed) || parsed < min) {
1145
+ io.out.write(` ${label} must be a number of at least ${min}; left at ${current}\n`);
1146
+ return;
1147
+ }
1148
+ const value = Math.floor(parsed);
1149
+ apply(value);
1150
+ io.ok(`${label} set to ${value}`);
1151
+ }
1152
+
1153
+ /** Read one of a fixed set of words, leaving the setting alone otherwise. */
1154
+ async function askChoice(
1155
+ io: SectionIo,
1156
+ label: string,
1157
+ allowed: ReadonlyArray<string>,
1158
+ current: string,
1159
+ apply: (value: string) => void,
1160
+ ): Promise<void> {
1161
+ const answer = await ask(io.rl, `${label} [${allowed.join("|")}]`, current);
1162
+ if (answer === null) return;
1163
+ const value = answer.trim().toLowerCase();
1164
+ if (!allowed.includes(value)) {
1165
+ io.out.write(` ${label} must be one of ${allowed.join(", ")}; left at ${current}\n`);
1166
+ return;
1167
+ }
1168
+ apply(value);
1169
+ io.ok(`${label} set to ${value}`);
1170
+ }
1171
+
1172
+ const SECTIONS: ReadonlyArray<SectionSpec> = [
1173
+ {
1174
+ id: "targets",
1175
+ title: "Targets & Auth",
1176
+ summary: "providers, endpoints, credentials, models",
1177
+ aliases: ["auth", "target", "providers"],
1178
+ fields: () => {
1179
+ const settings = readSettings();
1180
+ const rows: Array<readonly [string, string]> = [
1181
+ ["Chat target", `${settings.chat.target ?? NONE} (model: ${settings.chat.model ?? "target default"})`],
1182
+ [
1183
+ "Fleet target",
1184
+ `${settings.fleet.default.target ?? NONE} (model: ${settings.fleet.default.model ?? "target default"})`,
1185
+ ],
1186
+ ];
1187
+ if (settings.targets.length === 0) rows.push(["Registered", "(no targets registered)"]);
1188
+ else
1189
+ for (const target of settings.targets) {
1190
+ rows.push([
1191
+ ` ${target.id}`,
1192
+ `${target.runtime}, model ${target.defaultModel ?? "unset"}${
1193
+ settings.chat.target === target.id ? " [chat]" : ""
1194
+ }${settings.fleet.default.target === target.id ? " [fleet]" : ""}`,
1195
+ ]);
1196
+ }
1197
+ return rows;
1198
+ },
1199
+ actions: [
1200
+ {
1201
+ label: "Add a target",
1202
+ hint: "register a provider endpoint",
1203
+ run: async (io) => {
1204
+ const runtime = await pickRuntimeViaCategory(io.rl);
1205
+ if (runtime) await runTargetSetupInteractive(io.rl, runtime, emptyArgs());
1206
+ },
1207
+ },
1208
+ {
1209
+ label: "Set the chat target",
1210
+ hint: "which target answers in chat",
1211
+ run: async (io) => {
1212
+ await assignTarget(io, "chat");
1213
+ },
1214
+ },
1215
+ {
1216
+ label: "Set the fleet default target",
1217
+ hint: "which target dispatched workers use",
1218
+ run: async (io) => {
1219
+ await assignTarget(io, "fleet");
1220
+ },
1221
+ },
1222
+ {
1223
+ label: "Remove a target",
1224
+ run: async (io) => {
1225
+ const settings = readSettings();
1226
+ const ids = settings.targets.map((target) => target.id);
1227
+ if (ids.length === 0) {
1228
+ io.out.write(" No targets to remove.\n");
1229
+ return;
1230
+ }
1231
+ io.out.write(` Registered: ${ids.join(", ")}\n`);
1232
+ const chosen = await ask(io.rl, "Target id to remove");
1233
+ if (chosen === null || !ids.includes(chosen)) return;
1234
+ if (await askYesNo(io.rl, `Remove target '${chosen}'?`, false)) {
1235
+ runTargetRemove(chosen);
1236
+ }
1237
+ },
1238
+ },
1239
+ ],
1240
+ },
1241
+ {
1242
+ id: "models",
1243
+ title: "Models & Thinking",
1244
+ summary: "default model, thinking level, favorites",
1245
+ aliases: ["model", "thinking"],
1246
+ fields: () => {
1247
+ const settings = readSettings();
1248
+ return [
1249
+ ["Chat model", settings.chat.model ?? "(target default)"],
1250
+ ["Chat thinking", settings.chat.thinkingLevel],
1251
+ ["Fleet thinking", settings.fleet.default.thinkingLevel],
1252
+ ["Favorites", listOr(settings.chat.modelPicker.favorites)],
1253
+ ["Cycle set (Alt+J/K)", listOr(settings.chat.modelPicker.cycleSet)],
1254
+ ];
1255
+ },
1256
+ actions: [
1257
+ {
1258
+ label: "Chat thinking level",
1259
+ hint: THINKING_LEVELS.join(" | "),
1260
+ run: async (io) => {
1261
+ await askChoice(io, "Chat thinking level", THINKING_LEVELS, readSettings().chat.thinkingLevel, (value) => {
1262
+ updateSettings((draft) => {
1263
+ draft.chat.thinkingLevel = value as ThinkingLevel;
1264
+ });
1265
+ });
1266
+ },
1267
+ },
1268
+ {
1269
+ label: "Fleet thinking level",
1270
+ hint: THINKING_LEVELS.join(" | "),
1271
+ run: async (io) => {
1272
+ await askChoice(
1273
+ io,
1274
+ "Fleet thinking level",
1275
+ THINKING_LEVELS,
1276
+ readSettings().fleet.default.thinkingLevel,
1277
+ (value) => {
1278
+ updateSettings((draft) => {
1279
+ draft.fleet.default.thinkingLevel = value as ThinkingLevel;
1280
+ });
1281
+ },
1282
+ );
1283
+ },
1284
+ },
1285
+ {
1286
+ label: "Chat default model",
1287
+ hint: "blank clears the override",
1288
+ run: async (io) => {
1289
+ const model = await ask(
1290
+ io.rl,
1291
+ "Chat default model (blank for the target default)",
1292
+ readSettings().chat.model ?? "",
1293
+ );
1294
+ if (model === null) return;
1295
+ updateSettings((draft) => {
1296
+ draft.chat.model = model.length > 0 ? model : null;
1297
+ });
1298
+ io.ok(model.length > 0 ? `Chat default model set to ${model}` : "Chat default model cleared");
1299
+ },
1300
+ },
1301
+ {
1302
+ label: "Model favorites",
1303
+ hint: "comma-separated",
1304
+ run: async (io) => {
1305
+ await askList(io, "Model favorites", readSettings().chat.modelPicker.favorites, (values) => {
1306
+ updateSettings((draft) => {
1307
+ draft.chat.modelPicker.favorites = values;
1308
+ });
1309
+ });
1310
+ },
1311
+ },
1312
+ {
1313
+ label: "Cycle set",
1314
+ hint: "comma-separated, Alt+J/K walks it",
1315
+ run: async (io) => {
1316
+ await askList(io, "Cycle set", readSettings().chat.modelPicker.cycleSet, (values) => {
1317
+ updateSettings((draft) => {
1318
+ draft.chat.modelPicker.cycleSet = values;
1319
+ });
1320
+ });
1321
+ },
1322
+ },
1323
+ ],
1324
+ },
1325
+ {
1326
+ id: "chat",
1327
+ title: "Chat Defaults",
1328
+ summary: "streaming, progress, compaction",
1329
+ fields: () => {
1330
+ const settings = readSettings();
1331
+ return [
1332
+ ["Smooth streaming", settings.interface.smoothStreaming],
1333
+ ["Terminal progress", onOff(settings.interface.terminalProgress)],
1334
+ [
1335
+ "Max output tokens",
1336
+ settings.chat.maxOutputTokens > 0 ? String(settings.chat.maxOutputTokens) : "(runtime default)",
1337
+ ],
1338
+ ["Prompt prewarm", onOff(settings.chat.prewarm)],
1339
+ [
1340
+ "Auto-compaction",
1341
+ settings.context.compaction.auto
1342
+ ? `enabled at ${Math.round(settings.context.compaction.threshold * 100)}%`
1343
+ : "disabled",
1344
+ ],
1345
+ [
1346
+ "Working-set eviction",
1347
+ settings.context.workingSet.enabled ? `enabled (${settings.context.workingSet.policy})` : "disabled",
1348
+ ],
1349
+ ];
1350
+ },
1351
+ actions: [
1352
+ {
1353
+ label: "Smooth streaming",
1354
+ hint: "off | auto | on",
1355
+ run: async (io) => {
1356
+ await askChoice(
1357
+ io,
1358
+ "Smooth streaming",
1359
+ ["off", "auto", "on"],
1360
+ readSettings().interface.smoothStreaming,
1361
+ (value) => {
1362
+ updateSettings((draft) => {
1363
+ draft.interface.smoothStreaming = value as SmoothStreaming;
1364
+ });
1365
+ },
1366
+ );
1367
+ },
1368
+ },
1369
+ {
1370
+ label: "Terminal progress indicator",
1371
+ hint: "toggle",
1372
+ run: async (io) => {
1373
+ let next = false;
1374
+ updateSettings((draft) => {
1375
+ draft.interface.terminalProgress = !draft.interface.terminalProgress;
1376
+ next = draft.interface.terminalProgress;
1377
+ });
1378
+ io.ok(`Terminal progress ${onOff(next)}`);
1379
+ },
1380
+ },
1381
+ {
1382
+ label: "Max output tokens",
1383
+ hint: "0 for the runtime default",
1384
+ run: async (io) => {
1385
+ await askInteger(io, "Max output tokens", readSettings().chat.maxOutputTokens, (value) => {
1386
+ updateSettings((draft) => {
1387
+ draft.chat.maxOutputTokens = value;
1388
+ });
1389
+ });
1390
+ },
1391
+ },
1392
+ {
1393
+ label: "Prompt prewarm",
1394
+ hint: "toggle",
1395
+ run: async (io) => {
1396
+ let next = false;
1397
+ updateSettings((draft) => {
1398
+ draft.chat.prewarm = !draft.chat.prewarm;
1399
+ next = draft.chat.prewarm;
1400
+ });
1401
+ io.ok(`Prompt prewarm ${onOff(next)}`);
1402
+ },
1403
+ },
1404
+ {
1405
+ label: "Auto-compaction",
1406
+ hint: "toggle",
1407
+ run: async (io) => {
1408
+ let next = false;
1409
+ updateSettings((draft) => {
1410
+ draft.context.compaction.auto = !draft.context.compaction.auto;
1411
+ next = draft.context.compaction.auto;
1412
+ });
1413
+ io.ok(`Auto-compaction ${onOff(next)}`);
1414
+ },
1415
+ },
1416
+ ],
1417
+ },
1418
+ {
1419
+ id: "fleet",
1420
+ title: "Fleet",
1421
+ summary: "concurrency, retries, worker timeouts",
1422
+ fields: () => {
1423
+ const settings = readSettings();
1424
+ return [
1425
+ ["Concurrency limit", String(settings.fleet.concurrency)],
1426
+ ["Max retries", String(settings.fleet.retry.maxRetries)],
1427
+ ["Tool calls per run", String(settings.fleet.limits.toolCallsPerRun)],
1428
+ ["Run timeout", `${settings.fleet.limits.internalRunTimeoutMs / 1000}s`],
1429
+ ["Worker profiles", listOr(Object.keys(settings.fleet.profiles))],
1430
+ [
1431
+ "Subagent pins",
1432
+ listOr(Object.entries(settings.fleet.agentProfiles).map(([agent, profile]) => `${agent} -> ${profile}`)),
1433
+ ],
1434
+ ["Remote nodes", listOr(settings.fleet.nodes.map((node) => node.id))],
1435
+ ];
1436
+ },
1437
+ actions: [
1438
+ {
1439
+ label: "Concurrency limit",
1440
+ hint: "auto, or a positive integer",
1441
+ run: async (io) => {
1442
+ const current = String(readSettings().fleet.concurrency);
1443
+ const answer = await ask(io.rl, "Concurrency limit [auto or a number]", current);
1444
+ if (answer === null) return;
1445
+ if (answer.trim().toLowerCase() === "auto") {
1446
+ updateSettings((draft) => {
1447
+ draft.fleet.concurrency = "auto";
1448
+ });
1449
+ io.ok("Concurrency limit set to auto");
1450
+ return;
1451
+ }
1452
+ const parsed = Number(answer);
1453
+ if (!Number.isFinite(parsed) || parsed < 1) {
1454
+ io.out.write(` Concurrency limit must be auto or at least 1; left at ${current}\n`);
1455
+ return;
1456
+ }
1457
+ updateSettings((draft) => {
1458
+ draft.fleet.concurrency = Math.floor(parsed);
1459
+ });
1460
+ io.ok(`Concurrency limit set to ${Math.floor(parsed)}`);
1461
+ },
1462
+ },
1463
+ {
1464
+ label: "Max retries",
1465
+ run: async (io) => {
1466
+ await askInteger(io, "Max retries", readSettings().fleet.retry.maxRetries, (value) => {
1467
+ updateSettings((draft) => {
1468
+ draft.fleet.retry.maxRetries = value;
1469
+ });
1470
+ });
1471
+ },
1472
+ },
1473
+ {
1474
+ label: "Tool calls per run",
1475
+ run: async (io) => {
1476
+ await askInteger(
1477
+ io,
1478
+ "Tool calls per run",
1479
+ readSettings().fleet.limits.toolCallsPerRun,
1480
+ (value) => {
1481
+ updateSettings((draft) => {
1482
+ draft.fleet.limits.toolCallsPerRun = value;
1483
+ });
1484
+ },
1485
+ 1,
1486
+ );
1487
+ },
1488
+ },
1489
+ {
1490
+ label: "Run timeout",
1491
+ hint: "seconds",
1492
+ run: async (io) => {
1493
+ const current = readSettings().fleet.limits.internalRunTimeoutMs / 1000;
1494
+ const answer = await ask(io.rl, "Run timeout in seconds", String(current));
1495
+ if (answer === null) return;
1496
+ const parsed = Number(answer);
1497
+ if (!Number.isFinite(parsed) || parsed < 1) {
1498
+ io.out.write(` Run timeout must be at least 1 second; left at ${current}s\n`);
1499
+ return;
1500
+ }
1501
+ updateSettings((draft) => {
1502
+ draft.fleet.limits.internalRunTimeoutMs = Math.floor(parsed * 1000);
1503
+ });
1504
+ io.ok(`Run timeout set to ${Math.floor(parsed)}s`);
1505
+ },
1506
+ },
1507
+ ],
1508
+ },
1509
+ {
1510
+ id: "permissions",
1511
+ title: "Permissions & Autonomy",
1512
+ summary: "autonomy level, worker permissions, cost limits",
1513
+ aliases: ["autonomy", "safety"],
1514
+ fields: () => {
1515
+ const settings = readSettings();
1516
+ return [
1517
+ ["Autonomy level", settings.safety.autonomy],
1518
+ ["Worker permissions", settings.fleet.permissions.mode],
1519
+ ["Session cost limit", `$${settings.safety.limits.sessionCostUsd} USD`],
1520
+ ["Turn tool budget", String(settings.safety.limits.chatToolCallsPerTurn)],
1521
+ ["Review watchdog", onOff(settings.safety.review.enabled)],
1522
+ ];
1523
+ },
1524
+ actions: [
1525
+ {
1526
+ label: "Autonomy level",
1527
+ hint: "interactive | assisted | auto-edit | full",
1528
+ run: async (io) => {
1529
+ io.out.write(
1530
+ " interactive prompts for everything, assisted auto-reads and confirms writes,\n auto-edit confirms only bash, full runs unattended.\n",
1531
+ );
1532
+ await askChoice(
1533
+ io,
1534
+ "Autonomy level",
1535
+ ["interactive", "assisted", "auto-edit", "full"],
1536
+ readSettings().safety.autonomy,
1537
+ (value) => {
1538
+ updateSettings((draft) => {
1539
+ draft.safety.autonomy = value as AutonomyLevel;
1540
+ });
1541
+ },
1542
+ );
1543
+ },
1544
+ },
1545
+ {
1546
+ label: "Worker permission mode",
1547
+ hint: "deny | fail | escalate",
1548
+ run: async (io) => {
1549
+ await askChoice(
1550
+ io,
1551
+ "Worker permission mode",
1552
+ ["deny", "fail", "escalate"],
1553
+ readSettings().fleet.permissions.mode,
1554
+ (value) => {
1555
+ updateSettings((draft) => {
1556
+ draft.fleet.permissions.mode = value as WorkerPermissionMode;
1557
+ });
1558
+ },
1559
+ );
1560
+ },
1561
+ },
1562
+ {
1563
+ label: "Session cost limit",
1564
+ hint: "USD",
1565
+ run: async (io) => {
1566
+ const current = readSettings().safety.limits.sessionCostUsd;
1567
+ const answer = await ask(io.rl, "Session cost limit in USD", String(current));
1568
+ if (answer === null) return;
1569
+ const parsed = Number(answer);
1570
+ if (!Number.isFinite(parsed) || parsed <= 0) {
1571
+ io.out.write(` Session cost limit must be greater than 0; left at $${current}\n`);
1572
+ return;
1573
+ }
1574
+ updateSettings((draft) => {
1575
+ draft.safety.limits.sessionCostUsd = parsed;
1576
+ });
1577
+ io.ok(`Session cost limit set to $${parsed} USD`);
1578
+ },
1579
+ },
1580
+ {
1581
+ label: "Turn tool budget",
1582
+ run: async (io) => {
1583
+ await askInteger(
1584
+ io,
1585
+ "Turn tool budget",
1586
+ readSettings().safety.limits.chatToolCallsPerTurn,
1587
+ (value) => {
1588
+ updateSettings((draft) => {
1589
+ draft.safety.limits.chatToolCallsPerTurn = value;
1590
+ });
1591
+ },
1592
+ 1,
1593
+ );
1594
+ },
1595
+ },
1596
+ {
1597
+ label: "Turn-end review watchdog",
1598
+ hint: "toggle",
1599
+ run: async (io) => {
1600
+ let next = false;
1601
+ updateSettings((draft) => {
1602
+ draft.safety.review.enabled = !draft.safety.review.enabled;
1603
+ next = draft.safety.review.enabled;
1604
+ });
1605
+ io.ok(`Review watchdog ${onOff(next)}`);
1606
+ },
1607
+ },
1608
+ ],
1609
+ },
1610
+ {
1611
+ id: "panes",
1612
+ title: "Panes & Layout",
1613
+ summary: "terminal panes, startup layout, display mode",
1614
+ aliases: ["layout", "interface", "pane"],
1615
+ fields: () => {
1616
+ const settings = readSettings();
1617
+ return [
1618
+ ["Panes capability", settings.interface.panes.enabled],
1619
+ ["Startup layout", settings.interface.panes.layout],
1620
+ ["TUI mode", settings.interface.mode],
1621
+ ["Output detail", settings.interface.outputDetail],
1622
+ ["Desktop notifications", onOff(settings.interface.desktopNotifications)],
1623
+ ["Git commit attribution", onOff(settings.integrations.git.commitAttribution)],
1624
+ ];
1625
+ },
1626
+ actions: [
1627
+ {
1628
+ label: "Panes capability",
1629
+ hint: "off | auto | embedded",
1630
+ run: async (io) => {
1631
+ await askChoice(
1632
+ io,
1633
+ "Panes capability",
1634
+ ["off", "auto", "embedded"],
1635
+ readSettings().interface.panes.enabled,
1636
+ (value) => {
1637
+ updateSettings((draft) => {
1638
+ draft.interface.panes.enabled = value as PanesSettings["enabled"];
1639
+ });
1640
+ },
1641
+ );
1642
+ },
1643
+ },
1644
+ {
1645
+ label: "Startup layout",
1646
+ hint: "off | workers | cockpit",
1647
+ run: async (io) => {
1648
+ await askChoice(
1649
+ io,
1650
+ "Startup layout",
1651
+ ["off", "workers", "cockpit"],
1652
+ readSettings().interface.panes.layout,
1653
+ (value) => {
1654
+ updateSettings((draft) => {
1655
+ draft.interface.panes.layout = value as PanesSettings["layout"];
1656
+ });
1657
+ },
1658
+ );
1659
+ },
1660
+ },
1661
+ {
1662
+ label: "TUI mode",
1663
+ hint: "regular | fullscreen",
1664
+ run: async (io) => {
1665
+ await askChoice(io, "TUI mode", ["regular", "fullscreen"], readSettings().interface.mode, (value) => {
1666
+ updateSettings((draft) => {
1667
+ draft.interface.mode = value as TuiMode;
1668
+ });
1669
+ });
1670
+ },
1671
+ },
1672
+ {
1673
+ label: "Output detail",
1674
+ hint: "minimal | default | verbose",
1675
+ run: async (io) => {
1676
+ await askChoice(
1677
+ io,
1678
+ "Output detail",
1679
+ ["minimal", "default", "verbose"],
1680
+ readSettings().interface.outputDetail,
1681
+ (value) => {
1682
+ updateSettings((draft) => {
1683
+ draft.interface.outputDetail = value as OutputVerbosity;
1684
+ });
1685
+ },
1686
+ );
1687
+ },
1688
+ },
1689
+ {
1690
+ label: "Desktop notifications",
1691
+ hint: "toggle",
1692
+ run: async (io) => {
1693
+ let next = false;
1694
+ updateSettings((draft) => {
1695
+ draft.interface.desktopNotifications = !draft.interface.desktopNotifications;
1696
+ next = draft.interface.desktopNotifications;
1697
+ });
1698
+ io.ok(`Desktop notifications ${onOff(next)}`);
1699
+ },
1700
+ },
1701
+ {
1702
+ label: "Git commit attribution",
1703
+ hint: "toggle",
1704
+ run: async (io) => {
1705
+ let next = false;
1706
+ updateSettings((draft) => {
1707
+ draft.integrations.git.commitAttribution = !draft.integrations.git.commitAttribution;
1708
+ next = draft.integrations.git.commitAttribution;
1709
+ });
1710
+ io.ok(`Git commit attribution ${onOff(next)}`);
1711
+ },
1712
+ },
1713
+ ],
1714
+ },
1715
+ {
1716
+ id: "skills",
1717
+ title: "Skills & Extensions",
1718
+ summary: "project imports, ACP peers, plugins",
1719
+ aliases: ["extensions", "skill", "interop"],
1720
+ fields: () => {
1721
+ const settings = readSettings();
1722
+ const rows: Array<readonly [string, string]> = [
1723
+ ["Trust project imports", settings.integrations.projectResources.trustProjectImports ? "trusted" : "untrusted"],
1724
+ ["External ACP agents", String(settings.integrations.externalAgents.entries.length)],
1725
+ ];
1726
+ for (const agent of settings.integrations.externalAgents.entries) {
1727
+ rows.push([` ${agent.id}`, `${agent.command} (governance: ${agent.toolGovernance ?? "default"})`]);
1728
+ }
1729
+ rows.push(
1730
+ ["Runtime plugins", listOr(settings.integrations.runtimePlugins)],
1731
+ ["Library remote sync", onOff(settings.integrations.library.sync)],
1732
+ );
1733
+ return rows;
1734
+ },
1735
+ actions: [
1736
+ {
1737
+ label: "Trust project imports",
1738
+ hint: "toggle",
1739
+ run: async (io) => {
1740
+ let next = false;
1741
+ updateSettings((draft) => {
1742
+ draft.integrations.projectResources.trustProjectImports =
1743
+ !draft.integrations.projectResources.trustProjectImports;
1744
+ next = draft.integrations.projectResources.trustProjectImports;
1745
+ });
1746
+ io.ok(`Project imports ${next ? "trusted" : "untrusted"}`);
1747
+ },
1748
+ },
1749
+ {
1750
+ label: "Review external ACP agents",
1751
+ hint: "runs the interop review",
1752
+ run: async (io) => {
1753
+ await runInteropReview({ rl: io.rl, streams: io.streams });
1754
+ },
1755
+ },
1756
+ {
1757
+ label: "Library remote sync",
1758
+ hint: "toggle",
1759
+ run: async (io) => {
1760
+ let next = false;
1761
+ updateSettings((draft) => {
1762
+ draft.integrations.library.sync = !draft.integrations.library.sync;
1763
+ next = draft.integrations.library.sync;
1764
+ });
1765
+ io.ok(`Library remote sync ${onOff(next)}`);
1766
+ },
1767
+ },
1768
+ ],
1769
+ },
1770
+ {
1771
+ id: "diagnostics",
1772
+ title: "Diagnostics",
1773
+ summary: "version, directories, doctor, raw settings",
1774
+ aliases: ["doctor", "diag"],
1775
+ fields: () => {
1776
+ const dirs = resolveClioDirs();
1777
+ const info = getVersionInfo();
1778
+ return [
1779
+ ["Clio Coder", info.clio],
1780
+ ["Node.js", info.node],
1781
+ ["Platform", info.platform],
1782
+ ["Config dir", shortenPath(dirs.config)],
1783
+ ["Data dir", shortenPath(dirs.data)],
1784
+ ["State dir", shortenPath(dirs.state)],
1785
+ ["Cache dir", shortenPath(dirs.cache)],
1786
+ ];
1787
+ },
1788
+ actions: [
1789
+ {
1790
+ label: "Run doctor",
1791
+ hint: "read-only health check",
1792
+ run: async () => {
1793
+ const { runDoctorCommand } = await import("./doctor.js");
1794
+ await runDoctorCommand([]);
1795
+ },
1796
+ },
1797
+ {
1798
+ label: "Show raw settings.yaml",
1799
+ run: async (io) => {
1800
+ const file = settingsPath();
1801
+ if (!existsSync(file)) {
1802
+ io.out.write(" settings.yaml does not exist yet.\n");
1803
+ return;
1804
+ }
1805
+ io.out.write(`\n--- ${shortenPath(file)} ---\n${readFileSync(file, "utf8")}--- end ---\n`);
1806
+ },
1807
+ },
1808
+ ],
1809
+ },
1810
+ ];
1811
+
1812
+ /** A `ParsedArgs` with nothing set, for the target wizard called from a section. */
1813
+ function emptyArgs(): ParsedArgs {
1814
+ return {
1815
+ positional: [],
1816
+ help: false,
1817
+ list: false,
1818
+ all: false,
1819
+ interop: false,
1820
+ json: false,
1821
+ force: false,
1822
+ gateway: false,
1823
+ setOrchestrator: false,
1824
+ setBackground: false,
1825
+ setWorkerDefault: false,
1826
+ };
1827
+ }
1828
+
1829
+ async function askList(
1830
+ io: SectionIo,
1831
+ label: string,
1832
+ current: ReadonlyArray<string>,
1833
+ apply: (values: string[]) => void,
1834
+ ): Promise<void> {
1835
+ const answer = await ask(io.rl, `${label} (comma-separated, blank clears)`, current.join(", "));
1836
+ if (answer === null) return;
1837
+ const values = answer
1838
+ .split(",")
1839
+ .map((entry) => entry.trim())
1840
+ .filter((entry) => entry.length > 0);
1841
+ apply(values);
1842
+ io.ok(values.length === 0 ? `${label} cleared` : `${label} set to ${values.join(", ")}`);
1843
+ }
1844
+
1845
+ async function assignTarget(io: SectionIo, role: "chat" | "fleet"): Promise<void> {
1846
+ const settings = readSettings();
1847
+ const ids = settings.targets.map((target) => target.id);
1848
+ if (ids.length === 0) {
1849
+ io.out.write(" No targets registered yet. Add one first.\n");
1850
+ return;
1851
+ }
1852
+ io.out.write(` Registered: ${ids.join(", ")}\n`);
1853
+ const currentTarget = role === "chat" ? settings.chat.target : settings.fleet.default.target;
1854
+ const chosen = await ask(io.rl, `${role === "chat" ? "Chat" : "Fleet"} target id`, currentTarget ?? ids[0] ?? "");
1855
+ if (chosen === null) return;
1856
+ if (!ids.includes(chosen)) {
1857
+ io.out.write(` ${chosen} is not a registered target; nothing changed.\n`);
1858
+ return;
1859
+ }
1860
+ const target = settings.targets.find((entry) => entry.id === chosen);
1861
+ const model = await ask(io.rl, "Model (blank for the target default)", target?.defaultModel ?? "");
1862
+ if (model === null) return;
1863
+ updateSettings((draft) => {
1864
+ if (role === "chat") {
1865
+ draft.chat.target = chosen;
1866
+ draft.chat.model = model.length > 0 ? model : null;
1867
+ } else {
1868
+ draft.fleet.default.target = chosen;
1869
+ draft.fleet.default.model = model.length > 0 ? model : null;
1870
+ }
1871
+ });
1872
+ io.ok(`${role === "chat" ? "Chat" : "Fleet"} target set to ${chosen}`);
1873
+ }
1874
+
1875
+ interface MenuEntry {
1876
+ label: string;
1877
+ hint?: string;
1878
+ }
1879
+
1880
+ type MenuChoice = { kind: "index"; index: number } | { kind: "back" } | { kind: "quit" };
1881
+
1882
+ /**
1883
+ * Pick one entry, by arrow keys on a terminal and by number everywhere else.
1884
+ * The numbered fallback is not a lesser path: it is what a recorded session, a
1885
+ * pipe, and a dumb terminal get, and it accepts the same `b`/`q` tokens the rest
1886
+ * of this CLI uses.
1887
+ */
1888
+ async function pickEntry(
1889
+ rl: ReturnType<typeof createInterface>,
1890
+ io: ConfigureStreams,
1891
+ entries: ReadonlyArray<MenuEntry>,
1892
+ backLabel: string,
1893
+ plain: boolean,
1894
+ ): Promise<MenuChoice> {
1895
+ const out = io.out;
1896
+ if (canSelect(io.in as NodeJS.ReadStream, out as NodeJS.WriteStream)) {
1897
+ const result = await promptSelect({
1898
+ choices: entries.map((entry, index) => ({
1899
+ value: index,
1900
+ label: entry.label,
1901
+ ...(entry.hint === undefined ? {} : { hint: entry.hint }),
1902
+ })),
1903
+ railPrefix: railPrefix(plain),
1904
+ backLabel,
1905
+ input: io.in as NodeJS.ReadStream,
1906
+ output: out as NodeJS.WriteStream,
1907
+ });
1908
+ if (result.kind === "selected") return { kind: "index", index: result.value };
1909
+ return result.kind === "quit" ? { kind: "quit" } : { kind: "back" };
1910
+ }
1911
+
1912
+ const rail = railPrefix(plain);
1913
+ entries.forEach((entry, index) => {
1914
+ out.write(`${rail}${index + 1}. ${entry.label}${entry.hint === undefined ? "" : ` (${entry.hint})`}\n`);
1915
+ });
1916
+ out.write(`${rail}b. ${backLabel}\n${rail}q. Quit\n`);
1917
+ const answer = await ask(rl, "\nSelection", "b");
1918
+ if (answer === null) return { kind: "quit" };
1919
+ const trimmed = answer.trim().toLowerCase();
1920
+ if (trimmed === "b" || trimmed === "back" || trimmed.length === 0) return { kind: "back" };
1921
+ if (trimmed === "q" || trimmed === "quit") return { kind: "quit" };
1922
+ const index = Number.parseInt(trimmed, 10) - 1;
1923
+ if (Number.isInteger(index) && index >= 0 && index < entries.length) return { kind: "index", index };
1924
+ out.write(`${rail}Not one of the choices: ${answer}\n`);
1925
+ return { kind: "back" };
1926
+ }
1927
+
1928
+ /** Header, source file, and current values for one screen. */
1929
+ function renderSection(spec: SectionSpec, out: NodeJS.WritableStream): LifecyclePresenter {
1930
+ const presenter = createLifecyclePresenter({ stream: out });
1931
+ presenter.header(spec.title, "configure");
1932
+ presenter.note(`Source: ${shortenPath(settingsPath())}`);
1933
+ presenter.fields(spec.fields());
1934
+ return presenter;
1935
+ }
1936
+
1937
+ /** Show one screen and stay on it until the operator leaves or quits. */
1938
+ async function runSection(
1939
+ rl: ReturnType<typeof createInterface>,
1940
+ streams: ConfigureStreams,
1941
+ spec: SectionSpec,
1942
+ backLabel: string,
1943
+ ): Promise<"back" | "quit"> {
1944
+ for (;;) {
1945
+ const presenter = renderSection(spec, streams.out);
1946
+ const io: SectionIo = {
1947
+ rl,
1948
+ out: streams.out,
1949
+ streams,
1950
+ ok: (text) => {
1951
+ presenter.completedStep(text);
1952
+ },
1953
+ };
1954
+ presenter.blank();
1955
+ const choice = await pickEntry(rl, streams, spec.actions, backLabel, presenter.isPlain());
1956
+ if (choice.kind !== "index") return choice.kind;
1957
+ const action = spec.actions[choice.index];
1958
+ if (action !== undefined) await action.run(io);
1959
+ }
1960
+ }
1961
+
1962
+ /** The top-level screen. Leaving it is an ordinary exit, not a cancellation. */
1963
+ async function runConfigSectionsMenu(
1964
+ rl: ReturnType<typeof createInterface>,
1965
+ streams: ConfigureStreams,
1966
+ ): Promise<number> {
1967
+ for (;;) {
1968
+ const presenter = createLifecyclePresenter({ stream: streams.out });
1969
+ presenter.header("Clio Coder Configuration", "configure");
1970
+ presenter.note(`Source: ${shortenPath(settingsPath())}`);
1971
+ presenter.blank();
1972
+ const choice = await pickEntry(
1973
+ rl,
1974
+ streams,
1975
+ SECTIONS.map((section) => ({ label: section.title, hint: section.summary })),
1976
+ "quit",
1977
+ presenter.isPlain(),
1978
+ );
1979
+ // Escape and q both leave the top screen, and leaving a settings menu you
1980
+ // only looked at is not a failure: this used to exit 130 and print
1981
+ // "error: configuration cancelled" over a run that did exactly what was
1982
+ // asked. The first-run wizard still exits 130, because there a cancel
1983
+ // really does leave Clio unconfigured.
1984
+ if (choice.kind !== "index") return 0;
1985
+ const spec = SECTIONS[choice.index];
1986
+ if (spec === undefined) continue;
1987
+ if ((await runSection(rl, streams, spec, "back")) === "quit") return 0;
1988
+ }
1989
+ }
1990
+
1991
+ /** Resolve a `--section` value to exactly one screen, by id or listed alias. */
1992
+ function findSection(name: string): SectionSpec | undefined {
1993
+ const wanted = name.trim().toLowerCase();
1994
+ return SECTIONS.find((section) => section.id === wanted || (section.aliases ?? []).includes(wanted));
1995
+ }
1996
+
1997
+ const SECTION_IDS: ReadonlyArray<string> = SECTIONS.map((section) => section.id);
1998
+
1999
+ /** The streams this run reads from and writes to, which the tests replace. */
2000
+ interface ConfigureStreams {
2001
+ in: NodeJS.ReadableStream;
2002
+ out: NodeJS.WritableStream;
2003
+ }
2004
+
2005
+ async function runInteractive(
2006
+ rl: ReturnType<typeof createInterface>,
2007
+ preselectedRuntime: RuntimeDescriptor | null,
2008
+ defaults: ParsedArgs,
2009
+ streams: ConfigureStreams,
2010
+ ): Promise<number> {
2011
+ if (preselectedRuntime) {
2012
+ return await runTargetSetupInteractive(rl, preselectedRuntime, defaults);
2013
+ }
2014
+
2015
+ if (defaults.section !== undefined) {
2016
+ const spec = findSection(defaults.section);
2017
+ if (spec === undefined) {
2018
+ printError(`unknown section: ${defaults.section}. Sections: ${SECTION_IDS.join(", ")}`);
2019
+ return 2;
2020
+ }
2021
+ // Without a terminal there is nobody to answer, and printing a prompt into
2022
+ // a pipe is a dead end that reads as a hang. The screen is still worth
2023
+ // showing, so a scripted `configure --section fleet` is a read of that
2024
+ // section rather than a refusal.
2025
+ if ((streams.in as { isTTY?: boolean }).isTTY !== true) {
2026
+ renderSection(spec, streams.out).done("Done");
2027
+ return 0;
2028
+ }
2029
+ await runSection(rl, streams, spec, "quit");
2030
+ return 0;
2031
+ }
2032
+
2033
+ if (readSettings().targets.length === 0) {
2034
+ // The first run is its own screen flow. It only exists where a keypress can
2035
+ // be read; a pipe, a recorded session, and a dumb terminal keep the
2036
+ // numbered readline path below, which is still the only way to answer them.
2037
+ //
2038
+ // The readline interface is closed first. Left open it stays subscribed to
2039
+ // stdin's keypresses and echoes each one, which puts a stray line under
2040
+ // every menu the wizard draws and breaks the erase that replaces the menu
2041
+ // with its answer. The wizard opens its own where a browser sign-in needs
2042
+ // one.
2043
+ if (canRunOnboarding(streams)) {
2044
+ rl.close();
2045
+ return await runOnboardingWizard(streams);
2046
+ }
2047
+ const runtime = await pickRuntimeViaCategory(rl);
2048
+ if (!runtime) {
2049
+ printError("configuration cancelled");
2050
+ return 130;
2051
+ }
2052
+ return await runTargetSetupInteractive(rl, runtime, defaults);
2053
+ }
2054
+
2055
+ return await runConfigSectionsMenu(rl, streams);
2056
+ }
2057
+
2058
+ export async function runConfigureCommand(
2059
+ argv: ReadonlyArray<string>,
2060
+ inStream: NodeJS.ReadableStream = input,
2061
+ outStream: NodeJS.WritableStream = output,
2062
+ ): Promise<number> {
1514
2063
  let args: ParsedArgs;
1515
2064
  try {
1516
2065
  args = parseSetupArgs(argv);
@@ -1526,15 +2075,21 @@ export async function runConfigureCommand(argv: ReadonlyArray<string>): Promise<
1526
2075
  initializeClioHome();
1527
2076
  ensureRegistryPopulated();
1528
2077
 
2078
+ if (args.json) {
2079
+ const settings = readSettings();
2080
+ outStream.write(`${JSON.stringify(settings, null, 2)}\n`);
2081
+ return 0;
2082
+ }
2083
+
1529
2084
  if (args.list) {
1530
2085
  printRuntimeList(args.all);
1531
2086
  return 0;
1532
2087
  }
1533
2088
  if (args.interop) {
1534
- if (!input.isTTY) return runInteropReview(null);
2089
+ if (!input.isTTY) return runInteropReview({ rl: null });
1535
2090
  const rl = createInterface({ input, output });
1536
2091
  try {
1537
- return await runInteropReview(rl);
2092
+ return await runInteropReview({ rl, streams: { in: input, out: output } });
1538
2093
  } finally {
1539
2094
  rl.close();
1540
2095
  }
@@ -1559,7 +2114,7 @@ export async function runConfigureCommand(argv: ReadonlyArray<string>): Promise<
1559
2114
  return 2;
1560
2115
  }
1561
2116
  if (runtimeId === "lmstudio-native" && runtime.id === "lmstudio") {
1562
- process.stderr.write("warning: runtime 'lmstudio-native' is deprecated; using 'lmstudio'\n");
2117
+ warnLegacyNaming(runtimeId, runtime.id);
1563
2118
  }
1564
2119
  }
1565
2120
  const hasTargetSetupFlag =
@@ -1595,9 +2150,9 @@ export async function runConfigureCommand(argv: ReadonlyArray<string>): Promise<
1595
2150
  return 2;
1596
2151
  }
1597
2152
 
1598
- const rl = createInterface({ input, output });
2153
+ const rl = createInterface({ input: inStream, output: outStream });
1599
2154
  try {
1600
- return await runInteractive(rl, runtime, args);
2155
+ return await runInteractive(rl, runtime, args, { in: inStream, out: outStream });
1601
2156
  } catch (err) {
1602
2157
  printError(err instanceof Error ? err.message : String(err));
1603
2158
  return 1;