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