@iowarp/clio-coder 0.4.1 → 0.4.3

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