rovecode 0.3.3 → 0.4.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +527 -0
- package/README.md +46 -48
- package/bin/rovecode.js +24 -0
- package/dist/cli/app-j6gn14w3.js +2 -0
- package/dist/cli/ask-user-cwstt8fz.js +2 -0
- package/dist/cli/auth-login-9bbp9915.js +2 -0
- package/dist/cli/auth-m8p9grty.js +2 -0
- package/dist/cli/bench-16zqdms5.js +9 -0
- package/dist/cli/catalog-1xchffa4.js +2 -0
- package/dist/cli/cli-1n1zb64f.js +2 -0
- package/dist/cli/client-2t9gjkck.js +2 -0
- package/dist/cli/commands-exafvm2b.js +2 -0
- package/dist/cli/connect-6zde0kn3.js +2 -0
- package/dist/cli/context-cmd-5t43wgqt.js +2 -0
- package/dist/cli/context-report-kt01pw8y.js +2 -0
- package/dist/cli/count-remote-ap7x3vh6.js +2 -0
- package/dist/cli/design-ne5zszyh.js +2 -0
- package/dist/cli/dispatch-2r5myxye.js +2 -0
- package/dist/cli/doctor-ws4fh4tn.js +3 -0
- package/dist/cli/executor-bdrjn634.js +2 -0
- package/dist/cli/export-1mxb9g5p.js +2 -0
- package/dist/cli/files-g104xghh.js +2 -0
- package/dist/cli/gauntlet-07xrjpj7.js +2 -0
- package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
- package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
- package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
- package/dist/cli/hashline-znvrat11.js +2 -0
- package/dist/cli/http-xafw6fsh.js +143 -0
- package/dist/cli/index-1sgjm25y.js +2 -0
- package/dist/cli/init-g2m0tn4m.js +51 -0
- package/dist/cli/install-avaqjjqq.js +2 -0
- package/dist/cli/loop-mmpfft01.js +2 -0
- package/dist/cli/main-0904f6ps.js +5 -0
- package/dist/cli/main-0ab9fc26.js +9 -0
- package/dist/cli/main-0jys2ccn.js +3 -0
- package/dist/cli/main-0mtcdbs7.js +3 -0
- package/dist/cli/main-0z1w2zsg.js +3 -0
- package/dist/cli/main-1dchs7xv.js +18 -0
- package/dist/cli/main-1ereejm1.js +3 -0
- package/dist/cli/main-1k1kw6b5.js +3 -0
- package/dist/cli/main-27y4sm2k.js +38 -0
- package/dist/cli/main-2wwjex5j.js +58 -0
- package/dist/cli/main-2yeveeve.js +6 -0
- package/dist/cli/main-2yfck9b5.js +3 -0
- package/dist/cli/main-2zmzgkwh.js +3 -0
- package/dist/cli/main-351pz3z7.js +7 -0
- package/dist/cli/main-3gjqfh7a.js +6 -0
- package/dist/cli/main-3nf3kgve.js +3 -0
- package/dist/cli/main-3pjrb2hd.js +3 -0
- package/dist/cli/main-3rxcvgna.js +19 -0
- package/dist/cli/main-4b3jgy66.js +19 -0
- package/dist/cli/main-4wndhjdc.js +7 -0
- package/dist/cli/main-4xcmvxnk.js +3 -0
- package/dist/cli/main-5tbz0wbz.js +4 -0
- package/dist/cli/main-5ywnwthm.js +3 -0
- package/dist/cli/main-6b62vkz0.js +14 -0
- package/dist/cli/main-6dnk69vp.js +3 -0
- package/dist/cli/main-6genrmhs.js +136 -0
- package/dist/cli/main-73g7eff4.js +15 -0
- package/dist/cli/main-7c5thhjd.js +5 -0
- package/dist/cli/main-7rn6bqje.js +3 -0
- package/dist/cli/main-80haw7qk.js +4 -0
- package/dist/cli/main-875s60s2.js +4 -0
- package/dist/cli/main-8kjxbpw4.js +8 -0
- package/dist/cli/main-90ds1z4e.js +10 -0
- package/dist/cli/main-9etavkew.js +3 -0
- package/dist/cli/main-a9njrkk1.js +3 -0
- package/dist/cli/main-aecrjq2d.js +12 -0
- package/dist/cli/main-ck9asesq.js +9 -0
- package/dist/cli/main-cta9racd.js +4 -0
- package/dist/cli/main-ddv7j2ag.js +3 -0
- package/dist/cli/main-dfreez27.js +10 -0
- package/dist/cli/main-f7rw7des.js +3 -0
- package/dist/cli/main-ggcn7rd7.js +5 -0
- package/dist/cli/main-gzkmycnv.js +3 -0
- package/dist/cli/main-hq51jg8v.js +18 -0
- package/dist/cli/main-jft389w9.js +8 -0
- package/dist/cli/main-k1eqkg83.js +3 -0
- package/dist/cli/main-k2y8a2aw.js +9 -0
- package/dist/cli/main-kcpbykxz.js +4 -0
- package/dist/cli/main-kd488vje.js +22 -0
- package/dist/cli/main-kh32yvgk.js +5 -0
- package/dist/cli/main-kqxnqjnv.js +25 -0
- package/dist/cli/main-kyn0xnsg.js +3 -0
- package/dist/cli/main-m1kk6fp5.js +21 -0
- package/dist/cli/main-mv40pcr2.js +4 -0
- package/dist/cli/main-n0t3973w.js +3 -0
- package/dist/cli/main-nqveez48.js +4 -0
- package/dist/cli/main-pknhvrmj.js +3 -0
- package/dist/cli/main-pn1w7a7j.js +3 -0
- package/dist/cli/main-prxxs70n.js +4 -0
- package/dist/cli/main-q3vsesf9.js +3 -0
- package/dist/cli/main-qsevpgsv.js +3 -0
- package/dist/cli/main-rdgdw24b.js +25 -0
- package/dist/cli/main-rfth4tbm.js +16 -0
- package/dist/cli/main-rg0wn0xf.js +5 -0
- package/dist/cli/main-sdmxhtv8.js +4 -0
- package/dist/cli/main-skbp13js.js +18 -0
- package/dist/cli/main-t4xnd213.js +7 -0
- package/dist/cli/main-vqak588n.js +4 -0
- package/dist/cli/main-w2n1303f.js +9 -0
- package/dist/cli/main-wbrdspr2.js +5 -0
- package/dist/cli/main-wsrg79c1.js +7 -0
- package/dist/cli/main-x4r0fne4.js +5 -0
- package/dist/cli/main-xea2f3tn.js +6 -0
- package/dist/cli/main-xg704a3c.js +3 -0
- package/dist/cli/main-xvnrabfp.js +16 -0
- package/dist/cli/main-xy53xf0r.js +4 -0
- package/dist/cli/main-y1fqy60y.js +3 -0
- package/dist/cli/main-yn8cd281.js +34 -0
- package/dist/cli/main-yr0ksc0h.js +4 -0
- package/dist/cli/main-z2ex2vyf.js +4 -0
- package/dist/cli/main-z3aayzvq.js +3 -0
- package/dist/cli/main-zaqh35jg.js +3 -0
- package/dist/cli/main-zc2e8e46.js +4 -0
- package/dist/cli/main-zzrfw6cf.js +13 -0
- package/dist/cli/main.js +280 -0
- package/dist/cli/market-cmd-e14kmx9n.js +5 -0
- package/dist/cli/mcp-login-wq7ktdek.js +2 -0
- package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
- package/dist/cli/notify-b7qc0cjb.js +2 -0
- package/dist/cli/oauth-z8whcgfx.js +2 -0
- package/dist/cli/output-b3ewj3ps.js +16 -0
- package/dist/cli/profiles-6mr5he5e.js +2 -0
- package/dist/cli/provider-config-g7j42q8x.js +2 -0
- package/dist/cli/provider-jr1y8vvm.js +2 -0
- package/dist/cli/registry-s8yk86g0.js +2 -0
- package/dist/cli/registry-t6p8d4mn.js +2 -0
- package/dist/cli/repl-bajwe1mh.js +11 -0
- package/dist/cli/resume-rwn9nz7y.js +2 -0
- package/dist/cli/run-flags-nah7ndpt.js +2 -0
- package/dist/cli/runtime-n7gafzhb.js +2 -0
- package/dist/cli/sandbox-config-emdy18x4.js +2 -0
- package/dist/cli/server-b0nvs2bn.js +5 -0
- package/dist/cli/session-arg-y75wd4kj.js +2 -0
- package/dist/cli/session-j62evmjq.js +2 -0
- package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
- package/dist/cli/settings-df10wfez.js +2 -0
- package/dist/cli/setup-jzvv72fg.js +2 -0
- package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
- package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
- package/dist/cli/smoke-p7748apt.js +8 -0
- package/dist/cli/start-chat-s4st3mm0.js +12 -0
- package/dist/cli/stream-gmeyewds.js +2 -0
- package/dist/cli/task-gh0kkp3n.js +2 -0
- package/dist/cli/tasks-z1kfpe8e.js +2 -0
- package/dist/cli/thinking-0eqkrz6t.js +2 -0
- package/dist/cli/todo-5brcrt9m.js +2 -0
- package/dist/cli/tools-7pzm0vj9.js +2 -0
- package/dist/cli/tools-s635p6s8.js +2 -0
- package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
- package/dist/cli/update-check-pt31bm2f.js +2 -0
- package/dist/cli/update-cmd-tk131s9t.js +2 -0
- package/dist/cli/voice-56nabd8d.js +2 -0
- package/dist/cli/webfetch-xd8q596m.js +2 -0
- package/dist/cli/websearch-5hkf98k1.js +2 -0
- package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
- package/dist/cli/workspace-q10g5z3e.js +2 -0
- package/dist/lib/index.js +62 -0
- package/dist/lib/models-index.json +1 -0
- package/dist/lib/plugins.js +55 -0
- package/dist/lib/providers.js +17 -0
- package/dist/lib/public-api.js +20 -0
- package/dist/lib/sdk.js +360 -0
- package/package.json +84 -72
- package/bin/rovecode.ts +0 -21
- package/src/acp/server.ts +0 -374
- package/src/cli/auth-login.ts +0 -122
- package/src/cli/connect.ts +0 -244
- package/src/cli/context-cmd.ts +0 -199
- package/src/cli/dispatch.ts +0 -82
- package/src/cli/doctor.ts +0 -362
- package/src/cli/export.ts +0 -276
- package/src/cli/help.ts +0 -293
- package/src/cli/is-tui-invocation.ts +0 -8
- package/src/cli/main.ts +0 -583
- package/src/cli/market-cmd.ts +0 -658
- package/src/cli/mcp-login.ts +0 -141
- package/src/cli/mcp-market-cmd.ts +0 -302
- package/src/cli/output.ts +0 -382
- package/src/cli/repl.ts +0 -250
- package/src/cli/repomap-root.ts +0 -14
- package/src/cli/resume.ts +0 -57
- package/src/cli/run-flags.ts +0 -43
- package/src/cli/run-limits.ts +0 -78
- package/src/cli/runtime.ts +0 -931
- package/src/cli/session-arg.ts +0 -30
- package/src/cli/sessions-cmd.ts +0 -145
- package/src/cli/setup.ts +0 -153
- package/src/cli/skills-cmd.ts +0 -194
- package/src/cli/start-chat.ts +0 -65
- package/src/cli/trust-cmd.ts +0 -52
- package/src/coding/bash.ts +0 -148
- package/src/coding/checkpoints.ts +0 -327
- package/src/coding/diff.ts +0 -138
- package/src/coding/files.ts +0 -341
- package/src/coding/hashline.ts +0 -274
- package/src/coding/lsp-gate.ts +0 -254
- package/src/coding/lsp-servers.ts +0 -147
- package/src/coding/lsp.ts +0 -283
- package/src/coding/repomap-cache.ts +0 -99
- package/src/coding/repomap-files.ts +0 -192
- package/src/coding/repomap.ts +0 -481
- package/src/core/agents.ts +0 -255
- package/src/core/compaction.ts +0 -259
- package/src/core/config.ts +0 -289
- package/src/core/context-report.ts +0 -228
- package/src/core/context.ts +0 -60
- package/src/core/count-remote.ts +0 -107
- package/src/core/execpolicy-rules.ts +0 -196
- package/src/core/execpolicy.ts +0 -385
- package/src/core/executor.ts +0 -454
- package/src/core/guardrails.ts +0 -400
- package/src/core/hooks.ts +0 -411
- package/src/core/images.ts +0 -230
- package/src/core/intro.ts +0 -266
- package/src/core/loop.ts +0 -567
- package/src/core/modes.ts +0 -372
- package/src/core/orchestrator.ts +0 -245
- package/src/core/proc-group.ts +0 -48
- package/src/core/project-trust.ts +0 -98
- package/src/core/reflection.ts +0 -165
- package/src/core/sandbox-config.ts +0 -186
- package/src/core/session-id.ts +0 -24
- package/src/core/session-images.ts +0 -73
- package/src/core/session-ops.ts +0 -183
- package/src/core/session-text.ts +0 -29
- package/src/core/session.ts +0 -469
- package/src/core/settings.ts +0 -170
- package/src/core/tasks.ts +0 -646
- package/src/core/token-scale.ts +0 -108
- package/src/core/tools.ts +0 -309
- package/src/core/trust.ts +0 -104
- package/src/core/types.ts +0 -330
- package/src/core/update-check.ts +0 -171
- package/src/core/usage.ts +0 -204
- package/src/core/validate.ts +0 -121
- package/src/core/verify-gate.ts +0 -159
- package/src/core/verify.ts +0 -236
- package/src/core/voice.ts +0 -158
- package/src/core/win-job.ts +0 -183
- package/src/core/workspace.ts +0 -184
- package/src/design/audit.ts +0 -797
- package/src/design/direction.ts +0 -190
- package/src/design/rules.ts +0 -157
- package/src/eval/bench.ts +0 -150
- package/src/eval/gauntlet-runner.ts +0 -215
- package/src/eval/gauntlet-support.ts +0 -84
- package/src/eval/gauntlet-wave3.ts +0 -269
- package/src/eval/gauntlet-wave4.ts +0 -217
- package/src/eval/gauntlet.ts +0 -253
- package/src/index.ts +0 -17
- package/src/lanes/agy.ts +0 -95
- package/src/lanes/approval.ts +0 -24
- package/src/lanes/claude.ts +0 -129
- package/src/lanes/codex.ts +0 -127
- package/src/lanes/events.ts +0 -130
- package/src/lanes/job.ts +0 -142
- package/src/lanes/opencode.ts +0 -122
- package/src/lanes/process.ts +0 -184
- package/src/lanes/progress.ts +0 -183
- package/src/lanes/registry.ts +0 -178
- package/src/lanes/runner.ts +0 -124
- package/src/lanes/types.ts +0 -112
- package/src/market/catalogs/mcp-docs.json +0 -111
- package/src/market/catalogs/plugins.json +0 -111
- package/src/market/catalogs/skills.json +0 -478
- package/src/market/clone.ts +0 -72
- package/src/market/context-cost.ts +0 -121
- package/src/market/digest.ts +0 -106
- package/src/market/index.ts +0 -22
- package/src/market/install.ts +0 -578
- package/src/market/manifest.ts +0 -187
- package/src/market/prereq.ts +0 -145
- package/src/market/registry.ts +0 -363
- package/src/market/resolve.ts +0 -111
- package/src/market/types.ts +0 -236
- package/src/market/validate.ts +0 -227
- package/src/mcp/client.ts +0 -449
- package/src/mcp/config.ts +0 -252
- package/src/mcp/local-package.ts +0 -211
- package/src/mcp/market-catalog.ts +0 -84
- package/src/mcp/market-install.ts +0 -289
- package/src/mcp/market.ts +0 -362
- package/src/mcp/oauth.ts +0 -251
- package/src/mcp/prompts-resources.ts +0 -249
- package/src/mcp/shared.ts +0 -149
- package/src/mcp/status.ts +0 -67
- package/src/mcp/tools.ts +0 -275
- package/src/mcp/transport.ts +0 -122
- package/src/mcp/trust.ts +0 -25
- package/src/memory/blocks.ts +0 -278
- package/src/memory/recall.ts +0 -355
- package/src/memory/scope.ts +0 -182
- package/src/memory/store.ts +0 -105
- package/src/memory/tools.ts +0 -99
- package/src/plugins/cli.ts +0 -119
- package/src/plugins/discover.ts +0 -108
- package/src/plugins/index.ts +0 -50
- package/src/plugins/install.ts +0 -184
- package/src/plugins/load.ts +0 -124
- package/src/plugins/manifest.ts +0 -92
- package/src/plugins/state.ts +0 -83
- package/src/providers/auth.ts +0 -408
- package/src/providers/cache.ts +0 -223
- package/src/providers/catalog-local.ts +0 -160
- package/src/providers/catalog.ts +0 -421
- package/src/providers/middleware-context.ts +0 -86
- package/src/providers/middleware.ts +0 -373
- package/src/providers/model-list.ts +0 -23
- package/src/providers/oauth/common.ts +0 -105
- package/src/providers/oauth/device-code.ts +0 -107
- package/src/providers/oauth/github-copilot.ts +0 -146
- package/src/providers/oauth/loopback.ts +0 -158
- package/src/providers/oauth/openai.ts +0 -163
- package/src/providers/oauth/openrouter.ts +0 -89
- package/src/providers/oauth/pkce.ts +0 -45
- package/src/providers/oauth/registry.ts +0 -39
- package/src/providers/oauth/seam.ts +0 -89
- package/src/providers/profile-glm53.ts +0 -111
- package/src/providers/profile-sonnet5-persona.ts +0 -65
- package/src/providers/profile-sonnet5-voice.ts +0 -23
- package/src/providers/profiles.ts +0 -156
- package/src/providers/provider-config.ts +0 -311
- package/src/providers/registry.ts +0 -333
- package/src/providers/responses.ts +0 -209
- package/src/providers/retry.ts +0 -234
- package/src/providers/router.ts +0 -294
- package/src/providers/sse.ts +0 -26
- package/src/providers/stream-errors.ts +0 -117
- package/src/providers/stream.ts +0 -566
- package/src/providers/thinking.ts +0 -189
- package/src/providers/wire-messages.ts +0 -129
- package/src/providers/wire-responses.ts +0 -79
- package/src/providers/wire-select.ts +0 -53
- package/src/server/http.ts +0 -291
- package/src/server/openapi.ts +0 -246
- package/src/sextant/card-hits.ts +0 -102
- package/src/sextant/card-keys.ts +0 -55
- package/src/sextant/context-source.ts +0 -157
- package/src/sextant/crew-cards.ts +0 -350
- package/src/sextant/draw-agents.ts +0 -273
- package/src/sextant/draw-code.ts +0 -388
- package/src/sextant/draw-context.ts +0 -222
- package/src/sextant/draw-frame.ts +0 -164
- package/src/sextant/draw-market.ts +0 -573
- package/src/sextant/draw-messages.ts +0 -386
- package/src/sextant/draw-pet.ts +0 -230
- package/src/sextant/draw-plan.ts +0 -187
- package/src/sextant/draw-tabs.ts +0 -85
- package/src/sextant/draw-util.ts +0 -65
- package/src/sextant/draw-wizard.ts +0 -378
- package/src/sextant/engine.ts +0 -230
- package/src/sextant/frame-hits.ts +0 -25
- package/src/sextant/frame.ts +0 -101
- package/src/sextant/git-status.ts +0 -197
- package/src/sextant/grid.ts +0 -59
- package/src/sextant/input.ts +0 -119
- package/src/sextant/keys.ts +0 -521
- package/src/sextant/layout.ts +0 -86
- package/src/sextant/local-commands.ts +0 -169
- package/src/sextant/market-source.ts +0 -287
- package/src/sextant/mentions.ts +0 -200
- package/src/sextant/message-hits.ts +0 -26
- package/src/sextant/model.ts +0 -387
- package/src/sextant/overlays.ts +0 -456
- package/src/sextant/panel-hits.ts +0 -38
- package/src/sextant/pet.ts +0 -399
- package/src/sextant/screen.ts +0 -324
- package/src/sextant/scroll-hits.ts +0 -66
- package/src/sextant/scrollbar.ts +0 -82
- package/src/sextant/sextant-bridge.ts +0 -174
- package/src/sextant/sextant-cards.ts +0 -142
- package/src/sextant/sextant-diff-base.ts +0 -63
- package/src/sextant/sextant-files.ts +0 -154
- package/src/sextant/sextant-frame-loop.ts +0 -335
- package/src/sextant/sextant-renderer.ts +0 -574
- package/src/sextant/sextant-repo.ts +0 -140
- package/src/sextant/theme.ts +0 -66
- package/src/sextant/tool-rows.ts +0 -189
- package/src/sextant/types.ts +0 -493
- package/src/skills/index.ts +0 -387
- package/src/skills/pack.ts +0 -220
- package/src/skills/spec.ts +0 -162
- package/src/skills/tools.ts +0 -69
- package/src/skills/versioned.ts +0 -227
- package/src/telemetry/otel-export.ts +0 -122
- package/src/telemetry/otel-lanes.ts +0 -89
- package/src/telemetry/otel-logs.ts +0 -131
- package/src/telemetry/otel-metrics.ts +0 -136
- package/src/telemetry/otel.ts +0 -397
- package/src/telemetry/otlp.ts +0 -76
- package/src/tools/ask-user.ts +0 -156
- package/src/tools/bash-bg.ts +0 -94
- package/src/tools/bash-jobs.ts +0 -237
- package/src/tools/design.ts +0 -151
- package/src/tools/evalcell.ts +0 -338
- package/src/tools/html-text.ts +0 -139
- package/src/tools/provider.ts +0 -149
- package/src/tools/task.ts +0 -250
- package/src/tools/todo.ts +0 -320
- package/src/tools/webfetch.ts +0 -332
- package/src/tools/websearch.ts +0 -359
- package/src/tui/agents-cmd.ts +0 -41
- package/src/tui/app.ts +0 -749
- package/src/tui/attach.ts +0 -127
- package/src/tui/boot-notes.ts +0 -41
- package/src/tui/builtin-prompts.ts +0 -59
- package/src/tui/checkpoints-cmd.ts +0 -70
- package/src/tui/clipboard-image.ts +0 -81
- package/src/tui/clipboard.ts +0 -78
- package/src/tui/commands.ts +0 -283
- package/src/tui/config-view.ts +0 -53
- package/src/tui/context-cmds.ts +0 -282
- package/src/tui/cost.ts +0 -108
- package/src/tui/crash-guard.ts +0 -173
- package/src/tui/focus-terminal.ts +0 -34
- package/src/tui/git-cmds.ts +0 -273
- package/src/tui/git-plain.ts +0 -58
- package/src/tui/info-cmd.ts +0 -150
- package/src/tui/input-plain.ts +0 -76
- package/src/tui/mcp-cmd.ts +0 -128
- package/src/tui/memory-note.ts +0 -77
- package/src/tui/modes-cmd.ts +0 -45
- package/src/tui/notify-seq.ts +0 -100
- package/src/tui/notify.ts +0 -318
- package/src/tui/overlays.ts +0 -97
- package/src/tui/pi-renderer.ts +0 -428
- package/src/tui/providers-cmd.ts +0 -377
- package/src/tui/reasoning-view.ts +0 -56
- package/src/tui/renderer.ts +0 -128
- package/src/tui/replay-marker.ts +0 -29
- package/src/tui/session-cmd.ts +0 -148
- package/src/tui/session-manage.ts +0 -95
- package/src/tui/sextant-attach.ts +0 -102
- package/src/tui/sextant-io.ts +0 -202
- package/src/tui/sextant-smoke.ts +0 -110
- package/src/tui/shell-cmd.ts +0 -158
- package/src/tui/smoke.ts +0 -72
- package/src/tui/staged-terminal.ts +0 -50
- package/src/tui/startup.ts +0 -12
- package/src/tui/theme.ts +0 -59
- package/src/tui/todo-label.ts +0 -7
- package/src/tui/trust-card.ts +0 -107
- package/src/tui/tui-commands.ts +0 -87
- package/tsconfig.json +0 -30
- package/vendor/pi-tui/LICENSE +0 -21
- package/vendor/pi-tui/PATCHES.md +0 -12
- package/vendor/pi-tui/PROVENANCE.md +0 -12
- package/vendor/pi-tui/README.upstream.md +0 -854
- 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 +0 -158
- package/vendor/pi-tui/src/autocomplete.ts +0 -827
- package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
- package/vendor/pi-tui/src/components/box.ts +0 -138
- package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
- package/vendor/pi-tui/src/components/editor.ts +0 -2364
- package/vendor/pi-tui/src/components/h-stack.ts +0 -45
- package/vendor/pi-tui/src/components/image.ts +0 -128
- package/vendor/pi-tui/src/components/input.ts +0 -448
- package/vendor/pi-tui/src/components/loader.ts +0 -93
- package/vendor/pi-tui/src/components/markdown.ts +0 -1016
- package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
- package/vendor/pi-tui/src/components/select-list.ts +0 -230
- package/vendor/pi-tui/src/components/settings-list.ts +0 -277
- package/vendor/pi-tui/src/components/spacer.ts +0 -29
- package/vendor/pi-tui/src/components/stack.ts +0 -155
- package/vendor/pi-tui/src/components/text.ts +0 -108
- package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
- package/vendor/pi-tui/src/components/v-stack.ts +0 -34
- package/vendor/pi-tui/src/editor-component.ts +0 -75
- package/vendor/pi-tui/src/fuzzy.ts +0 -138
- package/vendor/pi-tui/src/index.ts +0 -149
- package/vendor/pi-tui/src/keybindings.ts +0 -321
- package/vendor/pi-tui/src/keys.ts +0 -1402
- package/vendor/pi-tui/src/kill-ring.ts +0 -47
- package/vendor/pi-tui/src/latex.ts +0 -1381
- package/vendor/pi-tui/src/layout-node.ts +0 -52
- package/vendor/pi-tui/src/layout.ts +0 -411
- package/vendor/pi-tui/src/native-modifiers.ts +0 -60
- package/vendor/pi-tui/src/native-module-path.ts +0 -32
- package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
- package/vendor/pi-tui/src/terminal-colors.ts +0 -74
- package/vendor/pi-tui/src/terminal-image.ts +0 -701
- package/vendor/pi-tui/src/terminal.ts +0 -554
- package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
- package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
- package/vendor/pi-tui/src/tui.ts +0 -1264
- package/vendor/pi-tui/src/undo-stack.ts +0 -29
- package/vendor/pi-tui/src/utils.ts +0 -1327
- package/vendor/pi-tui/src/word-navigation.ts +0 -118
- package/vendor/pi-tui/test/test-themes.ts +0 -39
- package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
- /package/{src/providers → dist/cli}/models-index.json +0 -0
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
/** The connect wizard (/connect, /setup on the sextant surface): ONE overlay that walks a model
|
|
2
|
-
* connection end to end — provider, key, model, test — instead of a chain of pickers that each
|
|
3
|
-
* close and reopen. Berkay's design, chosen 2026-09-08: a COLUMNED wizard, the left column the step
|
|
4
|
-
* headers (✓ done / ● current / ○ ahead) and the right side the current step's content, separated by
|
|
5
|
-
* SPACE (his pick over a dim rule and a filled block), so the header column reads as an index rather
|
|
6
|
-
* than a frame.
|
|
7
|
-
*
|
|
8
|
-
* Like draw-context.ts this file owns FLAT view types and imports no registry: the adapter that opens
|
|
9
|
-
* the wizard (sextant-renderer.ts openWizard) maps the provider table onto `WizardState`, and the
|
|
10
|
-
* effects the keys request come back as requests the renderer owns (keys.ts has no registry either).
|
|
11
|
-
* A test builds a state by hand.
|
|
12
|
-
*
|
|
13
|
-
* THE SECRET ROW: the key step's input is masked (•••). This is the only typed secret anywhere in the
|
|
14
|
-
* surface — the question card types in the clear because its answers are conversation, and /provider
|
|
15
|
-
* key refuses on-card entry for the same reason this wizard exists: a key typed in the clear lands in
|
|
16
|
-
* the transcript. The wizard is not the composer; its input is a form field. */
|
|
17
|
-
|
|
18
|
-
import { st } from "./draw-util.ts";
|
|
19
|
-
import { openOverlay } from "./overlays.ts";
|
|
20
|
-
import { ATTR, type HitZone, type KeyEvent, type Layout, type ScreenLike, type SextantState, type Theme } from "./types.ts";
|
|
21
|
-
|
|
22
|
-
// ------------------------------------------------------------------ state
|
|
23
|
-
|
|
24
|
-
/** one provider row in step 1 — flat, from SETUP_PICKS + the registry's knowledge */
|
|
25
|
-
export interface WizardProvider {
|
|
26
|
-
key: string;
|
|
27
|
-
label: string;
|
|
28
|
-
/** true when the registry already has a working entry: shown, and stepping onto it skips to MODEL */
|
|
29
|
-
configured?: boolean;
|
|
30
|
-
/** a local server: the KEY step is skipped for it */
|
|
31
|
-
local?: boolean;
|
|
32
|
-
/** one of the two "your own URL" doors (SETUP_PICKS 8/9): no registry row exists until the wizard
|
|
33
|
-
* has asked for a short id and a base URL and REGISTERED it — the protocol the row speaks */
|
|
34
|
-
url?: "openai" | "anthropic";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** what step 3 lists: the models the registry can name for the chosen provider (endpoint or file).
|
|
38
|
-
* `active` = the provider's pinned list today (spec.models — what /models serves from the file);
|
|
39
|
-
* `defaultModel` = the row's default. Both are FACTS the row carries, painted as tags, and the
|
|
40
|
-
* checked set starts FROM `active` so the step's opening state is the provider's current truth
|
|
41
|
-
* rather than a blank form that silently re-pins everything away. */
|
|
42
|
-
export interface WizardModel { id: string; note?: string; active?: boolean; defaultModel?: boolean }
|
|
43
|
-
|
|
44
|
-
/** The four steps, in walk order. ENDPOINT (id + base URL) is walked only for a url door row.
|
|
45
|
-
* There is no TEST step (Berkay, 2026-09-10): the walk ENDS at MODEL — the checked list is pinned
|
|
46
|
-
* and the default set the moment ⏎ confirms, no probe call runs after it. */
|
|
47
|
-
export const WIZARD_STEPS = ["PROVIDER", "ENDPOINT", "KEY", "MODEL"] as const;
|
|
48
|
-
export type WizardStepName = (typeof WIZARD_STEPS)[number];
|
|
49
|
-
|
|
50
|
-
export interface WizardState {
|
|
51
|
-
/** 0..4 — WIZARD_STEPS[step] is current; a url door walks ENDPOINT (1), a local server skips KEY (2) */
|
|
52
|
-
step: number;
|
|
53
|
-
providers: WizardProvider[];
|
|
54
|
-
/** index into `providers` once chosen */
|
|
55
|
-
selected: number;
|
|
56
|
-
/** the typed short id (ENDPOINT step) — the registry row this door will register under */
|
|
57
|
-
idTyped: string;
|
|
58
|
-
/** the typed base URL (ENDPOINT step) */
|
|
59
|
-
urlTyped: string;
|
|
60
|
-
/** which of the two ENDPOINT fields the caret is on: false = the id, true = the URL */
|
|
61
|
-
urlField: boolean;
|
|
62
|
-
/** the masked key input — the secret itself, never painted */
|
|
63
|
-
secret: string;
|
|
64
|
-
/** false while the key step is being confirmed: a paste that failed to store stays editable */
|
|
65
|
-
keyAccepted: boolean;
|
|
66
|
-
models: WizardModel[];
|
|
67
|
-
/** index into `models`, or -1 when the list is empty and the id is typed */
|
|
68
|
-
modelSel: number;
|
|
69
|
-
/** the CHECKED model ids, in list order — what confirmModels carries as `models`. Space toggles
|
|
70
|
-
* the row under the caret; the set starts as the provider's `active` list, so an untouched walk
|
|
71
|
-
* through the step re-pins exactly what was already pinned (no silent change). */
|
|
72
|
-
modelChecked: readonly string[];
|
|
73
|
-
/** the id ⏎ will name as makeDefault: the marked row, or the FIRST checked one. The default is a
|
|
74
|
-
* separate fact from the list — every checked row becomes active, exactly one becomes default. */
|
|
75
|
-
modelDefaultTo: string | undefined;
|
|
76
|
-
/** the typed model id when the list is empty (a proxy with no /models endpoint) */
|
|
77
|
-
modelTyped: string;
|
|
78
|
-
/** true from the moment the step opens until the endpoint answers (or fails): an empty list the
|
|
79
|
-
* person can SEE is loading is a different thing from an empty list that claims there is none */
|
|
80
|
-
modelsLoading: boolean;
|
|
81
|
-
/** one line under the content when a step refused to advance (bad key, empty model id…) */
|
|
82
|
-
error?: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** open the wizard at its first step; the adapter has already built the provider rows */
|
|
86
|
-
export function openWizard(s: SextantState, providers: WizardProvider[]): void {
|
|
87
|
-
openOverlay(s, null); // one overlay at a time — closes palette/market/help/context like every opener
|
|
88
|
-
s.wizard = { step: 0, providers, selected: 0, idTyped: "", urlTyped: "", urlField: false, secret: "", keyAccepted: false, models: [], modelSel: 0, modelChecked: [], modelDefaultTo: undefined, modelTyped: "", modelsLoading: false };
|
|
89
|
-
}
|
|
90
|
-
export function closeWizard(s: SextantState): void { s.wizard = null; }
|
|
91
|
-
|
|
92
|
-
// ------------------------------------------------------------------ view (pure — tests read these)
|
|
93
|
-
|
|
94
|
-
export const MASK = "•";
|
|
95
|
-
/** the masked row: one • per code point, capped so a pasted 2 KB token cannot push the layout */
|
|
96
|
-
export const MASKED_MAX = 24;
|
|
97
|
-
export function masked(secret: string): string {
|
|
98
|
-
const n = [...secret].length;
|
|
99
|
-
return MASK.repeat(Math.min(n, MASKED_MAX)) + (n > MASKED_MAX ? ` ${n - MASKED_MAX} more` : "");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** the left column's marker for step i: done, current or ahead */
|
|
103
|
-
export function stepMarker(i: number, step: number): string { return i < step ? "✓" : i === step ? "●" : "○"; }
|
|
104
|
-
|
|
105
|
-
/** what the right side of each step shows, as plain lines tagged with a tone — the drawer paints them,
|
|
106
|
-
* the tests pin them. `secretLine` is the masked row; `caret` marks the row the caret sits on. */
|
|
107
|
-
export type WizardTone = "head" | "normal" | "dim" | "warn" | "ok" | "accent";
|
|
108
|
-
export interface WizardLine { text: string; tone: WizardTone; caret?: boolean }
|
|
109
|
-
|
|
110
|
-
export function wizardLines(w: WizardState): WizardLine[] {
|
|
111
|
-
const provider = w.providers[w.selected];
|
|
112
|
-
const out: WizardLine[] = [];
|
|
113
|
-
if (w.step === 0) {
|
|
114
|
-
out.push({ text: "Which provider? ↑↓ choose, ⏎ confirm.", tone: "head" });
|
|
115
|
-
w.providers.forEach((p, i) => out.push({ text: `${i === w.selected ? "❯ " : " "}${p.label}${p.configured ? " · already set up" : ""}`, tone: i === w.selected ? "accent" : "normal" }));
|
|
116
|
-
return out;
|
|
117
|
-
}
|
|
118
|
-
if (provider === undefined) return out;
|
|
119
|
-
if (w.step === 1) {
|
|
120
|
-
// the url door's own two fields: a short id (the registry row this becomes) and the base URL.
|
|
121
|
-
// ONE caret — on the active field, after its text (the inactive field is dim, no caret anywhere
|
|
122
|
-
// else: two ▌ on screen at once read as two fields being typed into at the same time).
|
|
123
|
-
const idCaret = !w.urlField;
|
|
124
|
-
out.push({ text: `${provider.label.split(" (")[0] ?? provider.label} — name it and point it at the server.`, tone: "head" });
|
|
125
|
-
out.push({ text: `Short name? ${w.idTyped}${idCaret ? "▌" : ""}`, tone: idCaret ? "normal" : "dim", ...(idCaret ? { caret: true } : {}) });
|
|
126
|
-
out.push({ text: `Base URL? ${w.urlTyped}${w.urlField ? "▌" : ""}`, tone: w.urlField ? "normal" : "dim", ...(w.urlField ? { caret: true } : {}) });
|
|
127
|
-
out.push({ text: "⇥ switches the field · ⏎ registers it and moves to the key", tone: "dim" });
|
|
128
|
-
if (w.error !== undefined) out.push({ text: w.error, tone: "warn" });
|
|
129
|
-
return out;
|
|
130
|
-
}
|
|
131
|
-
if (w.step === 2) {
|
|
132
|
-
out.push({ text: `Paste the API key for ${provider.label.split(" — ")[0] ?? provider.label}.`, tone: "head" });
|
|
133
|
-
out.push({ text: "It is stored in the credentials file, never in the transcript.", tone: "dim" });
|
|
134
|
-
out.push({ text: "", tone: "dim" });
|
|
135
|
-
out.push({ text: masked(w.secret) || "", tone: "normal", caret: true });
|
|
136
|
-
if (w.error !== undefined) out.push({ text: w.error, tone: "warn" });
|
|
137
|
-
return out;
|
|
138
|
-
}
|
|
139
|
-
if (w.step === 3) {
|
|
140
|
-
out.push({ text: `Which models on ${provider.label.split(" — ")[0] ?? provider.label}? Space checks, ⏎ confirms.`, tone: "head" });
|
|
141
|
-
if (w.modelsLoading && w.models.length === 0) {
|
|
142
|
-
out.push({ text: "Fetching the model list from the endpoint…", tone: "accent" });
|
|
143
|
-
return out;
|
|
144
|
-
}
|
|
145
|
-
if (w.models.length > 0) {
|
|
146
|
-
w.models.forEach((m, i) => {
|
|
147
|
-
// the checkbox is the row's state (✓ checked / ☐ not); the tags behind the id are the
|
|
148
|
-
// provider's OWN facts — `active` (pinned today) and `default` — so the person edits a list
|
|
149
|
-
// they can see the current truth of, and `•` marks which checked row becomes the default
|
|
150
|
-
const checked = w.modelChecked.includes(m.id);
|
|
151
|
-
const tags = `${m.active === true ? " · active" : ""}${m.defaultModel === true ? " · default" : ""}`;
|
|
152
|
-
const mark = checked && w.modelDefaultTo === m.id ? "•" : "";
|
|
153
|
-
out.push({ text: `${i === w.modelSel ? "❯ " : " "}${checked ? "✓" : "☐"} ${m.id}${tags}${mark ? ` ${mark}` : ""}`, tone: i === w.modelSel ? "accent" : "normal" });
|
|
154
|
-
});
|
|
155
|
-
} else {
|
|
156
|
-
out.push({ text: "No model list from this endpoint — type the model id:", tone: "dim" });
|
|
157
|
-
out.push({ text: w.modelTyped, tone: "normal", caret: true });
|
|
158
|
-
}
|
|
159
|
-
if (w.error !== undefined) out.push({ text: w.error, tone: "warn" });
|
|
160
|
-
return out;
|
|
161
|
-
}
|
|
162
|
-
return out;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ------------------------------------------------------------------ draw
|
|
166
|
-
|
|
167
|
-
export function drawWizard(scr: ScreenLike, L: Layout, C: Theme, s: SextantState, hits?: HitZone[]): { x: number; y: number } | null {
|
|
168
|
-
const w = s.wizard;
|
|
169
|
-
if (!w) return null;
|
|
170
|
-
hits?.push({ rect: { x: 0, y: 0, w: L.w, h: L.h }, onClick: () => { /* the box below swallows it */ } });
|
|
171
|
-
|
|
172
|
-
const lines = wizardLines(w);
|
|
173
|
-
const w0 = Math.min(L.w - 4, 64);
|
|
174
|
-
const h = Math.max(10, Math.min(L.h - 4, Math.max(WIZARD_STEPS.length, lines.length) + 5));
|
|
175
|
-
const x = Math.floor((L.w - w0) / 2), y = Math.max(1, Math.floor((L.h - h) / 2));
|
|
176
|
-
scr.box(x, y, w0, h, st(C.accent), C.bg2);
|
|
177
|
-
scr.text(x + 2, y, [[" connect a model ", st(C.accent, -1, ATTR.BOLD)]]);
|
|
178
|
-
scr.put(x + w0 - 13, y, " esc closes ", st(C.dim, C.bg2));
|
|
179
|
-
hits?.push({ rect: { x, y, w: w0, h }, onClick: () => {} }); // a click inside is not a close
|
|
180
|
-
|
|
181
|
-
// the left column: the step headers, MARKED — done ✓ / current ● / ahead ○. Space separates the
|
|
182
|
-
// columns (Berkay's pick): no rule, no fill — the markers carry the progression on their own.
|
|
183
|
-
const leftW = 13;
|
|
184
|
-
WIZARD_STEPS.forEach((name, i) => {
|
|
185
|
-
const current = i === w.step;
|
|
186
|
-
scr.put(x + 2, y + 2 + i, `${stepMarker(i, w.step)} ${name}`, current ? st(C.accent, C.bg2, ATTR.BOLD) : i < w.step ? st(C.fg2, C.bg2) : st(C.muted, C.bg2));
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// the right side: the step's lines, clipped to the inner width, with the caret row on the box's edge
|
|
190
|
-
const innerX = x + 2 + leftW + 2;
|
|
191
|
-
const innerW = Math.max(10, w0 - (innerX - x) - 3);
|
|
192
|
-
const rows = h - 3;
|
|
193
|
-
lines.slice(0, rows).forEach((line, i) => {
|
|
194
|
-
const style = line.tone === "head" ? st(C.fg, C.bg2, ATTR.BOLD)
|
|
195
|
-
: line.tone === "warn" ? st(C.warn, C.bg2)
|
|
196
|
-
: line.tone === "ok" ? st(C.ok, C.bg2)
|
|
197
|
-
: line.tone === "accent" ? st(C.accent, C.bg2)
|
|
198
|
-
: line.tone === "dim" ? st(C.muted, C.bg2)
|
|
199
|
-
: st(C.fg2, C.bg2);
|
|
200
|
-
// a caret row with TEXT carries its own ▌ in the text (the endpoint fields); an EMPTY caret row
|
|
201
|
-
// (the masked secret, the typed model id) gets the ▌ painted here — exactly one caret either way
|
|
202
|
-
const text = line.text === "" && line.caret ? "" : line.text;
|
|
203
|
-
scr.clip(innerX, y + 1 + i, line.caret && line.text === "" ? `${text}▌` : text, style, innerW + 1);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// the footer hints, under both columns
|
|
207
|
-
const back = w.step > 0 ? " ← back" : "";
|
|
208
|
-
scr.put(x + 2, y + h - 1, `⏎ ${w.step === 0 ? "choose" : "next"}${back} esc cancel`, st(C.dim, C.bg2));
|
|
209
|
-
return null; // no composer caret: the wizard paints its own on the secret/model row
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ------------------------------------------------------------------ keys
|
|
213
|
-
|
|
214
|
-
/** What a key asked the renderer to DO. keys.ts owns no registry: the requests come back here and
|
|
215
|
-
* sextant-renderer.ts runs them against the live runtime. `none` = the key only moved the state. */
|
|
216
|
-
export type WizardRequest =
|
|
217
|
-
| { kind: "none" }
|
|
218
|
-
/** ENDPOINT confirmed for a url door: register the row (reg.add) under this id/URL/protocol */
|
|
219
|
-
| { kind: "registerEndpoint"; id: string; url: string; protocol: "openai" | "anthropic" }
|
|
220
|
-
/** KEY confirmed with a non-empty secret: store it (saveCredential + refresh) */
|
|
221
|
-
| { kind: "storeKey"; provider: string; secret: string }
|
|
222
|
-
/** MODEL confirmed (from a list): the CHECKED ids in list order — what the provider's active list
|
|
223
|
-
* becomes (setModels) — and `makeDefault`, the id that also becomes the default (the marked row,
|
|
224
|
-
* else the first checked; undefined only in the typed fallback, where there is no list to edit) */
|
|
225
|
-
| { kind: "confirmModels"; provider: string; models: string[]; makeDefault: string | undefined }
|
|
226
|
-
/** MODEL confirmed by TYPING (no list from the endpoint): one id, no list to pin */
|
|
227
|
-
| { kind: "confirmModel"; model: string }
|
|
228
|
-
/** the model step OPENED (or was re-entered after a key change): load the endpoint's list —
|
|
229
|
-
* the renderer fetches through the wizardModels seam and answers with wizardModelConfirmed */
|
|
230
|
-
| { kind: "loadModels" };
|
|
231
|
-
|
|
232
|
-
/** A paste, while the wizard is up — the KEY step's whole input method: a key arrives as ONE paste
|
|
233
|
-
* event, not as keystrokes, and keys.ts's onPaste routes every paste it does not recognise into the
|
|
234
|
-
* composer (which is how the pasted key ended up in the chat box). Returns true when the paste was
|
|
235
|
-
* consumed: KEY appends to the secret, MODEL appends to the typed id when there is no list, and
|
|
236
|
-
* every other step swallows it (a paste onto a list step is not a selection). Multi-line text
|
|
237
|
-
* collapses to one space — a secret or a model id is one line, and a trailing newline from a
|
|
238
|
-
* terminal copy never becomes part of it. */
|
|
239
|
-
export function wizardPaste(s: SextantState, text: string): boolean {
|
|
240
|
-
const w = s.wizard;
|
|
241
|
-
if (!w) return false;
|
|
242
|
-
const one = text.replace(/\s+/g, " ");
|
|
243
|
-
if (w.step === 1) { if (w.urlField) w.urlTyped += one; else w.idTyped += one; }
|
|
244
|
-
else if (w.step === 2) w.secret += one;
|
|
245
|
-
else if (w.step === 3 && w.models.length === 0) w.modelTyped += one;
|
|
246
|
-
else return true; // swallowed, not typed: nothing leaves the overlay
|
|
247
|
-
w.error = undefined;
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Keys, while the wizard is up. Swallows everything (an overlay that let keystrokes reach the
|
|
252
|
-
* composer would type into a message the human cannot see) except ⌃c, which closes like every overlay. */
|
|
253
|
-
export function onWizardKey(s: SextantState, ev: KeyEvent): WizardRequest {
|
|
254
|
-
const w = s.wizard;
|
|
255
|
-
if (!w) return { kind: "none" };
|
|
256
|
-
const { name, ctrl, ch, alt } = ev;
|
|
257
|
-
if (name === "escape" || (ctrl && name === "c")) { closeWizard(s); return { kind: "none" }; }
|
|
258
|
-
|
|
259
|
-
// the ENDPOINT step's two fields: ⇥ (and ⇧⇥) move the caret between the id and the URL
|
|
260
|
-
if (w.step === 1) {
|
|
261
|
-
if (name === "tab" || name === "shift-tab") { w.urlField = !w.urlField; return { kind: "none" }; }
|
|
262
|
-
}
|
|
263
|
-
const inList = w.step === 0 || (w.step === 3 && w.models.length > 0);
|
|
264
|
-
if (inList) {
|
|
265
|
-
const n = w.step === 0 ? w.providers.length : w.models.length;
|
|
266
|
-
if (name === "up" || name === "k") { if (w.step === 0) w.selected = (w.selected - 1 + n) % n; else w.modelSel = (w.modelSel - 1 + n) % n; return { kind: "none" }; }
|
|
267
|
-
if (name === "down" || name === "j") { if (w.step === 0) w.selected = (w.selected + 1) % n; else w.modelSel = (w.modelSel + 1) % n; return { kind: "none" }; }
|
|
268
|
-
if (name === "tab" && w.step === 3) { w.modelSel = (w.modelSel + 1) % n; return { kind: "none" }; }
|
|
269
|
-
// space on the model list: check/uncheck the row under the caret. Checking the FIRST box moves
|
|
270
|
-
// the default marker onto it (there was none); unchecking the marked row moves the marker to the
|
|
271
|
-
// next checked row — the default is always a checked id, never a ghost
|
|
272
|
-
if ((name === "space" || (ch === " " && !ctrl && !alt)) && w.step === 3) { toggleModelCheck(w); return { kind: "none" }; }
|
|
273
|
-
}
|
|
274
|
-
if (name === "left" && w.step > 0) { w.step -= 1; w.error = undefined; return { kind: "none" }; }
|
|
275
|
-
if (name === "enter") return wizardConfirm(s);
|
|
276
|
-
|
|
277
|
-
// the three typing steps: ENDPOINT (the two fields), KEY, and MODEL when the list is empty AND
|
|
278
|
-
// the fetch has answered (while loading there is no field to type into yet — keys wait)
|
|
279
|
-
const typing = w.step === 1 || w.step === 2 || (w.step === 3 && w.models.length === 0 && !w.modelsLoading);
|
|
280
|
-
if (typing) {
|
|
281
|
-
if (name === "backspace") {
|
|
282
|
-
if (w.step === 1) { if (w.urlField) w.urlTyped = [...w.urlTyped].slice(0, -1).join(""); else w.idTyped = [...w.idTyped].slice(0, -1).join(""); }
|
|
283
|
-
else if (w.step === 2) w.secret = [...w.secret].slice(0, -1).join("");
|
|
284
|
-
else w.modelTyped = [...w.modelTyped].slice(0, -1).join("");
|
|
285
|
-
w.error = undefined;
|
|
286
|
-
return { kind: "none" };
|
|
287
|
-
}
|
|
288
|
-
const c = name === "space" ? " " : ch;
|
|
289
|
-
if (!c || alt || ctrl) return { kind: "none" };
|
|
290
|
-
if (w.step === 1) { if (w.urlField) w.urlTyped += c; else w.idTyped += c; }
|
|
291
|
-
else if (w.step === 2) w.secret += c;
|
|
292
|
-
else w.modelTyped += c;
|
|
293
|
-
w.error = undefined;
|
|
294
|
-
return { kind: "none" };
|
|
295
|
-
}
|
|
296
|
-
return { kind: "none" };
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** ⏎ on the current step: advance, refuse with an error line, or emit the step's request */
|
|
300
|
-
function wizardConfirm(s: SextantState): WizardRequest {
|
|
301
|
-
const w = s.wizard!;
|
|
302
|
-
const provider = w.providers[w.selected];
|
|
303
|
-
if (w.step === 0) {
|
|
304
|
-
if (provider === undefined) return { kind: "none" };
|
|
305
|
-
w.error = undefined;
|
|
306
|
-
// a url door names its endpoint first; a local server has no key step; a configured provider's key already works
|
|
307
|
-
const skip = provider.url !== undefined ? 1 : provider.local === true || provider.configured === true ? 3 : 2;
|
|
308
|
-
w.step = skip;
|
|
309
|
-
return skip === 3 ? { kind: "loadModels" } : { kind: "none" };
|
|
310
|
-
}
|
|
311
|
-
if (w.step === 1) {
|
|
312
|
-
if (provider === undefined || provider.url === undefined) return { kind: "none" };
|
|
313
|
-
const id = w.idTyped.trim().toLowerCase();
|
|
314
|
-
if (id.length === 0) { w.error = "the short name is empty — it becomes the registry row's id"; return { kind: "none" }; }
|
|
315
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/.test(id)) { w.error = `"${id}" won't work as an id — letters, digits, - _ . (myproxy, vllm-home)`; return { kind: "none" }; }
|
|
316
|
-
if (!/^https?:\/\//.test(w.urlTyped.trim())) { w.error = `"${w.urlTyped.trim()}" is not an http(s) URL — the base URL the server answers on`; return { kind: "none" }; }
|
|
317
|
-
w.error = undefined;
|
|
318
|
-
return { kind: "registerEndpoint", id, url: w.urlTyped.trim(), protocol: provider.url };
|
|
319
|
-
}
|
|
320
|
-
if (w.step === 2) {
|
|
321
|
-
const secret = w.secret.trim();
|
|
322
|
-
if (secret.length === 0) { w.error = "the key is empty — paste it, or ← back to change provider"; return { kind: "none" }; }
|
|
323
|
-
if (provider === undefined) return { kind: "none" };
|
|
324
|
-
w.error = undefined;
|
|
325
|
-
return { kind: "storeKey", provider: provider.key, secret };
|
|
326
|
-
}
|
|
327
|
-
if (w.step === 3) {
|
|
328
|
-
if (provider === undefined) return { kind: "none" };
|
|
329
|
-
if (w.models.length > 0) {
|
|
330
|
-
const checked = w.models.filter((m) => w.modelChecked.includes(m.id)).map((m) => m.id);
|
|
331
|
-
if (checked.length === 0) { w.error = "no model checked — space checks a row, or ← back to the key"; return { kind: "none" }; }
|
|
332
|
-
w.error = undefined;
|
|
333
|
-
const makeDefault = w.modelDefaultTo !== undefined && checked.includes(w.modelDefaultTo) ? w.modelDefaultTo : checked[0]!;
|
|
334
|
-
return { kind: "confirmModels", provider: provider.key, models: checked, makeDefault };
|
|
335
|
-
}
|
|
336
|
-
const model = w.modelTyped.trim();
|
|
337
|
-
if (model.length === 0) { w.error = "no model chosen — pick a row or type the id"; return { kind: "none" }; }
|
|
338
|
-
w.error = undefined;
|
|
339
|
-
return { kind: "confirmModel", model };
|
|
340
|
-
}
|
|
341
|
-
return { kind: "none" };
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/** the renderer's answers, written back onto the state: registerEndpoint → the row exists now (or
|
|
345
|
-
* the error line says why not); storeKey → advance or refuse; confirmModel → the model list / advance;
|
|
346
|
-
* runTest → the probe outcome. */
|
|
347
|
-
export function wizardEndpointRegistered(w: WizardState, ok: boolean, error?: string): void {
|
|
348
|
-
if (ok) { w.step = 2; w.error = undefined; }
|
|
349
|
-
else w.error = error ?? "the endpoint could not be registered";
|
|
350
|
-
}
|
|
351
|
-
export function wizardKeyStored(w: WizardState, ok: boolean, error?: string): void {
|
|
352
|
-
if (ok) { w.keyAccepted = true; w.step = 3; w.modelsLoading = true; w.error = undefined; }
|
|
353
|
-
else { w.keyAccepted = false; w.error = error ?? "the key could not be stored"; }
|
|
354
|
-
}
|
|
355
|
-
export function wizardModelConfirmed(w: WizardState, models: WizardModel[]): void {
|
|
356
|
-
w.models = models;
|
|
357
|
-
w.modelsLoading = false;
|
|
358
|
-
w.modelSel = 0;
|
|
359
|
-
// the checked set starts as the provider's OWN active list (a fact the rows carry): an untouched
|
|
360
|
-
// pass through the step re-pins what was pinned, and the default marker starts on the row that is
|
|
361
|
-
// the provider's default today — the form opens on the current truth, not on blank
|
|
362
|
-
w.modelChecked = models.filter((m) => m.active === true).map((m) => m.id);
|
|
363
|
-
const def = models.find((m) => m.defaultModel === true);
|
|
364
|
-
w.modelDefaultTo = def !== undefined && w.modelChecked.includes(def.id) ? def.id : w.modelChecked[0];
|
|
365
|
-
w.step = 3;
|
|
366
|
-
w.error = undefined;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/** space on a model row: check/uncheck it, keeping the default marker on a CHECKED row */
|
|
370
|
-
export function toggleModelCheck(w: WizardState): void {
|
|
371
|
-
const m = w.models[w.modelSel];
|
|
372
|
-
if (m === undefined) return;
|
|
373
|
-
const has = w.modelChecked.includes(m.id);
|
|
374
|
-
const next = has ? w.modelChecked.filter((id) => id !== m.id) : [...w.modelChecked, m.id];
|
|
375
|
-
w.modelChecked = next;
|
|
376
|
-
if (w.modelDefaultTo === undefined || !next.includes(w.modelDefaultTo)) w.modelDefaultTo = next[0];
|
|
377
|
-
w.error = undefined;
|
|
378
|
-
}
|
package/src/sextant/engine.ts
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/** ported from the user's sextant v0.4.0 prototype, src/engine.js (+ wrap() from app.js lines 66-78) */
|
|
2
|
-
/* Pure helpers for the panels: syntax tokenizer, diff hunks/line marks over jsdiff output, fuzzy
|
|
3
|
-
match, word wrap, small formatters. The prototype's O(n·m) LCS diffLines() is NOT ported —
|
|
4
|
-
jsdiff (Myers, already a dependency) produces the ops, which then feed the same hunk/mark code. */
|
|
5
|
-
|
|
6
|
-
import { diffLines, type ChangeObject, type StructuredPatch } from "diff";
|
|
7
|
-
import type { DiffHunk } from "./types.ts";
|
|
8
|
-
|
|
9
|
-
export type Lang = "ts" | "json" | "md" | "text";
|
|
10
|
-
export const LANG_NAME: Readonly<Record<Lang, string>> = { ts: "TypeScript", json: "JSON", md: "Markdown", text: "Plain Text" };
|
|
11
|
-
|
|
12
|
-
/** split on \n, dropping the empty tail a trailing newline leaves */
|
|
13
|
-
export const splitLines = (s: string): string[] => { const l = s.split("\n"); if (l.length && l[l.length - 1] === "") l.pop(); return l; };
|
|
14
|
-
export const basename = (p: string): string => p.slice(p.lastIndexOf("/") + 1);
|
|
15
|
-
export const dirname = (p: string): string => (p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "");
|
|
16
|
-
|
|
17
|
-
/** language by extension; the TS tokenizer serves the whole JS/TS family (the prototype knew .ts/.tsx only) */
|
|
18
|
-
export function langOf(p: string): Lang {
|
|
19
|
-
if (/\.[cm]?[jt]sx?$/.test(p)) return "ts";
|
|
20
|
-
if (/\.jsonc?$/.test(p)) return "json";
|
|
21
|
-
if (/\.(md|markdown)$/.test(p)) return "md";
|
|
22
|
-
return "text";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/* ------------------------------------------------------------ tokenizer */
|
|
26
|
-
|
|
27
|
-
export type TokenClass = "kw" | "str" | "num" | "cm" | "fn" | "ty" | "pu" | "op" | "prop" | "id" | "key" | "dec" | "plain";
|
|
28
|
-
export type Token = [string, TokenClass];
|
|
29
|
-
|
|
30
|
-
const KW = new Set("import export from const let var function return if else async await new type interface extends implements class throw try catch finally for while of in as default void typeof instanceof null undefined true false this readonly declare satisfies keyof enum switch case break continue do yield static private public protected".split(" "));
|
|
31
|
-
const TS_RE = /(\/\/.*)|("(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b\d[\d_]*(?:\.\d+)?\b)|(@?[A-Za-z_$][\w$]*)|([{}()[\]])|([;,.:])|(=>|\?\?|\?\.|[<>=!+\-*/%&|^~?]+)/g;
|
|
32
|
-
|
|
33
|
-
/** one source line → [text, class] runs; "text" is one plain run, "md" goes through tokenizeMd */
|
|
34
|
-
export function tokenize(line: string, lang: string): Token[] {
|
|
35
|
-
if (lang === "md") return tokenizeMd(line);
|
|
36
|
-
if (lang === "text") return [[line, "plain"]];
|
|
37
|
-
const out: Token[] = [];
|
|
38
|
-
let last = 0;
|
|
39
|
-
let m: RegExpExecArray | null;
|
|
40
|
-
TS_RE.lastIndex = 0;
|
|
41
|
-
while ((m = TS_RE.exec(line))) {
|
|
42
|
-
if (m.index > last) out.push([line.slice(last, m.index), "plain"]);
|
|
43
|
-
const tok = m[0];
|
|
44
|
-
let cls: TokenClass;
|
|
45
|
-
if (m[1]) cls = "cm";
|
|
46
|
-
else if (m[2]) cls = lang === "json" && /^\s*:/.test(line.slice(m.index + tok.length)) ? "key" : "str";
|
|
47
|
-
else if (m[3]) cls = "num";
|
|
48
|
-
else if (m[4]) {
|
|
49
|
-
const before = line[m.index - 1];
|
|
50
|
-
const after = line.slice(m.index + tok.length);
|
|
51
|
-
if (tok[0] === "@") cls = "dec";
|
|
52
|
-
else if (KW.has(tok)) cls = "kw";
|
|
53
|
-
else if (before === ".") cls = "prop";
|
|
54
|
-
else if (/^\s*\(/.test(after) || (/^</.test(after) && /^[a-z]/.test(tok))) cls = "fn";
|
|
55
|
-
else if (/^[A-Z]/.test(tok)) cls = "ty";
|
|
56
|
-
else if (/^\??:/.test(after)) cls = "prop";
|
|
57
|
-
else cls = "id";
|
|
58
|
-
} else if (m[5] || m[6]) cls = "pu";
|
|
59
|
-
else cls = "op";
|
|
60
|
-
out.push([tok, cls]);
|
|
61
|
-
last = m.index + tok.length;
|
|
62
|
-
}
|
|
63
|
-
if (last < line.length) out.push([line.slice(last), "plain"]);
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function tokenizeMd(line: string): Token[] {
|
|
68
|
-
if (/^#{1,6}\s/.test(line)) return [[line, "kw"]];
|
|
69
|
-
if (/^\s{4}/.test(line)) return [[line, "str"]];
|
|
70
|
-
const out: Token[] = [];
|
|
71
|
-
let last = 0;
|
|
72
|
-
let m: RegExpExecArray | null;
|
|
73
|
-
const re = /`[^`]+`/g;
|
|
74
|
-
while ((m = re.exec(line))) {
|
|
75
|
-
if (m.index > last) out.push([line.slice(last, m.index), "plain"]);
|
|
76
|
-
out.push([m[0], "str"]);
|
|
77
|
-
last = m.index + m[0].length;
|
|
78
|
-
}
|
|
79
|
-
if (last < line.length) out.push([line.slice(last), "plain"]);
|
|
80
|
-
return out;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/* ------------------------------------------------------------ diff */
|
|
84
|
-
|
|
85
|
-
export type OpKind = "eq" | "del" | "ins";
|
|
86
|
-
/** one diff line; an/bn are the 1-based old/new line numbers (absent on the side the line is not on) */
|
|
87
|
-
export interface DiffOp { t: OpKind; text: string; an?: number; bn?: number }
|
|
88
|
-
/** a run of ops with its @@ header numbers: old start/count, new start/count */
|
|
89
|
-
export interface Hunk { ops: DiffOp[]; as: number; bs: number; ac: number; bc: number }
|
|
90
|
-
export type LineMark = "added" | "changed";
|
|
91
|
-
|
|
92
|
-
/** jsdiff diffLines() change objects → the prototype's op list with line numbers */
|
|
93
|
-
export function opsFromChanges(changes: readonly ChangeObject<string>[]): DiffOp[] {
|
|
94
|
-
const ops: DiffOp[] = [];
|
|
95
|
-
let an = 0, bn = 0;
|
|
96
|
-
for (const c of changes) {
|
|
97
|
-
const lines = c.value.split("\n");
|
|
98
|
-
if (lines.length > c.count) lines.pop(); // the value ends with its last line's newline
|
|
99
|
-
for (const text of lines) {
|
|
100
|
-
if (c.added) ops.push({ t: "ins", text, bn: ++bn });
|
|
101
|
-
else if (c.removed) ops.push({ t: "del", text, an: ++an });
|
|
102
|
-
else ops.push({ t: "eq", text, an: ++an, bn: ++bn });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return ops;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** line-diff two texts (jsdiff Myers) → ops; replaces the prototype's O(n·m) LCS table */
|
|
109
|
-
export function diffOps(a: string, b: string): DiffOp[] {
|
|
110
|
-
return opsFromChanges(diffLines(a, b));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** added/deleted line counts */
|
|
114
|
-
export const opStats = (ops: readonly DiffOp[]): { a: number; d: number } => {
|
|
115
|
-
let a = 0, d = 0;
|
|
116
|
-
for (const o of ops) { if (o.t === "ins") a++; else if (o.t === "del") d++; }
|
|
117
|
-
return { a, d };
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
function hunkOf(ops: readonly DiffOp[], s: number, e: number): Hunk {
|
|
121
|
-
const slice = ops.slice(s, e);
|
|
122
|
-
let as: number | null = null, bs: number | null = null, ac = 0, bc = 0;
|
|
123
|
-
for (const o of slice) {
|
|
124
|
-
if (o.an) { if (as == null) as = o.an; ac++; }
|
|
125
|
-
if (o.bn) { if (bs == null) bs = o.bn; bc++; }
|
|
126
|
-
}
|
|
127
|
-
return { ops: slice, as: as ?? 0, bs: bs ?? 0, ac, bc };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** group changes into hunks with `ctx` context lines; changes closer than 2·ctx share a hunk; Infinity = one hunk */
|
|
131
|
-
export function buildHunks(ops: readonly DiffOp[], ctx: number): Hunk[] {
|
|
132
|
-
if (!isFinite(ctx)) return ops.length ? [hunkOf(ops, 0, ops.length)] : [];
|
|
133
|
-
const hunks: Hunk[] = [];
|
|
134
|
-
let i = 0;
|
|
135
|
-
while (i < ops.length) {
|
|
136
|
-
if (ops[i]!.t === "eq") { i++; continue; }
|
|
137
|
-
const start = Math.max(0, i - ctx);
|
|
138
|
-
let end = i + 1, j = i + 1;
|
|
139
|
-
while (j < ops.length) {
|
|
140
|
-
if (ops[j]!.t !== "eq") { end = j + 1; j++; continue; }
|
|
141
|
-
let k = j;
|
|
142
|
-
while (k < ops.length && ops[k]!.t === "eq") k++;
|
|
143
|
-
if (k < ops.length && k - j <= ctx * 2) { j = k; continue; }
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
end = Math.min(ops.length, end + ctx);
|
|
147
|
-
hunks.push(hunkOf(ops, start, end));
|
|
148
|
-
i = end;
|
|
149
|
-
}
|
|
150
|
-
return hunks;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** new-line-number → "added" (pure insertion) | "changed" (insertion next to a deletion) */
|
|
154
|
-
export function lineMarks(ops: readonly DiffOp[]): Map<number, LineMark> {
|
|
155
|
-
const marks = new Map<number, LineMark>();
|
|
156
|
-
let i = 0;
|
|
157
|
-
while (i < ops.length) {
|
|
158
|
-
if (ops[i]!.t === "eq") { i++; continue; }
|
|
159
|
-
let j = i, hasDel = false;
|
|
160
|
-
while (j < ops.length && ops[j]!.t !== "eq") { if (ops[j]!.t === "del") hasDel = true; j++; }
|
|
161
|
-
for (let k = i; k < j; k++) { const o = ops[k]!; if (o.t === "ins" && o.bn) marks.set(o.bn, hasDel ? "changed" : "added"); }
|
|
162
|
-
i = j;
|
|
163
|
-
}
|
|
164
|
-
return marks;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/** a prototype hunk → the contract's DiffHunk rows */
|
|
168
|
-
export function toDiffHunk(h: Hunk): DiffHunk {
|
|
169
|
-
return { rows: h.ops.map((o) => ({ op: o.t === "ins" ? "+" : o.t === "del" ? "-" : " ", text: o.text })), oldStart: h.as, newStart: h.bs };
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/** jsdiff structuredPatch() hunks → the contract's DiffHunk rows ("" stays a context row) */
|
|
173
|
-
export function hunksFromPatch(patch: StructuredPatch): DiffHunk[] {
|
|
174
|
-
return patch.hunks.map((h) => ({
|
|
175
|
-
oldStart: h.oldStart,
|
|
176
|
-
newStart: h.newStart,
|
|
177
|
-
rows: h.lines.map((l) => {
|
|
178
|
-
const c = l[0];
|
|
179
|
-
const op = c === "+" ? "+" : c === "-" ? "-" : " ";
|
|
180
|
-
return { op, text: c === "+" || c === "-" || c === " " ? l.slice(1) : l };
|
|
181
|
-
}),
|
|
182
|
-
}));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* ------------------------------------------------------------ fuzzy */
|
|
186
|
-
|
|
187
|
-
export interface FuzzyMatch { score: number; idx: number[] }
|
|
188
|
-
|
|
189
|
-
/** subsequence match, case-insensitive: +3 for a run, +1 otherwise, +2 at a word/path start; null when q is not in s */
|
|
190
|
-
export function fuzzy(q: string, s: string): FuzzyMatch | null {
|
|
191
|
-
q = q.toLowerCase(); s = s.toLowerCase();
|
|
192
|
-
let qi = 0, score = 0, last = -2;
|
|
193
|
-
const idx: number[] = [];
|
|
194
|
-
for (let i = 0; i < s.length && qi < q.length; i++) {
|
|
195
|
-
if (s[i] === q[qi]) {
|
|
196
|
-
idx.push(i);
|
|
197
|
-
score += (last === i - 1 ? 3 : 1) + (i === 0 || s[i - 1] === " " || s[i - 1] === "/" ? 2 : 0);
|
|
198
|
-
last = i; qi++;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return qi === q.length ? { score, idx } : null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/* ------------------------------------------------------------ text */
|
|
205
|
-
|
|
206
|
-
/** greedy word wrap per paragraph (\n); a word longer than width is hard-split (app.js wrap) */
|
|
207
|
-
export function wrap(text: string, width: number): string[] {
|
|
208
|
-
width = Math.max(1, width); // width ≤ 0 never advanced the hard-split loop below (prototype quirk)
|
|
209
|
-
const out: string[] = [];
|
|
210
|
-
for (const para of String(text).split("\n")) {
|
|
211
|
-
let line = "";
|
|
212
|
-
for (const w of para.split(" ")) {
|
|
213
|
-
if (!line) { line = w; continue; }
|
|
214
|
-
if (line.length + 1 + w.length <= width) line += " " + w; else { out.push(line); line = w; }
|
|
215
|
-
}
|
|
216
|
-
while (line.length > width) { out.push(line.slice(0, width)); line = line.slice(width); }
|
|
217
|
-
out.push(line);
|
|
218
|
-
}
|
|
219
|
-
return out;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** mm:ss.t run clock */
|
|
223
|
-
export const fmtClock = (ms: number): string => {
|
|
224
|
-
const s = Math.max(0, ms) / 1000;
|
|
225
|
-
const m = Math.floor(s / 60);
|
|
226
|
-
const r = s - m * 60;
|
|
227
|
-
return `${String(m).padStart(2, "0")}:${r.toFixed(1).padStart(4, "0")}`;
|
|
228
|
-
};
|
|
229
|
-
export const fmtDur = (ms: number): string => (ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`);
|
|
230
|
-
export const fmtK = (n: number): string => (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n));
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/** Click zones on the outer frame's two border rows (draw-frame.ts headerRight / footerRight): the
|
|
2
|
-
* unread badge `◆ N` opens the notices (⌃b), the theme name in the footer tag cycles the theme (⌃t),
|
|
3
|
-
* and `effort X` prefills `/effort ` so the levels appear as suggestions. The words are located by
|
|
4
|
-
* the same segment math the painter uses, so a zone is exactly the word it reads. */
|
|
5
|
-
import { footerRight, headerRight } from "./draw-frame.ts";
|
|
6
|
-
import { segWidth } from "./layout.ts";
|
|
7
|
-
import type { HitZone, Rect, SextantState, Theme } from "./types.ts";
|
|
8
|
-
|
|
9
|
-
export function frameHits(F: Rect, s: SextantState, theme: Theme, now: number): HitZone[] {
|
|
10
|
-
const out: HitZone[] = [];
|
|
11
|
-
const head = headerRight(s, theme, now);
|
|
12
|
-
const hx = F.x + F.w - 2 - segWidth(head.segs);
|
|
13
|
-
if (head.badge) out.push({ rect: { x: hx + head.badge[0], y: F.y, w: head.badge[1], h: 1 }, onClick: () => {}, key: { type: "key", name: "b", ctrl: true } });
|
|
14
|
-
const foot = footerRight(s, theme);
|
|
15
|
-
const fx = F.x + F.w - 2 - segWidth(foot.segs), fy = F.y + F.h - 1;
|
|
16
|
-
if (foot.effort) {
|
|
17
|
-
out.push({
|
|
18
|
-
rect: { x: fx + foot.effort[0], y: fy, w: foot.effort[1], h: 1 },
|
|
19
|
-
// the prompt gets `/effort ` with the cursor at the end; the suggestion strip then lists the levels
|
|
20
|
-
onClick: () => { s.input.text = "/effort "; s.input.cur = s.input.text.length; s.input.sgSel = 0; s.focus = "messages"; },
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
out.push({ rect: { x: fx + foot.theme[0], y: fy, w: foot.theme[1], h: 1 }, onClick: () => {}, key: { type: "key", name: "t", ctrl: true } });
|
|
24
|
-
return out;
|
|
25
|
-
}
|