@iowarp/clio-coder 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/CONTRIBUTING.md +86 -19
- package/README.md +35 -6
- package/dist/{acp-TMDQZDIG.js → acp-H2NGRPWO.js} +11 -11
- package/dist/{agents-5N5NG3XG.js → agents-TL5LLUQP.js} +54 -53
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-Z5CCBXKQ.js → auth-E5SW4HMS.js} +19 -16
- package/dist/{builtins-K6TNDT24.js → builtins-IA7V7FUC.js} +9 -4
- package/dist/{chunk-ZW4HH5JJ.js → chunk-2APPQIER.js} +6 -6
- package/dist/{chunk-72GZI5EV.js → chunk-2JH2WHGE.js} +2 -2
- package/dist/{chunk-UBRFI4HS.js → chunk-2UG5F4C5.js} +127 -47
- package/dist/{chunk-UH632ZYL.js → chunk-2UH2KFUP.js} +2 -2
- package/dist/{chunk-3F7VUY77.js → chunk-2VIKGWFZ.js} +2 -2
- package/dist/{chunk-I66EAJFY.js → chunk-2WZ546HR.js} +267 -232
- package/dist/{chunk-RKSR6VSF.js → chunk-4IUZQIJ3.js} +29 -1
- package/dist/{chunk-2X4RYJTJ.js → chunk-4UVU7BJ5.js} +2 -2
- package/dist/{chunk-M2DAX4F6.js → chunk-4WR7VSYB.js} +2 -2
- package/dist/{chunk-5KW52TEP.js → chunk-54CBCGIR.js} +5 -5
- 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-LJID3DYZ.js → chunk-64I3JVYM.js} +2 -2
- package/dist/{chunk-4JDLP6ZS.js → chunk-6PTFB5VS.js} +7 -7
- package/dist/{chunk-VN3SHNBN.js → chunk-7DICMOS6.js} +2 -2
- package/dist/{chunk-HLAFFSEK.js → chunk-7DRAWPTZ.js} +2 -2
- package/dist/chunk-7E7I3WLS.js +3762 -0
- package/dist/{chunk-YJISEZKC.js → chunk-7ZYNNDKC.js} +6 -6
- package/dist/{chunk-I66ZTYNP.js → chunk-AF4YM7Z4.js} +236 -101
- package/dist/{chunk-2HFQNRV3.js → chunk-AX2THNSA.js} +12 -12
- package/dist/{chunk-PGF63K6I.js → chunk-B4OAX3SI.js} +65 -3
- package/dist/{chunk-W6NIE6OW.js → chunk-B4VEBZKF.js} +3 -3
- package/dist/{chunk-JBCS7CRR.js → chunk-BEPZRGGU.js} +10 -10
- package/dist/{chunk-XGDPUNND.js → chunk-CE5AX47J.js} +2 -2
- package/dist/{chunk-I64IFBLB.js → chunk-DWUOQKRU.js} +17 -10
- package/dist/{chunk-DZAW46HP.js → chunk-E3TPLWFX.js} +3 -3
- package/dist/{chunk-HIICAHCJ.js → chunk-EKCHAPYA.js} +2 -2
- package/dist/{chunk-XE3PCIXH.js → chunk-F5JHEYZM.js} +7 -7
- package/dist/{chunk-5PFYMY2V.js → chunk-FTMGRKEF.js} +2 -2
- package/dist/{chunk-ZNT2M6TG.js → chunk-G76U63X4.js} +17 -17
- package/dist/{chunk-34BHNEE3.js → chunk-GHS5EBTQ.js} +58 -7
- package/dist/{chunk-2NHR3NAY.js → chunk-GI7YYQ3F.js} +40 -34
- package/dist/{chunk-DYHAXKHD.js → chunk-GWZNEVM2.js} +12 -8
- package/dist/chunk-GYV6VZOC.js +26 -0
- package/dist/{chunk-MQXIVJ35.js → chunk-HAXOFFRH.js} +5 -5
- package/dist/{chunk-W4YEMFBX.js → chunk-HEQY7ZFI.js} +2 -2
- package/dist/{chunk-IKOZFYBN.js → chunk-I7ZPNEJM.js} +145 -102
- package/dist/{chunk-WNP7O5WZ.js → chunk-ID64D7PE.js} +4 -4
- package/dist/{chunk-XQRY4DTA.js → chunk-IGLP3ODT.js} +10 -10
- package/dist/chunk-IJNZMHLA.js +101 -0
- package/dist/{chunk-JWJGP5DQ.js → chunk-INY6HTFL.js} +7 -7
- package/dist/{chunk-PBP4B7XR.js → chunk-IUE3Y34X.js} +2 -2
- package/dist/{chunk-B74PXLU7.js → chunk-IWT4SF4R.js} +3 -3
- package/dist/{chunk-B7HM5Z7T.js → chunk-JDAY6FIL.js} +5 -5
- package/dist/{chunk-PJX3WQUQ.js → chunk-JEQ3XTHC.js} +2 -2
- package/dist/{chunk-FSP7CMNU.js → chunk-JGRC33J2.js} +50 -4
- package/dist/{chunk-X7IARSHT.js → chunk-JKKCYP3C.js} +9 -9
- package/dist/{chunk-HJWWJ6IL.js → chunk-JSC3U7TI.js} +16 -4
- package/dist/{chunk-SSEYRH53.js → chunk-KK4JZPBQ.js} +19 -140
- package/dist/{chunk-IHXBNWMM.js → chunk-KXDSS5WJ.js} +7 -3
- package/dist/{chunk-Q4XWMHX6.js → chunk-L47TF46W.js} +2 -2
- package/dist/{chunk-O3YUNJZ2.js → chunk-LDJG7DW3.js} +81 -24
- package/dist/{chunk-CDNVLKUX.js → chunk-LLDJM5XK.js} +13 -7
- package/dist/{chunk-F2I26BDK.js → chunk-MUW2BDDH.js} +4 -4
- package/dist/{chunk-HKMD33FO.js → chunk-MWUZBSAQ.js} +79 -76
- package/dist/{chunk-QQLGQY2A.js → chunk-N2Z7HLVY.js} +20 -20
- package/dist/{chunk-DZEK6CJN.js → chunk-NIQJ66N4.js} +19 -19
- package/dist/{chunk-CWVRRIEI.js → chunk-NZMNUPZZ.js} +2 -2
- package/dist/{chunk-462T4EGZ.js → chunk-O5CVSAG5.js} +2 -2
- package/dist/{chunk-TPEQIQIE.js → chunk-OML5D5V5.js} +8 -8
- package/dist/{chunk-IKSLQ4XV.js → chunk-PAJQJ7BS.js} +558 -216
- package/dist/{chunk-ZW55JB7N.js → chunk-PUVDKJ2Y.js} +2 -2
- package/dist/{chunk-UH347SHR.js → chunk-QWGDJJYJ.js} +11 -11
- package/dist/chunk-R6Q67RJH.js +134 -0
- package/dist/{chunk-CRFOIAX3.js → chunk-RRNP2ANY.js} +6 -6
- package/dist/{chunk-IDNA72AH.js → chunk-RSJ25QSL.js} +2 -2
- package/dist/chunk-SKHCAU7K.js +385 -0
- package/dist/{chunk-RLYRBIYQ.js → chunk-TM6LQDI3.js} +20 -12
- package/dist/chunk-UOIZ7DA4.js +41 -0
- package/dist/{chunk-P75RZCJW.js → chunk-UPZU6GE4.js} +3 -3
- package/dist/{chunk-MCMZMDAC.js → chunk-V2ANDPVT.js} +4 -4
- package/dist/{chunk-AK5XEFVZ.js → chunk-VA5FNYMT.js} +26 -13
- package/dist/{chunk-IMXMHHMQ.js → chunk-VW6DOEDG.js} +332 -57
- package/dist/{chunk-XOXV5GKE.js → chunk-W6RRQCPQ.js} +16 -7
- package/dist/{chunk-CYZW7JHJ.js → chunk-WBKFA554.js} +8 -8
- package/dist/{chunk-BO7Y52RY.js → chunk-WCXUNS7U.js} +7 -7
- package/dist/{chunk-ZGNYYXQ6.js → chunk-WRBAGUNF.js} +3 -3
- package/dist/{chunk-FVDGR2ZL.js → chunk-XIVNBFZS.js} +85 -30
- package/dist/{chunk-BYMNWQ7O.js → chunk-XPWWI35G.js} +299 -58
- package/dist/{chunk-KPXDY6QF.js → chunk-XRZT5WY5.js} +2 -2
- package/dist/{chunk-AZ4WMN4W.js → chunk-Y3CBHOR6.js} +2 -2
- package/dist/{chunk-VXMFAE2W.js → chunk-YPC6ZR5L.js} +19 -6
- package/dist/{chunk-54ODD65L.js → chunk-YQWYVTMC.js} +4 -4
- package/dist/{chunk-M2WXEHER.js → chunk-ZA4VCIGV.js} +2 -2
- package/dist/{chunk-7BHIY2MW.js → chunk-ZDN3Y73Y.js} +6 -6
- package/dist/{chunk-E7GT7O5N.js → chunk-ZWPRK62N.js} +7 -4
- package/dist/cli/index.js +38 -37
- package/dist/{clio-7VB377CC.js → clio-CMMK4KRR.js} +7 -7
- package/dist/{code-nav-YVLCYA7V.js → code-nav-MDZNQS33.js} +7 -7
- package/dist/{components-UBWCQSRW.js → components-UCUQ4QXW.js} +4 -4
- package/dist/{config-4HVOS65E.js → config-SVM5P5YI.js} +76 -74
- package/dist/{configure-PIWO7B24.js → configure-LE3IK2TJ.js} +26 -24
- package/dist/{context-IYEHL3WQ.js → context-2OHRKS42.js} +66 -63
- package/dist/{context-N6ZE3LGJ.js → context-E3VC7RX5.js} +15 -11
- package/dist/{context-KQYIWPWT.js → context-VNCR7KAG.js} +60 -45
- package/dist/{context-clear-G4OGZJDS.js → context-clear-BW4O37TG.js} +61 -59
- package/dist/context-map-COB37XXN.js +505 -0
- package/dist/{context-working-set-BWLF6LJP.js → context-working-set-VDS25HXZ.js} +17 -16
- package/dist/{dispatch-runner-2QQAITS3.js → dispatch-runner-5AHT53RF.js} +85 -74
- package/dist/{doctor-LHBD36VU.js → doctor-WNNVO6FY.js} +37 -37
- package/dist/{eval-C45FYRJ6.js → eval-7G7SGAYO.js} +285 -114
- package/dist/{eval-inventory-6DEJPLBF.js → eval-inventory-Y6QRFOH5.js} +4 -4
- package/dist/{evidence-6SHONYAF.js → evidence-VD6736FQ.js} +63 -62
- package/dist/{evolve-KRKMV72X.js → evolve-AL3NGVRL.js} +62 -61
- package/dist/{extensions-KPZ2UHBB.js → extensions-MOVJ32NM.js} +7 -7
- package/dist/{fleet-IVTCKDHT.js → fleet-QZHUMAGI.js} +110 -108
- package/dist/{fleet-commands-EDWL3IT7.js → fleet-commands-BAYT5FJZ.js} +10 -10
- package/dist/{fleet-decisions-YP3YEFGK.js → fleet-decisions-IREVMRU4.js} +7 -6
- package/dist/{fleet-graph-ZFWKHY2M.js → fleet-graph-YCTT3HTI.js} +19 -18
- package/dist/{fleet-inspect-FVUNCBML.js → fleet-inspect-QVJTDAVB.js} +55 -54
- package/dist/{fleet-preflight-UN5XED4R.js → fleet-preflight-25QAFPK4.js} +4 -4
- package/dist/{fleet-validate-XOWC4HSX.js → fleet-validate-5O57AAJ7.js} +23 -22
- package/dist/{fleet-verify-UN3SODEL.js → fleet-verify-CPH2W2T6.js} +56 -55
- package/dist/{fleet-view-TWHJKCN6.js → fleet-view-SWBR3VGQ.js} +55 -54
- package/dist/{init-T2QORQ3Y.js → init-J477LKZH.js} +78 -76
- package/dist/{interop-IN5I2A66.js → interop-3FCM6XLG.js} +11 -11
- package/dist/{library-LSCATDLZ.js → library-QUQEIUG6.js} +28 -27
- package/dist/{memory-HYOKAGGJ.js → memory-SGGSEP65.js} +64 -63
- package/dist/{models-2GPMFYCM.js → models-HEKUAXXK.js} +49 -43
- package/dist/{monitor-E4ASVUJH.js → monitor-HKU57TYQ.js} +61 -60
- package/dist/{orchestrator-DDMPR3PY.js → orchestrator-VDFAEFAI.js} +919 -546
- package/dist/{panes-E3RUXOW5.js → panes-DN2SSFOH.js} +3 -3
- package/dist/{panes-IXKLOKA2.js → panes-TALGNPZT.js} +8 -8
- package/dist/{paths-L7LGY6RN.js → paths-NBMFAIEZ.js} +5 -5
- package/dist/reset-EAJFFJVB.js +344 -0
- package/dist/{resources-OTRSN34L.js → resources-OVKSEFVE.js} +27 -20
- package/dist/{run-5DEYH5QK.js → run-7DP7ZF2J.js} +113 -109
- package/dist/{share-IHWTLO3M.js → share-WML67FT3.js} +26 -25
- package/dist/{skills-IYMXMKW4.js → skills-SG662R2K.js} +39 -31
- package/dist/{skills-eval-DROHSJAR.js → skills-eval-VVZEUU46.js} +74 -73
- package/dist/{skills-inventory-D7X4L4ZX.js → skills-inventory-I2E23GET.js} +21 -20
- package/dist/{slash-commands-QBM7UZ3B.js → slash-commands-S7MBJDQK.js} +35 -34
- package/dist/{steer-Z5DO23FJ.js → steer-2LQOMCPB.js} +3 -3
- package/dist/{support-U7QOWY26.js → support-CC2UJBJ6.js} +6 -6
- package/dist/{targets-P2FUC4IL.js → targets-4QC3HIEW.js} +48 -45
- package/dist/{terminal-lease-YREJ3JX2.js → terminal-lease-TUHIJ6Y2.js} +2 -2
- package/dist/{tools-5B7RO6MV.js → tools-TFGJICCU.js} +8 -8
- package/dist/{trace-YMGMUM6A.js → trace-FXMXUZUF.js} +7 -7
- package/dist/uninstall-5PEVOE5B.js +408 -0
- package/dist/upgrade-M4WXY6KN.js +303 -0
- package/dist/{usage-ME5MPXGX.js → usage-N7ZNVLEM.js} +147 -102
- package/dist/{verifiers-BVZ7IWOO.js → verifiers-DJTP4XX6.js} +15 -15
- package/dist/{verify-5K7ZKQFC.js → verify-RWE4PPEK.js} +9 -9
- package/dist/{wiki-generate-F5W5QTYY.js → wiki-generate-C7IQOXSP.js} +84 -82
- package/dist/{with-panes-BYOJCLAM.js → with-panes-4GCGSL7J.js} +9 -9
- package/dist/worker/entry.js +61 -60
- package/docs/architecture/artifact-placement.md +1 -0
- package/docs/architecture/artifact-versions.md +1 -1
- package/docs/architecture/context-engine.md +4 -0
- package/docs/architecture/middleware-and-components.md +1 -1
- package/docs/architecture/model-catalog.md +21 -10
- package/docs/architecture/observability.md +12 -1
- package/docs/architecture/prompt-envelope-and-tools.md +2 -0
- package/docs/architecture/provider-adapter-cookbook.md +63 -0
- package/docs/architecture/safety-model.md +15 -5
- package/docs/guide/built-in-agents.md +17 -3
- package/docs/guide/commands-and-modes.md +1 -1
- package/docs/guide/configuration-and-targets.md +97 -9
- package/docs/guide/configuration-reference.md +7 -2
- package/docs/guide/environment-variables.md +2 -0
- 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/process/development-pipeline.md +34 -1
- package/docs/process/eval-runner.md +67 -3
- 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 +40 -5
- 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 +46 -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 +23 -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 +65 -5
- package/skills/workflow/cut-it/evals.md +101 -0
- package/skills/workflow/design-council/SKILL.md +117 -27
- package/skills/workflow/design-council/evals.md +161 -0
- package/skills/workflow/grill-me/SKILL.md +86 -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/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 +1082 -528
- package/src/cli/context-map.ts +114 -0
- package/src/cli/context.ts +4 -0
- package/src/cli/index.ts +1 -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 -2
- package/src/cli/select.ts +664 -0
- package/src/cli/skills.ts +9 -2
- package/src/cli/targets.ts +3 -0
- package/src/cli/uninstall.ts +233 -165
- package/src/cli/upgrade.ts +204 -149
- package/src/cli/usage.ts +86 -27
- package/src/cli/validate-model.ts +3 -3
- 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/safe-exec.ts +17 -2
- package/src/core/skill-activation.ts +89 -2
- 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/wiki/map-seed.ts +589 -0
- package/src/domains/context/wiki/plan.ts +2 -2
- 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/contract.ts +11 -1
- package/src/domains/dispatch/extension.ts +134 -29
- package/src/domains/dispatch/types.ts +3 -0
- 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 +5 -2
- 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/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 +3 -35
- package/src/domains/middleware/memory-intervention.ts +127 -32
- package/src/domains/middleware/memory-step-endpoint.ts +3 -2
- package/src/domains/middleware/skills-reminder.ts +31 -2
- package/src/domains/observability/compaction-usage.ts +118 -0
- package/src/domains/observability/cost.ts +1 -1
- package/src/domains/observability/extension.ts +6 -1
- package/src/domains/observability/out-of-turn-usage.ts +52 -21
- 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 +2 -0
- package/src/domains/providers/model-runtime-capabilities.ts +15 -5
- package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +32 -12
- package/src/domains/providers/runtimes/antigravity/antigravity-code.ts +225 -45
- 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/autonomy.ts +15 -0
- package/src/domains/safety/index.ts +1 -0
- package/src/domains/safety/path-policy.ts +1 -1
- package/src/domains/safety/policy-engine.ts +34 -11
- package/src/domains/safety/protected-artifacts.ts +191 -88
- package/src/domains/safety/run-effects.ts +2 -22
- package/src/domains/safety/skill-authority.ts +55 -0
- package/src/domains/session/compaction/compact.ts +72 -22
- package/src/domains/session/entries.ts +6 -0
- package/src/domains/session/usage.ts +3 -3
- 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/openai-completions.ts +117 -14
- package/src/engine/external-subprocess.ts +114 -6
- package/src/entry/background-model-metadata.ts +18 -0
- package/src/entry/compaction-prompt.ts +57 -0
- package/src/entry/orchestrator.ts +405 -216
- 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/interactive-slash-runtime.ts +2 -1
- package/src/interactive/renderers/worker-entry.ts +32 -0
- package/src/interactive/slash-commands.ts +24 -6
- 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/worker-receipts.ts +1 -0
- package/src/interactive/worker-stream.ts +6 -1
- package/src/tools/context/index.ts +30 -9
- package/src/tools/dispatch-arguments.ts +1 -0
- package/src/tools/dispatch-event-text.ts +10 -0
- package/src/tools/dispatch-plan.ts +1 -0
- package/src/tools/dispatch-runner.ts +12 -0
- package/src/tools/registry.ts +11 -5
- 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/reset-OAQP3W4O.js +0 -230
- package/dist/uninstall-N34PCTGJ.js +0 -331
- package/dist/upgrade-PXK3S2YM.js +0 -325
|
@@ -0,0 +1,3762 @@
|
|
|
1
|
+
import { createRequire as __clioCreateRequire } from "node:module"; const require = __clioCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
createLifecyclePresenter,
|
|
4
|
+
shortenPath
|
|
5
|
+
} from "./chunk-SKHCAU7K.js";
|
|
6
|
+
import {
|
|
7
|
+
createDelayedManualCodeInput,
|
|
8
|
+
promptOAuthSelection
|
|
9
|
+
} from "./chunk-2STGEDFV.js";
|
|
10
|
+
import {
|
|
11
|
+
column,
|
|
12
|
+
terminalColumns,
|
|
13
|
+
truncate,
|
|
14
|
+
twoColumnRow,
|
|
15
|
+
wrapPlain
|
|
16
|
+
} from "./chunk-2IR2NMPA.js";
|
|
17
|
+
import {
|
|
18
|
+
fingerprintNativeRuntime
|
|
19
|
+
} from "./chunk-NEKRRTYW.js";
|
|
20
|
+
import {
|
|
21
|
+
INHERITED_PROJECT_CONTEXT,
|
|
22
|
+
acceptInteropAgents,
|
|
23
|
+
declineInteropAgents,
|
|
24
|
+
detectInteropAgents,
|
|
25
|
+
interopProposals,
|
|
26
|
+
renderProposalEntry,
|
|
27
|
+
resolveOnPath
|
|
28
|
+
} from "./chunk-INY6HTFL.js";
|
|
29
|
+
import {
|
|
30
|
+
initializeClioHome
|
|
31
|
+
} from "./chunk-I7XBWTYH.js";
|
|
32
|
+
import {
|
|
33
|
+
credentialWriteFailed,
|
|
34
|
+
printPlaintextCredentialWarning
|
|
35
|
+
} from "./chunk-PUVDKJ2Y.js";
|
|
36
|
+
import {
|
|
37
|
+
printError,
|
|
38
|
+
printOk
|
|
39
|
+
} from "./chunk-VPKWYKEY.js";
|
|
40
|
+
import {
|
|
41
|
+
source_default
|
|
42
|
+
} from "./chunk-5TSRNF4G.js";
|
|
43
|
+
import {
|
|
44
|
+
isOrchestratorEligibleRuntime,
|
|
45
|
+
probeCapabilitiesForModel,
|
|
46
|
+
readTargetModelSnapshot,
|
|
47
|
+
recordTargetModelSnapshot
|
|
48
|
+
} from "./chunk-XPWWI35G.js";
|
|
49
|
+
import {
|
|
50
|
+
credentialsPresent
|
|
51
|
+
} from "./chunk-2JH2WHGE.js";
|
|
52
|
+
import {
|
|
53
|
+
bindAgentProfileInSettings,
|
|
54
|
+
readSettings,
|
|
55
|
+
removeTargetFromSettings,
|
|
56
|
+
settingsPath,
|
|
57
|
+
updateSettings
|
|
58
|
+
} from "./chunk-GHS5EBTQ.js";
|
|
59
|
+
import {
|
|
60
|
+
getVersionInfo
|
|
61
|
+
} from "./chunk-XPLRXC72.js";
|
|
62
|
+
import {
|
|
63
|
+
greetLmStudio,
|
|
64
|
+
registerBuiltinRuntimes
|
|
65
|
+
} from "./chunk-VW6DOEDG.js";
|
|
66
|
+
import {
|
|
67
|
+
THINKING_LEVELS
|
|
68
|
+
} from "./chunk-UXCU4E3T.js";
|
|
69
|
+
import {
|
|
70
|
+
buildProviderSupportEntry,
|
|
71
|
+
configuredTargetsForRuntime,
|
|
72
|
+
describeRuntimeModels,
|
|
73
|
+
listKnownModelsForRuntime,
|
|
74
|
+
listProviderSupportEntries,
|
|
75
|
+
supportGroupLabel
|
|
76
|
+
} from "./chunk-LLDJM5XK.js";
|
|
77
|
+
import {
|
|
78
|
+
authStoragePath,
|
|
79
|
+
openAuthStorage,
|
|
80
|
+
registerClioOAuthProviders,
|
|
81
|
+
resolveAuthTarget,
|
|
82
|
+
resolveRuntimeAuthTarget,
|
|
83
|
+
targetRequiresAuth
|
|
84
|
+
} from "./chunk-ID64D7PE.js";
|
|
85
|
+
import {
|
|
86
|
+
getRuntimeRegistry
|
|
87
|
+
} from "./chunk-NUGM5KR6.js";
|
|
88
|
+
import {
|
|
89
|
+
getCatalogModelForRuntime
|
|
90
|
+
} from "./chunk-5OIVVPHF.js";
|
|
91
|
+
import {
|
|
92
|
+
resolveClioDirs
|
|
93
|
+
} from "./chunk-BYP5D4HI.js";
|
|
94
|
+
import {
|
|
95
|
+
init_esm_shims
|
|
96
|
+
} from "./chunk-3R73A4XB.js";
|
|
97
|
+
|
|
98
|
+
// src/cli/configure.ts
|
|
99
|
+
init_esm_shims();
|
|
100
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
101
|
+
import { stdin as input, stdout as output2 } from "node:process";
|
|
102
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
103
|
+
|
|
104
|
+
// src/cli/ask.ts
|
|
105
|
+
init_esm_shims();
|
|
106
|
+
async function ask(rl, label, defaultValue) {
|
|
107
|
+
const suffix = defaultValue && defaultValue.length > 0 ? ` [${defaultValue}]` : "";
|
|
108
|
+
try {
|
|
109
|
+
const answer = (await rl.question(`${label}${suffix}: `)).trim();
|
|
110
|
+
if (answer.length === 0) return defaultValue ?? "";
|
|
111
|
+
if (answer.toLowerCase() === "q" || answer.toLowerCase() === "quit") return null;
|
|
112
|
+
return answer;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function askYesNo(rl, label, defaultValue) {
|
|
118
|
+
const marker = defaultValue ? "Y/n" : "y/N";
|
|
119
|
+
for (; ; ) {
|
|
120
|
+
const answer = await ask(rl, `${label} [${marker}]`);
|
|
121
|
+
if (answer === null) return defaultValue;
|
|
122
|
+
if (answer.length === 0) return defaultValue;
|
|
123
|
+
const lc = answer.toLowerCase();
|
|
124
|
+
if (lc === "y" || lc === "yes") return true;
|
|
125
|
+
if (lc === "n" || lc === "no") return false;
|
|
126
|
+
process.stderr.write(`invalid response: ${answer}
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/cli/configure-interop.ts
|
|
132
|
+
init_esm_shims();
|
|
133
|
+
import { stdout as output } from "node:process";
|
|
134
|
+
|
|
135
|
+
// src/cli/configure-target.ts
|
|
136
|
+
init_esm_shims();
|
|
137
|
+
|
|
138
|
+
// src/cli/validate-model.ts
|
|
139
|
+
init_esm_shims();
|
|
140
|
+
var SAMPLE_SIZE = 10;
|
|
141
|
+
function sample(models) {
|
|
142
|
+
const shown = models.slice(0, SAMPLE_SIZE).join(", ");
|
|
143
|
+
return models.length > SAMPLE_SIZE ? `${shown}, \u2026` : shown;
|
|
144
|
+
}
|
|
145
|
+
function describeLiveTarget(live) {
|
|
146
|
+
return live.url ? `target '${live.targetId}' at ${live.url}` : `target '${live.targetId}'`;
|
|
147
|
+
}
|
|
148
|
+
function formatUnadvertisedModelReason(modelId, live) {
|
|
149
|
+
const resident = live.resident.length > 0 ? live.resident.join(", ") : "none";
|
|
150
|
+
return `${describeLiveTarget(live)} does not advertise model '${modelId}'. Advertised (${live.models.length}): ${sample(live.models)}. Resident instances: ${resident}. Pass one of the advertised ids with --model, or --force to save a model the target cannot serve.`;
|
|
151
|
+
}
|
|
152
|
+
function validateModelChoice(input2) {
|
|
153
|
+
const { runtimeId, modelId, knownModels, live, force } = input2;
|
|
154
|
+
if (live !== void 0) return validateAgainstLive(modelId, live, force);
|
|
155
|
+
if (knownModels.length === 0) return validateAgainstLive(modelId, live, force);
|
|
156
|
+
if (knownModels.includes(modelId)) return { ok: true };
|
|
157
|
+
if (force) {
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
warning: `model '${modelId}' not in ${runtimeId} catalog (use without --force to reject)`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
reason: `model '${modelId}' not in ${runtimeId} catalog. Known: ${sample(knownModels)}. Use --force to skip validation.`,
|
|
166
|
+
knownModels: knownModels.slice(0, SAMPLE_SIZE)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function validateAgainstLive(modelId, live, force) {
|
|
170
|
+
if (!live || live.models.length === 0) return { ok: true };
|
|
171
|
+
if (live.models.includes(modelId)) return { ok: true };
|
|
172
|
+
const reason = formatUnadvertisedModelReason(modelId, live);
|
|
173
|
+
if (force) return { ok: true, warning: `${reason.replace(/ Pass one of.*$/u, "")} Saved anyway because of --force.` };
|
|
174
|
+
return { ok: false, reason, knownModels: live.models.slice(0, SAMPLE_SIZE) };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/cli/configure-target.ts
|
|
178
|
+
var DEFAULT_PORTS = {
|
|
179
|
+
llamacpp: 8080,
|
|
180
|
+
"llamacpp-anthropic": 8080,
|
|
181
|
+
"llamacpp-completion": 8080,
|
|
182
|
+
"llamacpp-embed": 8080,
|
|
183
|
+
"llamacpp-rerank": 8080,
|
|
184
|
+
"lmstudio-native": 1234,
|
|
185
|
+
lmstudio: 1234,
|
|
186
|
+
"ollama-native": 11434,
|
|
187
|
+
vllm: 8e3,
|
|
188
|
+
sglang: 3e4,
|
|
189
|
+
"lemonade-anthropic": 8e3,
|
|
190
|
+
lemonade: 8e3,
|
|
191
|
+
"anthropic-compat": 8e3,
|
|
192
|
+
"openai-compat": 8e3,
|
|
193
|
+
litellm: 4e3
|
|
194
|
+
};
|
|
195
|
+
function trimTrailing(url) {
|
|
196
|
+
return url.endsWith("/") && url.length > 1 ? url.slice(0, -1) : url;
|
|
197
|
+
}
|
|
198
|
+
function normalizeUrl(input2, runtimeId) {
|
|
199
|
+
const trimmed = input2.trim();
|
|
200
|
+
if (trimmed.length === 0) return defaultUrlFor(runtimeId);
|
|
201
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed);
|
|
202
|
+
let withScheme = hasScheme ? trimmed : `http://${trimmed}`;
|
|
203
|
+
if (runtimeId === "lmstudio") withScheme = withScheme.replace(/^ws:/u, "http:").replace(/^wss:/u, "https:");
|
|
204
|
+
try {
|
|
205
|
+
const parsed = new URL(withScheme);
|
|
206
|
+
if (!hasScheme && !parsed.port && DEFAULT_PORTS[runtimeId]) {
|
|
207
|
+
parsed.port = String(DEFAULT_PORTS[runtimeId]);
|
|
208
|
+
}
|
|
209
|
+
return trimTrailing(parsed.toString());
|
|
210
|
+
} catch {
|
|
211
|
+
return trimTrailing(withScheme);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function defaultUrlFor(runtimeId) {
|
|
215
|
+
const port = DEFAULT_PORTS[runtimeId];
|
|
216
|
+
return port ? `http://127.0.0.1:${port}` : "http://127.0.0.1:8080";
|
|
217
|
+
}
|
|
218
|
+
function deriveTargetId(runtimeId, existing) {
|
|
219
|
+
const base = runtimeId;
|
|
220
|
+
const taken = new Set(existing.map((e) => e.id));
|
|
221
|
+
if (!taken.has(base)) return base;
|
|
222
|
+
for (let i = 2; i < 1e3; i++) {
|
|
223
|
+
const candidate = `${base}-${i}`;
|
|
224
|
+
if (!taken.has(candidate)) return candidate;
|
|
225
|
+
}
|
|
226
|
+
return `${base}-${Date.now()}`;
|
|
227
|
+
}
|
|
228
|
+
async function buildProbeContext(runtime, target) {
|
|
229
|
+
const probeCtx = {
|
|
230
|
+
credentialsPresent: credentialsPresent(),
|
|
231
|
+
httpTimeoutMs: 5e3
|
|
232
|
+
};
|
|
233
|
+
if (!runtime || !target || !targetRequiresAuth(target, runtime)) return probeCtx;
|
|
234
|
+
try {
|
|
235
|
+
const resolution = await openAuthStorage().resolveForTarget(resolveAuthTarget(target, runtime), {
|
|
236
|
+
includeFallback: false
|
|
237
|
+
});
|
|
238
|
+
if (resolution.apiKey) probeCtx.authToken = resolution.apiKey;
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
return probeCtx;
|
|
242
|
+
}
|
|
243
|
+
function runtimeKnownModelsFor(runtimeId) {
|
|
244
|
+
return listKnownModelsForRuntime(runtimeId);
|
|
245
|
+
}
|
|
246
|
+
function catalogContextWindowFor(runtime, modelId) {
|
|
247
|
+
const catalogModel = getCatalogModelForRuntime(runtime.id, modelId);
|
|
248
|
+
if (typeof catalogModel?.contextWindow === "number" && catalogModel.contextWindow > 0) {
|
|
249
|
+
return catalogModel.contextWindow;
|
|
250
|
+
}
|
|
251
|
+
if (runtimeKnownModelsFor(runtime.id).includes(modelId) && runtime.defaultCapabilities.contextWindow > 0) {
|
|
252
|
+
return runtime.defaultCapabilities.contextWindow;
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
function validateResolvedModel(runtime, target, modelId, force, inventory) {
|
|
257
|
+
if (!modelId) return true;
|
|
258
|
+
const knownModels = runtimeKnownModelsFor(runtime.id);
|
|
259
|
+
const live = inventory.source === "probe" ? liveInventoryFor(target, inventory) : void 0;
|
|
260
|
+
const validation = validateModelChoice({ runtimeId: runtime.id, modelId, knownModels, live, force });
|
|
261
|
+
if (!validation.ok) {
|
|
262
|
+
process.stderr.write(`error: ${validation.reason}
|
|
263
|
+
`);
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (validation.warning) process.stderr.write(`warning: ${validation.warning}
|
|
267
|
+
`);
|
|
268
|
+
if (knownModels.length === 0 && live === void 0 && runtime.kind === "http") {
|
|
269
|
+
process.stderr.write(
|
|
270
|
+
`warning: could not verify model '${modelId}': ${target.url ?? `target '${target.id}'`} returned no model list; it is saved as written
|
|
271
|
+
`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
function modelChoiceRefusal(runtime, target, modelId, inventory) {
|
|
277
|
+
const live = inventory.source === "probe" ? liveInventoryFor(target, inventory) : void 0;
|
|
278
|
+
const validation = validateModelChoice({
|
|
279
|
+
runtimeId: runtime.id,
|
|
280
|
+
modelId,
|
|
281
|
+
knownModels: runtimeKnownModelsFor(runtime.id),
|
|
282
|
+
live,
|
|
283
|
+
force: false
|
|
284
|
+
});
|
|
285
|
+
return validation.ok ? null : validation.reason;
|
|
286
|
+
}
|
|
287
|
+
function validateContextWindowOverride(runtime, modelId, contextWindow, force) {
|
|
288
|
+
if (contextWindow === void 0 || !modelId) return true;
|
|
289
|
+
const catalogMax = catalogContextWindowFor(runtime, modelId);
|
|
290
|
+
if (catalogMax === null || contextWindow <= catalogMax) return true;
|
|
291
|
+
const message = `--context-window ${contextWindow} exceeds catalog max ${catalogMax} for model '${modelId}'`;
|
|
292
|
+
if (!force) {
|
|
293
|
+
process.stderr.write(`error: ${message}. Use --force to override.
|
|
294
|
+
`);
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
process.stderr.write(`warning: ${message}
|
|
298
|
+
`);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
async function runtimeProbe(runtime, target, authToken) {
|
|
302
|
+
if (typeof runtime.probe !== "function") return null;
|
|
303
|
+
try {
|
|
304
|
+
const context = await buildProbeContext(runtime, target);
|
|
305
|
+
return await runtime.probe(target, authToken === void 0 ? context : { ...context, authToken });
|
|
306
|
+
} catch (err) {
|
|
307
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
async function runtimeProbeModels(runtime, target, authToken) {
|
|
311
|
+
if (typeof runtime.probeModels !== "function") return [];
|
|
312
|
+
try {
|
|
313
|
+
const context = await buildProbeContext(runtime, target);
|
|
314
|
+
return await runtime.probeModels(target, authToken === void 0 ? context : { ...context, authToken });
|
|
315
|
+
} catch {
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function applyTarget(settings, descriptor) {
|
|
320
|
+
const idx = settings.targets.findIndex((e) => e.id === descriptor.id);
|
|
321
|
+
if (idx >= 0) settings.targets[idx] = descriptor;
|
|
322
|
+
else settings.targets.push(descriptor);
|
|
323
|
+
}
|
|
324
|
+
function setOrchestratorPointer(settings, descriptor, model) {
|
|
325
|
+
const runtime = getRuntimeRegistry().get(descriptor.runtime);
|
|
326
|
+
if (!runtime) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`cannot use target '${descriptor.id}' as orchestrator target because runtime '${descriptor.runtime}' is not registered`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (!isOrchestratorEligibleRuntime(runtime)) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`cannot use target '${descriptor.id}' as orchestrator target because runtime '${runtime.id}' is not an HTTP/native runtime`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
settings.chat.target = descriptor.id;
|
|
337
|
+
settings.chat.model = model ?? descriptor.defaultModel ?? null;
|
|
338
|
+
}
|
|
339
|
+
function setWorkerDefaultPointer(settings, descriptor, model) {
|
|
340
|
+
settings.fleet.default.target = descriptor.id;
|
|
341
|
+
settings.fleet.default.model = model ?? descriptor.defaultModel ?? null;
|
|
342
|
+
}
|
|
343
|
+
function setBackgroundPointer(settings, descriptor, model) {
|
|
344
|
+
const runtime = getRuntimeRegistry().get(descriptor.runtime);
|
|
345
|
+
if (!runtime || !isOrchestratorEligibleRuntime(runtime)) {
|
|
346
|
+
throw new Error(
|
|
347
|
+
`cannot use target '${descriptor.id}' as background target because runtime '${descriptor.runtime}' is not an HTTP/native runtime`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
settings.context.memory.target = descriptor.id;
|
|
351
|
+
settings.context.memory.model = model ?? descriptor.defaultModel ?? null;
|
|
352
|
+
}
|
|
353
|
+
function assertOrchestratorReplacementEligible(settings, descriptor) {
|
|
354
|
+
if (settings.chat.target !== descriptor.id && settings.context.memory.target !== descriptor.id) return;
|
|
355
|
+
const role = settings.chat.target === descriptor.id ? "orchestrator" : "background";
|
|
356
|
+
const runtime = getRuntimeRegistry().get(descriptor.runtime);
|
|
357
|
+
if (!runtime) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`cannot update ${role} target '${descriptor.id}' because runtime '${descriptor.runtime}' is not registered`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
if (!isOrchestratorEligibleRuntime(runtime)) {
|
|
363
|
+
throw new Error(`cannot update ${role} target '${descriptor.id}' to non-HTTP/native runtime '${runtime.id}'`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function setWorkerProfilePointer(settings, name, descriptor, model) {
|
|
367
|
+
const trimmed = name.trim();
|
|
368
|
+
if (trimmed.length === 0) throw new Error("fleet profile name must be non-empty");
|
|
369
|
+
settings.fleet.profiles[trimmed] = {
|
|
370
|
+
target: descriptor.id,
|
|
371
|
+
model: model ?? descriptor.defaultModel ?? null,
|
|
372
|
+
thinkingLevel: "off"
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function contextWindowUndiscovered(descriptor, probe) {
|
|
376
|
+
if (!probe?.ok) return false;
|
|
377
|
+
if (typeof descriptor.capabilities?.contextWindow === "number" && descriptor.capabilities.contextWindow > 0) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
const discovered = probeCapabilitiesForModel(
|
|
381
|
+
{
|
|
382
|
+
target: descriptor,
|
|
383
|
+
probeCapabilities: probe.discoveredCapabilities ?? null,
|
|
384
|
+
probeModelCapabilities: probe.modelCapabilities ?? null,
|
|
385
|
+
probeModelId: probe.capabilityModelId ?? null
|
|
386
|
+
},
|
|
387
|
+
descriptor.defaultModel
|
|
388
|
+
);
|
|
389
|
+
return !(typeof discovered?.contextWindow === "number" && discovered.contextWindow > 0);
|
|
390
|
+
}
|
|
391
|
+
function warnUndiscoveredContextWindow(descriptor, probe) {
|
|
392
|
+
if (!contextWindowUndiscovered(descriptor, probe)) return;
|
|
393
|
+
process.stdout.write(
|
|
394
|
+
" warning: the target reported no context window; Clio will use the runtime default as a guess. Set one with --context-window.\n"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
function modelSupportsThinking(runtime, descriptor, probe) {
|
|
398
|
+
const discovered = probe ? probeCapabilitiesForModel(
|
|
399
|
+
{
|
|
400
|
+
target: descriptor,
|
|
401
|
+
probeCapabilities: probe.discoveredCapabilities ?? null,
|
|
402
|
+
probeModelCapabilities: probe.modelCapabilities ?? null,
|
|
403
|
+
probeModelId: probe.capabilityModelId ?? null
|
|
404
|
+
},
|
|
405
|
+
descriptor.defaultModel
|
|
406
|
+
) : null;
|
|
407
|
+
if (typeof discovered?.reasoning === "boolean") return discovered.reasoning;
|
|
408
|
+
if (typeof descriptor.capabilities?.reasoning === "boolean") return descriptor.capabilities.reasoning;
|
|
409
|
+
return runtime.defaultCapabilities.reasoning === true;
|
|
410
|
+
}
|
|
411
|
+
function probeReadings(probe) {
|
|
412
|
+
const readings = [];
|
|
413
|
+
if (probe.models && probe.models.length > 0) readings.push(`${probe.models.length} models`);
|
|
414
|
+
if (probe.serverVersion) readings.push(probe.serverVersion);
|
|
415
|
+
if (probe.discoveredCapabilities && Object.keys(probe.discoveredCapabilities).length > 0) {
|
|
416
|
+
readings.push("capabilities");
|
|
417
|
+
}
|
|
418
|
+
return readings;
|
|
419
|
+
}
|
|
420
|
+
function probeLines(descriptor, probe) {
|
|
421
|
+
const latency = probe.latencyMs !== void 0 ? ` (${probe.latencyMs}ms)` : "";
|
|
422
|
+
if (!probe.ok) {
|
|
423
|
+
if (descriptor.runtime === "antigravity-code") {
|
|
424
|
+
const next = probe.failureKind === "missing" ? "install the official Antigravity CLI and put `agy` on PATH" : probe.failureKind === "authentication" ? "run `agy` yourself and complete sign-in; Clio never reads or manages that session" : probe.failureKind === "unsupported-feature" ? "update `agy`; this integration requires its structured JSON model catalog" : "run `agy` directly to inspect setup, then re-run `clio-coder targets --probe`";
|
|
425
|
+
return [`probe failed${latency}: ${probe.error ?? "unknown"}`, ` ${next}`];
|
|
426
|
+
}
|
|
427
|
+
const where = descriptor.url ? ` ${descriptor.url}` : "";
|
|
428
|
+
return [
|
|
429
|
+
`probe failed${latency}:${where} ${probe.error ?? "unknown"}`,
|
|
430
|
+
` check the server is running and reachable, then re-run: clio-coder configure --id ${descriptor.id} --url <url>`
|
|
431
|
+
];
|
|
432
|
+
}
|
|
433
|
+
const readings = probeReadings(probe);
|
|
434
|
+
if (readings.length > 0) return [`probe ok${latency} ${readings.join(", ")}`];
|
|
435
|
+
return [
|
|
436
|
+
`probe reachable${latency}, but the target answered no model list and no version`,
|
|
437
|
+
" Clio cannot verify the model id or the context window from here and will send both as written.",
|
|
438
|
+
" re-read them once the server serves them with: clio-coder targets --probe"
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
function buildDescriptor(runtime, id, parts) {
|
|
442
|
+
const descriptor = { id, runtime: runtime.id };
|
|
443
|
+
if (parts.url) descriptor.url = parts.url;
|
|
444
|
+
if (parts.model) descriptor.defaultModel = parts.model;
|
|
445
|
+
const auth = {};
|
|
446
|
+
if (parts.apiKeyEnv) auth.apiKeyEnvVar = parts.apiKeyEnv;
|
|
447
|
+
if (parts.apiKeyRef) auth.apiKeyRef = parts.apiKeyRef;
|
|
448
|
+
if (parts.oauthProfile) auth.oauthProfile = parts.oauthProfile;
|
|
449
|
+
if (Object.keys(auth).length > 0) descriptor.auth = auth;
|
|
450
|
+
if (parts.gateway) descriptor.gateway = true;
|
|
451
|
+
if (parts.lifecycle) descriptor.lifecycle = parts.lifecycle;
|
|
452
|
+
const caps = {};
|
|
453
|
+
if (parts.contextWindow !== void 0) caps.contextWindow = parts.contextWindow;
|
|
454
|
+
if (parts.maxTokens !== void 0) caps.maxTokens = parts.maxTokens;
|
|
455
|
+
if (parts.reasoning !== void 0) caps.reasoning = parts.reasoning;
|
|
456
|
+
if (Object.keys(caps).length > 0) descriptor.capabilities = caps;
|
|
457
|
+
if (parts.lmstudio) descriptor.lmstudio = parts.lmstudio;
|
|
458
|
+
if (parts.litellm) descriptor.litellm = parts.litellm;
|
|
459
|
+
return descriptor;
|
|
460
|
+
}
|
|
461
|
+
function describeAuthStatus(runtime) {
|
|
462
|
+
const status = openAuthStorage().statusForTarget(resolveRuntimeAuthTarget(runtime), { includeFallback: false });
|
|
463
|
+
if (!status.available) return "none stored";
|
|
464
|
+
if (status.source === "environment") return `environment${status.detail ? ` (${status.detail})` : ""}`;
|
|
465
|
+
if (status.source === "stored-api-key") return "stored api key";
|
|
466
|
+
if (status.source === "stored-oauth") return "stored oauth";
|
|
467
|
+
return status.source;
|
|
468
|
+
}
|
|
469
|
+
function advertisedModelIds(inventory) {
|
|
470
|
+
const ids = [...inventory.models];
|
|
471
|
+
for (const id of Object.keys(inventory.modelStates ?? {})) if (!ids.includes(id)) ids.push(id);
|
|
472
|
+
return ids;
|
|
473
|
+
}
|
|
474
|
+
function residentModelIds(modelStates) {
|
|
475
|
+
return Object.entries(modelStates ?? {}).filter(([, status]) => status.state === "loaded" || status.state === "loading").map(([id]) => id);
|
|
476
|
+
}
|
|
477
|
+
function liveInventoryFor(target, inventory) {
|
|
478
|
+
return {
|
|
479
|
+
targetId: target.id,
|
|
480
|
+
url: target.url,
|
|
481
|
+
models: advertisedModelIds(inventory),
|
|
482
|
+
resident: residentModelIds(inventory.modelStates)
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
async function resolveSupportedWireModels(runtime, target, existing, authToken) {
|
|
486
|
+
const known = listKnownModelsForRuntime(runtime.id);
|
|
487
|
+
const liveAuthoritative = runtime.externalAgentLoop?.modelCatalog === "live-authoritative";
|
|
488
|
+
if (known.length > 0 && !liveAuthoritative) return { models: known, source: "catalog" };
|
|
489
|
+
let probeError;
|
|
490
|
+
if (runtime.kind === "http" || liveAuthoritative) {
|
|
491
|
+
const probe = await runtimeProbe(runtime, target, authToken);
|
|
492
|
+
if (probe?.ok && probe.models && probe.models.length > 0) {
|
|
493
|
+
recordTargetModelSnapshot(target, probe.models, probe.modelLabels ? { modelLabels: probe.modelLabels } : {});
|
|
494
|
+
return {
|
|
495
|
+
models: [...probe.models],
|
|
496
|
+
source: "probe",
|
|
497
|
+
modelStates: probe.modelStates,
|
|
498
|
+
...probe.modelLabels ? { labels: probe.modelLabels } : {}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
probeError = probe?.error;
|
|
502
|
+
const discovered = await runtimeProbeModels(runtime, target, authToken);
|
|
503
|
+
if (discovered.length > 0) {
|
|
504
|
+
recordTargetModelSnapshot(target, discovered);
|
|
505
|
+
return { models: discovered, source: "probe" };
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const cached = readTargetModelSnapshot(target);
|
|
509
|
+
if (cached && cached.models.length > 0) {
|
|
510
|
+
return {
|
|
511
|
+
models: [...cached.models],
|
|
512
|
+
source: "cache",
|
|
513
|
+
...cached.modelLabels ? { labels: cached.modelLabels } : {},
|
|
514
|
+
...probeError ? { probeError } : {}
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
if (known.length > 0) return { models: known, source: "catalog", ...probeError ? { probeError } : {} };
|
|
518
|
+
if (existing?.wireModels && existing.wireModels.length > 0) {
|
|
519
|
+
recordTargetModelSnapshot(target, existing.wireModels);
|
|
520
|
+
return { models: [...existing.wireModels], source: "legacy" };
|
|
521
|
+
}
|
|
522
|
+
return { models: [], source: "none", ...probeError ? { probeError } : {} };
|
|
523
|
+
}
|
|
524
|
+
function inventoryGap(runtime, target) {
|
|
525
|
+
if (runtime.externalAgentLoop?.modelCatalog === "live-authoritative") {
|
|
526
|
+
return "Antigravity returned no live model catalog; run `agy` yourself, complete sign-in, then probe again";
|
|
527
|
+
}
|
|
528
|
+
if (runtime.kind !== "http") return `${runtime.id} does not list its models; type the id the provider documents`;
|
|
529
|
+
return `${target.url ?? runtime.id} answered no model list; type the id the server serves`;
|
|
530
|
+
}
|
|
531
|
+
var LOCAL_APP_RUNTIME_IDS = /* @__PURE__ */ new Set(["ollama-native", "lmstudio"]);
|
|
532
|
+
var PROTOCOL_COMPAT_RUNTIME_IDS = /* @__PURE__ */ new Set(["openai-compat", "anthropic-compat"]);
|
|
533
|
+
function runtimesForCategory(entries, category) {
|
|
534
|
+
switch (category) {
|
|
535
|
+
case "local-app":
|
|
536
|
+
return entries.filter((entry) => LOCAL_APP_RUNTIME_IDS.has(entry.runtimeId));
|
|
537
|
+
case "local-http":
|
|
538
|
+
return entries.filter((entry) => entry.group === "local-http" && !LOCAL_APP_RUNTIME_IDS.has(entry.runtimeId));
|
|
539
|
+
case "chatgpt":
|
|
540
|
+
return entries.filter((entry) => entry.runtimeId === "openai-codex");
|
|
541
|
+
case "cloud-api": {
|
|
542
|
+
const byLabel = (a, b) => a.label.localeCompare(b.label) || a.runtimeId.localeCompare(b.runtimeId);
|
|
543
|
+
const named = entries.filter((entry) => entry.group === "cloud-api").slice().sort(byLabel);
|
|
544
|
+
const compat = entries.filter((entry) => PROTOCOL_COMPAT_RUNTIME_IDS.has(entry.runtimeId)).map((entry) => ({ ...entry, group: "cloud-api" })).sort(byLabel);
|
|
545
|
+
return [...named, ...compat];
|
|
546
|
+
}
|
|
547
|
+
case "all":
|
|
548
|
+
return entries.slice();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function railPrefix(plain) {
|
|
552
|
+
return plain ? " " : `${source_default.cyan("\u2502")} `;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/cli/select.ts
|
|
556
|
+
init_esm_shims();
|
|
557
|
+
import { emitKeypressEvents } from "node:readline";
|
|
558
|
+
function canSelect(input2 = process.stdin, output3 = process.stdout) {
|
|
559
|
+
return Boolean(input2.isTTY && output3.isTTY && typeof input2.setRawMode === "function" && process.env.TERM !== "dumb");
|
|
560
|
+
}
|
|
561
|
+
var ESC = "\x1B";
|
|
562
|
+
var CURSOR_HIDE = `${ESC}[?25l`;
|
|
563
|
+
var CURSOR_SHOW = `${ESC}[?25h`;
|
|
564
|
+
var ANSI_PATTERN = new RegExp(`${ESC}\\[[0-9;?]*[A-Za-z]`, "gu");
|
|
565
|
+
function printedWidth(text) {
|
|
566
|
+
return text.replace(ANSI_PATTERN, "").length;
|
|
567
|
+
}
|
|
568
|
+
function headingLines(heading) {
|
|
569
|
+
if (heading === void 0) return [];
|
|
570
|
+
return typeof heading === "string" ? [heading] : [...heading];
|
|
571
|
+
}
|
|
572
|
+
async function readKeys(input2, output3, hideCursor, onStart, onKey, onEnd) {
|
|
573
|
+
const wasRaw = input2.isRaw === true;
|
|
574
|
+
emitKeypressEvents(input2);
|
|
575
|
+
input2.setRawMode(true);
|
|
576
|
+
input2.resume();
|
|
577
|
+
if (hideCursor) output3.write(CURSOR_HIDE);
|
|
578
|
+
onStart();
|
|
579
|
+
try {
|
|
580
|
+
return await new Promise((resolve) => {
|
|
581
|
+
const finish2 = (result) => {
|
|
582
|
+
input2.off("keypress", handleKey);
|
|
583
|
+
input2.off("end", handleEnd);
|
|
584
|
+
input2.off("close", handleEnd);
|
|
585
|
+
resolve(result);
|
|
586
|
+
};
|
|
587
|
+
function handleEnd() {
|
|
588
|
+
finish2(onEnd());
|
|
589
|
+
}
|
|
590
|
+
function handleKey(str, key) {
|
|
591
|
+
onKey(str, key ?? {}, finish2);
|
|
592
|
+
}
|
|
593
|
+
input2.on("keypress", handleKey);
|
|
594
|
+
input2.once("end", handleEnd);
|
|
595
|
+
input2.once("close", handleEnd);
|
|
596
|
+
});
|
|
597
|
+
} finally {
|
|
598
|
+
if (hideCursor) output3.write(CURSOR_SHOW);
|
|
599
|
+
if (!wasRaw) input2.setRawMode(false);
|
|
600
|
+
input2.pause();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function makeFrame(output3) {
|
|
604
|
+
let drawn = 0;
|
|
605
|
+
return {
|
|
606
|
+
draw(lines) {
|
|
607
|
+
if (drawn > 0) output3.write(`${ESC}[${drawn}A${ESC}[0J`);
|
|
608
|
+
output3.write(`${lines.join("\n")}
|
|
609
|
+
`);
|
|
610
|
+
drawn = lines.length;
|
|
611
|
+
},
|
|
612
|
+
erase() {
|
|
613
|
+
if (drawn > 0) output3.write(`${ESC}[${drawn}A${ESC}[0J`);
|
|
614
|
+
drawn = 0;
|
|
615
|
+
},
|
|
616
|
+
drawn: () => drawn
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function choiceRows(spec, index, windowStart, viewport) {
|
|
620
|
+
const { choices, rail, railWidth, output: output3, markerWidth, marker } = spec;
|
|
621
|
+
const room = terminalColumns(output3) - railWidth;
|
|
622
|
+
const hasHints = choices.some((choice) => choice.hint !== void 0);
|
|
623
|
+
const labelWidth = Math.min(30, Math.max(...choices.map((choice) => choice.label.length)));
|
|
624
|
+
const hintRoom = room - markerWidth - labelWidth - 2;
|
|
625
|
+
const end = Math.min(choices.length, windowStart + viewport);
|
|
626
|
+
const lines = [];
|
|
627
|
+
if (windowStart > 0) lines.push(`${rail}${source_default.dim(` \u2191 ${windowStart} more`)}`);
|
|
628
|
+
for (let position = windowStart; position < end; position++) {
|
|
629
|
+
const choice = choices[position];
|
|
630
|
+
if (choice === void 0) continue;
|
|
631
|
+
const active = position === index;
|
|
632
|
+
const aligned = hasHints && hintRoom >= 12;
|
|
633
|
+
const label = aligned ? column(choice.label, labelWidth) : truncate(choice.label, room - markerWidth);
|
|
634
|
+
const hint = aligned && choice.hint !== void 0 ? truncate(choice.hint, hintRoom) : "";
|
|
635
|
+
const styledLabel = active ? source_default.cyan.bold(label) : label;
|
|
636
|
+
lines.push(`${rail}${marker(position)}${styledLabel}${hint.length > 0 ? ` ${source_default.dim(hint)}` : ""}`.trimEnd());
|
|
637
|
+
}
|
|
638
|
+
if (end < choices.length) lines.push(`${rail}${source_default.dim(` \u2193 ${choices.length - end} more`)}`);
|
|
639
|
+
return lines;
|
|
640
|
+
}
|
|
641
|
+
function viewportFor(output3, overhead, total, requested) {
|
|
642
|
+
if (requested !== void 0) return Math.max(1, Math.min(total, requested));
|
|
643
|
+
const rows = typeof output3.rows === "number" && output3.rows > 0 ? output3.rows : 24;
|
|
644
|
+
return Math.max(3, Math.min(total, rows - overhead));
|
|
645
|
+
}
|
|
646
|
+
async function promptSelect(options) {
|
|
647
|
+
const input2 = options.input ?? process.stdin;
|
|
648
|
+
const output3 = options.output ?? process.stdout;
|
|
649
|
+
const choices = options.choices;
|
|
650
|
+
if (choices.length === 0) return { kind: "back" };
|
|
651
|
+
const rail = options.railPrefix ?? "";
|
|
652
|
+
const railWidth = 3;
|
|
653
|
+
const head = headingLines(options.heading);
|
|
654
|
+
const frame = makeFrame(output3);
|
|
655
|
+
let index = Math.min(Math.max(options.initialIndex ?? 0, 0), choices.length - 1);
|
|
656
|
+
let windowStart = 0;
|
|
657
|
+
const render = () => {
|
|
658
|
+
const viewport = viewportFor(output3, head.length + 5, choices.length, options.maxVisible);
|
|
659
|
+
if (index < windowStart) windowStart = index;
|
|
660
|
+
else if (index >= windowStart + viewport) windowStart = index - viewport + 1;
|
|
661
|
+
windowStart = Math.max(0, Math.min(windowStart, choices.length - viewport));
|
|
662
|
+
const lines = [];
|
|
663
|
+
for (const line of head) lines.push(`${rail}${line}`.trimEnd());
|
|
664
|
+
if (head.length > 0) lines.push(rail.trimEnd());
|
|
665
|
+
lines.push(
|
|
666
|
+
...choiceRows(
|
|
667
|
+
{
|
|
668
|
+
choices,
|
|
669
|
+
rail,
|
|
670
|
+
railWidth,
|
|
671
|
+
output: output3,
|
|
672
|
+
markerWidth: 2,
|
|
673
|
+
marker: (position) => `${position === index ? source_default.cyan("\u276F") : " "} `
|
|
674
|
+
},
|
|
675
|
+
index,
|
|
676
|
+
windowStart,
|
|
677
|
+
viewport
|
|
678
|
+
)
|
|
679
|
+
);
|
|
680
|
+
lines.push(rail.trimEnd());
|
|
681
|
+
const legend = ["\u2191/\u2193 move", "enter select"];
|
|
682
|
+
if (options.backLabel !== void 0 && options.backLabel !== "quit") legend.push(`esc ${options.backLabel}`);
|
|
683
|
+
legend.push(options.backLabel === "quit" ? "esc or q quit" : "q quit");
|
|
684
|
+
lines.push(`${rail}${source_default.dim(legend.join(" \xB7 "))}`);
|
|
685
|
+
frame.draw(lines);
|
|
686
|
+
};
|
|
687
|
+
const result = await readKeys(
|
|
688
|
+
input2,
|
|
689
|
+
output3,
|
|
690
|
+
true,
|
|
691
|
+
render,
|
|
692
|
+
(_str, key, finish2) => {
|
|
693
|
+
if (key.ctrl && (key.name === "c" || key.name === "d")) {
|
|
694
|
+
finish2({ kind: "quit" });
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
switch (key.name) {
|
|
698
|
+
case "up":
|
|
699
|
+
case "k":
|
|
700
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
701
|
+
render();
|
|
702
|
+
return;
|
|
703
|
+
case "down":
|
|
704
|
+
case "j":
|
|
705
|
+
case "tab":
|
|
706
|
+
index = (index + 1) % choices.length;
|
|
707
|
+
render();
|
|
708
|
+
return;
|
|
709
|
+
case "pageup":
|
|
710
|
+
index = Math.max(0, index - 5);
|
|
711
|
+
render();
|
|
712
|
+
return;
|
|
713
|
+
case "pagedown":
|
|
714
|
+
index = Math.min(choices.length - 1, index + 5);
|
|
715
|
+
render();
|
|
716
|
+
return;
|
|
717
|
+
case "home":
|
|
718
|
+
index = 0;
|
|
719
|
+
render();
|
|
720
|
+
return;
|
|
721
|
+
case "end":
|
|
722
|
+
index = choices.length - 1;
|
|
723
|
+
render();
|
|
724
|
+
return;
|
|
725
|
+
case "return":
|
|
726
|
+
case "enter":
|
|
727
|
+
case "space": {
|
|
728
|
+
const choice = choices[index];
|
|
729
|
+
finish2(choice === void 0 ? { kind: "back" } : { kind: "selected", value: choice.value });
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
case "escape":
|
|
733
|
+
case "left":
|
|
734
|
+
case "backspace":
|
|
735
|
+
finish2({ kind: "back" });
|
|
736
|
+
return;
|
|
737
|
+
case "q":
|
|
738
|
+
finish2({ kind: "quit" });
|
|
739
|
+
return;
|
|
740
|
+
default: {
|
|
741
|
+
const digit = Number.parseInt(key.name ?? "", 10);
|
|
742
|
+
if (Number.isInteger(digit) && digit >= 1 && digit <= choices.length) {
|
|
743
|
+
index = digit - 1;
|
|
744
|
+
render();
|
|
745
|
+
const choice = choices[index];
|
|
746
|
+
if (choice !== void 0) finish2({ kind: "selected", value: choice.value });
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
() => ({ kind: "quit" })
|
|
752
|
+
);
|
|
753
|
+
if (options.clearOnExit) frame.erase();
|
|
754
|
+
return result;
|
|
755
|
+
}
|
|
756
|
+
async function promptMultiSelect(options) {
|
|
757
|
+
const input2 = options.input ?? process.stdin;
|
|
758
|
+
const output3 = options.output ?? process.stdout;
|
|
759
|
+
const choices = options.choices;
|
|
760
|
+
if (choices.length === 0) return { kind: "selected", values: [] };
|
|
761
|
+
const rail = options.railPrefix ?? "";
|
|
762
|
+
const railWidth = 3;
|
|
763
|
+
const head = headingLines(options.heading);
|
|
764
|
+
const frame = makeFrame(output3);
|
|
765
|
+
const checked = choices.map((choice) => choice.checked === true);
|
|
766
|
+
let index = 0;
|
|
767
|
+
let windowStart = 0;
|
|
768
|
+
const render = () => {
|
|
769
|
+
const viewport = viewportFor(output3, head.length + 5, choices.length, options.maxVisible);
|
|
770
|
+
if (index < windowStart) windowStart = index;
|
|
771
|
+
else if (index >= windowStart + viewport) windowStart = index - viewport + 1;
|
|
772
|
+
windowStart = Math.max(0, Math.min(windowStart, choices.length - viewport));
|
|
773
|
+
const lines = [];
|
|
774
|
+
for (const line of head) lines.push(`${rail}${line}`.trimEnd());
|
|
775
|
+
if (head.length > 0) lines.push(rail.trimEnd());
|
|
776
|
+
lines.push(
|
|
777
|
+
...choiceRows(
|
|
778
|
+
{
|
|
779
|
+
choices,
|
|
780
|
+
rail,
|
|
781
|
+
railWidth,
|
|
782
|
+
output: output3,
|
|
783
|
+
markerWidth: 4,
|
|
784
|
+
marker: (position) => {
|
|
785
|
+
const cursor = position === index ? source_default.cyan("\u276F") : " ";
|
|
786
|
+
const box = checked[position] === true ? source_default.green("\u25C9") : source_default.dim("\u25CB");
|
|
787
|
+
return `${cursor} ${box} `;
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
index,
|
|
791
|
+
windowStart,
|
|
792
|
+
viewport
|
|
793
|
+
)
|
|
794
|
+
);
|
|
795
|
+
lines.push(rail.trimEnd());
|
|
796
|
+
const legend = ["\u2191/\u2193 move", "space toggle", `enter ${options.confirmLabel ?? "confirm"}`];
|
|
797
|
+
if (options.backLabel !== void 0) legend.push(`esc ${options.backLabel}`);
|
|
798
|
+
lines.push(`${rail}${source_default.dim(legend.join(" \xB7 "))}`);
|
|
799
|
+
frame.draw(lines);
|
|
800
|
+
};
|
|
801
|
+
const result = await readKeys(
|
|
802
|
+
input2,
|
|
803
|
+
output3,
|
|
804
|
+
true,
|
|
805
|
+
render,
|
|
806
|
+
(_str, key, finish2) => {
|
|
807
|
+
if (key.ctrl && (key.name === "c" || key.name === "d")) {
|
|
808
|
+
finish2({ kind: "quit" });
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
switch (key.name) {
|
|
812
|
+
case "up":
|
|
813
|
+
case "k":
|
|
814
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
815
|
+
render();
|
|
816
|
+
return;
|
|
817
|
+
case "down":
|
|
818
|
+
case "j":
|
|
819
|
+
case "tab":
|
|
820
|
+
index = (index + 1) % choices.length;
|
|
821
|
+
render();
|
|
822
|
+
return;
|
|
823
|
+
case "home":
|
|
824
|
+
index = 0;
|
|
825
|
+
render();
|
|
826
|
+
return;
|
|
827
|
+
case "end":
|
|
828
|
+
index = choices.length - 1;
|
|
829
|
+
render();
|
|
830
|
+
return;
|
|
831
|
+
case "space":
|
|
832
|
+
checked[index] = checked[index] !== true;
|
|
833
|
+
render();
|
|
834
|
+
return;
|
|
835
|
+
case "a":
|
|
836
|
+
{
|
|
837
|
+
const turnOn = checked.some((value) => !value);
|
|
838
|
+
checked.fill(turnOn);
|
|
839
|
+
}
|
|
840
|
+
render();
|
|
841
|
+
return;
|
|
842
|
+
case "return":
|
|
843
|
+
case "enter":
|
|
844
|
+
finish2({
|
|
845
|
+
kind: "selected",
|
|
846
|
+
values: choices.filter((_choice, position) => checked[position] === true).map((choice) => choice.value)
|
|
847
|
+
});
|
|
848
|
+
return;
|
|
849
|
+
case "escape":
|
|
850
|
+
case "left":
|
|
851
|
+
finish2({ kind: "back" });
|
|
852
|
+
return;
|
|
853
|
+
default: {
|
|
854
|
+
const digit = Number.parseInt(key.name ?? "", 10);
|
|
855
|
+
if (Number.isInteger(digit) && digit >= 1 && digit <= choices.length) {
|
|
856
|
+
index = digit - 1;
|
|
857
|
+
checked[index] = checked[index] !== true;
|
|
858
|
+
render();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
() => ({ kind: "quit" })
|
|
864
|
+
);
|
|
865
|
+
if (options.clearOnExit) frame.erase();
|
|
866
|
+
return result;
|
|
867
|
+
}
|
|
868
|
+
function typedText(str, key) {
|
|
869
|
+
if (key.ctrl === true || key.meta === true) return null;
|
|
870
|
+
if (typeof str !== "string" || str.length === 0) return null;
|
|
871
|
+
if (/[\u0000-\u001F\u007F]/u.test(str)) return null;
|
|
872
|
+
return str;
|
|
873
|
+
}
|
|
874
|
+
async function promptText(options) {
|
|
875
|
+
const input2 = options.input ?? process.stdin;
|
|
876
|
+
const output3 = options.output ?? process.stdout;
|
|
877
|
+
const rail = options.railPrefix ?? "";
|
|
878
|
+
const railWidth = printedWidth(rail);
|
|
879
|
+
const head = headingLines(options.heading);
|
|
880
|
+
const frame = makeFrame(output3);
|
|
881
|
+
let value = options.initial ?? "";
|
|
882
|
+
let caret = value.length;
|
|
883
|
+
let error = null;
|
|
884
|
+
let below = 0;
|
|
885
|
+
let parked = false;
|
|
886
|
+
const render = () => {
|
|
887
|
+
if (parked) {
|
|
888
|
+
output3.write(`\r${below > 0 ? `${ESC}[${below}B` : ""}`);
|
|
889
|
+
parked = false;
|
|
890
|
+
}
|
|
891
|
+
const room = Math.max(8, terminalColumns(output3) - railWidth - 2);
|
|
892
|
+
const shown = options.mask === true ? "\u2022".repeat(value.length) : value;
|
|
893
|
+
const lines = [];
|
|
894
|
+
for (const line of head) lines.push(`${rail}${line}`.trimEnd());
|
|
895
|
+
if (head.length > 0) lines.push(rail.trimEnd());
|
|
896
|
+
const fieldIndex = lines.length;
|
|
897
|
+
lines.push(`${rail}${source_default.cyan("\u276F")} ${truncate(shown, room)}`);
|
|
898
|
+
if (error !== null) lines.push(`${rail}${source_default.yellow(truncate(error, room))}`);
|
|
899
|
+
else if (options.hint !== void 0) lines.push(`${rail}${source_default.dim(truncate(options.hint, room))}`);
|
|
900
|
+
lines.push(rail.trimEnd());
|
|
901
|
+
const legend = ["enter accept"];
|
|
902
|
+
if (options.backLabel !== void 0) legend.push(`esc ${options.backLabel}`);
|
|
903
|
+
legend.push("ctrl-c quit");
|
|
904
|
+
lines.push(`${rail}${source_default.dim(legend.join(" \xB7 "))}`);
|
|
905
|
+
frame.draw(lines);
|
|
906
|
+
below = lines.length - fieldIndex;
|
|
907
|
+
const columnOffset = railWidth + 2 + Math.min(caret, room);
|
|
908
|
+
output3.write(`${ESC}[${below}A\r${columnOffset > 0 ? `${ESC}[${columnOffset}C` : ""}`);
|
|
909
|
+
parked = true;
|
|
910
|
+
};
|
|
911
|
+
const unpark = () => {
|
|
912
|
+
if (!parked) return;
|
|
913
|
+
output3.write(`\r${below > 0 ? `${ESC}[${below}B` : ""}`);
|
|
914
|
+
parked = false;
|
|
915
|
+
};
|
|
916
|
+
const result = await readKeys(
|
|
917
|
+
input2,
|
|
918
|
+
output3,
|
|
919
|
+
false,
|
|
920
|
+
render,
|
|
921
|
+
(str, key, finish2) => {
|
|
922
|
+
if (key.ctrl === true) {
|
|
923
|
+
switch (key.name) {
|
|
924
|
+
case "c":
|
|
925
|
+
case "d":
|
|
926
|
+
unpark();
|
|
927
|
+
finish2({ kind: "quit" });
|
|
928
|
+
return;
|
|
929
|
+
case "a":
|
|
930
|
+
caret = 0;
|
|
931
|
+
render();
|
|
932
|
+
return;
|
|
933
|
+
case "e":
|
|
934
|
+
caret = value.length;
|
|
935
|
+
render();
|
|
936
|
+
return;
|
|
937
|
+
case "u":
|
|
938
|
+
value = value.slice(caret);
|
|
939
|
+
caret = 0;
|
|
940
|
+
render();
|
|
941
|
+
return;
|
|
942
|
+
case "k":
|
|
943
|
+
value = value.slice(0, caret);
|
|
944
|
+
render();
|
|
945
|
+
return;
|
|
946
|
+
case "w": {
|
|
947
|
+
const head_ = value.slice(0, caret).replace(/\S+\s*$/u, "");
|
|
948
|
+
value = head_ + value.slice(caret);
|
|
949
|
+
caret = head_.length;
|
|
950
|
+
render();
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
default:
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
switch (key.name) {
|
|
958
|
+
case "escape":
|
|
959
|
+
unpark();
|
|
960
|
+
finish2({ kind: "back" });
|
|
961
|
+
return;
|
|
962
|
+
case "return":
|
|
963
|
+
case "enter": {
|
|
964
|
+
const answer = value.trim();
|
|
965
|
+
const reason = options.validate?.(answer) ?? null;
|
|
966
|
+
if (reason !== null) {
|
|
967
|
+
error = reason;
|
|
968
|
+
render();
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
unpark();
|
|
972
|
+
finish2({ kind: "value", value: answer });
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
case "backspace":
|
|
976
|
+
if (caret > 0) {
|
|
977
|
+
value = value.slice(0, caret - 1) + value.slice(caret);
|
|
978
|
+
caret -= 1;
|
|
979
|
+
error = null;
|
|
980
|
+
render();
|
|
981
|
+
}
|
|
982
|
+
return;
|
|
983
|
+
case "delete":
|
|
984
|
+
if (caret < value.length) {
|
|
985
|
+
value = value.slice(0, caret) + value.slice(caret + 1);
|
|
986
|
+
error = null;
|
|
987
|
+
render();
|
|
988
|
+
}
|
|
989
|
+
return;
|
|
990
|
+
case "left":
|
|
991
|
+
caret = Math.max(0, caret - 1);
|
|
992
|
+
render();
|
|
993
|
+
return;
|
|
994
|
+
case "right":
|
|
995
|
+
caret = Math.min(value.length, caret + 1);
|
|
996
|
+
render();
|
|
997
|
+
return;
|
|
998
|
+
case "home":
|
|
999
|
+
caret = 0;
|
|
1000
|
+
render();
|
|
1001
|
+
return;
|
|
1002
|
+
case "end":
|
|
1003
|
+
caret = value.length;
|
|
1004
|
+
render();
|
|
1005
|
+
return;
|
|
1006
|
+
default: {
|
|
1007
|
+
const typed = typedText(str, key);
|
|
1008
|
+
if (typed === null) return;
|
|
1009
|
+
value = value.slice(0, caret) + typed + value.slice(caret);
|
|
1010
|
+
caret += typed.length;
|
|
1011
|
+
error = null;
|
|
1012
|
+
render();
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
},
|
|
1016
|
+
() => {
|
|
1017
|
+
unpark();
|
|
1018
|
+
return { kind: "quit" };
|
|
1019
|
+
}
|
|
1020
|
+
);
|
|
1021
|
+
if (options.clearOnExit) frame.erase();
|
|
1022
|
+
return result;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/cli/configure-interop.ts
|
|
1026
|
+
function describe(proposal) {
|
|
1027
|
+
const lines = [
|
|
1028
|
+
"",
|
|
1029
|
+
`${proposal.label} is installed and not configured as a delegation agent.`,
|
|
1030
|
+
"",
|
|
1031
|
+
renderProposalEntry(proposal),
|
|
1032
|
+
"",
|
|
1033
|
+
`projectContext stays ${INHERITED_PROJECT_CONTEXT}: this agent receives your task text, never the project projection.`,
|
|
1034
|
+
`toolGovernance is clio-coder-policy: its tool calls are gated by Clio safety.`
|
|
1035
|
+
];
|
|
1036
|
+
if (proposal.needsNetworkInstall) {
|
|
1037
|
+
lines.push(`The ACP adapter is not installed locally; npx fetches it the first time you delegate.`);
|
|
1038
|
+
}
|
|
1039
|
+
return `${lines.join("\n")}
|
|
1040
|
+
`;
|
|
1041
|
+
}
|
|
1042
|
+
function proposalHint(proposal) {
|
|
1043
|
+
const command = [proposal.entry.command, ...proposal.entry.args ?? []].join(" ");
|
|
1044
|
+
return proposal.needsNetworkInstall ? `${command} (npx fetches the adapter on first use)` : command;
|
|
1045
|
+
}
|
|
1046
|
+
async function reviewInteropAgents(io) {
|
|
1047
|
+
const report = await detectInteropAgents({ cwd: process.cwd(), probeVersion: true });
|
|
1048
|
+
const proposals = interopProposals(report, readSettings());
|
|
1049
|
+
if (proposals.length === 0) {
|
|
1050
|
+
if (!io.quiet) output.write("No new coding agents to connect.\n");
|
|
1051
|
+
return { code: 0, wired: [], back: false };
|
|
1052
|
+
}
|
|
1053
|
+
const streams = io.streams;
|
|
1054
|
+
const interactive = streams !== void 0 && canSelect(streams.in, streams.out);
|
|
1055
|
+
if (!interactive && io.rl === null) {
|
|
1056
|
+
for (const proposal of proposals) output.write(describe(proposal));
|
|
1057
|
+
output.write("\nRun `clio-coder configure --interop` on a terminal to connect any of these.\n");
|
|
1058
|
+
return { code: 0, wired: [], back: false };
|
|
1059
|
+
}
|
|
1060
|
+
const accepted = [];
|
|
1061
|
+
const declined = [];
|
|
1062
|
+
if (interactive && streams) {
|
|
1063
|
+
const presenter = io.presenter ?? createLifecyclePresenter({ stream: streams.out });
|
|
1064
|
+
const rail = io.rail ?? railPrefix(presenter.isPlain());
|
|
1065
|
+
presenter.note(
|
|
1066
|
+
`Clio found ${proposals.length === 1 ? "one coding agent" : `${proposals.length} coding agents`} it can delegate to. A peer receives your task text and never the project projection, and its tool calls are gated by Clio safety.`
|
|
1067
|
+
);
|
|
1068
|
+
const result = await promptMultiSelect({
|
|
1069
|
+
heading: ["", source_default.bold("Delegate to any of these?")],
|
|
1070
|
+
choices: proposals.map((proposal) => ({
|
|
1071
|
+
value: proposal.kind,
|
|
1072
|
+
label: proposal.label,
|
|
1073
|
+
hint: proposalHint(proposal)
|
|
1074
|
+
})),
|
|
1075
|
+
railPrefix: rail,
|
|
1076
|
+
backLabel: "back",
|
|
1077
|
+
confirmLabel: "confirm",
|
|
1078
|
+
clearOnExit: true,
|
|
1079
|
+
input: streams.in,
|
|
1080
|
+
output: streams.out
|
|
1081
|
+
});
|
|
1082
|
+
if (result.kind === "back") return { code: 0, wired: [], back: true };
|
|
1083
|
+
if (result.kind === "quit") return { code: 0, wired: [], back: false };
|
|
1084
|
+
for (const proposal of proposals) {
|
|
1085
|
+
(result.values.includes(proposal.kind) ? accepted : declined).push(proposal.kind);
|
|
1086
|
+
}
|
|
1087
|
+
} else if (io.rl !== null) {
|
|
1088
|
+
for (const proposal of proposals) {
|
|
1089
|
+
output.write(describe(proposal));
|
|
1090
|
+
const yes = await askYesNo(io.rl, `Add ${proposal.label} as delegation agent \`${proposal.entry.id}\`?`, false);
|
|
1091
|
+
(yes ? accepted : declined).push(proposal.kind);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
const wired = [];
|
|
1095
|
+
if (accepted.length > 0) {
|
|
1096
|
+
const result = acceptInteropAgents(accepted, report);
|
|
1097
|
+
for (const diagnostic of result.diagnostics) {
|
|
1098
|
+
if (io.presenter) io.presenter.warn(diagnostic);
|
|
1099
|
+
else output.write(`note: ${diagnostic}
|
|
1100
|
+
`);
|
|
1101
|
+
}
|
|
1102
|
+
for (const id of result.wired) {
|
|
1103
|
+
wired.push(id);
|
|
1104
|
+
if (!io.presenter) printOk(`delegation agent ${id} added; use \`/delegate ${id} <task>\``);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (declined.length > 0) {
|
|
1108
|
+
declineInteropAgents(declined, report);
|
|
1109
|
+
const line = `Declined ${declined.join(", ")}; Clio stays quiet about them until their version or path changes.`;
|
|
1110
|
+
if (io.presenter) io.presenter.note(line);
|
|
1111
|
+
else output.write(`${line}
|
|
1112
|
+
`);
|
|
1113
|
+
}
|
|
1114
|
+
return { code: 0, wired, back: false };
|
|
1115
|
+
}
|
|
1116
|
+
async function runInteropReview(io) {
|
|
1117
|
+
return (await reviewInteropAgents(io)).code;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// src/cli/configure-layout.ts
|
|
1121
|
+
init_esm_shims();
|
|
1122
|
+
function maxLength(values) {
|
|
1123
|
+
return values.reduce((widest, value) => Math.max(widest, value.length), 0);
|
|
1124
|
+
}
|
|
1125
|
+
var CONFIGURE_CATEGORY_CHOICES = [
|
|
1126
|
+
{
|
|
1127
|
+
category: "local-app",
|
|
1128
|
+
label: "Local app",
|
|
1129
|
+
summary: "Ollama or LM Studio (recommended for new users)",
|
|
1130
|
+
aliases: ["local"]
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
category: "local-http",
|
|
1134
|
+
label: "Local HTTP server",
|
|
1135
|
+
summary: "llama.cpp / vLLM / SGLang / OpenAI/Anthropic-compatible",
|
|
1136
|
+
aliases: ["http"]
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
category: "chatgpt",
|
|
1140
|
+
label: "ChatGPT plan",
|
|
1141
|
+
summary: "Plus or Pro via Codex OAuth",
|
|
1142
|
+
aliases: ["codex"]
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
category: "cloud-api",
|
|
1146
|
+
label: "Cloud API key",
|
|
1147
|
+
summary: "Anthropic, OpenAI, OpenRouter, Groq, Google, DeepSeek, Mistral, Bedrock, or any OpenAI/Anthropic-compatible endpoint (e.g. Inception)",
|
|
1148
|
+
aliases: ["cloud"]
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
category: "all",
|
|
1152
|
+
label: "All runtimes",
|
|
1153
|
+
summary: "advanced (full list)",
|
|
1154
|
+
aliases: ["a"]
|
|
1155
|
+
}
|
|
1156
|
+
];
|
|
1157
|
+
function formatCategoryMenu(width) {
|
|
1158
|
+
const leftWidth = maxLength(CONFIGURE_CATEGORY_CHOICES.map((choice, index) => `${index + 1}. ${choice.label}`)) + 2;
|
|
1159
|
+
const lines = [];
|
|
1160
|
+
for (const [index, choice] of CONFIGURE_CATEGORY_CHOICES.entries()) {
|
|
1161
|
+
lines.push(...twoColumnRow(`${index + 1}. ${choice.label}`, choice.summary, { indent: 2, leftWidth, width }));
|
|
1162
|
+
}
|
|
1163
|
+
return lines;
|
|
1164
|
+
}
|
|
1165
|
+
function matchCategoryChoice(answer) {
|
|
1166
|
+
const lc = answer.trim().toLowerCase();
|
|
1167
|
+
if (lc.length === 0) return null;
|
|
1168
|
+
for (const [index, choice] of CONFIGURE_CATEGORY_CHOICES.entries()) {
|
|
1169
|
+
if (lc === String(index + 1) || lc === choice.category || choice.aliases.includes(lc)) {
|
|
1170
|
+
return choice.category;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
function formatRuntimeMenu(items, width) {
|
|
1176
|
+
if (items.length === 0) return [];
|
|
1177
|
+
const leftWidth = maxLength(items.map((item, index) => `${String(index + 1).padStart(2)}. ${item.runtimeId}`)) + 1;
|
|
1178
|
+
const lines = [];
|
|
1179
|
+
let lastGroup = null;
|
|
1180
|
+
const grouped = new Set(items.map((item) => item.group)).size > 1;
|
|
1181
|
+
for (const [index, item] of items.entries()) {
|
|
1182
|
+
if (grouped && item.group !== lastGroup) {
|
|
1183
|
+
lastGroup = item.group;
|
|
1184
|
+
lines.push(` ${item.group}:`);
|
|
1185
|
+
}
|
|
1186
|
+
lines.push(
|
|
1187
|
+
...twoColumnRow(`${String(index + 1).padStart(2)}. ${item.runtimeId}`, item.summary, {
|
|
1188
|
+
indent: 4,
|
|
1189
|
+
leftWidth,
|
|
1190
|
+
width
|
|
1191
|
+
})
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
return lines;
|
|
1195
|
+
}
|
|
1196
|
+
var LIST_INDENT = 2;
|
|
1197
|
+
var LIST_GAP = 1;
|
|
1198
|
+
var LIST_MIN_MODELS = 16;
|
|
1199
|
+
function formatRuntimeList(rows, width) {
|
|
1200
|
+
if (rows.length === 0) return [];
|
|
1201
|
+
const targetCells = rows.map((row) => `targets=${row.targets}`);
|
|
1202
|
+
const idWidth = maxLength(rows.map((row) => row.runtimeId));
|
|
1203
|
+
const labelWidth = maxLength(rows.map((row) => row.label));
|
|
1204
|
+
const authWidth = maxLength(rows.map((row) => row.auth));
|
|
1205
|
+
const targetsWidth = maxLength(targetCells);
|
|
1206
|
+
const alignedWidth = LIST_INDENT + idWidth + LIST_GAP + labelWidth + LIST_GAP + authWidth + LIST_GAP + targetsWidth;
|
|
1207
|
+
const inline = width >= alignedWidth + LIST_GAP + LIST_MIN_MODELS;
|
|
1208
|
+
const pad = " ".repeat(LIST_INDENT);
|
|
1209
|
+
const detailPad = " ".repeat(LIST_INDENT + 2);
|
|
1210
|
+
const lines = [];
|
|
1211
|
+
let lastGroup = null;
|
|
1212
|
+
for (const [index, row] of rows.entries()) {
|
|
1213
|
+
if (row.group !== lastGroup) {
|
|
1214
|
+
if (lastGroup !== null) lines.push("");
|
|
1215
|
+
lastGroup = row.group;
|
|
1216
|
+
lines.push(`${row.group}:`);
|
|
1217
|
+
}
|
|
1218
|
+
const targetCell = targetCells[index] ?? "";
|
|
1219
|
+
const models = `models=${row.models}`;
|
|
1220
|
+
if (inline) {
|
|
1221
|
+
const id = column(row.runtimeId, idWidth);
|
|
1222
|
+
const label = column(row.label, labelWidth);
|
|
1223
|
+
const auth = column(row.auth, authWidth);
|
|
1224
|
+
lines.push(truncate(`${pad}${id} ${label} ${auth} ${column(targetCell, targetsWidth)} ${models}`, width));
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
lines.push(truncate(`${pad}${column(row.runtimeId, idWidth)} ${column(row.auth, authWidth)} ${targetCell}`, width));
|
|
1228
|
+
lines.push(truncate(`${detailPad}${row.label} \xB7 ${models}`, width));
|
|
1229
|
+
}
|
|
1230
|
+
return lines;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// src/cli/configure-oauth.ts
|
|
1234
|
+
init_esm_shims();
|
|
1235
|
+
async function loginOAuthRuntime(rl, runtime) {
|
|
1236
|
+
const auth = openAuthStorage();
|
|
1237
|
+
if (runtime.authNotice) process.stdout.write(`note: ${runtime.authNotice}
|
|
1238
|
+
`);
|
|
1239
|
+
const manualCodeInput = createDelayedManualCodeInput(
|
|
1240
|
+
rl,
|
|
1241
|
+
"Paste verification code if browser callback does not complete automatically: "
|
|
1242
|
+
);
|
|
1243
|
+
try {
|
|
1244
|
+
await auth.login(runtime.oauthProviderId ?? runtime.id, {
|
|
1245
|
+
onAuth: ({ url, instructions }) => {
|
|
1246
|
+
process.stdout.write(`
|
|
1247
|
+
Open: ${url}
|
|
1248
|
+
`);
|
|
1249
|
+
if (instructions) process.stdout.write(`${instructions}
|
|
1250
|
+
`);
|
|
1251
|
+
process.stdout.write("Waiting for the browser callback. A manual code prompt will appear if needed.\n");
|
|
1252
|
+
},
|
|
1253
|
+
onDeviceCode: ({ verificationUri, userCode }) => {
|
|
1254
|
+
process.stdout.write(`
|
|
1255
|
+
Open: ${verificationUri}
|
|
1256
|
+
`);
|
|
1257
|
+
process.stdout.write(`Enter code: ${userCode}
|
|
1258
|
+
`);
|
|
1259
|
+
},
|
|
1260
|
+
onPrompt: async (prompt) => {
|
|
1261
|
+
const answer = await rl.question(`${prompt.message}${prompt.allowEmpty ? " " : ": "}`);
|
|
1262
|
+
return prompt.allowEmpty ? answer : answer.trim();
|
|
1263
|
+
},
|
|
1264
|
+
onSelect: (prompt) => promptOAuthSelection(rl, prompt),
|
|
1265
|
+
onManualCodeInput: manualCodeInput.onManualCodeInput,
|
|
1266
|
+
onProgress: (message) => {
|
|
1267
|
+
process.stderr.write(`${message}
|
|
1268
|
+
`);
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
if (credentialWriteFailed(auth, `credential for ${runtime.id} was not stored`)) return false;
|
|
1272
|
+
printOk(`authenticated ${runtime.id}`);
|
|
1273
|
+
return true;
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
1276
|
+
return false;
|
|
1277
|
+
} finally {
|
|
1278
|
+
manualCodeInput.cancel();
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/cli/configure-onboarding.ts
|
|
1283
|
+
init_esm_shims();
|
|
1284
|
+
import { createInterface } from "node:readline/promises";
|
|
1285
|
+
function canRunOnboarding(streams) {
|
|
1286
|
+
return canSelect(streams.in, streams.out);
|
|
1287
|
+
}
|
|
1288
|
+
var LABEL_WIDTH = 11;
|
|
1289
|
+
function railWriter(out) {
|
|
1290
|
+
let lines = 0;
|
|
1291
|
+
const stream = {
|
|
1292
|
+
write(chunk) {
|
|
1293
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
1294
|
+
for (const char of text) if (char === "\n") lines += 1;
|
|
1295
|
+
return out.write(chunk);
|
|
1296
|
+
},
|
|
1297
|
+
isTTY: out.isTTY,
|
|
1298
|
+
columns: out.columns
|
|
1299
|
+
};
|
|
1300
|
+
return {
|
|
1301
|
+
stream,
|
|
1302
|
+
mark: () => lines,
|
|
1303
|
+
rewindTo(mark) {
|
|
1304
|
+
const back = lines - mark;
|
|
1305
|
+
if (back > 0) out.write(`\x1B[${back}A\x1B[0J`);
|
|
1306
|
+
lines = mark;
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function supportFor(runtime) {
|
|
1311
|
+
return buildProviderSupportEntry(runtime);
|
|
1312
|
+
}
|
|
1313
|
+
function allEntries() {
|
|
1314
|
+
return listProviderSupportEntries(getRuntimeRegistry().list());
|
|
1315
|
+
}
|
|
1316
|
+
function draftDescriptor(answers, runtime, withModel) {
|
|
1317
|
+
const apiKeyRef = answers.credential === "stored" || answers.credential === "keep" ? runtime.id : void 0;
|
|
1318
|
+
return buildDescriptor(runtime, answers.targetId ?? runtime.id, {
|
|
1319
|
+
...answers.url !== void 0 ? { url: answers.url } : {},
|
|
1320
|
+
...withModel && answers.model !== void 0 ? { model: answers.model } : {},
|
|
1321
|
+
...answers.apiKeyEnv !== void 0 ? { apiKeyEnv: answers.apiKeyEnv } : {},
|
|
1322
|
+
...apiKeyRef !== void 0 ? { apiKeyRef } : {},
|
|
1323
|
+
...runtime.auth === "oauth" ? { oauthProfile: runtime.oauthProviderId ?? runtime.id } : {}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
var CATEGORY_STEP = {
|
|
1327
|
+
id: "category",
|
|
1328
|
+
applies: () => true,
|
|
1329
|
+
run: async (wizard, answers) => {
|
|
1330
|
+
const current = CONFIGURE_CATEGORY_CHOICES.findIndex((choice2) => choice2.category === answers.category);
|
|
1331
|
+
const result = await promptSelect({
|
|
1332
|
+
heading: ["", source_default.bold("How will you connect Clio to a model?")],
|
|
1333
|
+
choices: CONFIGURE_CATEGORY_CHOICES.map((choice2) => ({
|
|
1334
|
+
value: choice2.category,
|
|
1335
|
+
label: choice2.label,
|
|
1336
|
+
hint: choice2.summary
|
|
1337
|
+
})),
|
|
1338
|
+
initialIndex: current >= 0 ? current : 0,
|
|
1339
|
+
railPrefix: wizard.rail,
|
|
1340
|
+
backLabel: "cancel",
|
|
1341
|
+
clearOnExit: true,
|
|
1342
|
+
input: wizard.input,
|
|
1343
|
+
output: wizard.output
|
|
1344
|
+
});
|
|
1345
|
+
if (result.kind === "quit") return "quit";
|
|
1346
|
+
if (result.kind === "back") return "back";
|
|
1347
|
+
if (answers.category !== result.value) {
|
|
1348
|
+
answers.runtime = void 0;
|
|
1349
|
+
answers.detected = void 0;
|
|
1350
|
+
}
|
|
1351
|
+
answers.category = result.value;
|
|
1352
|
+
const choice = CONFIGURE_CATEGORY_CHOICES.find((entry) => entry.category === result.value);
|
|
1353
|
+
wizard.answer("Connection", choice?.label ?? result.value);
|
|
1354
|
+
return "next";
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
var RUNTIME_STEP = {
|
|
1358
|
+
id: "runtime",
|
|
1359
|
+
applies: () => true,
|
|
1360
|
+
run: async (wizard, answers) => {
|
|
1361
|
+
const registry = getRuntimeRegistry();
|
|
1362
|
+
const chatEntries = allEntries().filter((entry) => {
|
|
1363
|
+
const runtime2 = registry.get(entry.runtimeId);
|
|
1364
|
+
return runtime2 !== null && isOrchestratorEligibleRuntime(runtime2);
|
|
1365
|
+
});
|
|
1366
|
+
const entries = answers.category ? runtimesForCategory(chatEntries, answers.category) : chatEntries;
|
|
1367
|
+
const usable = entries.length > 0 ? entries : chatEntries;
|
|
1368
|
+
const previous = usable.findIndex((entry) => entry.runtimeId === answers.runtime?.id);
|
|
1369
|
+
const featured = usable.findIndex((entry) => entry.featured);
|
|
1370
|
+
const result = await promptSelect({
|
|
1371
|
+
heading: ["", source_default.bold("Which runtime?")],
|
|
1372
|
+
choices: usable.map((entry) => ({ value: entry.runtimeId, label: entry.runtimeId, hint: entry.summary })),
|
|
1373
|
+
initialIndex: previous >= 0 ? previous : featured >= 0 ? featured : 0,
|
|
1374
|
+
railPrefix: wizard.rail,
|
|
1375
|
+
backLabel: "back",
|
|
1376
|
+
clearOnExit: true,
|
|
1377
|
+
input: wizard.input,
|
|
1378
|
+
output: wizard.output
|
|
1379
|
+
});
|
|
1380
|
+
if (result.kind === "quit") return "quit";
|
|
1381
|
+
if (result.kind === "back") return "back";
|
|
1382
|
+
const runtime = registry.get(result.value);
|
|
1383
|
+
if (!runtime) return "back";
|
|
1384
|
+
if (answers.runtime?.id !== runtime.id) {
|
|
1385
|
+
answers.targetId = void 0;
|
|
1386
|
+
answers.url = void 0;
|
|
1387
|
+
answers.detected = void 0;
|
|
1388
|
+
answers.credential = void 0;
|
|
1389
|
+
answers.apiKeyEnv = void 0;
|
|
1390
|
+
answers.apiKeyLiteral = void 0;
|
|
1391
|
+
answers.model = void 0;
|
|
1392
|
+
answers.inventory = void 0;
|
|
1393
|
+
answers.inventoryKey = void 0;
|
|
1394
|
+
}
|
|
1395
|
+
answers.runtime = runtime;
|
|
1396
|
+
wizard.answer("Runtime", `${runtime.id} ${source_default.dim(supportFor(runtime).summary)}`);
|
|
1397
|
+
return "next";
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
var TARGET_ID_STEP = {
|
|
1401
|
+
id: "target-id",
|
|
1402
|
+
applies: () => true,
|
|
1403
|
+
run: async (wizard, answers) => {
|
|
1404
|
+
const runtime = answers.runtime;
|
|
1405
|
+
if (!runtime) return "back";
|
|
1406
|
+
const result = await promptText({
|
|
1407
|
+
heading: ["", source_default.bold("Target id")],
|
|
1408
|
+
initial: answers.targetId ?? deriveTargetId(runtime.id, readSettings().targets),
|
|
1409
|
+
hint: "the name you will use for this target in `clio-coder targets`",
|
|
1410
|
+
railPrefix: wizard.rail,
|
|
1411
|
+
backLabel: "back",
|
|
1412
|
+
clearOnExit: true,
|
|
1413
|
+
validate: (value) => {
|
|
1414
|
+
if (value.length === 0) return "a target id is required";
|
|
1415
|
+
if (/\s/u.test(value)) return "a target id cannot contain spaces";
|
|
1416
|
+
return null;
|
|
1417
|
+
},
|
|
1418
|
+
input: wizard.input,
|
|
1419
|
+
output: wizard.output
|
|
1420
|
+
});
|
|
1421
|
+
if (result.kind === "quit") return "quit";
|
|
1422
|
+
if (result.kind === "back") return "back";
|
|
1423
|
+
if (answers.targetId !== result.value) answers.inventoryKey = void 0;
|
|
1424
|
+
answers.targetId = result.value;
|
|
1425
|
+
wizard.answer("Target id", result.value);
|
|
1426
|
+
return "next";
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
var URL_STEP = {
|
|
1430
|
+
id: "url",
|
|
1431
|
+
applies: (answers) => answers.runtime !== void 0 && supportFor(answers.runtime).supportsCustomUrl,
|
|
1432
|
+
run: async (wizard, answers) => {
|
|
1433
|
+
const runtime = answers.runtime;
|
|
1434
|
+
if (!runtime) return "back";
|
|
1435
|
+
const local = supportFor(runtime).group === "local-http";
|
|
1436
|
+
const result = await promptText({
|
|
1437
|
+
heading: ["", source_default.bold(local ? "Where is the server?" : "Base URL")],
|
|
1438
|
+
initial: answers.url ?? defaultUrlFor(runtime.id),
|
|
1439
|
+
hint: "host:port is enough; Clio fills in the scheme and the port it knows",
|
|
1440
|
+
railPrefix: wizard.rail,
|
|
1441
|
+
backLabel: "back",
|
|
1442
|
+
clearOnExit: true,
|
|
1443
|
+
validate: (value) => value.length === 0 ? "a URL is required for this runtime" : null,
|
|
1444
|
+
input: wizard.input,
|
|
1445
|
+
output: wizard.output
|
|
1446
|
+
});
|
|
1447
|
+
if (result.kind === "quit") return "quit";
|
|
1448
|
+
if (result.kind === "back") return "back";
|
|
1449
|
+
const url = normalizeUrl(result.value, runtime.id);
|
|
1450
|
+
if (answers.url !== url) {
|
|
1451
|
+
answers.inventoryKey = void 0;
|
|
1452
|
+
answers.detected = void 0;
|
|
1453
|
+
}
|
|
1454
|
+
answers.url = url;
|
|
1455
|
+
wizard.answer("URL", url);
|
|
1456
|
+
await reportReachability(wizard, answers, runtime, url);
|
|
1457
|
+
return "next";
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
async function reportReachability(wizard, answers, runtime, url) {
|
|
1461
|
+
const draft = draftDescriptor(answers, runtime, false);
|
|
1462
|
+
const probe = await runtimeProbe(runtime, draft, answers.apiKeyLiteral);
|
|
1463
|
+
if (probe !== null) {
|
|
1464
|
+
if (probe.ok) {
|
|
1465
|
+
const readings = probeReadings(probe);
|
|
1466
|
+
wizard.presenter.step(`reachable, ${readings.length > 0 ? readings.join(", ") : "no model list offered"}`);
|
|
1467
|
+
} else if (probe.authFailed) {
|
|
1468
|
+
wizard.presenter.warn("rejected the key");
|
|
1469
|
+
} else {
|
|
1470
|
+
wizard.presenter.warn(`not reachable, you can fix this later: ${probe.error ?? "no reply"}`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (probe?.ok && runtime.id === "lmstudio") {
|
|
1474
|
+
const greeting = await greetLmStudio(draft, { credentialsPresent: /* @__PURE__ */ new Set(), httpTimeoutMs: 750 });
|
|
1475
|
+
if (!greeting.ok) wizard.presenter.warn(`${url} answered, but not with the LM Studio greeting`);
|
|
1476
|
+
}
|
|
1477
|
+
if (PROTOCOL_COMPAT_RUNTIME_IDS.has(runtime.id)) {
|
|
1478
|
+
const fingerprint = await fingerprintNativeRuntime(url);
|
|
1479
|
+
if (fingerprint && getRuntimeRegistry().get(fingerprint.runtimeId)) {
|
|
1480
|
+
answers.detected = { runtimeId: fingerprint.runtimeId, displayName: fingerprint.displayName };
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
var DETECTED_RUNTIME_STEP = {
|
|
1485
|
+
id: "detected-runtime",
|
|
1486
|
+
applies: (answers) => answers.detected !== void 0 && answers.runtime !== void 0,
|
|
1487
|
+
run: async (wizard, answers) => {
|
|
1488
|
+
const detected = answers.detected;
|
|
1489
|
+
const runtime = answers.runtime;
|
|
1490
|
+
if (!detected || !runtime) return "next";
|
|
1491
|
+
const native = getRuntimeRegistry().get(detected.runtimeId);
|
|
1492
|
+
if (!native) return "next";
|
|
1493
|
+
const result = await promptSelect({
|
|
1494
|
+
heading: ["", source_default.bold(`That URL is serving ${detected.displayName}.`)],
|
|
1495
|
+
choices: [
|
|
1496
|
+
{
|
|
1497
|
+
value: true,
|
|
1498
|
+
label: `Use ${native.id}`,
|
|
1499
|
+
hint: "its own runtime, with resident-model lifecycle and its real capabilities"
|
|
1500
|
+
},
|
|
1501
|
+
{ value: false, label: `Keep ${runtime.id}`, hint: "the generic protocol runtime you picked" }
|
|
1502
|
+
],
|
|
1503
|
+
initialIndex: 0,
|
|
1504
|
+
railPrefix: wizard.rail,
|
|
1505
|
+
backLabel: "back",
|
|
1506
|
+
clearOnExit: true,
|
|
1507
|
+
input: wizard.input,
|
|
1508
|
+
output: wizard.output
|
|
1509
|
+
});
|
|
1510
|
+
if (result.kind === "quit") return "quit";
|
|
1511
|
+
if (result.kind === "back") return "back";
|
|
1512
|
+
if (result.value) {
|
|
1513
|
+
answers.runtime = native;
|
|
1514
|
+
answers.inventoryKey = void 0;
|
|
1515
|
+
wizard.answer("Detected", `${detected.displayName}, using runtime ${native.id}`);
|
|
1516
|
+
} else {
|
|
1517
|
+
wizard.answer("Detected", `${detected.displayName}, keeping runtime ${runtime.id}`);
|
|
1518
|
+
}
|
|
1519
|
+
return "next";
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
function defaultCredentialSource(runtime, targetId) {
|
|
1523
|
+
const status = openAuthStorage().statusForTarget(resolveRuntimeAuthTarget(runtime), { includeFallback: false });
|
|
1524
|
+
if (status.source === "stored-api-key") return "keep";
|
|
1525
|
+
if (status.source === "environment") return "env";
|
|
1526
|
+
return targetRequiresAuth({ id: targetId, runtime: runtime.id }, runtime) ? "env" : "skip";
|
|
1527
|
+
}
|
|
1528
|
+
async function connectOAuth(wizard, runtime) {
|
|
1529
|
+
const rl = createInterface({ input: wizard.streams.in, output: wizard.streams.out });
|
|
1530
|
+
try {
|
|
1531
|
+
return await loginOAuthRuntime(rl, runtime);
|
|
1532
|
+
} finally {
|
|
1533
|
+
rl.close();
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
var CREDENTIAL_STEP = {
|
|
1537
|
+
id: "credential",
|
|
1538
|
+
applies: (answers) => answers.runtime?.auth === "api-key" || answers.runtime?.auth === "oauth",
|
|
1539
|
+
run: async (wizard, answers) => {
|
|
1540
|
+
const runtime = answers.runtime;
|
|
1541
|
+
if (!runtime) return "back";
|
|
1542
|
+
const stored = describeAuthStatus(runtime);
|
|
1543
|
+
if (runtime.auth === "oauth") {
|
|
1544
|
+
const result2 = await promptSelect({
|
|
1545
|
+
heading: ["", source_default.bold(`Sign in to ${runtime.displayName}`), source_default.dim(`credential: ${stored}`)],
|
|
1546
|
+
choices: [
|
|
1547
|
+
{ value: "oauth-connect", label: "Connect now", hint: "opens your browser and waits for the callback" },
|
|
1548
|
+
{
|
|
1549
|
+
value: "oauth-skip",
|
|
1550
|
+
label: "Later",
|
|
1551
|
+
hint: `run \`clio-coder auth login ${runtime.id}\` before the first turn`
|
|
1552
|
+
}
|
|
1553
|
+
],
|
|
1554
|
+
initialIndex: answers.credential === "oauth-skip" ? 1 : 0,
|
|
1555
|
+
railPrefix: wizard.rail,
|
|
1556
|
+
backLabel: "back",
|
|
1557
|
+
clearOnExit: true,
|
|
1558
|
+
input: wizard.input,
|
|
1559
|
+
output: wizard.output
|
|
1560
|
+
});
|
|
1561
|
+
if (result2.kind === "quit") return "quit";
|
|
1562
|
+
if (result2.kind === "back") return "back";
|
|
1563
|
+
answers.credential = result2.value;
|
|
1564
|
+
if (result2.value === "oauth-connect") {
|
|
1565
|
+
const connected = await connectOAuth(wizard, runtime);
|
|
1566
|
+
if (!connected) {
|
|
1567
|
+
wizard.presenter.warn(`sign-in did not complete; run \`clio-coder auth login ${runtime.id}\` when ready`);
|
|
1568
|
+
wizard.answer("Credential", "not connected yet");
|
|
1569
|
+
return "next";
|
|
1570
|
+
}
|
|
1571
|
+
wizard.answer("Credential", `connected to ${runtime.id}`);
|
|
1572
|
+
return "next";
|
|
1573
|
+
}
|
|
1574
|
+
wizard.answer("Credential", "not connected yet");
|
|
1575
|
+
return "next";
|
|
1576
|
+
}
|
|
1577
|
+
const hasStored = stored !== "none stored";
|
|
1578
|
+
const choices = [
|
|
1579
|
+
{
|
|
1580
|
+
value: "env",
|
|
1581
|
+
label: "Environment variable",
|
|
1582
|
+
hint: "read at call time; nothing is written to disk"
|
|
1583
|
+
},
|
|
1584
|
+
{
|
|
1585
|
+
value: "stored",
|
|
1586
|
+
label: "Store the key",
|
|
1587
|
+
hint: "written to credentials.yaml, mode 0600, not encrypted"
|
|
1588
|
+
},
|
|
1589
|
+
...hasStored ? [{ value: "keep", label: "Keep what is there", hint: stored }] : [],
|
|
1590
|
+
{ value: "skip", label: "No key", hint: "this target needs no credential" }
|
|
1591
|
+
];
|
|
1592
|
+
const current = answers.credential ?? defaultCredentialSource(runtime, answers.targetId ?? runtime.id);
|
|
1593
|
+
const initial = Math.max(
|
|
1594
|
+
0,
|
|
1595
|
+
choices.findIndex((choice) => choice.value === current)
|
|
1596
|
+
);
|
|
1597
|
+
const result = await promptSelect({
|
|
1598
|
+
heading: ["", source_default.bold("How should Clio get the API key?"), source_default.dim(`currently: ${stored}`)],
|
|
1599
|
+
choices,
|
|
1600
|
+
initialIndex: initial,
|
|
1601
|
+
railPrefix: wizard.rail,
|
|
1602
|
+
backLabel: "back",
|
|
1603
|
+
clearOnExit: true,
|
|
1604
|
+
input: wizard.input,
|
|
1605
|
+
output: wizard.output
|
|
1606
|
+
});
|
|
1607
|
+
if (result.kind === "quit") return "quit";
|
|
1608
|
+
if (result.kind === "back") return "back";
|
|
1609
|
+
answers.credential = result.value;
|
|
1610
|
+
if (result.value !== "env") answers.apiKeyEnv = void 0;
|
|
1611
|
+
if (result.value !== "stored") answers.apiKeyLiteral = void 0;
|
|
1612
|
+
if (result.value === "keep") wizard.answer("Credential", stored);
|
|
1613
|
+
if (result.value === "skip") wizard.answer("Credential", "none");
|
|
1614
|
+
return "next";
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
var CREDENTIAL_VALUE_STEP = {
|
|
1618
|
+
id: "credential-value",
|
|
1619
|
+
applies: (answers) => answers.credential === "env" || answers.credential === "stored",
|
|
1620
|
+
run: async (wizard, answers) => {
|
|
1621
|
+
const runtime = answers.runtime;
|
|
1622
|
+
if (!runtime) return "back";
|
|
1623
|
+
const wantsEnv = answers.credential === "env";
|
|
1624
|
+
const result = await promptText({
|
|
1625
|
+
heading: ["", source_default.bold(wantsEnv ? "Which environment variable?" : "Paste the API key")],
|
|
1626
|
+
initial: wantsEnv ? answers.apiKeyEnv ?? runtime.credentialsEnvVar ?? "" : "",
|
|
1627
|
+
hint: wantsEnv ? "Clio reads it every time it calls the provider, so the key never lands on disk" : `stored at ${shortenPath(authStoragePath())}`,
|
|
1628
|
+
...wantsEnv ? {} : { mask: true },
|
|
1629
|
+
railPrefix: wizard.rail,
|
|
1630
|
+
backLabel: "back",
|
|
1631
|
+
clearOnExit: true,
|
|
1632
|
+
input: wizard.input,
|
|
1633
|
+
output: wizard.output
|
|
1634
|
+
});
|
|
1635
|
+
if (result.kind === "quit") return "quit";
|
|
1636
|
+
if (result.kind === "back") return "back";
|
|
1637
|
+
if (wantsEnv) {
|
|
1638
|
+
answers.apiKeyEnv = result.value.length > 0 ? result.value : void 0;
|
|
1639
|
+
answers.inventoryKey = void 0;
|
|
1640
|
+
wizard.answer("Credential", result.value.length > 0 ? `$${result.value}` : "none");
|
|
1641
|
+
return "next";
|
|
1642
|
+
}
|
|
1643
|
+
answers.apiKeyLiteral = result.value.length > 0 ? result.value : void 0;
|
|
1644
|
+
answers.inventoryKey = void 0;
|
|
1645
|
+
wizard.answer("Credential", result.value.length > 0 ? "stored in credentials.yaml" : "none");
|
|
1646
|
+
return "next";
|
|
1647
|
+
}
|
|
1648
|
+
};
|
|
1649
|
+
async function modelInventory(answers, runtime) {
|
|
1650
|
+
const key = [
|
|
1651
|
+
runtime.id,
|
|
1652
|
+
answers.targetId,
|
|
1653
|
+
answers.url,
|
|
1654
|
+
answers.apiKeyEnv,
|
|
1655
|
+
answers.apiKeyLiteral ? "literal" : ""
|
|
1656
|
+
].join("|");
|
|
1657
|
+
if (answers.inventoryKey === key && answers.inventory) return answers.inventory;
|
|
1658
|
+
const inventory = await resolveSupportedWireModels(
|
|
1659
|
+
runtime,
|
|
1660
|
+
draftDescriptor(answers, runtime, false),
|
|
1661
|
+
void 0,
|
|
1662
|
+
answers.apiKeyLiteral
|
|
1663
|
+
);
|
|
1664
|
+
answers.inventory = inventory;
|
|
1665
|
+
answers.inventoryKey = key;
|
|
1666
|
+
return inventory;
|
|
1667
|
+
}
|
|
1668
|
+
var MODEL_STEP = {
|
|
1669
|
+
id: "model",
|
|
1670
|
+
applies: () => true,
|
|
1671
|
+
run: async (wizard, answers) => {
|
|
1672
|
+
const runtime = answers.runtime;
|
|
1673
|
+
if (!runtime) return "back";
|
|
1674
|
+
const support = supportFor(runtime);
|
|
1675
|
+
const inventory = await modelInventory(answers, runtime);
|
|
1676
|
+
const catalogOrdered = support.modelSource === "catalog";
|
|
1677
|
+
const preferred = answers.model ?? (inventory.source === "probe" ? inventory.models[0] : support.defaultModel) ?? (catalogOrdered ? void 0 : inventory.models[0]) ?? void 0;
|
|
1678
|
+
if (inventory.models.length === 0) {
|
|
1679
|
+
wizard.presenter.warn(inventoryGap(runtime, { url: answers.url }));
|
|
1680
|
+
const result2 = await promptText({
|
|
1681
|
+
heading: ["", source_default.bold("Which model?")],
|
|
1682
|
+
initial: preferred ?? "",
|
|
1683
|
+
hint: "the wire id the provider documents; Clio sends it as written",
|
|
1684
|
+
railPrefix: wizard.rail,
|
|
1685
|
+
backLabel: "back",
|
|
1686
|
+
clearOnExit: true,
|
|
1687
|
+
validate: (value) => value.length === 0 ? "a model id is required" : null,
|
|
1688
|
+
input: wizard.input,
|
|
1689
|
+
output: wizard.output
|
|
1690
|
+
});
|
|
1691
|
+
if (result2.kind === "quit") return "quit";
|
|
1692
|
+
if (result2.kind === "back") return "back";
|
|
1693
|
+
answers.model = result2.value;
|
|
1694
|
+
wizard.answer("Model", result2.value);
|
|
1695
|
+
await readModelCapabilities(answers, runtime);
|
|
1696
|
+
return "next";
|
|
1697
|
+
}
|
|
1698
|
+
const selected = inventory.models.indexOf(preferred ?? "");
|
|
1699
|
+
const result = await promptSelect({
|
|
1700
|
+
heading: [
|
|
1701
|
+
"",
|
|
1702
|
+
source_default.bold("Which model?"),
|
|
1703
|
+
source_default.dim(
|
|
1704
|
+
inventory.source === "probe" ? "read live from the target just now" : inventory.source === "cache" ? "cached model snapshot; not verified in this run" : "model hints; not a live account catalog"
|
|
1705
|
+
)
|
|
1706
|
+
],
|
|
1707
|
+
choices: inventory.models.map((model) => {
|
|
1708
|
+
const state = inventory.modelStates?.[model]?.state;
|
|
1709
|
+
const hint = model === preferred ? "default" : state !== void 0 && state !== "unknown" ? state : void 0;
|
|
1710
|
+
const label = inventory.labels?.[model];
|
|
1711
|
+
return {
|
|
1712
|
+
value: model,
|
|
1713
|
+
label: label && label !== model ? `${model} \u2014 ${label}` : model,
|
|
1714
|
+
...hint === void 0 ? {} : { hint }
|
|
1715
|
+
};
|
|
1716
|
+
}),
|
|
1717
|
+
initialIndex: selected >= 0 ? selected : 0,
|
|
1718
|
+
railPrefix: wizard.rail,
|
|
1719
|
+
backLabel: "back",
|
|
1720
|
+
clearOnExit: true,
|
|
1721
|
+
input: wizard.input,
|
|
1722
|
+
output: wizard.output
|
|
1723
|
+
});
|
|
1724
|
+
if (result.kind === "quit") return "quit";
|
|
1725
|
+
if (result.kind === "back") return "back";
|
|
1726
|
+
const refusal = modelChoiceRefusal(
|
|
1727
|
+
runtime,
|
|
1728
|
+
{ id: answers.targetId ?? runtime.id, url: answers.url },
|
|
1729
|
+
result.value,
|
|
1730
|
+
inventory
|
|
1731
|
+
);
|
|
1732
|
+
if (refusal !== null) {
|
|
1733
|
+
wizard.presenter.warn(refusal);
|
|
1734
|
+
return "back";
|
|
1735
|
+
}
|
|
1736
|
+
answers.model = result.value;
|
|
1737
|
+
wizard.answer("Model", result.value);
|
|
1738
|
+
await readModelCapabilities(answers, runtime);
|
|
1739
|
+
return "next";
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
async function readModelCapabilities(answers, runtime) {
|
|
1743
|
+
const descriptor = draftDescriptor(answers, runtime, true);
|
|
1744
|
+
const probe = await runtimeProbe(runtime, descriptor);
|
|
1745
|
+
answers.probe = probe;
|
|
1746
|
+
if (probe?.ok && probe.models) {
|
|
1747
|
+
recordTargetModelSnapshot(descriptor, probe.models, probe.modelLabels ? { modelLabels: probe.modelLabels } : {});
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
var THINKING_HINTS = {
|
|
1751
|
+
off: "never ask this model to think before answering",
|
|
1752
|
+
minimal: "a sentence of reasoning",
|
|
1753
|
+
low: "short reasoning, the usual choice",
|
|
1754
|
+
medium: "more reasoning on hard turns",
|
|
1755
|
+
high: "long reasoning, slower and more expensive",
|
|
1756
|
+
xhigh: "longer still, where the model offers it",
|
|
1757
|
+
max: "everything the model will spend"
|
|
1758
|
+
};
|
|
1759
|
+
var THINKING_STEP = {
|
|
1760
|
+
id: "thinking",
|
|
1761
|
+
applies: (answers) => {
|
|
1762
|
+
const runtime = answers.runtime;
|
|
1763
|
+
if (!runtime || answers.model === void 0) return false;
|
|
1764
|
+
if (PROTOCOL_COMPAT_RUNTIME_IDS.has(runtime.id)) return true;
|
|
1765
|
+
return modelSupportsThinking(runtime, draftDescriptor(answers, runtime, true), answers.probe ?? null);
|
|
1766
|
+
},
|
|
1767
|
+
run: async (wizard, answers) => {
|
|
1768
|
+
const current = THINKING_LEVELS.indexOf(answers.thinking ?? "low");
|
|
1769
|
+
const result = await promptSelect({
|
|
1770
|
+
heading: ["", source_default.bold("How hard should it think?"), source_default.dim("changeable any time in `clio-coder configure`")],
|
|
1771
|
+
choices: THINKING_LEVELS.map((level) => ({ value: level, label: level, hint: THINKING_HINTS[level] })),
|
|
1772
|
+
initialIndex: current >= 0 ? current : 0,
|
|
1773
|
+
railPrefix: wizard.rail,
|
|
1774
|
+
backLabel: "back",
|
|
1775
|
+
clearOnExit: true,
|
|
1776
|
+
input: wizard.input,
|
|
1777
|
+
output: wizard.output
|
|
1778
|
+
});
|
|
1779
|
+
if (result.kind === "quit") return "quit";
|
|
1780
|
+
if (result.kind === "back") return "back";
|
|
1781
|
+
answers.thinking = result.value;
|
|
1782
|
+
wizard.answer("Thinking", result.value);
|
|
1783
|
+
return "next";
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
var ANTIGRAVITY_COLLEAGUE_STEP = {
|
|
1787
|
+
id: "antigravity-colleague",
|
|
1788
|
+
applies: (answers) => answers.runtime !== void 0 && isOrchestratorEligibleRuntime(answers.runtime) && resolveOnPath(["agy"]).presence === "present" && !readSettings().targets.some((target) => target.runtime === "antigravity-code"),
|
|
1789
|
+
run: async (wizard, answers) => {
|
|
1790
|
+
const runtime = getRuntimeRegistry().get("antigravity-code");
|
|
1791
|
+
if (!runtime) return "next";
|
|
1792
|
+
const choice = await promptSelect({
|
|
1793
|
+
heading: [
|
|
1794
|
+
"",
|
|
1795
|
+
source_default.bold("Add your local Antigravity research colleague?"),
|
|
1796
|
+
source_default.dim("optional, experimental, dispatch-only; Clio does not authenticate or inspect its session")
|
|
1797
|
+
],
|
|
1798
|
+
choices: [
|
|
1799
|
+
{ value: false, label: "Not now", hint: "your primary Clio target is unchanged" },
|
|
1800
|
+
{ value: true, label: "Add read-only colleague", hint: "probe the non-generating model catalog" }
|
|
1801
|
+
],
|
|
1802
|
+
initialIndex: answers.antigravity ? 1 : 0,
|
|
1803
|
+
railPrefix: wizard.rail,
|
|
1804
|
+
backLabel: "back",
|
|
1805
|
+
clearOnExit: true,
|
|
1806
|
+
input: wizard.input,
|
|
1807
|
+
output: wizard.output
|
|
1808
|
+
});
|
|
1809
|
+
if (choice.kind === "quit") return "quit";
|
|
1810
|
+
if (choice.kind === "back") return "back";
|
|
1811
|
+
if (!choice.value) {
|
|
1812
|
+
answers.antigravity = void 0;
|
|
1813
|
+
wizard.answer("Colleague", "not added");
|
|
1814
|
+
return "next";
|
|
1815
|
+
}
|
|
1816
|
+
const targetId = deriveTargetId(runtime.id, readSettings().targets);
|
|
1817
|
+
const descriptor = buildDescriptor(runtime, targetId, {});
|
|
1818
|
+
const inventory = await resolveSupportedWireModels(runtime, descriptor);
|
|
1819
|
+
if (inventory.source !== "probe" || inventory.models.length === 0) {
|
|
1820
|
+
answers.antigravity = void 0;
|
|
1821
|
+
wizard.presenter.warn(
|
|
1822
|
+
inventory.probeError ?? "Antigravity did not return a live catalog. Run `agy` yourself, complete sign-in, then add it from Configure \u2192 Targets."
|
|
1823
|
+
);
|
|
1824
|
+
wizard.answer("Colleague", "not added; sign in with agy first");
|
|
1825
|
+
return "next";
|
|
1826
|
+
}
|
|
1827
|
+
const picked = await promptSelect({
|
|
1828
|
+
heading: ["", source_default.bold("Antigravity research model"), source_default.dim("live account catalog")],
|
|
1829
|
+
choices: inventory.models.map((model) => ({
|
|
1830
|
+
value: model,
|
|
1831
|
+
label: inventory.labels?.[model] ? `${model} \u2014 ${inventory.labels[model]}` : model
|
|
1832
|
+
})),
|
|
1833
|
+
initialIndex: 0,
|
|
1834
|
+
railPrefix: wizard.rail,
|
|
1835
|
+
backLabel: "back",
|
|
1836
|
+
clearOnExit: true,
|
|
1837
|
+
input: wizard.input,
|
|
1838
|
+
output: wizard.output
|
|
1839
|
+
});
|
|
1840
|
+
if (picked.kind === "quit") return "quit";
|
|
1841
|
+
if (picked.kind === "back") return "back";
|
|
1842
|
+
answers.antigravity = { targetId, model: picked.value };
|
|
1843
|
+
wizard.answer("Colleague", `${targetId}/${picked.value} \u2192 world-knowledge`);
|
|
1844
|
+
return "next";
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
var PEERS_STEP = {
|
|
1848
|
+
id: "peers",
|
|
1849
|
+
applies: () => true,
|
|
1850
|
+
run: async (wizard, answers) => {
|
|
1851
|
+
const outcome = await reviewInteropAgents({
|
|
1852
|
+
rl: null,
|
|
1853
|
+
streams: wizard.streams,
|
|
1854
|
+
presenter: wizard.presenter,
|
|
1855
|
+
rail: wizard.rail,
|
|
1856
|
+
quiet: true
|
|
1857
|
+
});
|
|
1858
|
+
if (outcome.back) return "back";
|
|
1859
|
+
answers.wiredPeers = outcome.wired;
|
|
1860
|
+
return "next";
|
|
1861
|
+
}
|
|
1862
|
+
};
|
|
1863
|
+
var STEPS = [
|
|
1864
|
+
CATEGORY_STEP,
|
|
1865
|
+
RUNTIME_STEP,
|
|
1866
|
+
TARGET_ID_STEP,
|
|
1867
|
+
CREDENTIAL_STEP,
|
|
1868
|
+
CREDENTIAL_VALUE_STEP,
|
|
1869
|
+
URL_STEP,
|
|
1870
|
+
DETECTED_RUNTIME_STEP,
|
|
1871
|
+
MODEL_STEP,
|
|
1872
|
+
THINKING_STEP,
|
|
1873
|
+
ANTIGRAVITY_COLLEAGUE_STEP,
|
|
1874
|
+
PEERS_STEP
|
|
1875
|
+
];
|
|
1876
|
+
function persist(answers, descriptor, chatEligible) {
|
|
1877
|
+
updateSettings((settings) => {
|
|
1878
|
+
applyTarget(settings, descriptor);
|
|
1879
|
+
if (chatEligible) setOrchestratorPointer(settings, descriptor, answers.model ?? null);
|
|
1880
|
+
setWorkerDefaultPointer(settings, descriptor, answers.model ?? null);
|
|
1881
|
+
if (answers.thinking !== void 0) {
|
|
1882
|
+
settings.chat.thinkingLevel = answers.thinking;
|
|
1883
|
+
settings.fleet.default.thinkingLevel = answers.thinking;
|
|
1884
|
+
}
|
|
1885
|
+
if (answers.antigravity !== void 0) {
|
|
1886
|
+
const antigravityRuntime = getRuntimeRegistry().get("antigravity-code");
|
|
1887
|
+
if (!antigravityRuntime) throw new Error("Antigravity runtime disappeared before settings were written");
|
|
1888
|
+
const external = buildDescriptor(antigravityRuntime, answers.antigravity.targetId, {
|
|
1889
|
+
model: answers.antigravity.model
|
|
1890
|
+
});
|
|
1891
|
+
applyTarget(settings, external);
|
|
1892
|
+
setWorkerProfilePointer(settings, "world-knowledge-external", external, answers.antigravity.model);
|
|
1893
|
+
bindAgentProfileInSettings(settings, "world-knowledge", "world-knowledge-external");
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
}
|
|
1897
|
+
async function runOnboardingWizard(streams) {
|
|
1898
|
+
const writer = railWriter(streams.out);
|
|
1899
|
+
const presenter = createLifecyclePresenter({ stream: writer.stream });
|
|
1900
|
+
const rail = railPrefix(presenter.isPlain());
|
|
1901
|
+
const columns = streams.out.columns ?? 80;
|
|
1902
|
+
const wizard = {
|
|
1903
|
+
streams,
|
|
1904
|
+
presenter,
|
|
1905
|
+
rail,
|
|
1906
|
+
input: streams.in,
|
|
1907
|
+
output: streams.out,
|
|
1908
|
+
answer: (label, value) => {
|
|
1909
|
+
presenter.fields([[label.padEnd(LABEL_WIDTH), truncate(value, Math.max(12, columns - LABEL_WIDTH - 6))]]);
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
presenter.header("Welcome to Clio Coder", "configure");
|
|
1913
|
+
presenter.note("Pick the model you want Clio to talk to; escape goes back a step and changes nothing.");
|
|
1914
|
+
presenter.note(`Result: ${shortenPath(settingsPath())}`);
|
|
1915
|
+
const answers = {};
|
|
1916
|
+
const marks = new Array(STEPS.length).fill(writer.mark());
|
|
1917
|
+
let cursor = 0;
|
|
1918
|
+
let direction = 1;
|
|
1919
|
+
while (cursor < STEPS.length) {
|
|
1920
|
+
const step = STEPS[cursor];
|
|
1921
|
+
if (step === void 0) break;
|
|
1922
|
+
if (!step.applies(answers)) {
|
|
1923
|
+
cursor += direction;
|
|
1924
|
+
if (cursor < 0) return cancel(presenter);
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
marks[cursor] = writer.mark();
|
|
1928
|
+
const outcome = await step.run(wizard, answers);
|
|
1929
|
+
if (outcome === "quit") return cancel(presenter);
|
|
1930
|
+
if (outcome === "back") {
|
|
1931
|
+
direction = -1;
|
|
1932
|
+
cursor -= 1;
|
|
1933
|
+
while (cursor >= 0 && !(STEPS[cursor]?.applies(answers) ?? false)) cursor -= 1;
|
|
1934
|
+
if (cursor < 0) return cancel(presenter);
|
|
1935
|
+
writer.rewindTo(marks[cursor] ?? writer.mark());
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
direction = 1;
|
|
1939
|
+
cursor += 1;
|
|
1940
|
+
}
|
|
1941
|
+
return finish(wizard, answers);
|
|
1942
|
+
}
|
|
1943
|
+
function cancel(presenter) {
|
|
1944
|
+
presenter.done("Cancelled, nothing written");
|
|
1945
|
+
printError("configuration cancelled");
|
|
1946
|
+
return 130;
|
|
1947
|
+
}
|
|
1948
|
+
function finish(wizard, answers) {
|
|
1949
|
+
const runtime = answers.runtime;
|
|
1950
|
+
const targetId = answers.targetId;
|
|
1951
|
+
if (!runtime || targetId === void 0) return cancel(wizard.presenter);
|
|
1952
|
+
const presenter = wizard.presenter;
|
|
1953
|
+
if (answers.apiKeyLiteral !== void 0) {
|
|
1954
|
+
const auth = openAuthStorage();
|
|
1955
|
+
auth.setApiKey(runtime.id, answers.apiKeyLiteral);
|
|
1956
|
+
if (credentialWriteFailed(auth, `credential for ${runtime.id} was not stored; target '${targetId}' not saved`)) {
|
|
1957
|
+
presenter.done("Nothing written");
|
|
1958
|
+
return 1;
|
|
1959
|
+
}
|
|
1960
|
+
printPlaintextCredentialWarning();
|
|
1961
|
+
}
|
|
1962
|
+
const reasoning = PROTOCOL_COMPAT_RUNTIME_IDS.has(runtime.id) && answers.thinking !== void 0 ? answers.thinking !== "off" : void 0;
|
|
1963
|
+
const descriptor = buildDescriptor(runtime, targetId, {
|
|
1964
|
+
...answers.url !== void 0 ? { url: answers.url } : {},
|
|
1965
|
+
...answers.model !== void 0 ? { model: answers.model } : {},
|
|
1966
|
+
...answers.apiKeyEnv !== void 0 ? { apiKeyEnv: answers.apiKeyEnv } : {},
|
|
1967
|
+
...answers.credential === "stored" || answers.credential === "keep" ? { apiKeyRef: runtime.id } : {},
|
|
1968
|
+
...runtime.auth === "oauth" ? { oauthProfile: runtime.oauthProviderId ?? runtime.id } : {},
|
|
1969
|
+
...reasoning === void 0 ? {} : { reasoning }
|
|
1970
|
+
});
|
|
1971
|
+
const chatEligible = isOrchestratorEligibleRuntime(runtime);
|
|
1972
|
+
try {
|
|
1973
|
+
persist(answers, descriptor, chatEligible);
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
presenter.fail("settings were not written", error instanceof Error ? error.message : String(error));
|
|
1976
|
+
presenter.done("Nothing written");
|
|
1977
|
+
return 1;
|
|
1978
|
+
}
|
|
1979
|
+
presenter.completedStep(`target ${targetId} saved, runtime ${runtime.id}`);
|
|
1980
|
+
if (chatEligible)
|
|
1981
|
+
presenter.completedStep(`chat runs on ${targetId}${answers.model ? `, model ${answers.model}` : ""}`);
|
|
1982
|
+
else presenter.completedStep(`${runtime.id} cannot answer chat; ${targetId} is registered for dispatch only`);
|
|
1983
|
+
presenter.completedStep(`fleet default is ${targetId}`);
|
|
1984
|
+
if (answers.thinking !== void 0) presenter.completedStep(`thinking level ${answers.thinking}`);
|
|
1985
|
+
for (const peer of answers.wiredPeers ?? []) {
|
|
1986
|
+
presenter.completedStep(`delegation agent ${peer} added; use \`/delegate ${peer} <task>\``);
|
|
1987
|
+
}
|
|
1988
|
+
if (answers.antigravity !== void 0) {
|
|
1989
|
+
presenter.completedStep(
|
|
1990
|
+
`world-knowledge bound read-only to ${answers.antigravity.targetId}/${answers.antigravity.model}`
|
|
1991
|
+
);
|
|
1992
|
+
}
|
|
1993
|
+
presenter.completedStep(`settings written to ${shortenPath(settingsPath())}`);
|
|
1994
|
+
if (contextWindowUndiscovered(descriptor, answers.probe ?? null)) {
|
|
1995
|
+
presenter.warn(
|
|
1996
|
+
`${targetId} reported no context window, so Clio will use the runtime default as a guess. Set the real one with \`clio-coder configure --id ${targetId} --runtime ${runtime.id} --context-window <N>\`.`
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
presenter.commandAdvice("Start Clio:", "clio-coder");
|
|
2000
|
+
presenter.commandAdvice("Change any of this later:", "clio-coder configure");
|
|
2001
|
+
presenter.done("Done");
|
|
2002
|
+
return 0;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
// src/cli/configure.ts
|
|
2006
|
+
var HELP = `clio-coder configure
|
|
2007
|
+
|
|
2008
|
+
Configure model targets and runtime settings for chat and fleet dispatch.
|
|
2009
|
+
|
|
2010
|
+
Usage:
|
|
2011
|
+
clio-coder configure interactive configuration wizard
|
|
2012
|
+
clio-coder configure --section <name> open one section directly:
|
|
2013
|
+
targets, models, chat, fleet,
|
|
2014
|
+
permissions, panes, skills, diagnostics
|
|
2015
|
+
Without a terminal this prints the
|
|
2016
|
+
section's values and exits.
|
|
2017
|
+
clio-coder configure --json emit the effective settings as JSON
|
|
2018
|
+
clio-coder configure --interop review detected coding agents as delegation peers
|
|
2019
|
+
clio-coder configure --list list target runtimes (user-facing only)
|
|
2020
|
+
clio-coder configure --list --all list every registered runtime including aliases
|
|
2021
|
+
clio-coder configure --id <targetId> [flags] --runtime <runtimeId>
|
|
2022
|
+
clio-coder configure --help, -h show this message
|
|
2023
|
+
|
|
2024
|
+
In the interactive menus: arrow keys move, enter opens, escape goes back, q
|
|
2025
|
+
quits. Where the terminal cannot support that, the same menus are numbered and
|
|
2026
|
+
answered by typing a number, b for back, or q to quit.
|
|
2027
|
+
|
|
2028
|
+
Non-interactive flags:
|
|
2029
|
+
--id <targetId> target id to register (required when non-interactive)
|
|
2030
|
+
--runtime <runtimeId> runtime to use when registering non-interactively
|
|
2031
|
+
--url <host> target base URL (http(s):// or ws://)
|
|
2032
|
+
--model <wireModelId> default model id for this target
|
|
2033
|
+
--orchestrator-model <id> model to use when setting chat default
|
|
2034
|
+
--background-model <id> model to use for proactive task memory
|
|
2035
|
+
--fleet-model <id> model to use when setting fleet default
|
|
2036
|
+
(mutually exclusive with --agent-profile)
|
|
2037
|
+
--agent-profile <name> save this target as a named fleet profile
|
|
2038
|
+
--agent-profile-model <id> model to use for --agent-profile
|
|
2039
|
+
--bind-agent <agentId> bind this --agent-profile to an agent
|
|
2040
|
+
--api-key-env <VAR> read API key from this env var at call time
|
|
2041
|
+
--api-key <literal> store API key in credentials.yaml
|
|
2042
|
+
--force save a model outside the runtime catalog, or one the
|
|
2043
|
+
target does not advertise, without refusing
|
|
2044
|
+
--gateway mark the target as a gateway
|
|
2045
|
+
--lifecycle <user-managed|clio-coder-managed>
|
|
2046
|
+
resident model lifecycle policy
|
|
2047
|
+
--set-orchestrator use this target for chat
|
|
2048
|
+
--set-background use this target for proactive task memory
|
|
2049
|
+
--set-fleet-default use this target for the fleet default
|
|
2050
|
+
--context-window <N> capability override
|
|
2051
|
+
--max-tokens <N> output token capability override
|
|
2052
|
+
--reasoning <true|false> capability override
|
|
2053
|
+
`;
|
|
2054
|
+
function parseSetupArgs(argv) {
|
|
2055
|
+
const out = {
|
|
2056
|
+
positional: [],
|
|
2057
|
+
help: false,
|
|
2058
|
+
list: false,
|
|
2059
|
+
all: false,
|
|
2060
|
+
interop: false,
|
|
2061
|
+
json: false,
|
|
2062
|
+
force: false,
|
|
2063
|
+
gateway: false,
|
|
2064
|
+
setOrchestrator: false,
|
|
2065
|
+
setBackground: false,
|
|
2066
|
+
setWorkerDefault: false
|
|
2067
|
+
};
|
|
2068
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2069
|
+
const a = argv[i];
|
|
2070
|
+
const need = () => {
|
|
2071
|
+
const v = argv[i + 1];
|
|
2072
|
+
if (v === void 0) throw new Error(`${a} requires a value`);
|
|
2073
|
+
i += 1;
|
|
2074
|
+
return v;
|
|
2075
|
+
};
|
|
2076
|
+
switch (a) {
|
|
2077
|
+
case "--help":
|
|
2078
|
+
case "-h":
|
|
2079
|
+
out.help = true;
|
|
2080
|
+
break;
|
|
2081
|
+
case "--json":
|
|
2082
|
+
out.json = true;
|
|
2083
|
+
break;
|
|
2084
|
+
case "--section":
|
|
2085
|
+
out.section = need();
|
|
2086
|
+
break;
|
|
2087
|
+
case "--list":
|
|
2088
|
+
out.list = true;
|
|
2089
|
+
break;
|
|
2090
|
+
case "--all":
|
|
2091
|
+
out.all = true;
|
|
2092
|
+
break;
|
|
2093
|
+
case "--interop":
|
|
2094
|
+
out.interop = true;
|
|
2095
|
+
break;
|
|
2096
|
+
case "--remove":
|
|
2097
|
+
out.remove = need();
|
|
2098
|
+
break;
|
|
2099
|
+
case "--rename":
|
|
2100
|
+
out.renameOld = need();
|
|
2101
|
+
out.renameNew = need();
|
|
2102
|
+
break;
|
|
2103
|
+
case "--id":
|
|
2104
|
+
out.id = need();
|
|
2105
|
+
break;
|
|
2106
|
+
case "--runtime":
|
|
2107
|
+
out.runtime = need();
|
|
2108
|
+
break;
|
|
2109
|
+
case "--url":
|
|
2110
|
+
out.url = need();
|
|
2111
|
+
break;
|
|
2112
|
+
case "--model":
|
|
2113
|
+
out.model = need();
|
|
2114
|
+
break;
|
|
2115
|
+
case "--orchestrator-model":
|
|
2116
|
+
out.orchestratorModel = need();
|
|
2117
|
+
break;
|
|
2118
|
+
case "--background-model":
|
|
2119
|
+
out.backgroundModel = need();
|
|
2120
|
+
break;
|
|
2121
|
+
case "--fleet-model":
|
|
2122
|
+
out.workerModel = need();
|
|
2123
|
+
break;
|
|
2124
|
+
case "--agent-profile":
|
|
2125
|
+
out.workerProfile = need();
|
|
2126
|
+
break;
|
|
2127
|
+
case "--agent-profile-model":
|
|
2128
|
+
out.workerProfileModel = need();
|
|
2129
|
+
break;
|
|
2130
|
+
case "--bind-agent":
|
|
2131
|
+
out.bindAgent = need();
|
|
2132
|
+
break;
|
|
2133
|
+
case "--api-key-env":
|
|
2134
|
+
out.apiKeyEnv = need();
|
|
2135
|
+
break;
|
|
2136
|
+
case "--api-key":
|
|
2137
|
+
out.apiKey = need();
|
|
2138
|
+
break;
|
|
2139
|
+
case "--force":
|
|
2140
|
+
out.force = true;
|
|
2141
|
+
break;
|
|
2142
|
+
case "--gateway":
|
|
2143
|
+
out.gateway = true;
|
|
2144
|
+
break;
|
|
2145
|
+
case "--lifecycle": {
|
|
2146
|
+
const v = need();
|
|
2147
|
+
if (v !== "user-managed" && v !== "clio-coder-managed") {
|
|
2148
|
+
throw new Error("--lifecycle must be user-managed or clio-coder-managed");
|
|
2149
|
+
}
|
|
2150
|
+
out.lifecycle = v;
|
|
2151
|
+
break;
|
|
2152
|
+
}
|
|
2153
|
+
case "--set-orchestrator":
|
|
2154
|
+
out.setOrchestrator = true;
|
|
2155
|
+
break;
|
|
2156
|
+
case "--set-background":
|
|
2157
|
+
out.setBackground = true;
|
|
2158
|
+
break;
|
|
2159
|
+
case "--set-fleet-default":
|
|
2160
|
+
out.setWorkerDefault = true;
|
|
2161
|
+
break;
|
|
2162
|
+
case "--context-window": {
|
|
2163
|
+
const n = Number(need());
|
|
2164
|
+
if (!Number.isFinite(n) || n <= 0) throw new Error("--context-window must be a positive number");
|
|
2165
|
+
out.contextWindow = Math.floor(n);
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2168
|
+
case "--max-tokens": {
|
|
2169
|
+
const n = Number(need());
|
|
2170
|
+
if (!Number.isFinite(n) || n <= 0) throw new Error("--max-tokens must be a positive number");
|
|
2171
|
+
out.maxTokens = Math.floor(n);
|
|
2172
|
+
break;
|
|
2173
|
+
}
|
|
2174
|
+
case "--reasoning": {
|
|
2175
|
+
const v = need().toLowerCase();
|
|
2176
|
+
if (v !== "true" && v !== "false") throw new Error("--reasoning must be true or false");
|
|
2177
|
+
out.reasoning = v === "true";
|
|
2178
|
+
break;
|
|
2179
|
+
}
|
|
2180
|
+
default:
|
|
2181
|
+
if (a.startsWith("-")) throw new Error(`unknown flag: ${a}`);
|
|
2182
|
+
out.positional.push(a);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
return out;
|
|
2186
|
+
}
|
|
2187
|
+
function ensureRegistryPopulated() {
|
|
2188
|
+
registerClioOAuthProviders();
|
|
2189
|
+
const registry = getRuntimeRegistry();
|
|
2190
|
+
if (registry.list().length === 0) registerBuiltinRuntimes(registry);
|
|
2191
|
+
}
|
|
2192
|
+
var RUNTIME_LIST_CAPTION = "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.";
|
|
2193
|
+
function printRuntimeList(includeHidden) {
|
|
2194
|
+
const settings = readSettings();
|
|
2195
|
+
const auth = openAuthStorage();
|
|
2196
|
+
const rows = [];
|
|
2197
|
+
for (const entry of listProviderSupportEntries(getRuntimeRegistry().list(), { includeHidden })) {
|
|
2198
|
+
const runtime = getRuntimeRegistry().get(entry.runtimeId);
|
|
2199
|
+
const status = runtime && entry.connectable ? auth.statusForTarget(resolveRuntimeAuthTarget(runtime), { includeFallback: false }) : null;
|
|
2200
|
+
const authLabel = runtime?.auth === "oauth" ? status?.available ? "connected" : "login" : runtime?.auth === "api-key" ? status?.available ? "credential" : targetRequiresAuth({ id: entry.runtimeId, runtime: entry.runtimeId }, runtime) ? "needs-key" : "key-optional" : runtime?.auth ?? "none";
|
|
2201
|
+
rows.push({
|
|
2202
|
+
group: supportGroupLabel(entry.group),
|
|
2203
|
+
runtimeId: entry.runtimeId,
|
|
2204
|
+
label: entry.label,
|
|
2205
|
+
auth: authLabel,
|
|
2206
|
+
targets: configuredTargetsForRuntime(settings, entry.runtimeId).length,
|
|
2207
|
+
models: describeRuntimeModels(entry, 2)
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
const width = terminalColumns();
|
|
2211
|
+
for (const line of formatRuntimeList(rows, width)) {
|
|
2212
|
+
process.stdout.write(`${line}
|
|
2213
|
+
`);
|
|
2214
|
+
}
|
|
2215
|
+
for (const line of wrapPlain(RUNTIME_LIST_CAPTION, width)) {
|
|
2216
|
+
process.stdout.write(`${line}
|
|
2217
|
+
`);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
function printSummary(settings, descriptor, probe) {
|
|
2221
|
+
process.stdout.write(`
|
|
2222
|
+
saved target ${descriptor.id} (runtime=${descriptor.runtime})
|
|
2223
|
+
`);
|
|
2224
|
+
if (descriptor.url) process.stdout.write(` url ${descriptor.url}
|
|
2225
|
+
`);
|
|
2226
|
+
if (descriptor.defaultModel) process.stdout.write(` model ${descriptor.defaultModel}
|
|
2227
|
+
`);
|
|
2228
|
+
if (descriptor.auth?.apiKeyEnvVar) process.stdout.write(` apiKeyEnv ${descriptor.auth.apiKeyEnvVar}
|
|
2229
|
+
`);
|
|
2230
|
+
if (descriptor.gateway) process.stdout.write(" gateway true\n");
|
|
2231
|
+
if (descriptor.lifecycle) process.stdout.write(` lifecycle ${descriptor.lifecycle}
|
|
2232
|
+
`);
|
|
2233
|
+
if (probe) {
|
|
2234
|
+
for (const line of probeLines(descriptor, probe)) process.stdout.write(` ${line}
|
|
2235
|
+
`);
|
|
2236
|
+
}
|
|
2237
|
+
warnUndiscoveredContextWindow(descriptor, probe);
|
|
2238
|
+
if (settings.chat.target === descriptor.id) process.stdout.write(" orchestrator target\n");
|
|
2239
|
+
if (settings.context.memory.target === descriptor.id) process.stdout.write(" background memory target\n");
|
|
2240
|
+
if (settings.fleet.default.target === descriptor.id) process.stdout.write(" fleet default\n");
|
|
2241
|
+
for (const [name, profile] of Object.entries(settings.fleet.profiles)) {
|
|
2242
|
+
if (profile.target === descriptor.id) process.stdout.write(` fleet profile ${name}
|
|
2243
|
+
`);
|
|
2244
|
+
}
|
|
2245
|
+
process.stdout.write(`
|
|
2246
|
+
settings written to ${settingsPath()}
|
|
2247
|
+
`);
|
|
2248
|
+
}
|
|
2249
|
+
function resolveModelChoice(answer, wireModels, defaultValue) {
|
|
2250
|
+
if (answer.length === 0) return defaultValue;
|
|
2251
|
+
const numeric = Number(answer);
|
|
2252
|
+
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= wireModels.length) {
|
|
2253
|
+
return wireModels[numeric - 1];
|
|
2254
|
+
}
|
|
2255
|
+
return answer;
|
|
2256
|
+
}
|
|
2257
|
+
async function askModelChoice(rl, label, wireModels, defaultValue) {
|
|
2258
|
+
const answer = await ask(rl, `${label} (number or id)`, defaultValue ?? "");
|
|
2259
|
+
if (answer === null) return null;
|
|
2260
|
+
return resolveModelChoice(answer, wireModels, defaultValue) ?? "";
|
|
2261
|
+
}
|
|
2262
|
+
function refuseCatalogSeededModel(runtime, support) {
|
|
2263
|
+
printError(
|
|
2264
|
+
`--model is required for ${runtime.id}: its ${support.modelHints.length} model ids come from the provider catalog in name order, which recommends none of them`
|
|
2265
|
+
);
|
|
2266
|
+
process.stderr.write(
|
|
2267
|
+
` run \`clio-coder configure --runtime ${runtime.id}\` with no other flags to choose from the full list
|
|
2268
|
+
`
|
|
2269
|
+
);
|
|
2270
|
+
}
|
|
2271
|
+
async function runNonInteractive(runtime, args) {
|
|
2272
|
+
if (!args.id) {
|
|
2273
|
+
printError("--id is required when passing flags non-interactively");
|
|
2274
|
+
return 2;
|
|
2275
|
+
}
|
|
2276
|
+
if (args.workerProfileModel !== void 0 && args.workerProfile === void 0) {
|
|
2277
|
+
printError("--agent-profile-model requires --agent-profile");
|
|
2278
|
+
return 2;
|
|
2279
|
+
}
|
|
2280
|
+
if (args.bindAgent !== void 0 && args.workerProfile === void 0) {
|
|
2281
|
+
printError("--bind-agent requires --agent-profile");
|
|
2282
|
+
return 2;
|
|
2283
|
+
}
|
|
2284
|
+
if (args.workerProfile !== void 0 && args.workerProfile.trim().length === 0) {
|
|
2285
|
+
printError("--agent-profile must be non-empty");
|
|
2286
|
+
return 2;
|
|
2287
|
+
}
|
|
2288
|
+
if (args.workerProfile !== void 0 && args.workerModel !== void 0) {
|
|
2289
|
+
printError(
|
|
2290
|
+
"--fleet-model and --agent-profile conflict; use --agent-profile-model for the profile, or drop --agent-profile to set the fleet default"
|
|
2291
|
+
);
|
|
2292
|
+
return 2;
|
|
2293
|
+
}
|
|
2294
|
+
const settings = readSettings();
|
|
2295
|
+
const auth = openAuthStorage();
|
|
2296
|
+
const support = buildProviderSupportEntry(runtime);
|
|
2297
|
+
const existing = settings.targets.find((e) => e.id === args.id);
|
|
2298
|
+
if (existing && (getRuntimeRegistry().get(existing.runtime)?.id ?? existing.runtime) !== runtime.id) {
|
|
2299
|
+
printError(`target ${args.id} already exists with runtime ${existing.runtime}`);
|
|
2300
|
+
return 2;
|
|
2301
|
+
}
|
|
2302
|
+
let url = args.url ? normalizeUrl(args.url, runtime.id) : existing?.url;
|
|
2303
|
+
if (!url && support.supportsCustomUrl) {
|
|
2304
|
+
url = defaultUrlFor(runtime.id);
|
|
2305
|
+
}
|
|
2306
|
+
if (url && runtime.id === "lmstudio") {
|
|
2307
|
+
const greeting = await greetLmStudio(
|
|
2308
|
+
{ id: args.id, runtime: runtime.id, url },
|
|
2309
|
+
{ credentialsPresent: /* @__PURE__ */ new Set(), httpTimeoutMs: 750 }
|
|
2310
|
+
);
|
|
2311
|
+
if (!greeting.ok) {
|
|
2312
|
+
printError(`refusing to save LM Studio target '${args.id}': ${url} did not return the LM Studio greeting`);
|
|
2313
|
+
return 2;
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
if (url && (runtime.id === "openai-compat" || runtime.id === "anthropic-compat")) {
|
|
2317
|
+
const fingerprint = await fingerprintNativeRuntime(url);
|
|
2318
|
+
if (fingerprint) {
|
|
2319
|
+
process.stdout.write(
|
|
2320
|
+
`note: detected ${fingerprint.displayName} at ${url}; consider \`clio-coder targets convert ${args.id} --runtime ${fingerprint.runtimeId}\` for proper resident-model lifecycle
|
|
2321
|
+
`
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
const authStatus = auth.statusForTarget(resolveRuntimeAuthTarget(runtime), { includeFallback: false });
|
|
2326
|
+
const apiKeyEnv = args.apiKeyEnv ?? existing?.auth?.apiKeyEnvVar;
|
|
2327
|
+
const apiKeyRef = runtime.auth === "api-key" && (args.apiKey || existing?.auth?.apiKeyRef || authStatus.source === "stored-api-key") ? runtime.id : void 0;
|
|
2328
|
+
const oauthProfile = runtime.auth === "oauth" ? existing?.auth?.oauthProfile ?? runtime.oauthProviderId ?? runtime.id : void 0;
|
|
2329
|
+
const seed = buildDescriptor(runtime, args.id, {
|
|
2330
|
+
...url !== void 0 ? { url } : {},
|
|
2331
|
+
...args.model !== void 0 ? { model: args.model } : existing?.defaultModel ? { model: existing.defaultModel } : support.defaultModel ? { model: support.defaultModel } : {},
|
|
2332
|
+
...apiKeyEnv !== void 0 ? { apiKeyEnv } : {},
|
|
2333
|
+
...apiKeyRef !== void 0 ? { apiKeyRef } : {},
|
|
2334
|
+
...oauthProfile !== void 0 ? { oauthProfile } : {},
|
|
2335
|
+
gateway: args.gateway || existing?.gateway === true,
|
|
2336
|
+
...args.lifecycle !== void 0 ? { lifecycle: args.lifecycle } : existing?.lifecycle !== void 0 ? { lifecycle: existing.lifecycle } : {},
|
|
2337
|
+
...args.contextWindow !== void 0 ? { contextWindow: args.contextWindow } : {},
|
|
2338
|
+
...args.maxTokens !== void 0 ? { maxTokens: args.maxTokens } : {},
|
|
2339
|
+
...args.reasoning !== void 0 ? { reasoning: args.reasoning } : {},
|
|
2340
|
+
...existing?.lmstudio ? { lmstudio: existing.lmstudio } : {},
|
|
2341
|
+
...existing?.litellm ? { litellm: existing.litellm } : {}
|
|
2342
|
+
});
|
|
2343
|
+
const inventory = await resolveSupportedWireModels(runtime, seed, existing, args.apiKey);
|
|
2344
|
+
const wireModels = inventory.models;
|
|
2345
|
+
const model = args.model ?? existing?.defaultModel ?? (inventory.source === "probe" ? wireModels[0] : support.defaultModel) ?? (support.modelSource === "catalog" ? void 0 : wireModels[0]);
|
|
2346
|
+
if (model === void 0 && support.modelSource === "catalog") {
|
|
2347
|
+
refuseCatalogSeededModel(runtime, support);
|
|
2348
|
+
return 2;
|
|
2349
|
+
}
|
|
2350
|
+
if (!validateResolvedModel(runtime, seed, model, args.force, inventory)) return 2;
|
|
2351
|
+
if (!validateContextWindowOverride(runtime, model, args.contextWindow, args.force)) return 2;
|
|
2352
|
+
const descriptor = buildDescriptor(runtime, args.id, {
|
|
2353
|
+
...url !== void 0 ? { url } : {},
|
|
2354
|
+
...model ? { model } : {},
|
|
2355
|
+
...apiKeyEnv !== void 0 ? { apiKeyEnv } : {},
|
|
2356
|
+
...apiKeyRef !== void 0 ? { apiKeyRef } : {},
|
|
2357
|
+
...oauthProfile !== void 0 ? { oauthProfile } : {},
|
|
2358
|
+
gateway: args.gateway || existing?.gateway === true,
|
|
2359
|
+
...args.lifecycle !== void 0 ? { lifecycle: args.lifecycle } : existing?.lifecycle !== void 0 ? { lifecycle: existing.lifecycle } : {},
|
|
2360
|
+
...args.contextWindow !== void 0 ? { contextWindow: args.contextWindow } : {},
|
|
2361
|
+
...args.maxTokens !== void 0 ? { maxTokens: args.maxTokens } : {},
|
|
2362
|
+
...args.reasoning !== void 0 ? { reasoning: args.reasoning } : {},
|
|
2363
|
+
...existing?.lmstudio ? { lmstudio: existing.lmstudio } : {},
|
|
2364
|
+
...existing?.litellm ? { litellm: existing.litellm } : {}
|
|
2365
|
+
});
|
|
2366
|
+
const setOrchestrator = args.setOrchestrator || args.orchestratorModel !== void 0;
|
|
2367
|
+
const setBackground = args.setBackground || args.backgroundModel !== void 0;
|
|
2368
|
+
if ((setOrchestrator || setBackground) && !isOrchestratorEligibleRuntime(runtime)) {
|
|
2369
|
+
printError(
|
|
2370
|
+
`cannot use target '${descriptor.id}' as a chat or background target because runtime '${runtime.id}' is not an HTTP/native runtime`
|
|
2371
|
+
);
|
|
2372
|
+
return 1;
|
|
2373
|
+
}
|
|
2374
|
+
try {
|
|
2375
|
+
assertOrchestratorReplacementEligible(settings, descriptor);
|
|
2376
|
+
} catch (error) {
|
|
2377
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
2378
|
+
return 1;
|
|
2379
|
+
}
|
|
2380
|
+
if (args.apiKey) {
|
|
2381
|
+
auth.setApiKey(runtime.id, args.apiKey);
|
|
2382
|
+
if (credentialWriteFailed(auth, `credential for ${runtime.id} was not stored; target '${descriptor.id}' not saved`))
|
|
2383
|
+
return 1;
|
|
2384
|
+
printPlaintextCredentialWarning();
|
|
2385
|
+
}
|
|
2386
|
+
const setWorkerDefault = args.setWorkerDefault || args.workerProfile === void 0 && args.workerModel !== void 0;
|
|
2387
|
+
const applyConfiguration = (target) => {
|
|
2388
|
+
applyTarget(target, descriptor);
|
|
2389
|
+
if (setOrchestrator)
|
|
2390
|
+
setOrchestratorPointer(target, descriptor, args.orchestratorModel ?? descriptor.defaultModel ?? null);
|
|
2391
|
+
if (setBackground) setBackgroundPointer(target, descriptor, args.backgroundModel ?? descriptor.defaultModel ?? null);
|
|
2392
|
+
if (setWorkerDefault)
|
|
2393
|
+
setWorkerDefaultPointer(target, descriptor, args.workerModel ?? descriptor.defaultModel ?? null);
|
|
2394
|
+
if (args.workerProfile !== void 0) {
|
|
2395
|
+
setWorkerProfilePointer(
|
|
2396
|
+
target,
|
|
2397
|
+
args.workerProfile,
|
|
2398
|
+
descriptor,
|
|
2399
|
+
args.workerProfileModel ?? descriptor.defaultModel ?? null
|
|
2400
|
+
);
|
|
2401
|
+
if (args.bindAgent !== void 0) bindAgentProfileInSettings(target, args.bindAgent, args.workerProfile);
|
|
2402
|
+
}
|
|
2403
|
+
};
|
|
2404
|
+
applyConfiguration(settings);
|
|
2405
|
+
updateSettings(applyConfiguration);
|
|
2406
|
+
const probe = await runtimeProbe(runtime, descriptor);
|
|
2407
|
+
if (probe?.ok && probe.models) {
|
|
2408
|
+
recordTargetModelSnapshot(descriptor, probe.models, probe.modelLabels ? { modelLabels: probe.modelLabels } : {});
|
|
2409
|
+
}
|
|
2410
|
+
printSummary(settings, descriptor, probe);
|
|
2411
|
+
if (runtime.auth === "oauth" && !auth.statusForTarget(resolveRuntimeAuthTarget(runtime), { includeFallback: false }).available) {
|
|
2412
|
+
process.stdout.write(
|
|
2413
|
+
`note: authenticate ${runtime.id} with \`clio-coder auth login ${runtime.id}\` before using this target
|
|
2414
|
+
`
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
printOk(`target ${args.id} saved`);
|
|
2418
|
+
return 0;
|
|
2419
|
+
}
|
|
2420
|
+
async function pickRuntime(rl) {
|
|
2421
|
+
const registry = getRuntimeRegistry();
|
|
2422
|
+
const entries = listProviderSupportEntries(registry.list());
|
|
2423
|
+
return pickRuntimeFromEntries(rl, entries, "\nSupported runtimes:");
|
|
2424
|
+
}
|
|
2425
|
+
async function pickRuntimeFromEntries(rl, entries, heading) {
|
|
2426
|
+
const registry = getRuntimeRegistry();
|
|
2427
|
+
if (entries.length === 0) {
|
|
2428
|
+
process.stderr.write("no runtimes available in this category\n");
|
|
2429
|
+
return null;
|
|
2430
|
+
}
|
|
2431
|
+
process.stdout.write(`${heading}
|
|
2432
|
+
`);
|
|
2433
|
+
const menu = formatRuntimeMenu(
|
|
2434
|
+
entries.map((entry) => ({
|
|
2435
|
+
group: supportGroupLabel(entry.group),
|
|
2436
|
+
runtimeId: entry.runtimeId,
|
|
2437
|
+
summary: entry.summary
|
|
2438
|
+
})),
|
|
2439
|
+
terminalColumns()
|
|
2440
|
+
);
|
|
2441
|
+
for (const line of menu) process.stdout.write(`${line}
|
|
2442
|
+
`);
|
|
2443
|
+
const allowedIds = new Set(entries.map((entry) => entry.runtimeId));
|
|
2444
|
+
const defaultRuntimeId = entries.find((entry) => entry.featured)?.runtimeId ?? "";
|
|
2445
|
+
for (; ; ) {
|
|
2446
|
+
const answer = await ask(rl, "\nSelection (number or runtime id)", defaultRuntimeId);
|
|
2447
|
+
if (answer === null) return null;
|
|
2448
|
+
if (answer.length === 0) {
|
|
2449
|
+
process.stderr.write(`pick a number from 1 to ${entries.length}, or type a runtime id
|
|
2450
|
+
`);
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
const numeric = Number(answer);
|
|
2454
|
+
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= entries.length) {
|
|
2455
|
+
const picked = entries[numeric - 1];
|
|
2456
|
+
if (picked) {
|
|
2457
|
+
const runtime = registry.get(picked.runtimeId);
|
|
2458
|
+
if (runtime) return runtime;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
if (allowedIds.has(answer)) {
|
|
2462
|
+
const match = registry.get(answer);
|
|
2463
|
+
if (match) return match;
|
|
2464
|
+
}
|
|
2465
|
+
process.stderr.write(`unknown runtime id for this category: ${answer}
|
|
2466
|
+
`);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
async function pickCategory(rl) {
|
|
2470
|
+
process.stdout.write("\nHow will you connect Clio to a model?\n\n");
|
|
2471
|
+
for (const line of formatCategoryMenu(terminalColumns())) process.stdout.write(`${line}
|
|
2472
|
+
`);
|
|
2473
|
+
for (; ; ) {
|
|
2474
|
+
const answer = await ask(rl, "\nSelection", "1");
|
|
2475
|
+
if (answer === null) return null;
|
|
2476
|
+
if (answer.length === 0) continue;
|
|
2477
|
+
const category = matchCategoryChoice(answer);
|
|
2478
|
+
if (category) return category;
|
|
2479
|
+
process.stderr.write(`invalid choice: ${answer}
|
|
2480
|
+
`);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
async function pickRuntimeViaCategory(rl) {
|
|
2484
|
+
const registry = getRuntimeRegistry();
|
|
2485
|
+
const allEntries2 = listProviderSupportEntries(registry.list());
|
|
2486
|
+
const category = await pickCategory(rl);
|
|
2487
|
+
if (category === null) return null;
|
|
2488
|
+
if (category === "all") return pickRuntime(rl);
|
|
2489
|
+
const filtered = runtimesForCategory(allEntries2, category);
|
|
2490
|
+
if (filtered.length === 0) {
|
|
2491
|
+
process.stderr.write("no runtimes available for that category; falling back to full list\n");
|
|
2492
|
+
return pickRuntime(rl);
|
|
2493
|
+
}
|
|
2494
|
+
if (category === "chatgpt") {
|
|
2495
|
+
const only = filtered[0];
|
|
2496
|
+
if (only) {
|
|
2497
|
+
const runtime = registry.get(only.runtimeId);
|
|
2498
|
+
if (runtime) {
|
|
2499
|
+
process.stdout.write(`
|
|
2500
|
+
Using ${runtime.id} (${only.summary}).
|
|
2501
|
+
`);
|
|
2502
|
+
return runtime;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
return pickRuntime(rl);
|
|
2506
|
+
}
|
|
2507
|
+
if (filtered.length === 1) {
|
|
2508
|
+
const only = filtered[0];
|
|
2509
|
+
if (only) {
|
|
2510
|
+
const runtime = registry.get(only.runtimeId);
|
|
2511
|
+
if (runtime) {
|
|
2512
|
+
process.stdout.write(`
|
|
2513
|
+
Using ${runtime.id} (${only.summary}).
|
|
2514
|
+
`);
|
|
2515
|
+
return runtime;
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
const heading = category === "local-app" ? "\nLocal apps:" : category === "local-http" ? "\nLocal HTTP servers:" : "\nCloud APIs:";
|
|
2520
|
+
return pickRuntimeFromEntries(rl, filtered, heading);
|
|
2521
|
+
}
|
|
2522
|
+
async function maybeSteerToNativeRuntime(rl, currentRuntime, url) {
|
|
2523
|
+
if (currentRuntime.id !== "openai-compat" && currentRuntime.id !== "anthropic-compat") return currentRuntime;
|
|
2524
|
+
const fingerprint = await fingerprintNativeRuntime(url);
|
|
2525
|
+
if (!fingerprint) return currentRuntime;
|
|
2526
|
+
const native = getRuntimeRegistry().get(fingerprint.runtimeId);
|
|
2527
|
+
if (!native) return currentRuntime;
|
|
2528
|
+
process.stdout.write(`
|
|
2529
|
+
Detected ${fingerprint.displayName} at ${url}.
|
|
2530
|
+
`);
|
|
2531
|
+
const switchIt = await askYesNo(rl, `Use ${fingerprint.runtimeId} runtime instead of ${currentRuntime.id}?`, true);
|
|
2532
|
+
if (!switchIt) return currentRuntime;
|
|
2533
|
+
process.stdout.write(`Using ${fingerprint.runtimeId} runtime.
|
|
2534
|
+
`);
|
|
2535
|
+
return native;
|
|
2536
|
+
}
|
|
2537
|
+
async function runTargetSetupInteractive(rl, runtime, defaults) {
|
|
2538
|
+
const auth = openAuthStorage();
|
|
2539
|
+
const settings = readSettings();
|
|
2540
|
+
let support = buildProviderSupportEntry(runtime);
|
|
2541
|
+
const initialRuntimeId = runtime.id;
|
|
2542
|
+
const existingForRuntime = configuredTargetsForRuntime(settings, initialRuntimeId);
|
|
2543
|
+
const existing = (defaults.id ? settings.targets.find((entry) => entry.id === defaults.id && entry.runtime === initialRuntimeId) : null) ?? existingForRuntime[0] ?? null;
|
|
2544
|
+
if (existingForRuntime.length > 0) {
|
|
2545
|
+
process.stdout.write(`
|
|
2546
|
+
Existing targets for ${runtime.id}:
|
|
2547
|
+
`);
|
|
2548
|
+
for (const target of existingForRuntime) {
|
|
2549
|
+
process.stdout.write(` - ${target.id}${target.defaultModel ? ` (${target.defaultModel})` : ""}
|
|
2550
|
+
`);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
process.stdout.write(`
|
|
2554
|
+
Selected runtime: ${runtime.id} (${support.summary})
|
|
2555
|
+
`);
|
|
2556
|
+
process.stdout.write(`Credentials: ${describeAuthStatus(runtime)}
|
|
2557
|
+
`);
|
|
2558
|
+
if (support.modelHints.length > 0) {
|
|
2559
|
+
process.stdout.write(`Known models: ${describeRuntimeModels(support, 4)}
|
|
2560
|
+
`);
|
|
2561
|
+
}
|
|
2562
|
+
const suggestedId = defaults.id ?? existing?.id ?? deriveTargetId(runtime.id, settings.targets);
|
|
2563
|
+
const idInput = await ask(rl, "Target id", suggestedId);
|
|
2564
|
+
if (idInput === null || idInput.length === 0) {
|
|
2565
|
+
printError("target id is required");
|
|
2566
|
+
return 2;
|
|
2567
|
+
}
|
|
2568
|
+
const targetId = idInput;
|
|
2569
|
+
let url = existing?.url;
|
|
2570
|
+
if (support.supportsCustomUrl) {
|
|
2571
|
+
const urlDefault = defaults.url ?? existing?.url ?? defaultUrlFor(runtime.id);
|
|
2572
|
+
const urlInput = await ask(
|
|
2573
|
+
rl,
|
|
2574
|
+
support.group === "local-http" ? "Target URL" : "Base URL override (blank for runtime default)",
|
|
2575
|
+
urlDefault
|
|
2576
|
+
);
|
|
2577
|
+
if (urlInput === null) return 0;
|
|
2578
|
+
if (urlInput.length > 0) url = normalizeUrl(urlInput, runtime.id);
|
|
2579
|
+
} else if (defaults.url) {
|
|
2580
|
+
url = normalizeUrl(defaults.url, runtime.id);
|
|
2581
|
+
}
|
|
2582
|
+
if (url) {
|
|
2583
|
+
const steered = await maybeSteerToNativeRuntime(rl, runtime, url);
|
|
2584
|
+
if (steered.id !== runtime.id) {
|
|
2585
|
+
runtime = steered;
|
|
2586
|
+
support = buildProviderSupportEntry(runtime);
|
|
2587
|
+
}
|
|
2588
|
+
if (runtime.id === "lmstudio") {
|
|
2589
|
+
const greeting = await greetLmStudio(
|
|
2590
|
+
{ id: targetId, runtime: runtime.id, url },
|
|
2591
|
+
{ credentialsPresent: /* @__PURE__ */ new Set(), httpTimeoutMs: 750 }
|
|
2592
|
+
);
|
|
2593
|
+
if (!greeting.ok) {
|
|
2594
|
+
printError(`refusing to save LM Studio target '${targetId}': ${url} did not return the LM Studio greeting`);
|
|
2595
|
+
return 2;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
let apiKeyEnv;
|
|
2600
|
+
let apiKeyLiteral;
|
|
2601
|
+
let apiKeyRef = existing?.auth?.apiKeyRef;
|
|
2602
|
+
let oauthProfile = runtime.auth === "oauth" ? existing?.auth?.oauthProfile ?? runtime.oauthProviderId ?? runtime.id : void 0;
|
|
2603
|
+
const authStatus = auth.statusForTarget(resolveRuntimeAuthTarget(runtime), { includeFallback: false });
|
|
2604
|
+
if (runtime.auth === "api-key") {
|
|
2605
|
+
const needsCredential = targetRequiresAuth({ id: targetId, runtime: runtime.id }, runtime);
|
|
2606
|
+
const defaultSource = authStatus.source === "stored-api-key" ? "keep" : authStatus.source === "environment" || existing?.auth?.apiKeyEnvVar || needsCredential ? "env" : "skip";
|
|
2607
|
+
process.stdout.write(
|
|
2608
|
+
"\nCredential source: env reads a key from an environment variable, stored writes one to\ncredentials.yaml, keep leaves the current one alone, skip uses no key at all.\n"
|
|
2609
|
+
);
|
|
2610
|
+
const choice = await ask(rl, "Credential source [env|stored|keep|skip]", defaultSource);
|
|
2611
|
+
if (choice === null) return 0;
|
|
2612
|
+
const normalized = choice.trim().toLowerCase();
|
|
2613
|
+
if (normalized === "stored") {
|
|
2614
|
+
const literal = await ask(rl, "API key literal (stored in credentials.yaml, mode 0600)");
|
|
2615
|
+
if (literal !== null && literal.length > 0) {
|
|
2616
|
+
apiKeyLiteral = literal;
|
|
2617
|
+
apiKeyRef = runtime.id;
|
|
2618
|
+
}
|
|
2619
|
+
} else if (normalized === "env") {
|
|
2620
|
+
const envDefault = defaults.apiKeyEnv ?? existing?.auth?.apiKeyEnvVar ?? runtime.credentialsEnvVar ?? "";
|
|
2621
|
+
const envAnswer = await ask(rl, "Env var name (blank for no key)", envDefault);
|
|
2622
|
+
if (envAnswer === null) return 0;
|
|
2623
|
+
if (envAnswer.length > 0) apiKeyEnv = envAnswer;
|
|
2624
|
+
apiKeyRef = void 0;
|
|
2625
|
+
} else if (normalized === "keep") {
|
|
2626
|
+
apiKeyEnv = existing?.auth?.apiKeyEnvVar ?? (authStatus.source === "environment" ? runtime.credentialsEnvVar : void 0);
|
|
2627
|
+
apiKeyRef = existing?.auth?.apiKeyRef ?? (authStatus.source === "stored-api-key" ? runtime.id : void 0);
|
|
2628
|
+
} else {
|
|
2629
|
+
apiKeyEnv = void 0;
|
|
2630
|
+
apiKeyRef = void 0;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
if (runtime.auth === "oauth") {
|
|
2634
|
+
const connectNow = authStatus.available ? await askYesNo(rl, `Reconnect ${runtime.displayName}?`, false) : await askYesNo(rl, `Connect ${runtime.displayName} now?`, true);
|
|
2635
|
+
if (connectNow) {
|
|
2636
|
+
const connected = await loginOAuthRuntime(rl, runtime);
|
|
2637
|
+
if (!connected) return 1;
|
|
2638
|
+
}
|
|
2639
|
+
oauthProfile = runtime.oauthProviderId ?? runtime.id;
|
|
2640
|
+
}
|
|
2641
|
+
let model = defaults.model;
|
|
2642
|
+
let inventory = { models: [], source: "none" };
|
|
2643
|
+
const tentative = buildDescriptor(runtime, targetId, {
|
|
2644
|
+
...url !== void 0 ? { url } : {},
|
|
2645
|
+
...apiKeyEnv !== void 0 ? { apiKeyEnv } : {},
|
|
2646
|
+
...apiKeyRef !== void 0 ? { apiKeyRef } : {},
|
|
2647
|
+
...oauthProfile !== void 0 ? { oauthProfile } : {},
|
|
2648
|
+
gateway: defaults.gateway,
|
|
2649
|
+
...defaults.lifecycle !== void 0 ? { lifecycle: defaults.lifecycle } : existing?.lifecycle !== void 0 ? { lifecycle: existing.lifecycle } : {},
|
|
2650
|
+
...defaults.contextWindow !== void 0 ? { contextWindow: defaults.contextWindow } : {},
|
|
2651
|
+
...defaults.maxTokens !== void 0 ? { maxTokens: defaults.maxTokens } : {},
|
|
2652
|
+
...defaults.reasoning !== void 0 ? { reasoning: defaults.reasoning } : {},
|
|
2653
|
+
...existing?.lmstudio ? { lmstudio: existing.lmstudio } : {},
|
|
2654
|
+
...existing?.litellm ? { litellm: existing.litellm } : {}
|
|
2655
|
+
});
|
|
2656
|
+
if (runtime.kind === "http" || runtime.externalAgentLoop?.modelCatalog === "live-authoritative") {
|
|
2657
|
+
inventory = await resolveSupportedWireModels(runtime, tentative, existing ?? void 0);
|
|
2658
|
+
}
|
|
2659
|
+
const wireModels = inventory.models;
|
|
2660
|
+
const catalogOrdered = support.modelSource === "catalog";
|
|
2661
|
+
model = model ?? existing?.defaultModel ?? (inventory.source === "probe" ? wireModels[0] : support.defaultModel) ?? (catalogOrdered ? void 0 : wireModels[0]);
|
|
2662
|
+
if (wireModels.length > 0) {
|
|
2663
|
+
process.stdout.write("\nSelectable models:\n");
|
|
2664
|
+
for (const [index, wireModel] of wireModels.entries()) {
|
|
2665
|
+
const label = inventory.labels?.[wireModel];
|
|
2666
|
+
process.stdout.write(
|
|
2667
|
+
` ${index + 1}. ${wireModel}${label && label !== wireModel ? ` \u2014 ${label}` : ""}${wireModel === model ? " [default]" : ""}
|
|
2668
|
+
`
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
if (inventory.source === "cache") process.stdout.write(" cached model snapshot (not verified in this run)\n");
|
|
2672
|
+
if (inventory.probeError) process.stdout.write(` live probe unavailable: ${inventory.probeError}
|
|
2673
|
+
`);
|
|
2674
|
+
if (!model && catalogOrdered) {
|
|
2675
|
+
process.stdout.write(` listed in provider catalog order, which recommends none of them; pick one.
|
|
2676
|
+
`);
|
|
2677
|
+
}
|
|
2678
|
+
for (; ; ) {
|
|
2679
|
+
const pickedModel = await askModelChoice(rl, "Default target model", wireModels, model);
|
|
2680
|
+
if (pickedModel === null) return 0;
|
|
2681
|
+
if (pickedModel.length > 0) model = pickedModel;
|
|
2682
|
+
if (!model) {
|
|
2683
|
+
process.stdout.write(" a model is required: enter a number from the list or a model id.\n");
|
|
2684
|
+
continue;
|
|
2685
|
+
}
|
|
2686
|
+
if (validateResolvedModel(runtime, tentative, model, defaults.force, inventory)) break;
|
|
2687
|
+
}
|
|
2688
|
+
} else {
|
|
2689
|
+
for (; ; ) {
|
|
2690
|
+
if (model && validateResolvedModel(runtime, tentative, model, defaults.force, inventory)) break;
|
|
2691
|
+
const manual = await ask(rl, "Default model id (blank to leave empty)", model ?? "");
|
|
2692
|
+
if (manual === null) return 0;
|
|
2693
|
+
model = manual.length > 0 ? manual : void 0;
|
|
2694
|
+
if (!model) break;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
let contextWindowChoice = defaults.contextWindow;
|
|
2698
|
+
let reasoningChoice = defaults.reasoning;
|
|
2699
|
+
if (PROTOCOL_COMPAT_RUNTIME_IDS.has(runtime.id)) {
|
|
2700
|
+
if (contextWindowChoice === void 0) {
|
|
2701
|
+
const cwDefault = existing?.capabilities?.contextWindow ?? runtime.defaultCapabilities.contextWindow;
|
|
2702
|
+
const cwAnswer = await ask(
|
|
2703
|
+
rl,
|
|
2704
|
+
"Context window in tokens (blank for runtime default)",
|
|
2705
|
+
cwDefault > 0 ? String(cwDefault) : ""
|
|
2706
|
+
);
|
|
2707
|
+
if (cwAnswer === null) return 0;
|
|
2708
|
+
if (cwAnswer.length > 0) {
|
|
2709
|
+
const parsed = Number(cwAnswer);
|
|
2710
|
+
if (Number.isInteger(parsed) && parsed > 0) contextWindowChoice = parsed;
|
|
2711
|
+
else process.stderr.write(`ignoring invalid context window: ${cwAnswer}
|
|
2712
|
+
`);
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
if (reasoningChoice === void 0) {
|
|
2716
|
+
const reasoningDefault = existing?.capabilities?.reasoning ?? runtime.defaultCapabilities.reasoning === true;
|
|
2717
|
+
reasoningChoice = await askYesNo(rl, "Does this model support reasoning / thinking?", reasoningDefault);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (!validateContextWindowOverride(runtime, model, contextWindowChoice, defaults.force)) return 2;
|
|
2721
|
+
const gatewayDefault = defaults.gateway || existing?.gateway === true;
|
|
2722
|
+
const gatewayAnswer = gatewayDefault ? true : await askYesNo(rl, "Mark as gateway?", false);
|
|
2723
|
+
const descriptor = buildDescriptor(runtime, targetId, {
|
|
2724
|
+
...url !== void 0 ? { url } : {},
|
|
2725
|
+
...model !== void 0 ? { model } : {},
|
|
2726
|
+
...apiKeyEnv !== void 0 ? { apiKeyEnv } : {},
|
|
2727
|
+
...apiKeyRef !== void 0 ? { apiKeyRef } : {},
|
|
2728
|
+
...oauthProfile !== void 0 ? { oauthProfile } : {},
|
|
2729
|
+
gateway: gatewayAnswer,
|
|
2730
|
+
...defaults.lifecycle !== void 0 ? { lifecycle: defaults.lifecycle } : existing?.lifecycle !== void 0 ? { lifecycle: existing.lifecycle } : {},
|
|
2731
|
+
...contextWindowChoice !== void 0 ? { contextWindow: contextWindowChoice } : {},
|
|
2732
|
+
...defaults.maxTokens !== void 0 ? { maxTokens: defaults.maxTokens } : {},
|
|
2733
|
+
...reasoningChoice !== void 0 ? { reasoning: reasoningChoice } : {},
|
|
2734
|
+
...existing?.lmstudio ? { lmstudio: existing.lmstudio } : {},
|
|
2735
|
+
...existing?.litellm ? { litellm: existing.litellm } : {}
|
|
2736
|
+
});
|
|
2737
|
+
try {
|
|
2738
|
+
assertOrchestratorReplacementEligible(settings, descriptor);
|
|
2739
|
+
} catch (error) {
|
|
2740
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
2741
|
+
return 1;
|
|
2742
|
+
}
|
|
2743
|
+
if (apiKeyLiteral) {
|
|
2744
|
+
auth.setApiKey(runtime.id, apiKeyLiteral);
|
|
2745
|
+
if (credentialWriteFailed(auth, `credential for ${runtime.id} was not stored; target '${descriptor.id}' not saved`))
|
|
2746
|
+
return 1;
|
|
2747
|
+
printPlaintextCredentialWarning();
|
|
2748
|
+
}
|
|
2749
|
+
const probe = await runtimeProbe(runtime, descriptor);
|
|
2750
|
+
if (probe?.ok && probe.models) recordTargetModelSnapshot(descriptor, probe.models);
|
|
2751
|
+
if (probe) {
|
|
2752
|
+
process.stdout.write("\n");
|
|
2753
|
+
for (const line of probeLines(descriptor, probe)) process.stdout.write(`${line}
|
|
2754
|
+
`);
|
|
2755
|
+
if (!probe.ok) {
|
|
2756
|
+
const keepAnyway = await askYesNo(rl, "save this target anyway, without a reply from it?", true);
|
|
2757
|
+
if (!keepAnyway) {
|
|
2758
|
+
printError("aborted; settings not changed");
|
|
2759
|
+
return 0;
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
const setOrchestrator = !isOrchestratorEligibleRuntime(runtime) ? false : defaults.setOrchestrator ? true : await askYesNo(rl, "use as orchestrator (chat) target?", !settings.chat.target);
|
|
2764
|
+
const setWorkerDefault = defaults.setWorkerDefault ? true : await askYesNo(rl, "use as fleet default?", !settings.fleet.default.target);
|
|
2765
|
+
const setBackground = !isOrchestratorEligibleRuntime(runtime) ? false : defaults.setBackground ? true : await askYesNo(rl, "use as background memory target?", false);
|
|
2766
|
+
const orchestratorModel = setOrchestrator ? defaults.orchestratorModel ?? await askModelChoice(
|
|
2767
|
+
rl,
|
|
2768
|
+
"Orchestrator model",
|
|
2769
|
+
wireModels,
|
|
2770
|
+
settings.chat.target === targetId ? settings.chat.model ?? model : model
|
|
2771
|
+
) : void 0;
|
|
2772
|
+
if (orchestratorModel === null) return 0;
|
|
2773
|
+
const workerModel = setWorkerDefault ? defaults.workerModel ?? await askModelChoice(
|
|
2774
|
+
rl,
|
|
2775
|
+
"Fleet model",
|
|
2776
|
+
wireModels,
|
|
2777
|
+
settings.fleet.default.target === targetId ? settings.fleet.default.model ?? model : model
|
|
2778
|
+
) : void 0;
|
|
2779
|
+
if (workerModel === null) return 0;
|
|
2780
|
+
const backgroundModel = setBackground ? defaults.backgroundModel ?? await askModelChoice(
|
|
2781
|
+
rl,
|
|
2782
|
+
"Background memory model",
|
|
2783
|
+
wireModels,
|
|
2784
|
+
settings.context.memory.target === targetId ? settings.context.memory.model ?? model : model
|
|
2785
|
+
) : void 0;
|
|
2786
|
+
if (backgroundModel === null) return 0;
|
|
2787
|
+
const applyWizardChoice = (target) => {
|
|
2788
|
+
applyTarget(target, descriptor);
|
|
2789
|
+
if (setOrchestrator) setOrchestratorPointer(target, descriptor, orchestratorModel);
|
|
2790
|
+
if (setBackground) setBackgroundPointer(target, descriptor, backgroundModel);
|
|
2791
|
+
if (setWorkerDefault) setWorkerDefaultPointer(target, descriptor, workerModel);
|
|
2792
|
+
};
|
|
2793
|
+
applyWizardChoice(settings);
|
|
2794
|
+
updateSettings(applyWizardChoice);
|
|
2795
|
+
printSummary(settings, descriptor, probe);
|
|
2796
|
+
printOk(`target ${targetId} saved`);
|
|
2797
|
+
await runInteropReview({ rl });
|
|
2798
|
+
return 0;
|
|
2799
|
+
}
|
|
2800
|
+
function runTargetRemove(id) {
|
|
2801
|
+
if (!readSettings().targets.some((e) => e.id === id)) {
|
|
2802
|
+
printError(`no target with id ${id}`);
|
|
2803
|
+
return 1;
|
|
2804
|
+
}
|
|
2805
|
+
updateSettings((settings) => {
|
|
2806
|
+
removeTargetFromSettings(settings, id);
|
|
2807
|
+
});
|
|
2808
|
+
printOk(`removed target ${id}`);
|
|
2809
|
+
return 0;
|
|
2810
|
+
}
|
|
2811
|
+
function runTargetRename(oldId, newId) {
|
|
2812
|
+
if (oldId === newId) {
|
|
2813
|
+
printError("old and new id are identical");
|
|
2814
|
+
return 2;
|
|
2815
|
+
}
|
|
2816
|
+
const current = readSettings();
|
|
2817
|
+
if (current.targets.some((e) => e.id === newId)) {
|
|
2818
|
+
printError(`target id already exists: ${newId}`);
|
|
2819
|
+
return 2;
|
|
2820
|
+
}
|
|
2821
|
+
if (!current.targets.some((e) => e.id === oldId)) {
|
|
2822
|
+
printError(`no target with id ${oldId}`);
|
|
2823
|
+
return 1;
|
|
2824
|
+
}
|
|
2825
|
+
updateSettings((settings) => {
|
|
2826
|
+
const target = settings.targets.find((e) => e.id === oldId);
|
|
2827
|
+
if (!target) return;
|
|
2828
|
+
target.id = newId;
|
|
2829
|
+
if (settings.chat.target === oldId) settings.chat.target = newId;
|
|
2830
|
+
if (settings.context.memory.target === oldId) settings.context.memory.target = newId;
|
|
2831
|
+
if (settings.fleet.default.target === oldId) settings.fleet.default.target = newId;
|
|
2832
|
+
for (const profile of Object.values(settings.fleet.profiles)) {
|
|
2833
|
+
if (profile.target === oldId) profile.target = newId;
|
|
2834
|
+
}
|
|
2835
|
+
settings.chat.modelPicker.cycleSet = settings.chat.modelPicker.cycleSet.map((entry) => {
|
|
2836
|
+
const [head, ...rest] = entry.split("/");
|
|
2837
|
+
if (head !== oldId) return entry;
|
|
2838
|
+
return rest.length === 0 ? newId : `${newId}/${rest.join("/")}`;
|
|
2839
|
+
});
|
|
2840
|
+
});
|
|
2841
|
+
printOk(`renamed ${oldId} to ${newId}`);
|
|
2842
|
+
return 0;
|
|
2843
|
+
}
|
|
2844
|
+
var NONE = "(none)";
|
|
2845
|
+
var onOff = (value) => value ? "enabled" : "disabled";
|
|
2846
|
+
var listOr = (values, fallback = NONE) => values.length > 0 ? values.join(", ") : fallback;
|
|
2847
|
+
async function askInteger(io, label, current, apply, min = 0) {
|
|
2848
|
+
const answer = await ask(io.rl, label, String(current));
|
|
2849
|
+
if (answer === null) return;
|
|
2850
|
+
const parsed = Number(answer);
|
|
2851
|
+
if (!Number.isFinite(parsed) || parsed < min) {
|
|
2852
|
+
io.out.write(` ${label} must be a number of at least ${min}; left at ${current}
|
|
2853
|
+
`);
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
const value = Math.floor(parsed);
|
|
2857
|
+
apply(value);
|
|
2858
|
+
io.ok(`${label} set to ${value}`);
|
|
2859
|
+
}
|
|
2860
|
+
async function askChoice(io, label, allowed, current, apply) {
|
|
2861
|
+
const answer = await ask(io.rl, `${label} [${allowed.join("|")}]`, current);
|
|
2862
|
+
if (answer === null) return;
|
|
2863
|
+
const value = answer.trim().toLowerCase();
|
|
2864
|
+
if (!allowed.includes(value)) {
|
|
2865
|
+
io.out.write(` ${label} must be one of ${allowed.join(", ")}; left at ${current}
|
|
2866
|
+
`);
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2869
|
+
apply(value);
|
|
2870
|
+
io.ok(`${label} set to ${value}`);
|
|
2871
|
+
}
|
|
2872
|
+
var SECTIONS = [
|
|
2873
|
+
{
|
|
2874
|
+
id: "targets",
|
|
2875
|
+
title: "Targets & Auth",
|
|
2876
|
+
summary: "providers, endpoints, credentials, models",
|
|
2877
|
+
aliases: ["auth", "target", "providers"],
|
|
2878
|
+
fields: () => {
|
|
2879
|
+
const settings = readSettings();
|
|
2880
|
+
const rows = [
|
|
2881
|
+
["Chat target", `${settings.chat.target ?? NONE} (model: ${settings.chat.model ?? "target default"})`],
|
|
2882
|
+
[
|
|
2883
|
+
"Fleet target",
|
|
2884
|
+
`${settings.fleet.default.target ?? NONE} (model: ${settings.fleet.default.model ?? "target default"})`
|
|
2885
|
+
]
|
|
2886
|
+
];
|
|
2887
|
+
if (settings.targets.length === 0) rows.push(["Registered", "(no targets registered)"]);
|
|
2888
|
+
else
|
|
2889
|
+
for (const target of settings.targets) {
|
|
2890
|
+
rows.push([
|
|
2891
|
+
` ${target.id}`,
|
|
2892
|
+
`${target.runtime}, model ${target.defaultModel ?? "unset"}${settings.chat.target === target.id ? " [chat]" : ""}${settings.fleet.default.target === target.id ? " [fleet]" : ""}`
|
|
2893
|
+
]);
|
|
2894
|
+
}
|
|
2895
|
+
return rows;
|
|
2896
|
+
},
|
|
2897
|
+
actions: [
|
|
2898
|
+
{
|
|
2899
|
+
label: "Add a target",
|
|
2900
|
+
hint: "register a provider endpoint",
|
|
2901
|
+
run: async (io) => {
|
|
2902
|
+
const runtime = await pickRuntimeViaCategory(io.rl);
|
|
2903
|
+
if (runtime) await runTargetSetupInteractive(io.rl, runtime, emptyArgs());
|
|
2904
|
+
}
|
|
2905
|
+
},
|
|
2906
|
+
{
|
|
2907
|
+
label: "Set the chat target",
|
|
2908
|
+
hint: "which target answers in chat",
|
|
2909
|
+
run: async (io) => {
|
|
2910
|
+
await assignTarget(io, "chat");
|
|
2911
|
+
}
|
|
2912
|
+
},
|
|
2913
|
+
{
|
|
2914
|
+
label: "Set the fleet default target",
|
|
2915
|
+
hint: "which target dispatched workers use",
|
|
2916
|
+
run: async (io) => {
|
|
2917
|
+
await assignTarget(io, "fleet");
|
|
2918
|
+
}
|
|
2919
|
+
},
|
|
2920
|
+
{
|
|
2921
|
+
label: "Remove a target",
|
|
2922
|
+
run: async (io) => {
|
|
2923
|
+
const settings = readSettings();
|
|
2924
|
+
const ids = settings.targets.map((target) => target.id);
|
|
2925
|
+
if (ids.length === 0) {
|
|
2926
|
+
io.out.write(" No targets to remove.\n");
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
io.out.write(` Registered: ${ids.join(", ")}
|
|
2930
|
+
`);
|
|
2931
|
+
const chosen = await ask(io.rl, "Target id to remove");
|
|
2932
|
+
if (chosen === null || !ids.includes(chosen)) return;
|
|
2933
|
+
if (await askYesNo(io.rl, `Remove target '${chosen}'?`, false)) {
|
|
2934
|
+
runTargetRemove(chosen);
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
]
|
|
2939
|
+
},
|
|
2940
|
+
{
|
|
2941
|
+
id: "models",
|
|
2942
|
+
title: "Models & Thinking",
|
|
2943
|
+
summary: "default model, thinking level, favorites",
|
|
2944
|
+
aliases: ["model", "thinking"],
|
|
2945
|
+
fields: () => {
|
|
2946
|
+
const settings = readSettings();
|
|
2947
|
+
return [
|
|
2948
|
+
["Chat model", settings.chat.model ?? "(target default)"],
|
|
2949
|
+
["Chat thinking", settings.chat.thinkingLevel],
|
|
2950
|
+
["Fleet thinking", settings.fleet.default.thinkingLevel],
|
|
2951
|
+
["Favorites", listOr(settings.chat.modelPicker.favorites)],
|
|
2952
|
+
["Cycle set (Alt+J/K)", listOr(settings.chat.modelPicker.cycleSet)]
|
|
2953
|
+
];
|
|
2954
|
+
},
|
|
2955
|
+
actions: [
|
|
2956
|
+
{
|
|
2957
|
+
label: "Chat thinking level",
|
|
2958
|
+
hint: THINKING_LEVELS.join(" | "),
|
|
2959
|
+
run: async (io) => {
|
|
2960
|
+
await askChoice(io, "Chat thinking level", THINKING_LEVELS, readSettings().chat.thinkingLevel, (value) => {
|
|
2961
|
+
updateSettings((draft) => {
|
|
2962
|
+
draft.chat.thinkingLevel = value;
|
|
2963
|
+
});
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2966
|
+
},
|
|
2967
|
+
{
|
|
2968
|
+
label: "Fleet thinking level",
|
|
2969
|
+
hint: THINKING_LEVELS.join(" | "),
|
|
2970
|
+
run: async (io) => {
|
|
2971
|
+
await askChoice(
|
|
2972
|
+
io,
|
|
2973
|
+
"Fleet thinking level",
|
|
2974
|
+
THINKING_LEVELS,
|
|
2975
|
+
readSettings().fleet.default.thinkingLevel,
|
|
2976
|
+
(value) => {
|
|
2977
|
+
updateSettings((draft) => {
|
|
2978
|
+
draft.fleet.default.thinkingLevel = value;
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
},
|
|
2984
|
+
{
|
|
2985
|
+
label: "Chat default model",
|
|
2986
|
+
hint: "blank clears the override",
|
|
2987
|
+
run: async (io) => {
|
|
2988
|
+
const model = await ask(
|
|
2989
|
+
io.rl,
|
|
2990
|
+
"Chat default model (blank for the target default)",
|
|
2991
|
+
readSettings().chat.model ?? ""
|
|
2992
|
+
);
|
|
2993
|
+
if (model === null) return;
|
|
2994
|
+
updateSettings((draft) => {
|
|
2995
|
+
draft.chat.model = model.length > 0 ? model : null;
|
|
2996
|
+
});
|
|
2997
|
+
io.ok(model.length > 0 ? `Chat default model set to ${model}` : "Chat default model cleared");
|
|
2998
|
+
}
|
|
2999
|
+
},
|
|
3000
|
+
{
|
|
3001
|
+
label: "Model favorites",
|
|
3002
|
+
hint: "comma-separated",
|
|
3003
|
+
run: async (io) => {
|
|
3004
|
+
await askList(io, "Model favorites", readSettings().chat.modelPicker.favorites, (values) => {
|
|
3005
|
+
updateSettings((draft) => {
|
|
3006
|
+
draft.chat.modelPicker.favorites = values;
|
|
3007
|
+
});
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
},
|
|
3011
|
+
{
|
|
3012
|
+
label: "Cycle set",
|
|
3013
|
+
hint: "comma-separated, Alt+J/K walks it",
|
|
3014
|
+
run: async (io) => {
|
|
3015
|
+
await askList(io, "Cycle set", readSettings().chat.modelPicker.cycleSet, (values) => {
|
|
3016
|
+
updateSettings((draft) => {
|
|
3017
|
+
draft.chat.modelPicker.cycleSet = values;
|
|
3018
|
+
});
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
]
|
|
3023
|
+
},
|
|
3024
|
+
{
|
|
3025
|
+
id: "chat",
|
|
3026
|
+
title: "Chat Defaults",
|
|
3027
|
+
summary: "streaming, progress, compaction",
|
|
3028
|
+
fields: () => {
|
|
3029
|
+
const settings = readSettings();
|
|
3030
|
+
return [
|
|
3031
|
+
["Smooth streaming", settings.interface.smoothStreaming],
|
|
3032
|
+
["Terminal progress", onOff(settings.interface.terminalProgress)],
|
|
3033
|
+
[
|
|
3034
|
+
"Max output tokens",
|
|
3035
|
+
settings.chat.maxOutputTokens > 0 ? String(settings.chat.maxOutputTokens) : "(runtime default)"
|
|
3036
|
+
],
|
|
3037
|
+
["Prompt prewarm", onOff(settings.chat.prewarm)],
|
|
3038
|
+
[
|
|
3039
|
+
"Auto-compaction",
|
|
3040
|
+
settings.context.compaction.auto ? `enabled at ${Math.round(settings.context.compaction.threshold * 100)}%` : "disabled"
|
|
3041
|
+
],
|
|
3042
|
+
[
|
|
3043
|
+
"Working-set eviction",
|
|
3044
|
+
settings.context.workingSet.enabled ? `enabled (${settings.context.workingSet.policy})` : "disabled"
|
|
3045
|
+
]
|
|
3046
|
+
];
|
|
3047
|
+
},
|
|
3048
|
+
actions: [
|
|
3049
|
+
{
|
|
3050
|
+
label: "Smooth streaming",
|
|
3051
|
+
hint: "off | auto | on",
|
|
3052
|
+
run: async (io) => {
|
|
3053
|
+
await askChoice(
|
|
3054
|
+
io,
|
|
3055
|
+
"Smooth streaming",
|
|
3056
|
+
["off", "auto", "on"],
|
|
3057
|
+
readSettings().interface.smoothStreaming,
|
|
3058
|
+
(value) => {
|
|
3059
|
+
updateSettings((draft) => {
|
|
3060
|
+
draft.interface.smoothStreaming = value;
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
},
|
|
3066
|
+
{
|
|
3067
|
+
label: "Terminal progress indicator",
|
|
3068
|
+
hint: "toggle",
|
|
3069
|
+
run: async (io) => {
|
|
3070
|
+
let next = false;
|
|
3071
|
+
updateSettings((draft) => {
|
|
3072
|
+
draft.interface.terminalProgress = !draft.interface.terminalProgress;
|
|
3073
|
+
next = draft.interface.terminalProgress;
|
|
3074
|
+
});
|
|
3075
|
+
io.ok(`Terminal progress ${onOff(next)}`);
|
|
3076
|
+
}
|
|
3077
|
+
},
|
|
3078
|
+
{
|
|
3079
|
+
label: "Max output tokens",
|
|
3080
|
+
hint: "0 for the runtime default",
|
|
3081
|
+
run: async (io) => {
|
|
3082
|
+
await askInteger(io, "Max output tokens", readSettings().chat.maxOutputTokens, (value) => {
|
|
3083
|
+
updateSettings((draft) => {
|
|
3084
|
+
draft.chat.maxOutputTokens = value;
|
|
3085
|
+
});
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
},
|
|
3089
|
+
{
|
|
3090
|
+
label: "Prompt prewarm",
|
|
3091
|
+
hint: "toggle",
|
|
3092
|
+
run: async (io) => {
|
|
3093
|
+
let next = false;
|
|
3094
|
+
updateSettings((draft) => {
|
|
3095
|
+
draft.chat.prewarm = !draft.chat.prewarm;
|
|
3096
|
+
next = draft.chat.prewarm;
|
|
3097
|
+
});
|
|
3098
|
+
io.ok(`Prompt prewarm ${onOff(next)}`);
|
|
3099
|
+
}
|
|
3100
|
+
},
|
|
3101
|
+
{
|
|
3102
|
+
label: "Auto-compaction",
|
|
3103
|
+
hint: "toggle",
|
|
3104
|
+
run: async (io) => {
|
|
3105
|
+
let next = false;
|
|
3106
|
+
updateSettings((draft) => {
|
|
3107
|
+
draft.context.compaction.auto = !draft.context.compaction.auto;
|
|
3108
|
+
next = draft.context.compaction.auto;
|
|
3109
|
+
});
|
|
3110
|
+
io.ok(`Auto-compaction ${onOff(next)}`);
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
]
|
|
3114
|
+
},
|
|
3115
|
+
{
|
|
3116
|
+
id: "fleet",
|
|
3117
|
+
title: "Fleet",
|
|
3118
|
+
summary: "concurrency, retries, worker timeouts",
|
|
3119
|
+
fields: () => {
|
|
3120
|
+
const settings = readSettings();
|
|
3121
|
+
return [
|
|
3122
|
+
["Concurrency limit", String(settings.fleet.concurrency)],
|
|
3123
|
+
["Max retries", String(settings.fleet.retry.maxRetries)],
|
|
3124
|
+
["Tool calls per run", String(settings.fleet.limits.toolCallsPerRun)],
|
|
3125
|
+
["Run timeout", `${settings.fleet.limits.internalRunTimeoutMs / 1e3}s`],
|
|
3126
|
+
["Worker profiles", listOr(Object.keys(settings.fleet.profiles))],
|
|
3127
|
+
[
|
|
3128
|
+
"Subagent pins",
|
|
3129
|
+
listOr(Object.entries(settings.fleet.agentProfiles).map(([agent, profile]) => `${agent} -> ${profile}`))
|
|
3130
|
+
],
|
|
3131
|
+
["Remote nodes", listOr(settings.fleet.nodes.map((node) => node.id))]
|
|
3132
|
+
];
|
|
3133
|
+
},
|
|
3134
|
+
actions: [
|
|
3135
|
+
{
|
|
3136
|
+
label: "Concurrency limit",
|
|
3137
|
+
hint: "auto, or a positive integer",
|
|
3138
|
+
run: async (io) => {
|
|
3139
|
+
const current = String(readSettings().fleet.concurrency);
|
|
3140
|
+
const answer = await ask(io.rl, "Concurrency limit [auto or a number]", current);
|
|
3141
|
+
if (answer === null) return;
|
|
3142
|
+
if (answer.trim().toLowerCase() === "auto") {
|
|
3143
|
+
updateSettings((draft) => {
|
|
3144
|
+
draft.fleet.concurrency = "auto";
|
|
3145
|
+
});
|
|
3146
|
+
io.ok("Concurrency limit set to auto");
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
const parsed = Number(answer);
|
|
3150
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
3151
|
+
io.out.write(` Concurrency limit must be auto or at least 1; left at ${current}
|
|
3152
|
+
`);
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
updateSettings((draft) => {
|
|
3156
|
+
draft.fleet.concurrency = Math.floor(parsed);
|
|
3157
|
+
});
|
|
3158
|
+
io.ok(`Concurrency limit set to ${Math.floor(parsed)}`);
|
|
3159
|
+
}
|
|
3160
|
+
},
|
|
3161
|
+
{
|
|
3162
|
+
label: "Max retries",
|
|
3163
|
+
run: async (io) => {
|
|
3164
|
+
await askInteger(io, "Max retries", readSettings().fleet.retry.maxRetries, (value) => {
|
|
3165
|
+
updateSettings((draft) => {
|
|
3166
|
+
draft.fleet.retry.maxRetries = value;
|
|
3167
|
+
});
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
},
|
|
3171
|
+
{
|
|
3172
|
+
label: "Tool calls per run",
|
|
3173
|
+
run: async (io) => {
|
|
3174
|
+
await askInteger(
|
|
3175
|
+
io,
|
|
3176
|
+
"Tool calls per run",
|
|
3177
|
+
readSettings().fleet.limits.toolCallsPerRun,
|
|
3178
|
+
(value) => {
|
|
3179
|
+
updateSettings((draft) => {
|
|
3180
|
+
draft.fleet.limits.toolCallsPerRun = value;
|
|
3181
|
+
});
|
|
3182
|
+
},
|
|
3183
|
+
1
|
|
3184
|
+
);
|
|
3185
|
+
}
|
|
3186
|
+
},
|
|
3187
|
+
{
|
|
3188
|
+
label: "Run timeout",
|
|
3189
|
+
hint: "seconds",
|
|
3190
|
+
run: async (io) => {
|
|
3191
|
+
const current = readSettings().fleet.limits.internalRunTimeoutMs / 1e3;
|
|
3192
|
+
const answer = await ask(io.rl, "Run timeout in seconds", String(current));
|
|
3193
|
+
if (answer === null) return;
|
|
3194
|
+
const parsed = Number(answer);
|
|
3195
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
3196
|
+
io.out.write(` Run timeout must be at least 1 second; left at ${current}s
|
|
3197
|
+
`);
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
updateSettings((draft) => {
|
|
3201
|
+
draft.fleet.limits.internalRunTimeoutMs = Math.floor(parsed * 1e3);
|
|
3202
|
+
});
|
|
3203
|
+
io.ok(`Run timeout set to ${Math.floor(parsed)}s`);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
]
|
|
3207
|
+
},
|
|
3208
|
+
{
|
|
3209
|
+
id: "permissions",
|
|
3210
|
+
title: "Permissions & Autonomy",
|
|
3211
|
+
summary: "autonomy level, worker permissions, cost limits",
|
|
3212
|
+
aliases: ["autonomy", "safety"],
|
|
3213
|
+
fields: () => {
|
|
3214
|
+
const settings = readSettings();
|
|
3215
|
+
return [
|
|
3216
|
+
["Autonomy level", settings.safety.autonomy],
|
|
3217
|
+
["Worker permissions", settings.fleet.permissions.mode],
|
|
3218
|
+
["Session cost limit", `$${settings.safety.limits.sessionCostUsd} USD`],
|
|
3219
|
+
["Turn tool budget", String(settings.safety.limits.chatToolCallsPerTurn)],
|
|
3220
|
+
["Review watchdog", onOff(settings.safety.review.enabled)]
|
|
3221
|
+
];
|
|
3222
|
+
},
|
|
3223
|
+
actions: [
|
|
3224
|
+
{
|
|
3225
|
+
label: "Autonomy level",
|
|
3226
|
+
hint: "interactive | assisted | auto-edit | full",
|
|
3227
|
+
run: async (io) => {
|
|
3228
|
+
io.out.write(
|
|
3229
|
+
" interactive prompts for everything, assisted auto-reads and confirms writes,\n auto-edit confirms only bash, full runs unattended.\n"
|
|
3230
|
+
);
|
|
3231
|
+
await askChoice(
|
|
3232
|
+
io,
|
|
3233
|
+
"Autonomy level",
|
|
3234
|
+
["interactive", "assisted", "auto-edit", "full"],
|
|
3235
|
+
readSettings().safety.autonomy,
|
|
3236
|
+
(value) => {
|
|
3237
|
+
updateSettings((draft) => {
|
|
3238
|
+
draft.safety.autonomy = value;
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
);
|
|
3242
|
+
}
|
|
3243
|
+
},
|
|
3244
|
+
{
|
|
3245
|
+
label: "Worker permission mode",
|
|
3246
|
+
hint: "deny | fail | escalate",
|
|
3247
|
+
run: async (io) => {
|
|
3248
|
+
await askChoice(
|
|
3249
|
+
io,
|
|
3250
|
+
"Worker permission mode",
|
|
3251
|
+
["deny", "fail", "escalate"],
|
|
3252
|
+
readSettings().fleet.permissions.mode,
|
|
3253
|
+
(value) => {
|
|
3254
|
+
updateSettings((draft) => {
|
|
3255
|
+
draft.fleet.permissions.mode = value;
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
);
|
|
3259
|
+
}
|
|
3260
|
+
},
|
|
3261
|
+
{
|
|
3262
|
+
label: "Session cost limit",
|
|
3263
|
+
hint: "USD",
|
|
3264
|
+
run: async (io) => {
|
|
3265
|
+
const current = readSettings().safety.limits.sessionCostUsd;
|
|
3266
|
+
const answer = await ask(io.rl, "Session cost limit in USD", String(current));
|
|
3267
|
+
if (answer === null) return;
|
|
3268
|
+
const parsed = Number(answer);
|
|
3269
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3270
|
+
io.out.write(` Session cost limit must be greater than 0; left at $${current}
|
|
3271
|
+
`);
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
updateSettings((draft) => {
|
|
3275
|
+
draft.safety.limits.sessionCostUsd = parsed;
|
|
3276
|
+
});
|
|
3277
|
+
io.ok(`Session cost limit set to $${parsed} USD`);
|
|
3278
|
+
}
|
|
3279
|
+
},
|
|
3280
|
+
{
|
|
3281
|
+
label: "Turn tool budget",
|
|
3282
|
+
run: async (io) => {
|
|
3283
|
+
await askInteger(
|
|
3284
|
+
io,
|
|
3285
|
+
"Turn tool budget",
|
|
3286
|
+
readSettings().safety.limits.chatToolCallsPerTurn,
|
|
3287
|
+
(value) => {
|
|
3288
|
+
updateSettings((draft) => {
|
|
3289
|
+
draft.safety.limits.chatToolCallsPerTurn = value;
|
|
3290
|
+
});
|
|
3291
|
+
},
|
|
3292
|
+
1
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
},
|
|
3296
|
+
{
|
|
3297
|
+
label: "Turn-end review watchdog",
|
|
3298
|
+
hint: "toggle",
|
|
3299
|
+
run: async (io) => {
|
|
3300
|
+
let next = false;
|
|
3301
|
+
updateSettings((draft) => {
|
|
3302
|
+
draft.safety.review.enabled = !draft.safety.review.enabled;
|
|
3303
|
+
next = draft.safety.review.enabled;
|
|
3304
|
+
});
|
|
3305
|
+
io.ok(`Review watchdog ${onOff(next)}`);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
]
|
|
3309
|
+
},
|
|
3310
|
+
{
|
|
3311
|
+
id: "panes",
|
|
3312
|
+
title: "Panes & Layout",
|
|
3313
|
+
summary: "terminal panes, startup layout, display mode",
|
|
3314
|
+
aliases: ["layout", "interface", "pane"],
|
|
3315
|
+
fields: () => {
|
|
3316
|
+
const settings = readSettings();
|
|
3317
|
+
return [
|
|
3318
|
+
["Panes capability", settings.interface.panes.enabled],
|
|
3319
|
+
["Startup layout", settings.interface.panes.layout],
|
|
3320
|
+
["TUI mode", settings.interface.mode],
|
|
3321
|
+
["Output detail", settings.interface.outputDetail],
|
|
3322
|
+
["Desktop notifications", onOff(settings.interface.desktopNotifications)],
|
|
3323
|
+
["Git commit attribution", onOff(settings.integrations.git.commitAttribution)]
|
|
3324
|
+
];
|
|
3325
|
+
},
|
|
3326
|
+
actions: [
|
|
3327
|
+
{
|
|
3328
|
+
label: "Panes capability",
|
|
3329
|
+
hint: "off | auto | embedded",
|
|
3330
|
+
run: async (io) => {
|
|
3331
|
+
await askChoice(
|
|
3332
|
+
io,
|
|
3333
|
+
"Panes capability",
|
|
3334
|
+
["off", "auto", "embedded"],
|
|
3335
|
+
readSettings().interface.panes.enabled,
|
|
3336
|
+
(value) => {
|
|
3337
|
+
updateSettings((draft) => {
|
|
3338
|
+
draft.interface.panes.enabled = value;
|
|
3339
|
+
});
|
|
3340
|
+
}
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
},
|
|
3344
|
+
{
|
|
3345
|
+
label: "Startup layout",
|
|
3346
|
+
hint: "off | workers | cockpit",
|
|
3347
|
+
run: async (io) => {
|
|
3348
|
+
await askChoice(
|
|
3349
|
+
io,
|
|
3350
|
+
"Startup layout",
|
|
3351
|
+
["off", "workers", "cockpit"],
|
|
3352
|
+
readSettings().interface.panes.layout,
|
|
3353
|
+
(value) => {
|
|
3354
|
+
updateSettings((draft) => {
|
|
3355
|
+
draft.interface.panes.layout = value;
|
|
3356
|
+
});
|
|
3357
|
+
}
|
|
3358
|
+
);
|
|
3359
|
+
}
|
|
3360
|
+
},
|
|
3361
|
+
{
|
|
3362
|
+
label: "TUI mode",
|
|
3363
|
+
hint: "regular | fullscreen",
|
|
3364
|
+
run: async (io) => {
|
|
3365
|
+
await askChoice(io, "TUI mode", ["regular", "fullscreen"], readSettings().interface.mode, (value) => {
|
|
3366
|
+
updateSettings((draft) => {
|
|
3367
|
+
draft.interface.mode = value;
|
|
3368
|
+
});
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
},
|
|
3372
|
+
{
|
|
3373
|
+
label: "Output detail",
|
|
3374
|
+
hint: "minimal | default | verbose",
|
|
3375
|
+
run: async (io) => {
|
|
3376
|
+
await askChoice(
|
|
3377
|
+
io,
|
|
3378
|
+
"Output detail",
|
|
3379
|
+
["minimal", "default", "verbose"],
|
|
3380
|
+
readSettings().interface.outputDetail,
|
|
3381
|
+
(value) => {
|
|
3382
|
+
updateSettings((draft) => {
|
|
3383
|
+
draft.interface.outputDetail = value;
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
},
|
|
3389
|
+
{
|
|
3390
|
+
label: "Desktop notifications",
|
|
3391
|
+
hint: "toggle",
|
|
3392
|
+
run: async (io) => {
|
|
3393
|
+
let next = false;
|
|
3394
|
+
updateSettings((draft) => {
|
|
3395
|
+
draft.interface.desktopNotifications = !draft.interface.desktopNotifications;
|
|
3396
|
+
next = draft.interface.desktopNotifications;
|
|
3397
|
+
});
|
|
3398
|
+
io.ok(`Desktop notifications ${onOff(next)}`);
|
|
3399
|
+
}
|
|
3400
|
+
},
|
|
3401
|
+
{
|
|
3402
|
+
label: "Git commit attribution",
|
|
3403
|
+
hint: "toggle",
|
|
3404
|
+
run: async (io) => {
|
|
3405
|
+
let next = false;
|
|
3406
|
+
updateSettings((draft) => {
|
|
3407
|
+
draft.integrations.git.commitAttribution = !draft.integrations.git.commitAttribution;
|
|
3408
|
+
next = draft.integrations.git.commitAttribution;
|
|
3409
|
+
});
|
|
3410
|
+
io.ok(`Git commit attribution ${onOff(next)}`);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
]
|
|
3414
|
+
},
|
|
3415
|
+
{
|
|
3416
|
+
id: "skills",
|
|
3417
|
+
title: "Skills & Extensions",
|
|
3418
|
+
summary: "project imports, ACP peers, plugins",
|
|
3419
|
+
aliases: ["extensions", "skill", "interop"],
|
|
3420
|
+
fields: () => {
|
|
3421
|
+
const settings = readSettings();
|
|
3422
|
+
const rows = [
|
|
3423
|
+
["Trust project imports", settings.integrations.projectResources.trustProjectImports ? "trusted" : "untrusted"],
|
|
3424
|
+
["External ACP agents", String(settings.integrations.externalAgents.entries.length)]
|
|
3425
|
+
];
|
|
3426
|
+
for (const agent of settings.integrations.externalAgents.entries) {
|
|
3427
|
+
rows.push([` ${agent.id}`, `${agent.command} (governance: ${agent.toolGovernance ?? "default"})`]);
|
|
3428
|
+
}
|
|
3429
|
+
rows.push(
|
|
3430
|
+
["Runtime plugins", listOr(settings.integrations.runtimePlugins)],
|
|
3431
|
+
["Library remote sync", onOff(settings.integrations.library.sync)]
|
|
3432
|
+
);
|
|
3433
|
+
return rows;
|
|
3434
|
+
},
|
|
3435
|
+
actions: [
|
|
3436
|
+
{
|
|
3437
|
+
label: "Trust project imports",
|
|
3438
|
+
hint: "toggle",
|
|
3439
|
+
run: async (io) => {
|
|
3440
|
+
let next = false;
|
|
3441
|
+
updateSettings((draft) => {
|
|
3442
|
+
draft.integrations.projectResources.trustProjectImports = !draft.integrations.projectResources.trustProjectImports;
|
|
3443
|
+
next = draft.integrations.projectResources.trustProjectImports;
|
|
3444
|
+
});
|
|
3445
|
+
io.ok(`Project imports ${next ? "trusted" : "untrusted"}`);
|
|
3446
|
+
}
|
|
3447
|
+
},
|
|
3448
|
+
{
|
|
3449
|
+
label: "Review external ACP agents",
|
|
3450
|
+
hint: "runs the interop review",
|
|
3451
|
+
run: async (io) => {
|
|
3452
|
+
await runInteropReview({ rl: io.rl, streams: io.streams });
|
|
3453
|
+
}
|
|
3454
|
+
},
|
|
3455
|
+
{
|
|
3456
|
+
label: "Library remote sync",
|
|
3457
|
+
hint: "toggle",
|
|
3458
|
+
run: async (io) => {
|
|
3459
|
+
let next = false;
|
|
3460
|
+
updateSettings((draft) => {
|
|
3461
|
+
draft.integrations.library.sync = !draft.integrations.library.sync;
|
|
3462
|
+
next = draft.integrations.library.sync;
|
|
3463
|
+
});
|
|
3464
|
+
io.ok(`Library remote sync ${onOff(next)}`);
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
]
|
|
3468
|
+
},
|
|
3469
|
+
{
|
|
3470
|
+
id: "diagnostics",
|
|
3471
|
+
title: "Diagnostics",
|
|
3472
|
+
summary: "version, directories, doctor, raw settings",
|
|
3473
|
+
aliases: ["doctor", "diag"],
|
|
3474
|
+
fields: () => {
|
|
3475
|
+
const dirs = resolveClioDirs();
|
|
3476
|
+
const info = getVersionInfo();
|
|
3477
|
+
return [
|
|
3478
|
+
["Clio Coder", info.clio],
|
|
3479
|
+
["Node.js", info.node],
|
|
3480
|
+
["Platform", info.platform],
|
|
3481
|
+
["Config dir", shortenPath(dirs.config)],
|
|
3482
|
+
["Data dir", shortenPath(dirs.data)],
|
|
3483
|
+
["State dir", shortenPath(dirs.state)],
|
|
3484
|
+
["Cache dir", shortenPath(dirs.cache)]
|
|
3485
|
+
];
|
|
3486
|
+
},
|
|
3487
|
+
actions: [
|
|
3488
|
+
{
|
|
3489
|
+
label: "Run doctor",
|
|
3490
|
+
hint: "read-only health check",
|
|
3491
|
+
run: async () => {
|
|
3492
|
+
const { runDoctorCommand } = await import("./doctor-WNNVO6FY.js");
|
|
3493
|
+
await runDoctorCommand([]);
|
|
3494
|
+
}
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
label: "Show raw settings.yaml",
|
|
3498
|
+
run: async (io) => {
|
|
3499
|
+
const file = settingsPath();
|
|
3500
|
+
if (!existsSync(file)) {
|
|
3501
|
+
io.out.write(" settings.yaml does not exist yet.\n");
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
io.out.write(`
|
|
3505
|
+
--- ${shortenPath(file)} ---
|
|
3506
|
+
${readFileSync(file, "utf8")}--- end ---
|
|
3507
|
+
`);
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
]
|
|
3511
|
+
}
|
|
3512
|
+
];
|
|
3513
|
+
function emptyArgs() {
|
|
3514
|
+
return {
|
|
3515
|
+
positional: [],
|
|
3516
|
+
help: false,
|
|
3517
|
+
list: false,
|
|
3518
|
+
all: false,
|
|
3519
|
+
interop: false,
|
|
3520
|
+
json: false,
|
|
3521
|
+
force: false,
|
|
3522
|
+
gateway: false,
|
|
3523
|
+
setOrchestrator: false,
|
|
3524
|
+
setBackground: false,
|
|
3525
|
+
setWorkerDefault: false
|
|
3526
|
+
};
|
|
3527
|
+
}
|
|
3528
|
+
async function askList(io, label, current, apply) {
|
|
3529
|
+
const answer = await ask(io.rl, `${label} (comma-separated, blank clears)`, current.join(", "));
|
|
3530
|
+
if (answer === null) return;
|
|
3531
|
+
const values = answer.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
3532
|
+
apply(values);
|
|
3533
|
+
io.ok(values.length === 0 ? `${label} cleared` : `${label} set to ${values.join(", ")}`);
|
|
3534
|
+
}
|
|
3535
|
+
async function assignTarget(io, role) {
|
|
3536
|
+
const settings = readSettings();
|
|
3537
|
+
const ids = settings.targets.map((target2) => target2.id);
|
|
3538
|
+
if (ids.length === 0) {
|
|
3539
|
+
io.out.write(" No targets registered yet. Add one first.\n");
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
io.out.write(` Registered: ${ids.join(", ")}
|
|
3543
|
+
`);
|
|
3544
|
+
const currentTarget = role === "chat" ? settings.chat.target : settings.fleet.default.target;
|
|
3545
|
+
const chosen = await ask(io.rl, `${role === "chat" ? "Chat" : "Fleet"} target id`, currentTarget ?? ids[0] ?? "");
|
|
3546
|
+
if (chosen === null) return;
|
|
3547
|
+
if (!ids.includes(chosen)) {
|
|
3548
|
+
io.out.write(` ${chosen} is not a registered target; nothing changed.
|
|
3549
|
+
`);
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
const target = settings.targets.find((entry) => entry.id === chosen);
|
|
3553
|
+
const model = await ask(io.rl, "Model (blank for the target default)", target?.defaultModel ?? "");
|
|
3554
|
+
if (model === null) return;
|
|
3555
|
+
updateSettings((draft) => {
|
|
3556
|
+
if (role === "chat") {
|
|
3557
|
+
draft.chat.target = chosen;
|
|
3558
|
+
draft.chat.model = model.length > 0 ? model : null;
|
|
3559
|
+
} else {
|
|
3560
|
+
draft.fleet.default.target = chosen;
|
|
3561
|
+
draft.fleet.default.model = model.length > 0 ? model : null;
|
|
3562
|
+
}
|
|
3563
|
+
});
|
|
3564
|
+
io.ok(`${role === "chat" ? "Chat" : "Fleet"} target set to ${chosen}`);
|
|
3565
|
+
}
|
|
3566
|
+
async function pickEntry(rl, io, entries, backLabel, plain) {
|
|
3567
|
+
const out = io.out;
|
|
3568
|
+
if (canSelect(io.in, out)) {
|
|
3569
|
+
const result = await promptSelect({
|
|
3570
|
+
choices: entries.map((entry, index2) => ({
|
|
3571
|
+
value: index2,
|
|
3572
|
+
label: entry.label,
|
|
3573
|
+
...entry.hint === void 0 ? {} : { hint: entry.hint }
|
|
3574
|
+
})),
|
|
3575
|
+
railPrefix: railPrefix(plain),
|
|
3576
|
+
backLabel,
|
|
3577
|
+
input: io.in,
|
|
3578
|
+
output: out
|
|
3579
|
+
});
|
|
3580
|
+
if (result.kind === "selected") return { kind: "index", index: result.value };
|
|
3581
|
+
return result.kind === "quit" ? { kind: "quit" } : { kind: "back" };
|
|
3582
|
+
}
|
|
3583
|
+
const rail = railPrefix(plain);
|
|
3584
|
+
entries.forEach((entry, index2) => {
|
|
3585
|
+
out.write(`${rail}${index2 + 1}. ${entry.label}${entry.hint === void 0 ? "" : ` (${entry.hint})`}
|
|
3586
|
+
`);
|
|
3587
|
+
});
|
|
3588
|
+
out.write(`${rail}b. ${backLabel}
|
|
3589
|
+
${rail}q. Quit
|
|
3590
|
+
`);
|
|
3591
|
+
const answer = await ask(rl, "\nSelection", "b");
|
|
3592
|
+
if (answer === null) return { kind: "quit" };
|
|
3593
|
+
const trimmed = answer.trim().toLowerCase();
|
|
3594
|
+
if (trimmed === "b" || trimmed === "back" || trimmed.length === 0) return { kind: "back" };
|
|
3595
|
+
if (trimmed === "q" || trimmed === "quit") return { kind: "quit" };
|
|
3596
|
+
const index = Number.parseInt(trimmed, 10) - 1;
|
|
3597
|
+
if (Number.isInteger(index) && index >= 0 && index < entries.length) return { kind: "index", index };
|
|
3598
|
+
out.write(`${rail}Not one of the choices: ${answer}
|
|
3599
|
+
`);
|
|
3600
|
+
return { kind: "back" };
|
|
3601
|
+
}
|
|
3602
|
+
function renderSection(spec, out) {
|
|
3603
|
+
const presenter = createLifecyclePresenter({ stream: out });
|
|
3604
|
+
presenter.header(spec.title, "configure");
|
|
3605
|
+
presenter.note(`Source: ${shortenPath(settingsPath())}`);
|
|
3606
|
+
presenter.fields(spec.fields());
|
|
3607
|
+
return presenter;
|
|
3608
|
+
}
|
|
3609
|
+
async function runSection(rl, streams, spec, backLabel) {
|
|
3610
|
+
for (; ; ) {
|
|
3611
|
+
const presenter = renderSection(spec, streams.out);
|
|
3612
|
+
const io = {
|
|
3613
|
+
rl,
|
|
3614
|
+
out: streams.out,
|
|
3615
|
+
streams,
|
|
3616
|
+
ok: (text) => {
|
|
3617
|
+
presenter.completedStep(text);
|
|
3618
|
+
}
|
|
3619
|
+
};
|
|
3620
|
+
presenter.blank();
|
|
3621
|
+
const choice = await pickEntry(rl, streams, spec.actions, backLabel, presenter.isPlain());
|
|
3622
|
+
if (choice.kind !== "index") return choice.kind;
|
|
3623
|
+
const action = spec.actions[choice.index];
|
|
3624
|
+
if (action !== void 0) await action.run(io);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
async function runConfigSectionsMenu(rl, streams) {
|
|
3628
|
+
for (; ; ) {
|
|
3629
|
+
const presenter = createLifecyclePresenter({ stream: streams.out });
|
|
3630
|
+
presenter.header("Clio Coder Configuration", "configure");
|
|
3631
|
+
presenter.note(`Source: ${shortenPath(settingsPath())}`);
|
|
3632
|
+
presenter.blank();
|
|
3633
|
+
const choice = await pickEntry(
|
|
3634
|
+
rl,
|
|
3635
|
+
streams,
|
|
3636
|
+
SECTIONS.map((section) => ({ label: section.title, hint: section.summary })),
|
|
3637
|
+
"quit",
|
|
3638
|
+
presenter.isPlain()
|
|
3639
|
+
);
|
|
3640
|
+
if (choice.kind !== "index") return 0;
|
|
3641
|
+
const spec = SECTIONS[choice.index];
|
|
3642
|
+
if (spec === void 0) continue;
|
|
3643
|
+
if (await runSection(rl, streams, spec, "back") === "quit") return 0;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
function findSection(name) {
|
|
3647
|
+
const wanted = name.trim().toLowerCase();
|
|
3648
|
+
return SECTIONS.find((section) => section.id === wanted || (section.aliases ?? []).includes(wanted));
|
|
3649
|
+
}
|
|
3650
|
+
var SECTION_IDS = SECTIONS.map((section) => section.id);
|
|
3651
|
+
async function runInteractive(rl, preselectedRuntime, defaults, streams) {
|
|
3652
|
+
if (preselectedRuntime) {
|
|
3653
|
+
return await runTargetSetupInteractive(rl, preselectedRuntime, defaults);
|
|
3654
|
+
}
|
|
3655
|
+
if (defaults.section !== void 0) {
|
|
3656
|
+
const spec = findSection(defaults.section);
|
|
3657
|
+
if (spec === void 0) {
|
|
3658
|
+
printError(`unknown section: ${defaults.section}. Sections: ${SECTION_IDS.join(", ")}`);
|
|
3659
|
+
return 2;
|
|
3660
|
+
}
|
|
3661
|
+
if (streams.in.isTTY !== true) {
|
|
3662
|
+
renderSection(spec, streams.out).done("Done");
|
|
3663
|
+
return 0;
|
|
3664
|
+
}
|
|
3665
|
+
await runSection(rl, streams, spec, "quit");
|
|
3666
|
+
return 0;
|
|
3667
|
+
}
|
|
3668
|
+
if (readSettings().targets.length === 0) {
|
|
3669
|
+
if (canRunOnboarding(streams)) {
|
|
3670
|
+
rl.close();
|
|
3671
|
+
return await runOnboardingWizard(streams);
|
|
3672
|
+
}
|
|
3673
|
+
const runtime = await pickRuntimeViaCategory(rl);
|
|
3674
|
+
if (!runtime) {
|
|
3675
|
+
printError("configuration cancelled");
|
|
3676
|
+
return 130;
|
|
3677
|
+
}
|
|
3678
|
+
return await runTargetSetupInteractive(rl, runtime, defaults);
|
|
3679
|
+
}
|
|
3680
|
+
return await runConfigSectionsMenu(rl, streams);
|
|
3681
|
+
}
|
|
3682
|
+
async function runConfigureCommand(argv, inStream = input, outStream = output2) {
|
|
3683
|
+
let args;
|
|
3684
|
+
try {
|
|
3685
|
+
args = parseSetupArgs(argv);
|
|
3686
|
+
} catch (err) {
|
|
3687
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
3688
|
+
process.stdout.write(HELP);
|
|
3689
|
+
return 2;
|
|
3690
|
+
}
|
|
3691
|
+
if (args.help) {
|
|
3692
|
+
process.stdout.write(HELP);
|
|
3693
|
+
return 0;
|
|
3694
|
+
}
|
|
3695
|
+
initializeClioHome();
|
|
3696
|
+
ensureRegistryPopulated();
|
|
3697
|
+
if (args.json) {
|
|
3698
|
+
const settings = readSettings();
|
|
3699
|
+
outStream.write(`${JSON.stringify(settings, null, 2)}
|
|
3700
|
+
`);
|
|
3701
|
+
return 0;
|
|
3702
|
+
}
|
|
3703
|
+
if (args.list) {
|
|
3704
|
+
printRuntimeList(args.all);
|
|
3705
|
+
return 0;
|
|
3706
|
+
}
|
|
3707
|
+
if (args.interop) {
|
|
3708
|
+
if (!input.isTTY) return runInteropReview({ rl: null });
|
|
3709
|
+
const rl2 = createInterface2({ input, output: output2 });
|
|
3710
|
+
try {
|
|
3711
|
+
return await runInteropReview({ rl: rl2, streams: { in: input, out: output2 } });
|
|
3712
|
+
} finally {
|
|
3713
|
+
rl2.close();
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
if (args.remove) return runTargetRemove(args.remove);
|
|
3717
|
+
if (args.renameOld && args.renameNew) return runTargetRename(args.renameOld, args.renameNew);
|
|
3718
|
+
if (args.positional.length > 0) {
|
|
3719
|
+
printError(
|
|
3720
|
+
"`clio-coder configure` accepts flags, not positional runtimes. Use `clio-coder auth login <runtime>` first when authentication is needed, then `clio-coder configure --runtime <runtimeId> ...`."
|
|
3721
|
+
);
|
|
3722
|
+
return 2;
|
|
3723
|
+
}
|
|
3724
|
+
const runtimeId = args.runtime;
|
|
3725
|
+
let runtime = null;
|
|
3726
|
+
if (runtimeId) {
|
|
3727
|
+
runtime = getRuntimeRegistry().get(runtimeId);
|
|
3728
|
+
if (!runtime) {
|
|
3729
|
+
printError(`unknown runtime id: ${runtimeId}`);
|
|
3730
|
+
process.stdout.write("run `clio-coder configure --list` to see registered runtimes\n");
|
|
3731
|
+
return 2;
|
|
3732
|
+
}
|
|
3733
|
+
if (runtimeId === "lmstudio-native" && runtime.id === "lmstudio") {
|
|
3734
|
+
process.stderr.write("warning: runtime 'lmstudio-native' is deprecated; using 'lmstudio'\n");
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
const hasTargetSetupFlag = args.id !== void 0 || args.url !== void 0 || args.model !== void 0 || args.workerProfile !== void 0 || args.workerProfileModel !== void 0 || args.apiKey !== void 0 || args.apiKeyEnv !== void 0 || args.gateway || args.lifecycle !== void 0 || args.setOrchestrator || args.setWorkerDefault || args.contextWindow !== void 0 || args.maxTokens !== void 0 || args.reasoning !== void 0;
|
|
3738
|
+
const nonInteractive = runtime !== null && hasTargetSetupFlag;
|
|
3739
|
+
if (nonInteractive && runtime) return runNonInteractive(runtime, args);
|
|
3740
|
+
if ((runtimeId !== void 0 || hasTargetSetupFlag) && !input.isTTY) {
|
|
3741
|
+
printError(
|
|
3742
|
+
runtime === null ? "--runtime is required when configuring a target non-interactively" : "--id is required when passing flags non-interactively"
|
|
3743
|
+
);
|
|
3744
|
+
return 2;
|
|
3745
|
+
}
|
|
3746
|
+
const rl = createInterface2({ input: inStream, output: outStream });
|
|
3747
|
+
try {
|
|
3748
|
+
return await runInteractive(rl, runtime, args, { in: inStream, out: outStream });
|
|
3749
|
+
} catch (err) {
|
|
3750
|
+
printError(err instanceof Error ? err.message : String(err));
|
|
3751
|
+
return 1;
|
|
3752
|
+
} finally {
|
|
3753
|
+
rl.close();
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
export {
|
|
3758
|
+
runTargetRemove,
|
|
3759
|
+
runTargetRename,
|
|
3760
|
+
runConfigureCommand
|
|
3761
|
+
};
|
|
3762
|
+
//# sourceMappingURL=chunk-7E7I3WLS.js.map
|