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
package/src/cli/setup.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/** `rovecode setup` — connect a model in one sitting: pick a provider, name the model, paste the key
|
|
2
|
+
* (masked, via auth.ts readSecret), one tiny real call, and the pick becomes the default in
|
|
3
|
+
* ~/.rovecode/providers.json. Every step reads through an injectable seam (ask / secret / save /
|
|
4
|
+
* probe) so the whole flow is unit-testable with scripted answers and no network. Piped stdin
|
|
5
|
+
* (no TTY) prints the three-line recipe and exits 2 — a wizard must not consume a script's input.
|
|
6
|
+
* Wording: core/voice.ts rules — rovecode speaks, short lines, every problem ends with "→ next:". */
|
|
7
|
+
|
|
8
|
+
import { createInterface } from "node:readline";
|
|
9
|
+
import { readSecret, saveCredential } from "../providers/auth.ts";
|
|
10
|
+
import { PROVIDER_ID_RE, inferProtocol, isConfigured } from "../providers/provider-config.ts";
|
|
11
|
+
import type { ProviderRegistry } from "../providers/registry.ts";
|
|
12
|
+
import { next, noModelHint } from "../core/voice.ts";
|
|
13
|
+
|
|
14
|
+
export interface ProbeResult { ok: boolean; model: string; detail: string }
|
|
15
|
+
|
|
16
|
+
export interface SetupDeps {
|
|
17
|
+
registry: ProviderRegistry;
|
|
18
|
+
/** default console.log */
|
|
19
|
+
out?: (line: string) => void;
|
|
20
|
+
/** one plain-text answer (default: readline on stdin, echoed) */
|
|
21
|
+
ask?: (prompt: string) => Promise<string>;
|
|
22
|
+
/** one secret (default: readSecret — masked on a TTY) */
|
|
23
|
+
secret?: (prompt: string) => Promise<string>;
|
|
24
|
+
/** default auth.ts saveCredential */
|
|
25
|
+
save?: (id: string, secret: string, keyEnv: string) => void;
|
|
26
|
+
/** default registry.probe (one tiny real call) */
|
|
27
|
+
probe?: (id: string, model?: string) => Promise<ProbeResult>;
|
|
28
|
+
/** default process.stdin.isTTY === true */
|
|
29
|
+
tty?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Pick { key: string; label: string; id?: string; local?: boolean; url?: "openai" | "anthropic" }
|
|
33
|
+
|
|
34
|
+
/** the numbered menu: the common hosted providers, the two local servers, and two "your own URL" doors */
|
|
35
|
+
export const SETUP_PICKS: readonly Pick[] = [
|
|
36
|
+
{ key: "0", id: "rovecode", label: "rovecode — sign in with your account, no key pasting (recommended)" },
|
|
37
|
+
{ key: "1", id: "anthropic", label: "anthropic — Claude models" },
|
|
38
|
+
{ key: "2", id: "openai", label: "openai" },
|
|
39
|
+
{ key: "3", id: "openrouter", label: "openrouter — many models behind one key" },
|
|
40
|
+
{ key: "4", id: "deepseek", label: "deepseek" },
|
|
41
|
+
{ key: "5", id: "groq", label: "groq" },
|
|
42
|
+
{ key: "6", id: "ollama", label: "ollama — local, no key needed", local: true },
|
|
43
|
+
{ key: "7", id: "lmstudio", label: "lmstudio — local, no key needed", local: true },
|
|
44
|
+
{ key: "8", label: "another OpenAI-compatible URL (a proxy, vLLM, …)", url: "openai" },
|
|
45
|
+
{ key: "9", label: "another Anthropic-protocol URL", url: "anthropic" },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export const SETUP_DONE = 'Done. Try: rovecode "explain this repo"';
|
|
49
|
+
|
|
50
|
+
/** one echoed line from the terminal — shared with main.ts's numbered model picker */
|
|
51
|
+
export function askLine(prompt: string): Promise<string> {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
54
|
+
let settled = false;
|
|
55
|
+
rl.question(prompt, (a) => { if (settled) return; settled = true; rl.close(); resolve(a.trim()); });
|
|
56
|
+
rl.once("close", () => { if (!settled) { settled = true; resolve(""); } });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** exit code: 0 connected (or added with a clear next step), 2 not a TTY / cancelled / invalid input */
|
|
61
|
+
export async function runSetup(deps: SetupDeps): Promise<number> {
|
|
62
|
+
const out = deps.out ?? console.log;
|
|
63
|
+
const ask = deps.ask ?? askLine;
|
|
64
|
+
const secret = deps.secret ?? ((p: string) => readSecret(p));
|
|
65
|
+
const save = deps.save ?? saveCredential;
|
|
66
|
+
const reg = deps.registry;
|
|
67
|
+
const probe = deps.probe ?? ((id: string, model?: string) => reg.probe(id, model));
|
|
68
|
+
const tty = deps.tty ?? process.stdin.isTTY === true;
|
|
69
|
+
|
|
70
|
+
if (!tty) { out(noModelHint("cli")); out(" (rovecode setup needs a terminal — this stdin is a pipe)"); return 2; }
|
|
71
|
+
|
|
72
|
+
out("◆ rovecode setup — let's connect a model. Two or three questions.");
|
|
73
|
+
out("");
|
|
74
|
+
for (const p of SETUP_PICKS) out(` ${p.key} ${p.label}`);
|
|
75
|
+
out("");
|
|
76
|
+
let pick: Pick | undefined;
|
|
77
|
+
for (let tries = 0; tries < 3 && pick === undefined; tries++) {
|
|
78
|
+
const a = (await ask("Which one? [0-9, empty = cancel]: ")).trim();
|
|
79
|
+
if (a.length === 0) { out(`cancelled — nothing changed. ${next("rovecode setup")} whenever you like`); return 2; }
|
|
80
|
+
pick = SETUP_PICKS.find((p) => p.key === a || p.id === a);
|
|
81
|
+
if (pick === undefined) out(` "${a}" is not on the list — a number from 0 to 9, please.`);
|
|
82
|
+
}
|
|
83
|
+
if (pick === undefined) return 2;
|
|
84
|
+
|
|
85
|
+
// ---- rovecode account: device login does everything — key, provider, default model ----
|
|
86
|
+
if (pick.id === "rovecode") {
|
|
87
|
+
const { runDeviceLogin } = await import("../account/login.ts");
|
|
88
|
+
const { ensureRovecodeProvider, ROVECODE_DEFAULT_MODEL } = await import("../account/provision.ts");
|
|
89
|
+
out("◆ signing you in — approve in the browser that opens.");
|
|
90
|
+
const result = await runDeviceLogin({
|
|
91
|
+
apiBase: process.env.ROVECODE_AUTH_API ?? "https://www.rovecode.dev",
|
|
92
|
+
onCode: ({ userCode, verificationUri, expiresIn }) => {
|
|
93
|
+
out("");
|
|
94
|
+
out(` your code: ${userCode}`);
|
|
95
|
+
out(` open: ${verificationUri}`);
|
|
96
|
+
out(` expires in about ${Math.max(1, Math.round(expiresIn / 60))} min — waiting for approval…`);
|
|
97
|
+
out("");
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
if (!result.ok) { out(` sign-in failed (${result.reason}). ${next("rovecode login")} or pick another provider`); return 2; }
|
|
101
|
+
out(`◆ linked ${result.account.email || "your account"}.`);
|
|
102
|
+
if (!result.account.apiKey) { out(` the site didn't mint a key this time. ${next("rovecode setup")} and pick another door, or re-run rovecode login`); return 2; }
|
|
103
|
+
const pv = ensureRovecodeProvider(result.account.apiKey);
|
|
104
|
+
if (pv.error !== undefined) { out(` provider setup failed: ${pv.error}. ${next("rovecode provider add rovecode https://api.rovecode.dev/v1")}`); return 2; }
|
|
105
|
+
out(`◆ rovecode provider ready (key stored hidden, ~/.rovecode/credentials.json).`);
|
|
106
|
+
out(`◆ testing rovecode/${ROVECODE_DEFAULT_MODEL} …`);
|
|
107
|
+
const r = await probe("rovecode", ROVECODE_DEFAULT_MODEL);
|
|
108
|
+
if (r.ok) out(` ${r.detail}`);
|
|
109
|
+
else out(` probe failed: ${r.detail} — the key is stored; ${next("rovecode provider test rovecode " + ROVECODE_DEFAULT_MODEL)}`);
|
|
110
|
+
if (pv.defaulted) out(`◆ default → rovecode/${ROVECODE_DEFAULT_MODEL}.`);
|
|
111
|
+
else out(`◆ kept your existing default model — switch anytime: rovecode model use rovecode/${ROVECODE_DEFAULT_MODEL}`);
|
|
112
|
+
out("");
|
|
113
|
+
out(SETUP_DONE);
|
|
114
|
+
out(" or open the cockpit: rovecode");
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---- the provider: a built-in, a keyless local server, or a URL of your own ----
|
|
119
|
+
let id: string;
|
|
120
|
+
if (pick.url !== undefined) {
|
|
121
|
+
id = (await ask("Short name for it (letters, digits, - _ .): ")).trim().toLowerCase();
|
|
122
|
+
if (!PROVIDER_ID_RE.test(id)) { out(` "${id}" won't work as an id. ${next("rovecode setup, and pick a name like myproxy")}`); return 2; }
|
|
123
|
+
const baseUrl = (await ask("Base URL (e.g. https://host/v1): ")).trim();
|
|
124
|
+
if (!/^https?:\/\//.test(baseUrl)) { out(` "${baseUrl}" is not an http(s) URL. ${next("rovecode setup")}`); return 2; }
|
|
125
|
+
const protocol = pick.url === "anthropic" ? "anthropic" : baseUrl.includes("anthropic.com") ? inferProtocol(baseUrl) : "openai";
|
|
126
|
+
const noKey = (await ask("Does it need an API key? [Y/n]: ")).trim().toLowerCase().startsWith("n");
|
|
127
|
+
const r = reg.add({ id, baseUrl, protocol, ...(noKey ? { noKey: true } : {}) }, "user");
|
|
128
|
+
if ("error" in r) { out(` ${r.error}. ${next("rovecode setup")}`); return 2; }
|
|
129
|
+
out(`◆ ${id} registered (${protocol} protocol, ${baseUrl}).`);
|
|
130
|
+
} else {
|
|
131
|
+
id = pick.id!;
|
|
132
|
+
const known = reg.get(id);
|
|
133
|
+
if (known === undefined) { out(` ${id} is missing from the built-in table — that is a bug. ${next("rovecode provider add " + id + " <baseUrl>")}`); return 2; }
|
|
134
|
+
if (pick.local === true && known.noKey !== true) {
|
|
135
|
+
// the built-in row expects a key env; a local server needs none — the user entry says so
|
|
136
|
+
const r = reg.add({ id, baseUrl: known.baseUrl, protocol: known.protocol, noKey: true, ...(known.defaultModel !== undefined ? { defaultModel: known.defaultModel } : {}) }, "user");
|
|
137
|
+
if ("error" in r) { out(` ${r.error}. ${next("rovecode setup")}`); return 2; }
|
|
138
|
+
out(`◆ ${id} marked as a local server (${known.baseUrl}, no key).`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- the model ----
|
|
143
|
+
const spec = reg.get(id)!;
|
|
144
|
+
const suggested = spec.defaultModel;
|
|
145
|
+
const modelAnswer = (await ask(suggested !== undefined ? `Model id [${suggested}]: ` : "Model id (empty = choose later with rovecode model list): ")).trim();
|
|
146
|
+
const model = modelAnswer.length > 0 ? modelAnswer : suggested;
|
|
147
|
+
|
|
148
|
+
// ---- the key ----
|
|
149
|
+
let hasKey = isConfigured(spec);
|
|
150
|
+
if (!hasKey) {
|
|
151
|
+
const s = await secret(`${spec.keyEnv} (hidden): `);
|
|
152
|
+
if (s.trim().length === 0) {
|
|
153
|
+
out(`◆ no key stored — I can't call ${id} without one.`);
|
|
154
|
+
out(` ${next(`rovecode auth set ${id}`)} (or set ${spec.keyEnv} in your environment)`);
|
|
155
|
+
} else {
|
|
156
|
+
save(id, s.trim(), spec.keyEnv);
|
|
157
|
+
reg.refresh();
|
|
158
|
+
hasKey = true;
|
|
159
|
+
out(`◆ key stored for ${id} (hidden; ~/.rovecode/credentials.json).`);
|
|
160
|
+
}
|
|
161
|
+
} else out(`◆ ${id} already has a key (${spec.keySource === "stored" ? "stored" : `env ${spec.keyEnv}`}).`);
|
|
162
|
+
|
|
163
|
+
// ---- one tiny real call ----
|
|
164
|
+
if (hasKey && model !== undefined) {
|
|
165
|
+
out(`◆ testing ${id}/${model} …`);
|
|
166
|
+
const r = await probe(id, model);
|
|
167
|
+
if (r.ok) out(` ${r.detail}`);
|
|
168
|
+
else {
|
|
169
|
+
out(` that didn't work: ${r.detail}`);
|
|
170
|
+
out(` ${next(`check the key and the URL, then rovecode provider test ${id} ${model}`)}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---- make it the default ----
|
|
175
|
+
if (model !== undefined) {
|
|
176
|
+
const d = reg.setDefault(`${id}/${model}`, "user");
|
|
177
|
+
if ("error" in d) { out(` ${d.error}. ${next(`rovecode model use ${id}/<model>`)}`); return 2; }
|
|
178
|
+
out(`◆ default → ${d.provider}/${d.model} (~/.rovecode/providers.json; running TUIs switch live).`);
|
|
179
|
+
} else {
|
|
180
|
+
out(`◆ ${id} is set up but has no model yet.`);
|
|
181
|
+
out(` ${next(`rovecode model list ${id}, then rovecode model use ${id}/<model>`)}`);
|
|
182
|
+
}
|
|
183
|
+
out("");
|
|
184
|
+
out(SETUP_DONE);
|
|
185
|
+
out(" or open the cockpit: rovecode");
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** `rovecode update [--check] [--channel beta|latest|auto]` — and the flow the TUI's /update and
|
|
2
|
+
* the autoUpdate boot hook share. The brain is core/update.ts (detect → plan → run); this file is
|
|
3
|
+
* the surface: spawn glue, the words on the terminal, the exit code.
|
|
4
|
+
*
|
|
5
|
+
* Rules: never swap the running process (on Windows the files under a live bun are being replaced;
|
|
6
|
+
* the honest promise is "restart to run it"), never move between channels silently (a beta stays
|
|
7
|
+
* on beta unless --channel says otherwise), and a binary install gets the release page URL rather
|
|
8
|
+
* than a half-done self-swap. */
|
|
9
|
+
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { channelFor, detectInstallMode, planUpdate, runUpdate, type DetectedInstall, type UpdateChannel } from "../core/update.ts";
|
|
13
|
+
import { checkForUpdate, updateLine } from "../core/update-check.ts";
|
|
14
|
+
|
|
15
|
+
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
16
|
+
|
|
17
|
+
async function spawnCmd(cmd: string[], cwd?: string): Promise<{ code: number; out: string }> {
|
|
18
|
+
const argv = cmd[0] === "npm" ? [npmCmd, ...cmd.slice(1)] : cmd;
|
|
19
|
+
const p = Bun.spawn(argv, { cwd, stdout: "pipe", stderr: "pipe" });
|
|
20
|
+
const [o, e] = await Promise.all([new Response(p.stdout).text(), new Response(p.stderr).text()]);
|
|
21
|
+
const code = await p.exited;
|
|
22
|
+
return { code, out: o + e };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function npmGlobalPrefix(): Promise<string | undefined> {
|
|
26
|
+
try {
|
|
27
|
+
const p = Bun.spawn([npmCmd, "prefix", "-g"], { stdout: "pipe", stderr: "ignore" });
|
|
28
|
+
const t = (await new Response(p.stdout).text()).trim();
|
|
29
|
+
await p.exited;
|
|
30
|
+
return t.length > 0 ? t : undefined;
|
|
31
|
+
} catch { return undefined; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UpdateFlowOpts {
|
|
35
|
+
version: string;
|
|
36
|
+
/** the running entry module's path (import.meta.path of the CLI entry) */
|
|
37
|
+
entry: string;
|
|
38
|
+
/** every line the flow wants the surface to show (CLI: stdout; TUI: notes) */
|
|
39
|
+
log(line: string): void;
|
|
40
|
+
/** unit-test seam: replace the spawners */
|
|
41
|
+
deps?: { spawn?: typeof spawnCmd; prefix?: typeof npmGlobalPrefix; install?: DetectedInstall };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The whole job: check → detect → plan → run. Returns the process exit code (0 = fine or already
|
|
45
|
+
* current, 1 = an update was wanted and did not land, 2 = bad flags). */
|
|
46
|
+
export async function updateFlow(words: string[], opts: UpdateFlowOpts): Promise<number> {
|
|
47
|
+
const checkOnly = words.includes("--check");
|
|
48
|
+
const chanWord = words.includes("--channel") ? words[words.indexOf("--channel") + 1] : "auto";
|
|
49
|
+
if (chanWord !== "auto" && chanWord !== "beta" && chanWord !== "latest") {
|
|
50
|
+
opts.log("error: --channel must be beta, latest or auto");
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
const status = await checkForUpdate(opts.version, checkOnly ? { cacheOnly: true } : {});
|
|
54
|
+
if (checkOnly) { opts.log(updateLine(status, true) ?? `up to date (${opts.version})`); return 0; }
|
|
55
|
+
if (!status.newer) {
|
|
56
|
+
opts.log(status.latest === undefined
|
|
57
|
+
? `no newer release is known (${updateLine(status, true) ?? `current: ${opts.version}`})`
|
|
58
|
+
: `up to date (${opts.version})`);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
const install = opts.deps?.install ?? detectInstallMode({ entry: opts.entry });
|
|
62
|
+
const distBuilt = install.mode === "source" && install.root !== undefined && existsSync(join(install.root, "dist", "cli", "main.js"));
|
|
63
|
+
const plan = planUpdate(install, { currentVersion: opts.version, channel: chanWord as UpdateChannel | "auto", distBuilt });
|
|
64
|
+
opts.log(`update available: ${opts.version} → ${status.latest} · this copy is a ${install.mode} install · channel ${plan.channel} (${channelFor(opts.version, chanWord as UpdateChannel | "auto")} follows the running version)`);
|
|
65
|
+
if (plan.commands.length === 0) { opts.log(plan.manual ?? "nothing to run"); return 1; }
|
|
66
|
+
const r = await runUpdate(plan, install, {
|
|
67
|
+
spawn: opts.deps?.spawn ?? spawnCmd,
|
|
68
|
+
npmGlobalPrefix: opts.deps?.prefix ?? npmGlobalPrefix,
|
|
69
|
+
log: opts.log,
|
|
70
|
+
});
|
|
71
|
+
opts.log(r.ok ? r.detail : `update failed: ${r.detail}`);
|
|
72
|
+
return r.ok ? 0 : 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** the CLI surface: `rovecode update …` */
|
|
76
|
+
export async function cmdUpdate(words: string[], version: string, entry: string): Promise<number> {
|
|
77
|
+
return updateFlow(words, { version, entry, log: (l) => console.log(l) });
|
|
78
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** `rovecode workflow run <file.ts> [--resume <runId>] [--json]` (F3, sdk-blueprint.md §4).
|
|
2
|
+
*
|
|
3
|
+
* The file's default export is a WorkflowSpec (defineWorkflow). Steps ride the runtime's
|
|
4
|
+
* TaskManager, so every running step appears on the Mission Control agent tree (/ui,
|
|
5
|
+
* GET /events) while the workflow runs. Gates ask on the TTY; off a TTY a gate REJECTS
|
|
6
|
+
* (a headless workflow must be gate-free — asking nobody would hang forever). */
|
|
7
|
+
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { defineWorkflow, listWorkflowRuns, runWorkflow, type WorkflowExecutor, type WorkflowSpec } from "../workflow/engine.ts";
|
|
10
|
+
import { askLine } from "./setup.ts";
|
|
11
|
+
|
|
12
|
+
export async function cmdWorkflow(words: string[]): Promise<number> {
|
|
13
|
+
const [action, ...rest] = words;
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const dir = join(cwd, ".rovecode", "workflows");
|
|
16
|
+
|
|
17
|
+
if (action === "list") {
|
|
18
|
+
const runs = listWorkflowRuns(dir);
|
|
19
|
+
if (process.argv.includes("--json")) { console.log(JSON.stringify({ runs }, null, 2)); return 0; }
|
|
20
|
+
if (runs.length === 0) { console.log(`no workflow runs in ${dir}`); return 0; }
|
|
21
|
+
for (const r of runs) console.log(`${r.runId} done: ${r.done.length > 0 ? r.done.join(", ") : "(none)"}`);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (action !== "run" || rest[0] === undefined) {
|
|
26
|
+
console.error("usage: rovecode workflow run <file.ts> [--resume <runId>] [--json] | rovecode workflow list");
|
|
27
|
+
return 2;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const file = resolve(cwd, rest[0]);
|
|
31
|
+
let spec: WorkflowSpec;
|
|
32
|
+
try {
|
|
33
|
+
const mod = (await import(file)) as { default?: WorkflowSpec };
|
|
34
|
+
if (!mod.default) throw new Error("the file must `export default defineWorkflow({…})`");
|
|
35
|
+
spec = defineWorkflow(mod.default);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(`error: ${e instanceof Error ? e.message : String(e)}`);
|
|
38
|
+
return 2;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resumeIdx = process.argv.indexOf("--resume");
|
|
42
|
+
const resumeId = resumeIdx !== -1 ? process.argv[resumeIdx + 1] : undefined;
|
|
43
|
+
const asJson = process.argv.includes("--json");
|
|
44
|
+
|
|
45
|
+
const { bootRuntime } = await import("./runtime.ts");
|
|
46
|
+
const rt = await bootRuntime();
|
|
47
|
+
const exit = async (code: number): Promise<never> => {
|
|
48
|
+
rt.tasks.cancelAll();
|
|
49
|
+
await rt.tasks.drain(2_000);
|
|
50
|
+
await rt.hooks.close();
|
|
51
|
+
await rt.mcp?.close().catch(() => {});
|
|
52
|
+
// cmdRun's lesson: runtime keeps the event loop alive (watchers, MCP); return
|
|
53
|
+
// the code and the process never exits. One-shot commands end the process.
|
|
54
|
+
return process.exit(code);
|
|
55
|
+
};
|
|
56
|
+
const why = rt.noProviderReason();
|
|
57
|
+
if (why !== null && process.env.ROVECODE_MOCK !== "1") { console.error(`error: ${why}`); return exit(2); }
|
|
58
|
+
|
|
59
|
+
const tty = process.stdin.isTTY === true;
|
|
60
|
+
const exec: WorkflowExecutor = {
|
|
61
|
+
runAgent: async (step) => {
|
|
62
|
+
const start = rt.tasks.start(
|
|
63
|
+
{ agent: step.agent ?? "main", goal: step.goal },
|
|
64
|
+
{ label: `${spec.name}/${step.name}` },
|
|
65
|
+
);
|
|
66
|
+
if (!start.ok) return { ok: false, summary: start.reason };
|
|
67
|
+
const info = await rt.tasks.result(start.id);
|
|
68
|
+
if (!info) return { ok: false, summary: "task vanished" };
|
|
69
|
+
return {
|
|
70
|
+
ok: info.status === "done",
|
|
71
|
+
summary: info.summary ?? info.error ?? info.status,
|
|
72
|
+
...(info.usage !== undefined ? { usage: info.usage } : {}),
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
askGate: async (prompt) => {
|
|
76
|
+
if (!tty) { console.error(`gate '${prompt}': no TTY — rejected (run workflows with gates interactively)`); return false; }
|
|
77
|
+
const a = (await askLine(`gate [${spec.name}]: ${prompt} [y/N] `)).trim().toLowerCase();
|
|
78
|
+
return a === "y" || a === "yes";
|
|
79
|
+
},
|
|
80
|
+
cancelAgent: () => { rt.tasks.cancelAll(); },
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const emit = (e: { type: string } & Record<string, unknown>): void => {
|
|
84
|
+
if (asJson) { console.log(JSON.stringify(e)); return; }
|
|
85
|
+
if (e.type === "workflow_started") console.log(`workflow ${e.name} (${e.runId}) — ${e.steps} steps`);
|
|
86
|
+
else if (e.type === "step_started") console.log(` ▸ ${e.step} (attempt ${e.attempt})`);
|
|
87
|
+
else if (e.type === "step_done") console.log(` ✓ ${e.step} — ${String(e.summary ?? "").slice(0, 120)}`);
|
|
88
|
+
else if (e.type === "step_failed") console.error(` ✗ ${e.step} — ${String(e.error ?? "").slice(0, 120)}`);
|
|
89
|
+
else if (e.type === "gate_waiting") { /* askGate prints the prompt */ }
|
|
90
|
+
else if (e.type === "workflow_done") console.log(`workflow ${e.status} (${e.runId})`);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const result = await runWorkflow(spec, exec, {
|
|
94
|
+
dir,
|
|
95
|
+
emit,
|
|
96
|
+
...(resumeId !== undefined ? { runId: resumeId } : {}),
|
|
97
|
+
});
|
|
98
|
+
if (asJson) console.log(JSON.stringify({ runId: result.runId, status: result.status, steps: result.steps, usage: result.usage }, null, 2));
|
|
99
|
+
return exit(result.status === "done" ? 0 : 1);
|
|
100
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/** Shadow-git checkpoints (PORT #11, cline port, Apache-2.0 — see THIRD_PARTY_NOTICES).
|
|
2
|
+
*
|
|
3
|
+
* KNOWN COST, and the design that would remove it — measured 2026-09-06, kept here so the next reader
|
|
4
|
+
* starts from the answer rather than the question. The shadow git-dir is per SESSION, so every new
|
|
5
|
+
* session pays `git init` + a full `add` of the workspace inside its FIRST write or bash call: 3.45–3.56 s
|
|
6
|
+
* in this repository (init+config 160 ms, first add 1.4 s, first commit 1.5 s writing 565 loose objects
|
|
7
|
+
* on Windows). Later calls in that session are 210–335 ms, of which ~200 ms is git. It does not amortise
|
|
8
|
+
* across sessions because nothing is shared.
|
|
9
|
+
*
|
|
10
|
+
* The cut is one shadow repo per REPOSITORY with a ref per session — a new session's first snapshot
|
|
11
|
+
* becomes an incremental add, ~80 ms measured. cline keeps one shadow repo per TASK because a task is its
|
|
12
|
+
* unit of restore; ours is the repository, so sessions in one workspace can share an object store and
|
|
13
|
+
* differ only by ref. It is NOT a configuration change: a shared git-dir means a shared index and a
|
|
14
|
+
* shared HEAD, and today's `add . && commit` / `reset --hard && clean -fd` would collide on index.lock
|
|
15
|
+
* (the second writer silently gets no checkpoint) and move HEAD under another session. It has to be
|
|
16
|
+
* re-done on plumbing: GIT_INDEX_FILE=<shadow>/<session>.index per session, then add → write-tree →
|
|
17
|
+
* commit-tree -p <that session's last> → update-ref refs/rovecode/<session> (atomic per ref, and the
|
|
18
|
+
* object store is atomic per object); restore becomes read-tree --reset -u then clean -fd against that
|
|
19
|
+
* same index, with no HEAD involved. A hash stays a hash, so restoring one session's checkpoint from
|
|
20
|
+
* another finally works — the case per-session storage never allowed, and the one that needs its own
|
|
21
|
+
* test, alongside a two-writers concurrency test. Sessions whose old per-session repo exists keep using
|
|
22
|
+
* it; nothing needs migrating.
|
|
23
|
+
*
|
|
24
|
+
* A SECOND git repository whose git-dir lives under .rovecode/checkpoints/<session> and whose
|
|
25
|
+
* work-tree is the WORKSPACE, so the user's own .git is never written. This is cline's
|
|
26
|
+
* shadow-git design: the @8eb5f3d snapshot's docs still describe it (docs/core-workflows/
|
|
27
|
+
* checkpoints.mdx:17 "shadow Git repository separate from your project's actual Git
|
|
28
|
+
* history … Your main Git repository stays untouched"), but its v4 CODE moved to in-repo
|
|
29
|
+
* `git stash create` + refs/cline/* (sdk/packages/core/src/hooks/checkpoint-hooks.ts:172,
|
|
30
|
+
* sdk/packages/core/src/session/checkpoint-restore.ts:444-477), which requires a git
|
|
31
|
+
* workspace and rewrites the user's HEAD — incompatible with this bar. The mechanics here
|
|
32
|
+
* therefore port cline's last shadow-git implementation, v3.89.2
|
|
33
|
+
* apps/vscode/src/integrations/checkpoints/:
|
|
34
|
+
* - `git init` in the checkpoints dir, then core.worktree=<workspace>, commit.gpgSign
|
|
35
|
+
* off, own identity (CheckpointGitOperations.ts:88-94); git-dir = <dir>/.git and
|
|
36
|
+
* worktree-mismatch reuse check (CheckpointUtils.ts:20-23, GitOperations.ts:70-73)
|
|
37
|
+
* - excludes written to <git-dir>/info/exclude, list headed by ".git/"
|
|
38
|
+
* (CheckpointExclusions.ts:42-46 + 297-301)
|
|
39
|
+
* - snapshot = `add . --ignore-errors` (CheckpointGitOperations.ts:213) +
|
|
40
|
+
* `commit --allow-empty --no-verify` (CheckpointTracker.ts:251-253)
|
|
41
|
+
* - restore = `reset --hard <hash>` (CheckpointTracker.ts:364) + `clean -fd` so files
|
|
42
|
+
* created after the checkpoint are rewound away while ignored paths (node_modules,
|
|
43
|
+
* .rovecode, build output) survive — the reset+clean pair of the snapshot's own restore
|
|
44
|
+
* (checkpoint-restore.ts:458-470)
|
|
45
|
+
* Deviations from v3.89.2: the shadow repo lives IN-WORKSPACE under .rovecode (bar) so
|
|
46
|
+
* ".rovecode/" is excluded from itself; the nested-.git rename dance
|
|
47
|
+
* (CheckpointGitOperations.ts:148-166, 207 ".git_disabled") is NOT ported — renaming the
|
|
48
|
+
* user's nested .git would violate "user .git never touched", so nested repos become
|
|
49
|
+
* inert gitlink entries instead (their contents are not checkpointed, never modified);
|
|
50
|
+
* core.autocrlf=false is set so restores are byte-exact on Windows.
|
|
51
|
+
*
|
|
52
|
+
* Conversation restore returns the session entryId to branch to — the CALLER feeds it to
|
|
53
|
+
* SessionStore.branch() (port #2 leaf machinery); this module never imports session.ts. */
|
|
54
|
+
|
|
55
|
+
import { execFile } from "node:child_process";
|
|
56
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
57
|
+
import { join, resolve } from "node:path";
|
|
58
|
+
|
|
59
|
+
export interface Checkpoint {
|
|
60
|
+
hash: string; // shadow commit hash
|
|
61
|
+
label: string; // e.g. the mutating tool's name
|
|
62
|
+
entryId?: string; // session entry to branch to on conversation restore
|
|
63
|
+
createdAt: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type RestoreMode = "files" | "conversation" | "both";
|
|
67
|
+
|
|
68
|
+
export type RestoreResult =
|
|
69
|
+
| { ok: true; mode: RestoreMode; checkpoint: Checkpoint; entryId?: string }
|
|
70
|
+
| { ok: false; error: string };
|
|
71
|
+
|
|
72
|
+
/** ToolKind values whose calls mutate the workspace → snapshot after each (bar:
|
|
73
|
+
* "snapshot commit after every mutating tool call"). memory writes land under the
|
|
74
|
+
* excluded .rovecode/; spawned children's own write/execute calls hit the same hook. */
|
|
75
|
+
export const MUTATING_KINDS: ReadonlySet<string> = new Set(["write", "execute"]);
|
|
76
|
+
|
|
77
|
+
/** Conversation-restore anchor for a snapshot: the LAST role:"user" message on the
|
|
78
|
+
* active path. At snapshot time the tail entry is the assistant message that ISSUED
|
|
79
|
+
* the in-flight tool call (the loop appends it pre-dispatch), so anchoring the tail
|
|
80
|
+
* branches to a history ending in tool_calls with no tool replies → provider 400.
|
|
81
|
+
* cline anchors the user run message instead (checkpoint-restore.ts:217-250).
|
|
82
|
+
* Wiring contract (like MUTATING_KINDS): runtime.ts withCheckpoint computes
|
|
83
|
+
* `anchorEntryId(activeStore.messages())`. */
|
|
84
|
+
export function anchorEntryId(messages: ReadonlyArray<{ id: string; role: string }>): string | undefined {
|
|
85
|
+
return messages.findLast((m) => m.role === "user")?.id;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface CheckpointsInit {
|
|
89
|
+
workspace: string;
|
|
90
|
+
sessionId: string;
|
|
91
|
+
/** override the shadow root (default <workspace>/.rovecode/checkpoints) — tests/global mode */
|
|
92
|
+
shadowRoot?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** cline's default exclusions (CheckpointExclusions.ts:42-70), structural entries first, then the media /
|
|
96
|
+
* archive / binary categories. The first port kept only the structural entries, and the snapshot's
|
|
97
|
+
* `git add .` then hashed every file the WORKSPACE tracks: in a repo carrying 165 MB of tracked video
|
|
98
|
+
* (site/media-src) the first mutating tool call of every session took 26–35 s and left a 232 MB shadow
|
|
99
|
+
* repo under .rovecode/checkpoints/<session> (measured 2026-09-06, scripts/probe-turn.ts). A checkpoint
|
|
100
|
+
* exists to restore what the agent changed, and the agent does not edit videos, screenshots, archives or
|
|
101
|
+
* compiled binaries — those are excluded by extension, like cline does. With videos alone excluded the
|
|
102
|
+
* same repo still took 10.7 s and 79 MB: 82 MB of site/screenshots/*.png. Text of any size is still
|
|
103
|
+
* snapshotted, and so is SVG (text a designer or the agent writes). */
|
|
104
|
+
const EXCLUDES = [
|
|
105
|
+
".git/",
|
|
106
|
+
".rovecode/", // the shadow repo itself lives here (deviation: in-workspace)
|
|
107
|
+
"node_modules/",
|
|
108
|
+
"dist/",
|
|
109
|
+
"build/",
|
|
110
|
+
"out/",
|
|
111
|
+
".next/",
|
|
112
|
+
"__pycache__/",
|
|
113
|
+
".venv/",
|
|
114
|
+
"venv/",
|
|
115
|
+
".DS_Store",
|
|
116
|
+
// media (cline getMediaFilePatterns): video, audio, raster images — not SVG
|
|
117
|
+
"*.mp4", "*.m4v", "*.mov", "*.avi", "*.mkv", "*.webm", "*.wmv", "*.flv", "*.mpg", "*.mpeg",
|
|
118
|
+
"*.mp3", "*.m4a", "*.wav", "*.flac", "*.ogg", "*.aac", "*.wma",
|
|
119
|
+
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp", "*.ico", "*.webp", "*.tif", "*.tiff", "*.heic", "*.avif", "*.psd",
|
|
120
|
+
// archives and disk images (getLargeDataFilePatterns)
|
|
121
|
+
"*.zip", "*.tar", "*.gz", "*.tgz", "*.bz2", "*.xz", "*.7z", "*.rar", "*.iso", "*.dmg",
|
|
122
|
+
// compiled binaries and native libraries
|
|
123
|
+
"*.exe", "*.dll", "*.so", "*.dylib", "*.node", "*.wasm", "*.o", "*.a", "*.class", "*.jar", "*.pyc",
|
|
124
|
+
// databases and caches (getDatabaseFilePatterns / getCacheFilePatterns)
|
|
125
|
+
"*.sqlite", "*.sqlite3", "*.db", "*.mdb", "*.log",
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
/** `verb` names the failing subcommand in errors; the default suits bare invocations
|
|
129
|
+
* like ["init"], but --git-dir'd calls must pass it (the first non-dash arg there is
|
|
130
|
+
* the git-dir PATH — blaming a path instead of the verb misled /restore users). */
|
|
131
|
+
function runGit(args: string[], cwd: string, verb = args.find((a) => !a.startsWith("-")) ?? ""): Promise<string> {
|
|
132
|
+
// Explicit env hygiene: a caller's GIT_* vars must not redirect shadow commands
|
|
133
|
+
// at the USER repo (cline relies on simple-git cwd instead — GitOperations.ts:88).
|
|
134
|
+
const env = { ...process.env };
|
|
135
|
+
for (const k of ["GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_OBJECT_DIRECTORY", "GIT_COMMON_DIR"]) delete env[k];
|
|
136
|
+
return new Promise((res, rej) => {
|
|
137
|
+
execFile("git", args, { cwd, env, windowsHide: true, maxBuffer: 16 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
138
|
+
if (err) rej(new Error(`git ${verb} failed: ${stderr.trim() || err.message}`));
|
|
139
|
+
else res(stdout.trim());
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Canonical form for workspace-identity compares: realpath fixes case/8.3 aliases of
|
|
145
|
+
* EXISTING paths (C:\foo vs c:\foo reopened the shadow repo as "another workspace"
|
|
146
|
+
* and silently disabled checkpoints); the case-fold below covers paths realpath
|
|
147
|
+
* cannot resolve, on the case-insensitive platform only. */
|
|
148
|
+
function canonPath(p: string): string {
|
|
149
|
+
let r = p;
|
|
150
|
+
try { r = (realpathSync.native ?? realpathSync)(p); } catch { /* nonexistent: compare as given */ }
|
|
151
|
+
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class Checkpoints {
|
|
155
|
+
/** history, oldest first (sidecar-backed: survives process restarts) */
|
|
156
|
+
private readonly log: Checkpoint[] = [];
|
|
157
|
+
|
|
158
|
+
private constructor(
|
|
159
|
+
readonly workspace: string,
|
|
160
|
+
/** shadow repo GIT DIR: <workspace>/.rovecode/checkpoints/<session>/.git */
|
|
161
|
+
readonly gitDir: string,
|
|
162
|
+
private readonly sidecar: string,
|
|
163
|
+
) {}
|
|
164
|
+
|
|
165
|
+
/** Every shadow command names its git-dir and work-tree explicitly, so no cwd or
|
|
166
|
+
* environment state can ever point one at the user's repo. */
|
|
167
|
+
private git(...args: string[]): Promise<string> {
|
|
168
|
+
return runGit(["--git-dir", this.gitDir, "--work-tree", this.workspace, ...args], this.workspace, args[0]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Create or reopen the shadow repo for a session. Works whether or not the workspace
|
|
172
|
+
* is a git repo — the shadow git-dir is entirely separate (non-git workspaces bar). */
|
|
173
|
+
static async init(opts: CheckpointsInit): Promise<Checkpoints> {
|
|
174
|
+
const workspace = resolve(opts.workspace);
|
|
175
|
+
// dot-only ids ("."/"..") survive the charwise filter but escape or collapse the
|
|
176
|
+
// shadow root under join() — fold them (and "") to underscores
|
|
177
|
+
const cleaned = opts.sessionId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
178
|
+
const session = /^\.*$/.test(cleaned) ? cleaned.replace(/\./g, "_") || "_" : cleaned;
|
|
179
|
+
const shadowDir = join(opts.shadowRoot ?? join(workspace, ".rovecode", "checkpoints"), session);
|
|
180
|
+
const gitDir = join(shadowDir, ".git"); // cline layout: <checkpointsDir>/.git (CheckpointUtils.ts:20-23)
|
|
181
|
+
mkdirSync(shadowDir, { recursive: true });
|
|
182
|
+
const cp = new Checkpoints(workspace, gitDir, join(shadowDir, "checkpoints.jsonl"));
|
|
183
|
+
|
|
184
|
+
if (!existsSync(join(gitDir, "HEAD"))) {
|
|
185
|
+
// plain `git init` in the shadow dir, exactly GitOperations.ts:88
|
|
186
|
+
await runGit(["init"], shadowDir);
|
|
187
|
+
// GitOperations.ts:91-94 config block (identity ours; autocrlf is an rovecode addition)
|
|
188
|
+
for (const [k, v] of [
|
|
189
|
+
["core.worktree", workspace],
|
|
190
|
+
["commit.gpgSign", "false"],
|
|
191
|
+
["core.autocrlf", "false"],
|
|
192
|
+
["user.name", "Rovecode Checkpoint"],
|
|
193
|
+
["user.email", "checkpoint@rovecode.local"],
|
|
194
|
+
] as const) await cp.git("config", k, v);
|
|
195
|
+
} else {
|
|
196
|
+
// reuse check: refuse a shadow repo whose recorded worktree is another path
|
|
197
|
+
// (GitOperations.ts:70-73 "Checkpoints can only be used in the original workspace").
|
|
198
|
+
// Compared canonically — a case-variant reopen (C:\foo vs c:\foo) is the SAME dir.
|
|
199
|
+
const wt = await cp.git("config", "core.worktree").catch(() => "");
|
|
200
|
+
if (canonPath(resolve(wt)) !== canonPath(workspace)) throw new Error(`checkpoints: shadow repo belongs to ${wt}, not ${workspace}`);
|
|
201
|
+
}
|
|
202
|
+
// (re)write excludes into the shadow git-dir every init (CheckpointExclusions.ts:297-301)
|
|
203
|
+
mkdirSync(join(gitDir, "info"), { recursive: true });
|
|
204
|
+
writeFileSync(join(gitDir, "info", "exclude"), EXCLUDES.join("\n") + "\n");
|
|
205
|
+
cp.loadSidecar();
|
|
206
|
+
return cp;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private loadSidecar(): void {
|
|
210
|
+
if (!existsSync(this.sidecar)) return;
|
|
211
|
+
for (const line of readFileSync(this.sidecar, "utf8").split("\n")) {
|
|
212
|
+
if (!line.trim()) continue;
|
|
213
|
+
try {
|
|
214
|
+
const c = JSON.parse(line) as Checkpoint;
|
|
215
|
+
if (typeof c.hash === "string" && typeof c.label === "string") this.log.push(c);
|
|
216
|
+
} catch { /* corrupt sidecar line: skip, never throw (session.ts reload pattern) */ }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Snapshot the whole workspace: stage-all + allow-empty commit
|
|
221
|
+
* (GitOperations.ts:213 + CheckpointTracker.ts:251-253). Call after every mutating
|
|
222
|
+
* tool call; `entryId` is the session entry a conversation restore should branch to. */
|
|
223
|
+
async snapshot(label: string, entryId?: string): Promise<Checkpoint> {
|
|
224
|
+
await this.git("add", ".", "--ignore-errors");
|
|
225
|
+
await this.git("commit", "--allow-empty", "--no-verify", "-m", `rovecode-checkpoint: ${label}`);
|
|
226
|
+
const hash = await this.git("rev-parse", "HEAD");
|
|
227
|
+
const c: Checkpoint = { hash, label, createdAt: Date.now(), ...(entryId !== undefined ? { entryId } : {}) };
|
|
228
|
+
appendFileSync(this.sidecar, JSON.stringify(c) + "\n");
|
|
229
|
+
this.log.push(c);
|
|
230
|
+
return c;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** History oldest→newest. */
|
|
234
|
+
list(): Checkpoint[] { return [...this.log]; }
|
|
235
|
+
|
|
236
|
+
/** Restore a checkpoint by full hash or unique prefix.
|
|
237
|
+
* - "files": worktree → checkpoint state (reset --hard + clean -fd; ignored paths survive)
|
|
238
|
+
* - "conversation": NO file changes; returns the entryId for SessionStore.branch()
|
|
239
|
+
* - "both": files restored AND entryId returned
|
|
240
|
+
* Never throws — bad refs/modes AND shadow-git failures (a stale index.lock used to
|
|
241
|
+
* escape here and kill the TUI on unhandled rejection) come back structured, the
|
|
242
|
+
* error naming the failing verb (reset/clean). */
|
|
243
|
+
async restore(ref: string, mode: RestoreMode): Promise<RestoreResult> {
|
|
244
|
+
const hits = this.log.filter((c) => c.hash === ref || c.hash.startsWith(ref));
|
|
245
|
+
// duplicate hashes (identical content re-snapshotted) are ONE candidate — the
|
|
246
|
+
// LATEST entry wins so its (newer) conversation anchor is the one restored
|
|
247
|
+
const target = hits.at(-1);
|
|
248
|
+
if (!target || ref.length < 4) return { ok: false, error: `no checkpoint matches ${ref}` };
|
|
249
|
+
if (new Set(hits.map((h) => h.hash)).size > 1) return { ok: false, error: `ambiguous checkpoint prefix ${ref}` };
|
|
250
|
+
// conversation restore needs a recorded entryId — reject BEFORE touching any file,
|
|
251
|
+
// so "both" can never half-apply
|
|
252
|
+
if (mode !== "files" && target.entryId === undefined) {
|
|
253
|
+
return { ok: false, error: `checkpoint ${target.hash.slice(0, 8)} has no session entryId` };
|
|
254
|
+
}
|
|
255
|
+
if (mode !== "conversation") {
|
|
256
|
+
try {
|
|
257
|
+
await this.git("reset", "--hard", target.hash); // CheckpointTracker.ts:364
|
|
258
|
+
// remove files created after the checkpoint; single -f spares nested git repos,
|
|
259
|
+
// no -x spares ignored/excluded paths (checkpoint-restore.ts:458-470)
|
|
260
|
+
await this.git("clean", "-fd");
|
|
261
|
+
} catch (e) {
|
|
262
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
ok: true, mode, checkpoint: target,
|
|
267
|
+
...(mode !== "files" && target.entryId !== undefined ? { entryId: target.entryId } : {}),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|