rovecode 0.4.0-beta.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -72
- package/THIRD_PARTY_NOTICES.md +0 -44
- package/bin/rovecode.ts +21 -0
- package/package.json +16 -38
- package/src/account/keys.ts +97 -0
- package/src/account/login.ts +158 -0
- package/src/account/provision.ts +47 -0
- package/src/account/store.ts +63 -0
- package/src/acp/server.ts +373 -0
- package/src/cli/account-cmd.ts +116 -0
- package/src/cli/connect.ts +244 -0
- package/src/cli/context-cmd.ts +199 -0
- package/src/cli/dispatch.ts +109 -0
- package/src/cli/doctor.ts +324 -0
- package/src/cli/export.ts +278 -0
- package/src/cli/help.ts +240 -0
- package/src/cli/is-tui-invocation.ts +8 -0
- package/src/cli/main.ts +599 -0
- package/src/cli/market-cmd.ts +658 -0
- package/src/cli/mcp-market-cmd.ts +299 -0
- package/src/cli/output.ts +382 -0
- package/src/cli/repl.ts +172 -0
- package/src/cli/resume.ts +32 -0
- package/src/cli/run-limits.ts +78 -0
- package/src/cli/runtime.ts +792 -0
- package/src/cli/setup.ts +187 -0
- package/src/cli/update-cmd.ts +78 -0
- package/src/cli/workflow-cmd.ts +100 -0
- package/src/coding/checkpoints.ts +270 -0
- package/src/coding/diff.ts +136 -0
- package/src/coding/files.ts +339 -0
- package/src/coding/hashline.ts +319 -0
- package/src/coding/lsp.ts +406 -0
- package/src/coding/repomap-cache.ts +99 -0
- package/src/coding/repomap-files.ts +110 -0
- package/src/coding/repomap.ts +392 -0
- package/src/core/compaction.ts +399 -0
- package/src/core/config.ts +289 -0
- package/src/core/context-report.ts +228 -0
- package/src/core/context.ts +60 -0
- package/src/core/count-remote.ts +107 -0
- package/src/core/execpolicy-rules.ts +196 -0
- package/src/core/execpolicy.ts +385 -0
- package/src/core/executor.ts +397 -0
- package/src/core/guardrails.ts +400 -0
- package/src/core/hooks.ts +398 -0
- package/src/core/images.ts +230 -0
- package/src/core/intro.ts +236 -0
- package/src/core/loop.ts +621 -0
- package/src/core/modes.ts +372 -0
- package/src/core/orchestrator.ts +207 -0
- package/src/core/reflection.ts +165 -0
- package/src/core/sandbox-config.ts +167 -0
- package/src/core/session-images.ts +73 -0
- package/src/core/session.ts +398 -0
- package/src/core/settings.ts +98 -0
- package/src/core/stuck-detector.ts +273 -0
- package/src/core/tasks.ts +374 -0
- package/src/core/token-scale.ts +108 -0
- package/src/core/tool-output-budget.ts +166 -0
- package/src/core/tools.ts +288 -0
- package/src/core/types.ts +330 -0
- package/src/core/update-check.ts +171 -0
- package/src/core/update.ts +158 -0
- package/src/core/usage.ts +204 -0
- package/src/core/validate.ts +121 -0
- package/src/core/verify-gate.ts +159 -0
- package/src/core/verify.ts +237 -0
- package/src/core/voice.ts +158 -0
- package/src/core/win-job.ts +183 -0
- package/src/design/audit.ts +797 -0
- package/src/design/direction.ts +190 -0
- package/src/design/rules.ts +157 -0
- package/src/eval/bench.ts +150 -0
- package/src/eval/gauntlet-runner.ts +218 -0
- package/src/eval/gauntlet.ts +226 -0
- package/src/eval/grader.ts +186 -0
- package/src/eval/record.ts +202 -0
- package/src/eval/redact.ts +141 -0
- package/src/eval/replay.ts +147 -0
- package/src/eval/trajectory.ts +373 -0
- package/src/index.ts +17 -0
- package/src/market/catalogs/mcp-docs.json +111 -0
- package/src/market/catalogs/plugins.json +111 -0
- package/src/market/catalogs/skills.json +478 -0
- package/src/market/clone.ts +72 -0
- package/src/market/context-cost.ts +121 -0
- package/src/market/digest.ts +106 -0
- package/src/market/index.ts +22 -0
- package/src/market/install.ts +578 -0
- package/src/market/manifest.ts +187 -0
- package/src/market/prereq.ts +145 -0
- package/src/market/registry.ts +363 -0
- package/src/market/resolve.ts +111 -0
- package/src/market/types.ts +236 -0
- package/src/market/validate.ts +227 -0
- package/src/mcp/client.ts +431 -0
- package/src/mcp/config.ts +239 -0
- package/src/mcp/local-package.ts +211 -0
- package/src/mcp/market-catalog.ts +84 -0
- package/src/mcp/market-install.ts +289 -0
- package/src/mcp/market.ts +0 -0
- package/src/mcp/tools.ts +131 -0
- package/src/mcp/trust.ts +49 -0
- package/src/memory/blocks.ts +175 -0
- package/src/memory/recall.ts +355 -0
- package/src/memory/store.ts +105 -0
- package/src/memory/tools.ts +99 -0
- package/src/plugins/cli.ts +123 -0
- package/src/plugins/discover.ts +108 -0
- package/src/plugins/index.ts +50 -0
- package/src/plugins/init.ts +140 -0
- package/src/plugins/install.ts +184 -0
- package/src/plugins/load.ts +149 -0
- package/src/plugins/manifest.ts +106 -0
- package/src/plugins/state.ts +83 -0
- package/src/providers/auth.ts +293 -0
- package/src/providers/cache.ts +223 -0
- package/src/providers/catalog-local.ts +160 -0
- package/src/providers/catalog.ts +408 -0
- package/src/providers/middleware-context.ts +86 -0
- package/src/providers/middleware.ts +373 -0
- package/src/providers/profile-glm53.ts +111 -0
- package/src/providers/profile-sonnet5-persona.ts +65 -0
- package/src/providers/profile-sonnet5-voice.ts +23 -0
- package/src/providers/profiles.ts +156 -0
- package/src/providers/provider-config.ts +311 -0
- package/src/providers/registry.ts +302 -0
- package/src/providers/response-validation.ts +80 -0
- package/src/providers/retry.ts +234 -0
- package/src/providers/router.ts +294 -0
- package/src/providers/sse.ts +26 -0
- package/src/providers/stream-errors.ts +117 -0
- package/src/providers/stream.ts +569 -0
- package/src/providers/thinking.ts +189 -0
- package/src/providers/wire-messages.ts +129 -0
- package/src/sdk/client.ts +225 -0
- package/src/sdk/index.ts +3 -0
- package/src/server/dashboard.ts +144 -0
- package/src/server/http.ts +343 -0
- package/src/server/openapi.ts +246 -0
- package/src/sextant/card-hits.ts +102 -0
- package/src/sextant/card-keys.ts +55 -0
- package/src/sextant/context-source.ts +157 -0
- package/src/sextant/draw-agents.ts +273 -0
- package/src/sextant/draw-code.ts +388 -0
- package/src/sextant/draw-context.ts +222 -0
- package/src/sextant/draw-frame.ts +164 -0
- package/src/sextant/draw-market.ts +573 -0
- package/src/sextant/draw-messages.ts +386 -0
- package/src/sextant/draw-pet.ts +230 -0
- package/src/sextant/draw-plan.ts +159 -0
- package/src/sextant/draw-tabs.ts +85 -0
- package/src/sextant/draw-util.ts +65 -0
- package/src/sextant/engine.ts +230 -0
- package/src/sextant/frame-hits.ts +25 -0
- package/src/sextant/frame.ts +101 -0
- package/src/sextant/git-status.ts +197 -0
- package/src/sextant/grid.ts +59 -0
- package/src/sextant/input.ts +119 -0
- package/src/sextant/keys.ts +488 -0
- package/src/sextant/layout.ts +86 -0
- package/src/sextant/local-commands.ts +156 -0
- package/src/sextant/market-source.ts +287 -0
- package/src/sextant/mentions.ts +141 -0
- package/src/sextant/message-hits.ts +26 -0
- package/src/sextant/model.ts +387 -0
- package/src/sextant/overlays.ts +451 -0
- package/src/sextant/panel-hits.ts +38 -0
- package/src/sextant/pet.ts +399 -0
- package/src/sextant/screen.ts +324 -0
- package/src/sextant/scroll-hits.ts +66 -0
- package/src/sextant/scrollbar.ts +82 -0
- package/src/sextant/selection.ts +123 -0
- package/src/sextant/sextant-bridge.ts +174 -0
- package/src/sextant/sextant-cards.ts +142 -0
- package/src/sextant/sextant-diff-base.ts +63 -0
- package/src/sextant/sextant-files.ts +154 -0
- package/src/sextant/sextant-frame-loop.ts +314 -0
- package/src/sextant/sextant-renderer.ts +478 -0
- package/src/sextant/sextant-repo.ts +131 -0
- package/src/sextant/theme.ts +66 -0
- package/src/sextant/tool-rows.ts +189 -0
- package/src/sextant/types.ts +473 -0
- package/src/skills/index.ts +306 -0
- package/src/skills/tools.ts +69 -0
- package/src/skills/versioned.ts +227 -0
- package/src/telemetry/otel.ts +353 -0
- package/src/telemetry/otlp.ts +68 -0
- package/src/tools/ask-user.ts +156 -0
- package/src/tools/design.ts +151 -0
- package/src/tools/evalcell.ts +338 -0
- package/src/tools/html-text.ts +139 -0
- package/src/tools/provider.ts +149 -0
- package/src/tools/task.ts +216 -0
- package/src/tools/todo.ts +320 -0
- package/src/tools/webfetch.ts +331 -0
- package/src/tui/app.ts +608 -0
- package/src/tui/attach.ts +127 -0
- package/src/tui/checkpoints-cmd.ts +70 -0
- package/src/tui/clipboard-image.ts +81 -0
- package/src/tui/commands.ts +277 -0
- package/src/tui/cost.ts +108 -0
- package/src/tui/info-cmd.ts +144 -0
- package/src/tui/mcp-cmd.ts +128 -0
- package/src/tui/modes-cmd.ts +45 -0
- package/src/tui/overlays.ts +97 -0
- package/src/tui/pi-renderer.ts +424 -0
- package/src/tui/providers-cmd.ts +366 -0
- package/src/tui/renderer.ts +101 -0
- package/src/tui/replay-marker.ts +29 -0
- package/src/tui/session-cmd.ts +146 -0
- package/src/tui/sextant-attach.ts +68 -0
- package/src/tui/sextant-io.ts +184 -0
- package/src/tui/sextant-smoke.ts +110 -0
- package/src/tui/smoke.ts +72 -0
- package/src/tui/theme.ts +59 -0
- package/src/tui/todo-label.ts +7 -0
- package/src/workflow/engine.ts +266 -0
- package/tsconfig.json +30 -0
- package/vendor/pi-tui/LICENSE +21 -0
- package/vendor/pi-tui/PATCHES.md +12 -0
- package/vendor/pi-tui/PROVENANCE.md +12 -0
- package/vendor/pi-tui/README.upstream.md +854 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
- package/vendor/pi-tui/src/autocomplete.ts +827 -0
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
- package/vendor/pi-tui/src/components/box.ts +138 -0
- package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
- package/vendor/pi-tui/src/components/editor.ts +2364 -0
- package/vendor/pi-tui/src/components/h-stack.ts +45 -0
- package/vendor/pi-tui/src/components/image.ts +128 -0
- package/vendor/pi-tui/src/components/input.ts +448 -0
- package/vendor/pi-tui/src/components/loader.ts +93 -0
- package/vendor/pi-tui/src/components/markdown.ts +1016 -0
- package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
- package/vendor/pi-tui/src/components/select-list.ts +230 -0
- package/vendor/pi-tui/src/components/settings-list.ts +277 -0
- package/vendor/pi-tui/src/components/spacer.ts +29 -0
- package/vendor/pi-tui/src/components/stack.ts +155 -0
- package/vendor/pi-tui/src/components/text.ts +108 -0
- package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
- package/vendor/pi-tui/src/components/v-stack.ts +34 -0
- package/vendor/pi-tui/src/editor-component.ts +75 -0
- package/vendor/pi-tui/src/fuzzy.ts +138 -0
- package/vendor/pi-tui/src/index.ts +149 -0
- package/vendor/pi-tui/src/keybindings.ts +321 -0
- package/vendor/pi-tui/src/keys.ts +1402 -0
- package/vendor/pi-tui/src/kill-ring.ts +47 -0
- package/vendor/pi-tui/src/latex.ts +1381 -0
- package/vendor/pi-tui/src/layout-node.ts +52 -0
- package/vendor/pi-tui/src/layout.ts +411 -0
- package/vendor/pi-tui/src/native-modifiers.ts +60 -0
- package/vendor/pi-tui/src/native-module-path.ts +32 -0
- package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
- package/vendor/pi-tui/src/terminal-colors.ts +74 -0
- package/vendor/pi-tui/src/terminal-image.ts +701 -0
- package/vendor/pi-tui/src/terminal.ts +554 -0
- package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
- package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
- package/vendor/pi-tui/src/tui.ts +1264 -0
- package/vendor/pi-tui/src/undo-stack.ts +29 -0
- package/vendor/pi-tui/src/utils.ts +1327 -0
- package/vendor/pi-tui/src/word-navigation.ts +118 -0
- package/vendor/pi-tui/test/test-themes.ts +39 -0
- package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
- package/CHANGELOG.md +0 -527
- package/bin/rovecode.js +0 -24
- package/dist/cli/app-j6gn14w3.js +0 -2
- package/dist/cli/ask-user-cwstt8fz.js +0 -2
- package/dist/cli/auth-login-9bbp9915.js +0 -2
- package/dist/cli/auth-m8p9grty.js +0 -2
- package/dist/cli/bench-16zqdms5.js +0 -9
- package/dist/cli/catalog-1xchffa4.js +0 -2
- package/dist/cli/cli-1n1zb64f.js +0 -2
- package/dist/cli/client-2t9gjkck.js +0 -2
- package/dist/cli/commands-exafvm2b.js +0 -2
- package/dist/cli/connect-6zde0kn3.js +0 -2
- package/dist/cli/context-cmd-5t43wgqt.js +0 -2
- package/dist/cli/context-report-kt01pw8y.js +0 -2
- package/dist/cli/count-remote-ap7x3vh6.js +0 -2
- package/dist/cli/design-ne5zszyh.js +0 -2
- package/dist/cli/dispatch-2r5myxye.js +0 -2
- package/dist/cli/doctor-ws4fh4tn.js +0 -3
- package/dist/cli/executor-bdrjn634.js +0 -2
- package/dist/cli/export-1mxb9g5p.js +0 -2
- package/dist/cli/files-g104xghh.js +0 -2
- package/dist/cli/gauntlet-07xrjpj7.js +0 -2
- package/dist/cli/gauntlet-runner-xvy64436.js +0 -10
- package/dist/cli/gauntlet-wave3-jm91yt5w.js +0 -5
- package/dist/cli/gauntlet-wave4-r13py7p1.js +0 -14
- package/dist/cli/hashline-znvrat11.js +0 -2
- package/dist/cli/http-xafw6fsh.js +0 -143
- package/dist/cli/index-1sgjm25y.js +0 -2
- package/dist/cli/init-g2m0tn4m.js +0 -51
- package/dist/cli/install-avaqjjqq.js +0 -2
- package/dist/cli/loop-mmpfft01.js +0 -2
- package/dist/cli/main-0904f6ps.js +0 -5
- package/dist/cli/main-0ab9fc26.js +0 -9
- package/dist/cli/main-0jys2ccn.js +0 -3
- package/dist/cli/main-0mtcdbs7.js +0 -3
- package/dist/cli/main-0z1w2zsg.js +0 -3
- package/dist/cli/main-1dchs7xv.js +0 -18
- package/dist/cli/main-1ereejm1.js +0 -3
- package/dist/cli/main-1k1kw6b5.js +0 -3
- package/dist/cli/main-27y4sm2k.js +0 -38
- package/dist/cli/main-2wwjex5j.js +0 -58
- package/dist/cli/main-2yeveeve.js +0 -6
- package/dist/cli/main-2yfck9b5.js +0 -3
- package/dist/cli/main-2zmzgkwh.js +0 -3
- package/dist/cli/main-351pz3z7.js +0 -7
- package/dist/cli/main-3gjqfh7a.js +0 -6
- package/dist/cli/main-3nf3kgve.js +0 -3
- package/dist/cli/main-3pjrb2hd.js +0 -3
- package/dist/cli/main-3rxcvgna.js +0 -19
- package/dist/cli/main-4b3jgy66.js +0 -19
- package/dist/cli/main-4wndhjdc.js +0 -7
- package/dist/cli/main-4xcmvxnk.js +0 -3
- package/dist/cli/main-5tbz0wbz.js +0 -4
- package/dist/cli/main-5ywnwthm.js +0 -3
- package/dist/cli/main-6b62vkz0.js +0 -14
- package/dist/cli/main-6dnk69vp.js +0 -3
- package/dist/cli/main-6genrmhs.js +0 -136
- package/dist/cli/main-73g7eff4.js +0 -15
- package/dist/cli/main-7c5thhjd.js +0 -5
- package/dist/cli/main-7rn6bqje.js +0 -3
- package/dist/cli/main-80haw7qk.js +0 -4
- package/dist/cli/main-875s60s2.js +0 -4
- package/dist/cli/main-8kjxbpw4.js +0 -8
- package/dist/cli/main-90ds1z4e.js +0 -10
- package/dist/cli/main-9etavkew.js +0 -3
- package/dist/cli/main-a9njrkk1.js +0 -3
- package/dist/cli/main-aecrjq2d.js +0 -12
- package/dist/cli/main-ck9asesq.js +0 -9
- package/dist/cli/main-cta9racd.js +0 -4
- package/dist/cli/main-ddv7j2ag.js +0 -3
- package/dist/cli/main-dfreez27.js +0 -10
- package/dist/cli/main-f7rw7des.js +0 -3
- package/dist/cli/main-ggcn7rd7.js +0 -5
- package/dist/cli/main-gzkmycnv.js +0 -3
- package/dist/cli/main-hq51jg8v.js +0 -18
- package/dist/cli/main-jft389w9.js +0 -8
- package/dist/cli/main-k1eqkg83.js +0 -3
- package/dist/cli/main-k2y8a2aw.js +0 -9
- package/dist/cli/main-kcpbykxz.js +0 -4
- package/dist/cli/main-kd488vje.js +0 -22
- package/dist/cli/main-kh32yvgk.js +0 -5
- package/dist/cli/main-kqxnqjnv.js +0 -25
- package/dist/cli/main-kyn0xnsg.js +0 -3
- package/dist/cli/main-m1kk6fp5.js +0 -21
- package/dist/cli/main-mv40pcr2.js +0 -4
- package/dist/cli/main-n0t3973w.js +0 -3
- package/dist/cli/main-nqveez48.js +0 -4
- package/dist/cli/main-pknhvrmj.js +0 -3
- package/dist/cli/main-pn1w7a7j.js +0 -3
- package/dist/cli/main-prxxs70n.js +0 -4
- package/dist/cli/main-q3vsesf9.js +0 -3
- package/dist/cli/main-qsevpgsv.js +0 -3
- package/dist/cli/main-rdgdw24b.js +0 -25
- package/dist/cli/main-rfth4tbm.js +0 -16
- package/dist/cli/main-rg0wn0xf.js +0 -5
- package/dist/cli/main-sdmxhtv8.js +0 -4
- package/dist/cli/main-skbp13js.js +0 -18
- package/dist/cli/main-t4xnd213.js +0 -7
- package/dist/cli/main-vqak588n.js +0 -4
- package/dist/cli/main-w2n1303f.js +0 -9
- package/dist/cli/main-wbrdspr2.js +0 -5
- package/dist/cli/main-wsrg79c1.js +0 -7
- package/dist/cli/main-x4r0fne4.js +0 -5
- package/dist/cli/main-xea2f3tn.js +0 -6
- package/dist/cli/main-xg704a3c.js +0 -3
- package/dist/cli/main-xvnrabfp.js +0 -16
- package/dist/cli/main-xy53xf0r.js +0 -4
- package/dist/cli/main-y1fqy60y.js +0 -3
- package/dist/cli/main-yn8cd281.js +0 -34
- package/dist/cli/main-yr0ksc0h.js +0 -4
- package/dist/cli/main-z2ex2vyf.js +0 -4
- package/dist/cli/main-z3aayzvq.js +0 -3
- package/dist/cli/main-zaqh35jg.js +0 -3
- package/dist/cli/main-zc2e8e46.js +0 -4
- package/dist/cli/main-zzrfw6cf.js +0 -13
- package/dist/cli/main.js +0 -280
- package/dist/cli/market-cmd-e14kmx9n.js +0 -5
- package/dist/cli/mcp-login-wq7ktdek.js +0 -2
- package/dist/cli/mcp-market-cmd-9mg3jecy.js +0 -2
- package/dist/cli/notify-b7qc0cjb.js +0 -2
- package/dist/cli/oauth-z8whcgfx.js +0 -2
- package/dist/cli/output-b3ewj3ps.js +0 -16
- package/dist/cli/profiles-6mr5he5e.js +0 -2
- package/dist/cli/provider-config-g7j42q8x.js +0 -2
- package/dist/cli/provider-jr1y8vvm.js +0 -2
- package/dist/cli/registry-s8yk86g0.js +0 -2
- package/dist/cli/registry-t6p8d4mn.js +0 -2
- package/dist/cli/repl-bajwe1mh.js +0 -11
- package/dist/cli/resume-rwn9nz7y.js +0 -2
- package/dist/cli/run-flags-nah7ndpt.js +0 -2
- package/dist/cli/runtime-n7gafzhb.js +0 -2
- package/dist/cli/sandbox-config-emdy18x4.js +0 -2
- package/dist/cli/server-b0nvs2bn.js +0 -5
- package/dist/cli/session-arg-y75wd4kj.js +0 -2
- package/dist/cli/session-j62evmjq.js +0 -2
- package/dist/cli/sessions-cmd-tsnwz0ns.js +0 -7
- package/dist/cli/settings-df10wfez.js +0 -2
- package/dist/cli/setup-jzvv72fg.js +0 -2
- package/dist/cli/sextant-smoke-37m81ke6.js +0 -5
- package/dist/cli/skills-cmd-gjxnxnhx.js +0 -2
- package/dist/cli/smoke-p7748apt.js +0 -8
- package/dist/cli/start-chat-s4st3mm0.js +0 -12
- package/dist/cli/stream-gmeyewds.js +0 -2
- package/dist/cli/task-gh0kkp3n.js +0 -2
- package/dist/cli/tasks-z1kfpe8e.js +0 -2
- package/dist/cli/thinking-0eqkrz6t.js +0 -2
- package/dist/cli/todo-5brcrt9m.js +0 -2
- package/dist/cli/tools-7pzm0vj9.js +0 -2
- package/dist/cli/tools-s635p6s8.js +0 -2
- package/dist/cli/trust-cmd-cjav8zgm.js +0 -2
- package/dist/cli/update-check-pt31bm2f.js +0 -2
- package/dist/cli/update-cmd-tk131s9t.js +0 -2
- package/dist/cli/voice-56nabd8d.js +0 -2
- package/dist/cli/webfetch-xd8q596m.js +0 -2
- package/dist/cli/websearch-5hkf98k1.js +0 -2
- package/dist/cli/workflow-cmd-cy3cvzjp.js +0 -4
- package/dist/cli/workspace-q10g5z3e.js +0 -2
- package/dist/lib/index.js +0 -62
- package/dist/lib/models-index.json +0 -1
- package/dist/lib/plugins.js +0 -55
- package/dist/lib/providers.js +0 -17
- package/dist/lib/public-api.js +0 -20
- package/dist/lib/sdk.js +0 -360
- /package/{dist/cli → src/providers}/models-index.json +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** Pure CLI argv parsing — separated from main.ts (which runs at import) so
|
|
2
|
+
* dispatch is unit-testable. Flags (--*) never become the command, and neither
|
|
3
|
+
* does the VALUE of a value-taking flag: `rovecode --out o.md export abc` must
|
|
4
|
+
* dispatch `export`, not `o.md` — an unknown cmd falls through to the bare-
|
|
5
|
+
* prompt one-shot, which spends tokens on a real provider. */
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { parseEffort, type ThinkingEffort } from "../core/types.ts";
|
|
9
|
+
|
|
10
|
+
export interface CliInvocation {
|
|
11
|
+
/** first non-flag arg after the script path; "" = interactive default */
|
|
12
|
+
cmd: string;
|
|
13
|
+
plain: boolean;
|
|
14
|
+
yolo: boolean;
|
|
15
|
+
/** the middle tier: writes inside the workspace stop asking (ROVECODE_ACCEPT_EDITS=1 does the same) */
|
|
16
|
+
acceptEdits: boolean;
|
|
17
|
+
/** `--effort off|low|medium|high` — how hard the model thinks (ROVECODE_EFFORT does the same); absent when not given */
|
|
18
|
+
effort?: ThinkingEffort;
|
|
19
|
+
/** port #44: `--classic` forces the pi-tui chat (the sextant surface is the default on a capable TTY) */
|
|
20
|
+
classic: boolean;
|
|
21
|
+
/** port #44: `--pet <name>` names the sextant pet (TUI-only value flag; absent when not given) */
|
|
22
|
+
pet?: string;
|
|
23
|
+
/** args after the command, flags excluded (one-shot prompt words, ids) */
|
|
24
|
+
rest: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Every flag the CLI hand-parses a VALUE for out of process.argv: --resume
|
|
28
|
+
* (main.ts, TUI session id), --key (cmdAuth, key name), --out (export.ts, target
|
|
29
|
+
* path), --output (output.ts, cmdRun output mode), --pet (here, the sextant pet
|
|
30
|
+
* name), --protocol / --key-env / --model / --scope (cmdProvider, `rovecode provider add`;
|
|
31
|
+
* providers/registry.ts parseAddArgs). parseCli only skips the value when locating cmd;
|
|
32
|
+
* the owners still read it themselves, and `rest` keeps post-command values
|
|
33
|
+
* (cmdAuth/export.ts/output.ts drop their own). Add here when a new value flag lands. */
|
|
34
|
+
/** Every flag that takes a value. A flag missing from this set does not merely parse oddly: its VALUE
|
|
35
|
+
* becomes a prompt word. `rovecode run "yazi golgesi gozukmuyor" --max-cost 0.15` sent the model
|
|
36
|
+
* "yazi golgesi gozukmuyor 0.15", and the model read the stray number as the shadow's opacity and used
|
|
37
|
+
* it. --max-cost landed in 60456d6 without being added here; the same class of bug had just been fixed
|
|
38
|
+
* for every other value flag in the same commit, which is exactly why the table has to be one list. */
|
|
39
|
+
export const VALUE_FLAGS: ReadonlySet<string> = new Set(["--resume", "--key", "--out", "--output", "--pet", "--effort", "--protocol", "--key-env", "--model", "--scope", "--max-turns", "--max-seconds", "--max-cost", "--api", "--token"]);
|
|
40
|
+
|
|
41
|
+
/** file extensions a stray argv word tends to carry: code, docs, data. Not a guess at natural language —
|
|
42
|
+
* a word that ends like a file is treated like one, a word that does not is left alone */
|
|
43
|
+
const FILE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|c|h|cpp|cs|swift|sh|ps1|bat|md|mdx|txt|json|jsonc|yaml|yml|toml|ini|env|html|css|scss|svg|csv|sql|lock)$/i;
|
|
44
|
+
|
|
45
|
+
/** A first word that is shaped like a PATH rather than a command or a sentence: it starts the way paths
|
|
46
|
+
* start (./ ../ / .\ ..\ \ or a drive letter), or ends the way files end, or names something that
|
|
47
|
+
* exists on disk. Only single words are ever path-shaped — a sentence is a prompt however it begins.
|
|
48
|
+
* main.ts asks before sending such a word to the provider: an unknown first word falls through to the
|
|
49
|
+
* one-shot `run`, and on 2026-09-06 `rovecode ./src/cli/main.ts` typed by mistake became a billed prompt.
|
|
50
|
+
* `exists` is injectable for tests. */
|
|
51
|
+
export function pathShaped(word: string, exists: (p: string) => boolean = existsSync): boolean {
|
|
52
|
+
if (word.length === 0 || /\s/.test(word)) return false;
|
|
53
|
+
if (/^(\.\.?[\\/]|[\\/]|[A-Za-z]:[\\/])/.test(word)) return true;
|
|
54
|
+
if (FILE_EXT.test(word)) return true;
|
|
55
|
+
try { return exists(word); } catch { return false; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Protect a mistyped command from becoming a paid one-shot prompt. Only a single, plain
|
|
59
|
+
* command-like word is considered; sentences and punctuation-rich prompts remain prompts. */
|
|
60
|
+
export function suggestCommand(word: string, commands: Iterable<string>): string | undefined {
|
|
61
|
+
if (!/^[a-z][a-z-]{1,23}$/i.test(word)) return undefined;
|
|
62
|
+
const distance = (a: string, b: string): number => {
|
|
63
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
64
|
+
for (let i = 1; i <= a.length; i++) {
|
|
65
|
+
let diagonal = row[0]!;
|
|
66
|
+
row[0] = i;
|
|
67
|
+
for (let j = 1; j <= b.length; j++) {
|
|
68
|
+
const above = row[j]!;
|
|
69
|
+
row[j] = Math.min(row[j]! + 1, row[j - 1]! + 1, diagonal + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
70
|
+
diagonal = above;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return row[b.length]!;
|
|
74
|
+
};
|
|
75
|
+
const needle = word.toLowerCase();
|
|
76
|
+
let best: { command: string; distance: number } | undefined;
|
|
77
|
+
for (const command of commands) {
|
|
78
|
+
const d = distance(needle, command.toLowerCase());
|
|
79
|
+
if (!best || d < best.distance || (d === best.distance && command < best.command)) best = { command, distance: d };
|
|
80
|
+
}
|
|
81
|
+
const limit = needle.length <= 4 ? 1 : 2;
|
|
82
|
+
return best && best.distance <= limit ? best.command : undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function parseCli(argv: string[]): CliInvocation {
|
|
86
|
+
const args = argv.slice(2);
|
|
87
|
+
const isFlag = (a: string) => a.startsWith("-");
|
|
88
|
+
// a token is the command unless it is a flag or the value of the value flag before it
|
|
89
|
+
const cmdIdx = args.findIndex((a, i) => !isFlag(a) && !(i > 0 && VALUE_FLAGS.has(args[i - 1]!)));
|
|
90
|
+
const wantsHelp = args.includes("--help") || args.includes("-h");
|
|
91
|
+
// --pet <name>: a flag-shaped "value" is not a value (the flag after it stays a flag)
|
|
92
|
+
const petIdx = args.indexOf("--pet");
|
|
93
|
+
const petArg = petIdx !== -1 ? args[petIdx + 1] : undefined;
|
|
94
|
+
const pet = petArg !== undefined && !isFlag(petArg) ? petArg : undefined;
|
|
95
|
+
// --effort <level>: an unrecognized word is DROPPED, not read as "off" — a typo must leave the
|
|
96
|
+
// runtime's own default in place rather than silently turning thinking off
|
|
97
|
+
const effIdx = args.indexOf("--effort");
|
|
98
|
+
const effortArg = parseEffort(effIdx !== -1 ? args[effIdx + 1] : undefined);
|
|
99
|
+
return {
|
|
100
|
+
cmd: wantsHelp && cmdIdx === -1 ? "help" : cmdIdx === -1 ? "" : args[cmdIdx]!,
|
|
101
|
+
plain: args.includes("--plain"),
|
|
102
|
+
yolo: args.includes("--yolo"),
|
|
103
|
+
acceptEdits: args.includes("--accept-edits"),
|
|
104
|
+
...(effortArg !== undefined ? { effort: effortArg } : {}),
|
|
105
|
+
classic: args.includes("--classic"),
|
|
106
|
+
...(pet !== undefined ? { pet } : {}),
|
|
107
|
+
rest: cmdIdx === -1 ? [] : args.slice(cmdIdx + 1).filter((a) => !isFlag(a)),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/** `rovecode doctor` — "what is wrong with my setup", answered once, in one pass.
|
|
2
|
+
*
|
|
3
|
+
* Every check here already existed and already spoke — each at its own moment: the LSP note at boot, the
|
|
4
|
+
* provider hint when a run fails, the prerequisite row inside an install plan, the MCP loader's skipped-server
|
|
5
|
+
* warnings, the trust gate's line. A person whose setup is half-wrong met them one at a time, in the order
|
|
6
|
+
* the day happened to raise them. This runs the same code and lays the answers side by side.
|
|
7
|
+
*
|
|
8
|
+
* Rules it keeps:
|
|
9
|
+
* - It invents nothing. Every row is produced by the module that owns that decision (providers/registry.ts,
|
|
10
|
+
* mcp/config.ts, mcp/market-install.ts, coding/lsp.ts, market/prereq.ts, core/settings.ts). A check that
|
|
11
|
+
* would need code this repository does not have is listed under `notChecked`, by name.
|
|
12
|
+
* - It never prints a secret. Provider rows carry ids, scopes and key NAMES; the registry's own redaction is
|
|
13
|
+
* not even consulted, because nothing here needs the value.
|
|
14
|
+
* - It makes no provider request: reachability would be a billed call, so it is a listed non-check. MCP
|
|
15
|
+
* servers ARE connected (that is a local process, and "does it connect" is the question); `--no-connect`
|
|
16
|
+
* skips it and says so.
|
|
17
|
+
* - Exit 0 = nothing is broken; 1 = something the person must fix before a run can work as configured. A
|
|
18
|
+
* fresh install with no provider is a FINDING (exit 0, status "note"), not a failure — nothing is broken,
|
|
19
|
+
* something is not done yet. `--json` is one document, on every exit. */
|
|
20
|
+
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { rovecodeHome } from "../providers/auth.ts";
|
|
25
|
+
import { ProviderRegistry } from "../providers/registry.ts";
|
|
26
|
+
import { isConfigured } from "../providers/provider-config.ts";
|
|
27
|
+
import { resolvePermission, settingsPath } from "../core/settings.ts";
|
|
28
|
+
import type { PermissionLevel } from "../core/types.ts";
|
|
29
|
+
import { checkPrereq, type PrereqEnv } from "../market/prereq.ts";
|
|
30
|
+
import { lspAvailabilityNote } from "../coding/lsp.ts";
|
|
31
|
+
import { configuredServers } from "../mcp/market-install.ts";
|
|
32
|
+
import { loadMcpConfig, placeholderHoles, type McpServerConfig } from "../mcp/config.ts";
|
|
33
|
+
import { mcpTrustStatus, trustedPredicate } from "../mcp/trust.ts";
|
|
34
|
+
import { loadState as loadPluginState } from "../plugins/state.ts";
|
|
35
|
+
import { launchesViaNpx, npxOfferLine } from "../mcp/local-package.ts";
|
|
36
|
+
import { resolveVerify, VERIFY_BLIND_SPOT, verifyLabelWithCost } from "../core/verify.ts";
|
|
37
|
+
|
|
38
|
+
export type DoctorStatus = "ok" | "note" | "warn" | "fail";
|
|
39
|
+
|
|
40
|
+
export interface DoctorCheck {
|
|
41
|
+
id: "home" | "provider" | "permission" | "tools" | "verify" | "mcp" | "checkpoints";
|
|
42
|
+
status: DoctorStatus;
|
|
43
|
+
summary: string;
|
|
44
|
+
detail?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface DoctorReport {
|
|
48
|
+
ok: boolean;
|
|
49
|
+
exitCode: 0 | 1;
|
|
50
|
+
cwd: string;
|
|
51
|
+
home: string;
|
|
52
|
+
checks: DoctorCheck[];
|
|
53
|
+
/** what this command did NOT look at, by name — so a clean report is never read as "everything is fine" */
|
|
54
|
+
notChecked: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface DoctorDeps {
|
|
58
|
+
cwd?: string;
|
|
59
|
+
/** the rovecode home; default rovecodeHome() (ROVECODE_HOME or ~/.rovecode) */
|
|
60
|
+
home?: string;
|
|
61
|
+
env?: Record<string, string | undefined>;
|
|
62
|
+
/** PATH lookup for the tools row — tests inject a fixed environment */
|
|
63
|
+
prereqEnv?: PrereqEnv;
|
|
64
|
+
/** the LSP probe — tests inject; default Bun.which */
|
|
65
|
+
which?: (name: string) => string | null;
|
|
66
|
+
/** try to connect every loadable MCP server (default true; `--no-connect` turns it off) */
|
|
67
|
+
connect?: boolean;
|
|
68
|
+
connectTimeoutMs?: number;
|
|
69
|
+
out?: (line: string) => void;
|
|
70
|
+
err?: (line: string) => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const DOCTOR_USAGE = [
|
|
74
|
+
"usage: rovecode doctor [--json] [--no-connect]",
|
|
75
|
+
" one pass over the setup: home · provider · permission level · tools on PATH · the verify check · MCP servers · checkpoints",
|
|
76
|
+
" --no-connect do not start the MCP servers to see whether they answer (they are otherwise connected and closed)",
|
|
77
|
+
" --json one document on stdout; exit 0 = nothing broken, 1 = something to fix (a missing provider is a note, not a failure)",
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const PERMISSIONS: readonly string[] = ["ask", "accept-edits", "auto"];
|
|
81
|
+
/** the size at which a shadow repository becomes worth mentioning: 14e9118 measured 232 MB as the bad case */
|
|
82
|
+
const CHECKPOINTS_WARN_BYTES = 200 * 1024 * 1024;
|
|
83
|
+
|
|
84
|
+
function userHome(env: Record<string, string | undefined>): string {
|
|
85
|
+
return (process.platform === "win32" ? env.USERPROFILE : env.HOME) || homedir();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function dirSize(root: string): { bytes: number; files: number } {
|
|
89
|
+
let bytes = 0, files = 0;
|
|
90
|
+
const walk = (d: string): void => {
|
|
91
|
+
let names: string[];
|
|
92
|
+
try { names = readdirSync(d); } catch { return; }
|
|
93
|
+
for (const n of names) {
|
|
94
|
+
const p = join(d, n);
|
|
95
|
+
try {
|
|
96
|
+
const st = statSync(p);
|
|
97
|
+
if (st.isDirectory()) walk(p); else { bytes += st.size; files++; }
|
|
98
|
+
} catch { /* vanished mid-walk: the number is a snapshot, not a ledger */ }
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
walk(root);
|
|
102
|
+
return { bytes, files };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const mb = (bytes: number): string => `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
106
|
+
|
|
107
|
+
/** the permission level in effect and WHERE it came from — the same ladder core/settings.ts resolvePermission
|
|
108
|
+
* walks, re-read here only to name the rung; the level itself is resolvePermission's answer */
|
|
109
|
+
function permissionCheck(cwd: string, env: Record<string, string | undefined>): DoctorCheck {
|
|
110
|
+
const level = resolvePermission(cwd, undefined, { ROVECODE_PERMISSION: env.ROVECODE_PERMISSION, ROVECODE_YOLO: env.ROVECODE_YOLO, ROVECODE_ACCEPT_EDITS: env.ROVECODE_ACCEPT_EDITS });
|
|
111
|
+
const fileLevel = (scope: "user" | "project"): PermissionLevel | undefined => {
|
|
112
|
+
try {
|
|
113
|
+
const raw = JSON.parse(readFileSync(settingsPath(scope, cwd), "utf8")) as { permission?: unknown };
|
|
114
|
+
return typeof raw.permission === "string" && PERMISSIONS.includes(raw.permission) ? raw.permission as PermissionLevel : undefined;
|
|
115
|
+
} catch { return undefined; }
|
|
116
|
+
};
|
|
117
|
+
const named = (env.ROVECODE_PERMISSION ?? "").trim().toLowerCase();
|
|
118
|
+
const source = PERMISSIONS.includes(named) ? "ROVECODE_PERMISSION"
|
|
119
|
+
: env.ROVECODE_YOLO === "1" ? "ROVECODE_YOLO=1"
|
|
120
|
+
: env.ROVECODE_ACCEPT_EDITS === "1" ? "ROVECODE_ACCEPT_EDITS=1"
|
|
121
|
+
: fileLevel("project") !== undefined ? settingsPath("project", cwd)
|
|
122
|
+
: fileLevel("user") !== undefined ? settingsPath("user", cwd)
|
|
123
|
+
: "the default";
|
|
124
|
+
const words = level === "auto" ? "auto — never asks (deny rules and plan mode still hold)"
|
|
125
|
+
: level === "accept-edits" ? "accept edits — writes inside this folder do not ask; shell, subagents, network and writes outside it do"
|
|
126
|
+
: "ask first — every write, shell command and subagent asks";
|
|
127
|
+
return { id: "permission", status: level === "auto" ? "note" : "ok", summary: `${words} · from ${source}`,
|
|
128
|
+
...(level === "auto" ? { detail: ["a one-shot run adds --yolo per run; the TUI's /yolo --save is what made it stick if this surprises you"] } : {}) };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function runDoctor(deps: DoctorDeps = {}): Promise<DoctorReport> {
|
|
132
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
133
|
+
const env = deps.env ?? process.env;
|
|
134
|
+
const home = deps.home ?? rovecodeHome();
|
|
135
|
+
const which = deps.which ?? ((n: string) => Bun.which(n));
|
|
136
|
+
const checks: DoctorCheck[] = [];
|
|
137
|
+
const notChecked: string[] = [
|
|
138
|
+
"whether the provider answers — a request would be billed; `rovecode provider test <id>` makes one small call on purpose",
|
|
139
|
+
"plugins — `rovecode plugin list` shows each one's state",
|
|
140
|
+
"the bash sandbox rung (wsl/docker) — probed at session start, not here",
|
|
141
|
+
"hooks — loaded at session start; a broken one is reported there",
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
// ---- home
|
|
145
|
+
{
|
|
146
|
+
const explicit = env.ROVECODE_HOME !== undefined;
|
|
147
|
+
const legacy = join(userHome(env), ".cumulus");
|
|
148
|
+
const legacyThere = existsSync(legacy);
|
|
149
|
+
const detail: string[] = [];
|
|
150
|
+
if (legacyThere) detail.push(explicit
|
|
151
|
+
? `a legacy ${legacy} is still on this machine; an explicit ROVECODE_HOME is never filled from it (only the default home inherits, once, and says so)`
|
|
152
|
+
: `a legacy ${legacy} is still on this machine; the default home was copied from it once and both are kept — delete the old one when you are sure`);
|
|
153
|
+
checks.push({ id: "home", status: existsSync(home) ? "ok" : "note",
|
|
154
|
+
summary: `${home}${explicit ? " (ROVECODE_HOME)" : ""}${existsSync(home) ? "" : " — does not exist yet; the first thing you store creates it"}`,
|
|
155
|
+
...(detail.length ? { detail } : {}) });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---- provider (ids, scopes and key NAMES only — never a value)
|
|
159
|
+
{
|
|
160
|
+
const reg = new ProviderRegistry(cwd, { env });
|
|
161
|
+
const def = reg.defaultRef();
|
|
162
|
+
const configured = reg.list().filter(isConfigured);
|
|
163
|
+
const warnings = reg.warnings();
|
|
164
|
+
const detail = configured.map((p) => `${p.id} (${p.scope}${p.noKey ? ", no key needed" : `, key from ${p.keySource === "env" ? `env ${p.keyEnv}` : p.keySource}`})`);
|
|
165
|
+
for (const w of warnings) detail.push(`providers.json: ${w}`);
|
|
166
|
+
if (def === null) {
|
|
167
|
+
checks.push({ id: "provider", status: warnings.length ? "warn" : "note",
|
|
168
|
+
summary: "no provider configured yet — nothing is broken, nothing is connected: `rovecode connect` (or provider add + auth set)", ...(detail.length ? { detail } : {}) });
|
|
169
|
+
} else {
|
|
170
|
+
checks.push({ id: "provider", status: warnings.length ? "warn" : "ok",
|
|
171
|
+
summary: `default ${def.provider}/${def.model || "(no model — rovecode model use <provider/model>)"} · ${configured.length} provider${configured.length === 1 ? "" : "s"} configured`, ...(detail.length ? { detail } : {}) });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---- permission
|
|
176
|
+
checks.push(permissionCheck(cwd, env));
|
|
177
|
+
|
|
178
|
+
// ---- MCP: what the files say, what the loader takes, what actually answers
|
|
179
|
+
const parseWarnings: string[] = [];
|
|
180
|
+
const rows = configuredServers(cwd, home, parseWarnings);
|
|
181
|
+
const loadWarnings: string[] = [];
|
|
182
|
+
const loaded = loadMcpConfig(cwd, loadWarnings, { home, env, trusted: trustedPredicate(loadPluginState(home)) });
|
|
183
|
+
const needsNpx = rows.some((r) => r.server.transport === "stdio" && /^npx(\.cmd)?$/i.test(r.server.command ?? ""));
|
|
184
|
+
const needsUvx = rows.some((r) => r.server.transport === "stdio" && /^uvx(\.exe)?$/i.test(r.server.command ?? ""));
|
|
185
|
+
{
|
|
186
|
+
const detail: string[] = [];
|
|
187
|
+
let status: DoctorStatus = "ok";
|
|
188
|
+
const raise = (s: DoctorStatus): void => { const rank = { ok: 0, note: 1, warn: 2, fail: 3 }; if (rank[s] > rank[status]) status = s; };
|
|
189
|
+
for (const w of parseWarnings) { detail.push(`✗ ${w}`); raise("fail"); }
|
|
190
|
+
const loadedNames = new Set(loaded.map((c) => c.name));
|
|
191
|
+
let connected = new Set<string>(); const failed = new Map<string, string>();
|
|
192
|
+
if (deps.connect !== false && loaded.length > 0) {
|
|
193
|
+
const { McpManager } = await import("../mcp/client.ts");
|
|
194
|
+
const mgr = new McpManager(loaded, deps.connectTimeoutMs !== undefined ? { connectTimeoutMs: deps.connectTimeoutMs } : {});
|
|
195
|
+
const r = await mgr.connect();
|
|
196
|
+
connected = new Set(r.connected);
|
|
197
|
+
for (const f of r.failed) failed.set(f.name, f.error);
|
|
198
|
+
await mgr.close().catch(() => {});
|
|
199
|
+
} else if (deps.connect === false && loaded.length > 0) {
|
|
200
|
+
notChecked.push("whether the MCP servers answer (--no-connect)");
|
|
201
|
+
}
|
|
202
|
+
for (const row of rows) {
|
|
203
|
+
const s: McpServerConfig = row.server;
|
|
204
|
+
const where = `${row.scope}${row.scope === "user" ? "" : ` ${row.file}`}`;
|
|
205
|
+
const trust = row.scope === "user" ? "trusted" : mcpTrustStatus(home, row.file);
|
|
206
|
+
const holes = placeholderHoles(s);
|
|
207
|
+
const skipped = loadWarnings.find((w) => w.includes(`server "${s.name}"`));
|
|
208
|
+
if (s.enabled === false) { detail.push(`· ${s.name} — disabled in ${where}`); continue; }
|
|
209
|
+
if (trust !== "trusted") { detail.push(`· ${s.name} — off: its file is not trusted on this machine (${row.file}) — rovecode mcp show, then rovecode mcp trust`); raise("note"); continue; }
|
|
210
|
+
if (holes.length) { detail.push(`! ${s.name} — skipped: still has ${holes.join(", ")} to fill in — edit the args in ${row.file}`); raise("warn"); continue; }
|
|
211
|
+
if (skipped !== undefined && !loadedNames.has(s.name)) { detail.push(`! ${s.name} — skipped: ${skipped.replace(/^.*?server "[^"]+" /, "")}`); raise("warn"); continue; }
|
|
212
|
+
if (!loadedNames.has(s.name)) { detail.push(`! ${s.name} — not loaded (${where})`); raise("warn"); continue; }
|
|
213
|
+
if (failed.has(s.name)) { detail.push(`✗ ${s.name} — did not connect: ${failed.get(s.name)}`); raise("fail"); continue; }
|
|
214
|
+
if (connected.has(s.name)) { detail.push(`✓ ${s.name} — connected (${where})`); continue; }
|
|
215
|
+
detail.push(`· ${s.name} — loads (${where}); not connected in this pass`);
|
|
216
|
+
}
|
|
217
|
+
const npx = rows.filter((r) => launchesViaNpx(r.server) && loadedNames.has(r.server.name)).map((r) => r.server.name);
|
|
218
|
+
const offer = npxOfferLine(npx);
|
|
219
|
+
if (offer !== undefined) detail.push(offer);
|
|
220
|
+
// "load" = would start: a disabled entry is kept by the loader (so `mcp list` can show it) but never launched
|
|
221
|
+
const starting = loaded.filter((c) => c.enabled !== false).length;
|
|
222
|
+
const summary = rows.length === 0 ? "no MCP servers configured — rovecode mcp search <query>"
|
|
223
|
+
: `${rows.length} configured · ${starting} load${deps.connect === false ? "" : ` · ${connected.size} connected · ${failed.size} failed`}`;
|
|
224
|
+
checks.push({ id: "mcp", status, summary, ...(detail.length ? { detail } : {}) });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---- tools on PATH: what an install plan would say, all at once — and what each absence costs HERE
|
|
228
|
+
{
|
|
229
|
+
const detail: string[] = [];
|
|
230
|
+
let status: DoctorStatus = "ok";
|
|
231
|
+
const raise = (s: DoctorStatus): void => { const rank = { ok: 0, note: 1, warn: 2, fail: 3 }; if (rank[s] > rank[status]) status = s; };
|
|
232
|
+
const want: { program: string; needed: boolean; why?: string }[] = [
|
|
233
|
+
{ program: "git", needed: true, why: "checkpoints (undo without touching your repo) and plugin/skill installs need it" },
|
|
234
|
+
{ program: "node", needed: needsNpx, why: "an MCP server here starts through npx" },
|
|
235
|
+
{ program: "npm", needed: needsNpx, why: "an MCP server here starts through npx (install-once uses npm)" },
|
|
236
|
+
{ program: "npx", needed: needsNpx, why: "an MCP server here starts through npx" },
|
|
237
|
+
{ program: "uvx", needed: needsUvx, why: "an MCP server here starts through uvx" },
|
|
238
|
+
];
|
|
239
|
+
for (const w of want) {
|
|
240
|
+
const p = checkPrereq(w.program, deps.prereqEnv ?? {});
|
|
241
|
+
if (p.found) { detail.push(`✓ ${w.program}`); continue; }
|
|
242
|
+
const cost = w.needed ? ` — ${w.why}` : ` — nothing configured here needs it${w.program === "git" ? "" : " yet"}`;
|
|
243
|
+
detail.push(`${w.needed ? "✗" : "·"} ${w.program} not on PATH${cost}${p.hint ? ` (${p.hint})` : ""}`);
|
|
244
|
+
raise(w.needed ? (w.program === "git" ? "warn" : "fail") : "note");
|
|
245
|
+
}
|
|
246
|
+
const lsp = lspAvailabilityNote(cwd, which);
|
|
247
|
+
if (lsp !== null) { detail.push(`! ${lsp.replace(/^lsp: /, "")}`); raise("warn"); }
|
|
248
|
+
else if (existsSync(join(cwd, "tsconfig.json"))) detail.push("✓ typescript-language-server — edits and writes come back with diagnostics");
|
|
249
|
+
else detail.push("· typescript-language-server — not a TypeScript project here (no tsconfig.json), so the gate does not apply");
|
|
250
|
+
checks.push({ id: "tools", status, summary: detail.filter((l) => l.startsWith("✓")).length + " of " + detail.length + " present", detail });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---- verify: the check the loop runs before "done" (core/verify.ts) — the same kind of fact as `tools` and
|
|
254
|
+
// `permission`: what this project WOULD run, or that nothing is configured and why nothing was inferred. Never a
|
|
255
|
+
// failure: a project with no gate is a project with no gate. The refusals are the row's detail on purpose — that
|
|
256
|
+
// list is how a person decides, once, whether to turn the gate on with the `verify` key.
|
|
257
|
+
{
|
|
258
|
+
const plan = resolveVerify(cwd);
|
|
259
|
+
const summary = plan.source === "settings" ? `${verifyLabelWithCost(cwd, plan)} · from ${plan.reason}`
|
|
260
|
+
: plan.source === "inferred" ? `${verifyLabelWithCost(cwd, plan)} · ${plan.reason} — set \`verify\` in .rovecode/settings.json to pin or replace it`
|
|
261
|
+
: `none · ${plan.reason}`;
|
|
262
|
+
const detail = plan.refused.map((r) => `not inferred: ${r}`);
|
|
263
|
+
if (plan.commands.length > 0) detail.push(VERIFY_BLIND_SPOT);
|
|
264
|
+
checks.push({ id: "verify", status: plan.source === "settings" ? "ok" : "note", summary, ...(detail.length ? { detail } : {}) });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ---- checkpoints: the shadow repositories under this workspace
|
|
268
|
+
{
|
|
269
|
+
const root = join(cwd, ".rovecode", "checkpoints");
|
|
270
|
+
if (!existsSync(root)) checks.push({ id: "checkpoints", status: "ok", summary: "no shadow repository here yet (the first change of a session creates one)" });
|
|
271
|
+
else {
|
|
272
|
+
let sessions = 0; try { sessions = readdirSync(root).length; } catch { /* unreadable: size says 0 */ }
|
|
273
|
+
const { bytes, files } = dirSize(root);
|
|
274
|
+
const big = bytes >= CHECKPOINTS_WARN_BYTES;
|
|
275
|
+
checks.push({ id: "checkpoints", status: big ? "warn" : "ok",
|
|
276
|
+
summary: `${sessions} session${sessions === 1 ? "" : "s"} · ${mb(bytes)} in ${files} files under .rovecode/checkpoints`,
|
|
277
|
+
...(big ? { detail: [
|
|
278
|
+
"that is large: something big under this folder is being snapshotted before every change (untracked files are included; media, archives and binaries are excluded by pattern, other large files are not)",
|
|
279
|
+
"delete .rovecode/checkpoints to reclaim it — nothing of yours lives there — or ROVECODE_NO_CHECKPOINTS=1 turns snapshots off",
|
|
280
|
+
] } : {}) });
|
|
281
|
+
notChecked.push("which files the NEXT snapshot would hash — there is no dry run for that");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const exitCode = checks.some((c) => c.status === "fail") ? 1 : 0;
|
|
286
|
+
return { ok: exitCode === 0, exitCode, cwd, home, checks, notChecked };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const MARK: Record<DoctorStatus, string> = { ok: "✓", note: "·", warn: "!", fail: "✗" };
|
|
290
|
+
|
|
291
|
+
export function renderDoctor(r: DoctorReport): string[] {
|
|
292
|
+
const lines: string[] = [];
|
|
293
|
+
for (const c of r.checks) {
|
|
294
|
+
lines.push(`${MARK[c.status]} ${c.id.padEnd(12)} ${c.summary}`);
|
|
295
|
+
for (const d of c.detail ?? []) lines.push(` ${d}`);
|
|
296
|
+
}
|
|
297
|
+
lines.push("");
|
|
298
|
+
lines.push("not checked:");
|
|
299
|
+
for (const n of r.notChecked) lines.push(` - ${n}`);
|
|
300
|
+
lines.push("");
|
|
301
|
+
lines.push(r.exitCode === 0
|
|
302
|
+
? (r.checks.some((c) => c.status === "warn" || c.status === "note") ? "nothing is broken; the lines marked ! and · are things to know or finish" : "everything checked is in order")
|
|
303
|
+
: "something is broken — the lines marked ✗ say what");
|
|
304
|
+
return lines;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** `rovecode doctor [--json] [--no-connect]` — exit 0 nothing broken · 1 something to fix · 2 usage */
|
|
308
|
+
export async function cmdDoctor(args: string[], deps: DoctorDeps = {}): Promise<number> {
|
|
309
|
+
const out = deps.out ?? ((l: string) => console.log(l));
|
|
310
|
+
const err = deps.err ?? ((l: string) => console.error(l));
|
|
311
|
+
const json = args.includes("--json");
|
|
312
|
+
for (const a of args) {
|
|
313
|
+
if (!a.startsWith("-")) continue;
|
|
314
|
+
if (a === "--json" || a === "--no-connect") continue;
|
|
315
|
+
const msg = `unknown flag ${a}`;
|
|
316
|
+
err(msg); err(DOCTOR_USAGE.join("\n"));
|
|
317
|
+
if (json) out(JSON.stringify({ ok: false, error: msg, usage: DOCTOR_USAGE }, null, 2));
|
|
318
|
+
return 2;
|
|
319
|
+
}
|
|
320
|
+
const report = await runDoctor({ ...deps, ...(args.includes("--no-connect") ? { connect: false } : {}) });
|
|
321
|
+
if (json) out(JSON.stringify(report, null, 2));
|
|
322
|
+
else for (const l of renderDoctor(report)) out(l);
|
|
323
|
+
return report.exitCode;
|
|
324
|
+
}
|