@armadra/agent 0.0.0-stage → 0.3.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/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +470 -2
- package/dist/agent/agent.d.ts +56 -0
- package/dist/agent/agent.js +129 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +158 -0
- package/dist/agent/queue.d.ts +25 -0
- package/dist/agent/queue.js +55 -0
- package/dist/agent/retry.d.ts +22 -0
- package/dist/agent/retry.js +92 -0
- package/dist/agent/schema.d.ts +27 -0
- package/dist/agent/schema.js +186 -0
- package/dist/agent/session-cache.d.ts +100 -0
- package/dist/agent/session-cache.js +514 -0
- package/dist/agent/session-compaction.d.ts +49 -0
- package/dist/agent/session-compaction.js +251 -0
- package/dist/agent/session-core.d.ts +103 -0
- package/dist/agent/session-core.js +7 -0
- package/dist/agent/session-run.d.ts +55 -0
- package/dist/agent/session-run.js +246 -0
- package/dist/agent/session-state.d.ts +39 -0
- package/dist/agent/session-state.js +119 -0
- package/dist/agent/session-subagent.d.ts +54 -0
- package/dist/agent/session-subagent.js +157 -0
- package/dist/agent/session-sync.d.ts +20 -0
- package/dist/agent/session-sync.js +111 -0
- package/dist/agent/session-tools.d.ts +24 -0
- package/dist/agent/session-tools.js +277 -0
- package/dist/agent/session.d.ts +117 -0
- package/dist/agent/session.js +485 -0
- package/dist/agent/system-prompt.d.ts +49 -0
- package/dist/agent/system-prompt.js +121 -0
- package/dist/agent/tool-runner.d.ts +64 -0
- package/dist/agent/tool-runner.js +309 -0
- package/dist/agent/transform.d.ts +26 -0
- package/dist/agent/transform.js +171 -0
- package/dist/agent/types.d.ts +378 -0
- package/dist/agent/types.js +21 -0
- package/dist/ai/apis/anthropic-messages.d.ts +26 -0
- package/dist/ai/apis/anthropic-messages.js +214 -0
- package/dist/ai/apis/anthropic-request.d.ts +42 -0
- package/dist/ai/apis/anthropic-request.js +282 -0
- package/dist/ai/apis/api.d.ts +28 -0
- package/dist/ai/apis/api.js +102 -0
- package/dist/ai/apis/cache-params.d.ts +48 -0
- package/dist/ai/apis/cache-params.js +163 -0
- package/dist/ai/apis/google-generative-ai.d.ts +32 -0
- package/dist/ai/apis/google-generative-ai.js +249 -0
- package/dist/ai/apis/google-request.d.ts +40 -0
- package/dist/ai/apis/google-request.js +255 -0
- package/dist/ai/apis/openai-compat.d.ts +35 -0
- package/dist/ai/apis/openai-compat.js +142 -0
- package/dist/ai/apis/openai-completions.d.ts +28 -0
- package/dist/ai/apis/openai-completions.js +246 -0
- package/dist/ai/apis/openai-request.d.ts +35 -0
- package/dist/ai/apis/openai-request.js +303 -0
- package/dist/ai/apis/openai-responses-request.d.ts +52 -0
- package/dist/ai/apis/openai-responses-request.js +266 -0
- package/dist/ai/apis/openai-responses.d.ts +28 -0
- package/dist/ai/apis/openai-responses.js +304 -0
- package/dist/ai/apis/shared.d.ts +56 -0
- package/dist/ai/apis/shared.js +179 -0
- package/dist/ai/cache/economics.d.ts +24 -0
- package/dist/ai/cache/economics.js +69 -0
- package/dist/ai/cache/fingerprint.d.ts +17 -0
- package/dist/ai/cache/fingerprint.js +39 -0
- package/dist/ai/cache/miss.d.ts +49 -0
- package/dist/ai/cache/miss.js +96 -0
- package/dist/ai/cache/reporting.d.ts +39 -0
- package/dist/ai/cache/reporting.js +103 -0
- package/dist/ai/cache/types.d.ts +94 -0
- package/dist/ai/cache/types.js +19 -0
- package/dist/ai/cache/warmer.d.ts +94 -0
- package/dist/ai/cache/warmer.js +224 -0
- package/dist/ai/context.d.ts +35 -0
- package/dist/ai/context.js +114 -0
- package/dist/ai/cost.d.ts +21 -0
- package/dist/ai/cost.js +60 -0
- package/dist/ai/event-stream.d.ts +33 -0
- package/dist/ai/event-stream.js +101 -0
- package/dist/ai/fake/fake-provider.d.ts +68 -0
- package/dist/ai/fake/fake-provider.js +278 -0
- package/dist/ai/fake/fake-script.d.ts +69 -0
- package/dist/ai/fake/fake-script.js +142 -0
- package/dist/ai/http.d.ts +54 -0
- package/dist/ai/http.js +212 -0
- package/dist/ai/json-partial.d.ts +17 -0
- package/dist/ai/json-partial.js +243 -0
- package/dist/ai/overflow.d.ts +22 -0
- package/dist/ai/overflow.js +61 -0
- package/dist/ai/providers/auth.d.ts +71 -0
- package/dist/ai/providers/auth.js +202 -0
- package/dist/ai/providers/builtin.d.ts +23 -0
- package/dist/ai/providers/builtin.js +148 -0
- package/dist/ai/providers/catalog-data.d.ts +5 -0
- package/dist/ai/providers/catalog-data.js +19 -0
- package/dist/ai/providers/catalog.d.ts +28 -0
- package/dist/ai/providers/catalog.js +166 -0
- package/dist/ai/providers/channels.d.ts +51 -0
- package/dist/ai/providers/channels.js +96 -0
- package/dist/ai/providers/enrich.d.ts +34 -0
- package/dist/ai/providers/enrich.js +86 -0
- package/dist/ai/providers/models-dev-cache.d.ts +53 -0
- package/dist/ai/providers/models-dev-cache.js +147 -0
- package/dist/ai/providers/models-dev.d.ts +99 -0
- package/dist/ai/providers/models-dev.js +315 -0
- package/dist/ai/providers/registry.d.ts +114 -0
- package/dist/ai/providers/registry.js +458 -0
- package/dist/ai/sse.d.ts +32 -0
- package/dist/ai/sse.js +140 -0
- package/dist/ai/thinking.d.ts +42 -0
- package/dist/ai/thinking.js +101 -0
- package/dist/ai/types.d.ts +448 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +27837 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +85 -0
- package/dist/cli/args.js +408 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +399 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +107 -0
- package/dist/cli/compose-session.d.ts +72 -0
- package/dist/cli/compose-session.js +370 -0
- package/dist/cli/compose-store.d.ts +24 -0
- package/dist/cli/compose-store.js +125 -0
- package/dist/cli/compose.d.ts +108 -0
- package/dist/cli/compose.js +221 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +207 -0
- package/dist/cli/deps.js +12 -0
- package/dist/cli/exit-codes.d.ts +29 -0
- package/dist/cli/exit-codes.js +40 -0
- package/dist/cli/main.d.ts +27 -0
- package/dist/cli/main.js +177 -0
- package/dist/cli/runtime.d.ts +90 -0
- package/dist/cli/runtime.js +8 -0
- package/dist/cli/startup-screen.d.ts +15 -0
- package/dist/cli/startup-screen.js +56 -0
- package/dist/cli/startup-steps.d.ts +35 -0
- package/dist/cli/startup-steps.js +202 -0
- package/dist/cli/subcommands/auth.d.ts +12 -0
- package/dist/cli/subcommands/auth.js +91 -0
- package/dist/cli/subcommands/config.d.ts +53 -0
- package/dist/cli/subcommands/config.js +277 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +42 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -0
- package/dist/cli/subcommands/init.d.ts +6 -0
- package/dist/cli/subcommands/init.js +22 -0
- package/dist/cli/subcommands/model-meta.d.ts +16 -0
- package/dist/cli/subcommands/model-meta.js +54 -0
- package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
- package/dist/cli/subcommands/models-cache-probe.js +190 -0
- package/dist/cli/subcommands/models-discover.d.ts +46 -0
- package/dist/cli/subcommands/models-discover.js +232 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +152 -0
- package/dist/cli/subcommands/providers-list.d.ts +8 -0
- package/dist/cli/subcommands/providers-list.js +97 -0
- package/dist/cli/subcommands/providers-plan.d.ts +79 -0
- package/dist/cli/subcommands/providers-plan.js +215 -0
- package/dist/cli/subcommands/providers.d.ts +27 -0
- package/dist/cli/subcommands/providers.js +433 -0
- package/dist/cli/subcommands/sessions.d.ts +11 -0
- package/dist/cli/subcommands/sessions.js +100 -0
- package/dist/codemode/capability.d.ts +37 -0
- package/dist/codemode/capability.js +55 -0
- package/dist/codemode/declarations.d.ts +44 -0
- package/dist/codemode/declarations.js +111 -0
- package/dist/codemode/host-side.d.ts +59 -0
- package/dist/codemode/host-side.js +253 -0
- package/dist/codemode/modes.d.ts +23 -0
- package/dist/codemode/modes.js +40 -0
- package/dist/codemode/protocol.d.ts +91 -0
- package/dist/codemode/protocol.js +114 -0
- package/dist/codemode/sandbox-entry.d.ts +25 -0
- package/dist/codemode/sandbox-entry.js +323 -0
- package/dist/codemode/store.d.ts +26 -0
- package/dist/codemode/store.js +53 -0
- package/dist/codemode/tool.d.ts +105 -0
- package/dist/codemode/tool.js +283 -0
- package/dist/compaction/branch-summary.d.ts +30 -0
- package/dist/compaction/branch-summary.js +79 -0
- package/dist/compaction/breaker.d.ts +32 -0
- package/dist/compaction/breaker.js +65 -0
- package/dist/compaction/cut-point.d.ts +23 -0
- package/dist/compaction/cut-point.js +70 -0
- package/dist/compaction/estimate.d.ts +30 -0
- package/dist/compaction/estimate.js +114 -0
- package/dist/compaction/prune-tier.d.ts +31 -0
- package/dist/compaction/prune-tier.js +83 -0
- package/dist/compaction/serialize.d.ts +20 -0
- package/dist/compaction/serialize.js +115 -0
- package/dist/compaction/summarize-tier.d.ts +111 -0
- package/dist/compaction/summarize-tier.js +305 -0
- package/dist/config/auth-file.d.ts +39 -0
- package/dist/config/auth-file.js +92 -0
- package/dist/config/context-files.d.ts +30 -0
- package/dist/config/context-files.js +77 -0
- package/dist/config/init.d.ts +30 -0
- package/dist/config/init.js +92 -0
- package/dist/config/json-schema.d.ts +14 -0
- package/dist/config/json-schema.js +182 -0
- package/dist/config/load.d.ts +45 -0
- package/dist/config/load.js +97 -0
- package/dist/config/merge.d.ts +73 -0
- package/dist/config/merge.js +248 -0
- package/dist/config/paths.d.ts +38 -0
- package/dist/config/paths.js +108 -0
- package/dist/config/profile.d.ts +27 -0
- package/dist/config/profile.js +65 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.js +476 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +190 -0
- package/dist/config/types.js +29 -0
- package/dist/config/write.d.ts +10 -0
- package/dist/config/write.js +25 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +30 -0
- package/dist/hooks/config.d.ts +49 -0
- package/dist/hooks/config.js +102 -0
- package/dist/hooks/dispatcher.d.ts +41 -0
- package/dist/hooks/dispatcher.js +104 -0
- package/dist/hooks/matcher.d.ts +22 -0
- package/dist/hooks/matcher.js +108 -0
- package/dist/hooks/protocol.d.ts +33 -0
- package/dist/hooks/protocol.js +240 -0
- package/dist/hooks/runner.d.ts +30 -0
- package/dist/hooks/runner.js +143 -0
- package/dist/hooks/types.d.ts +147 -0
- package/dist/hooks/types.js +20 -0
- package/dist/host/api-impl.d.ts +75 -0
- package/dist/host/api-impl.js +173 -0
- package/dist/host/loader.d.ts +31 -0
- package/dist/host/loader.js +147 -0
- package/dist/host/types.d.ts +169 -0
- package/dist/host/types.js +14 -0
- package/dist/host.d.ts +5 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +28 -0
- package/dist/modes/commands-core.d.ts +44 -0
- package/dist/modes/commands-core.js +171 -0
- package/dist/modes/image-input.d.ts +27 -0
- package/dist/modes/image-input.js +78 -0
- package/dist/modes/interactive/approval-dialog.d.ts +48 -0
- package/dist/modes/interactive/approval-dialog.js +202 -0
- package/dist/modes/interactive/commands.d.ts +41 -0
- package/dist/modes/interactive/commands.js +201 -0
- package/dist/modes/interactive/completion.d.ts +53 -0
- package/dist/modes/interactive/completion.js +157 -0
- package/dist/modes/interactive/interactive-mode.d.ts +46 -0
- package/dist/modes/interactive/interactive-mode.js +491 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -0
- package/dist/modes/interactive/key-dispatch.js +107 -0
- package/dist/modes/interactive/line/line-editor.d.ts +50 -0
- package/dist/modes/interactive/line/line-editor.js +232 -0
- package/dist/modes/interactive/line/line-mode.d.ts +20 -0
- package/dist/modes/interactive/line/line-mode.js +218 -0
- package/dist/modes/interactive/line/line-render.d.ts +35 -0
- package/dist/modes/interactive/line/line-render.js +171 -0
- package/dist/modes/interactive/line/paste-state.d.ts +26 -0
- package/dist/modes/interactive/line/paste-state.js +72 -0
- package/dist/modes/interactive/message-view.d.ts +77 -0
- package/dist/modes/interactive/message-view.js +269 -0
- package/dist/modes/interactive/pickers.d.ts +42 -0
- package/dist/modes/interactive/pickers.js +115 -0
- package/dist/modes/interactive/startup-ui.d.ts +33 -0
- package/dist/modes/interactive/startup-ui.js +268 -0
- package/dist/modes/interactive/status-bar.d.ts +48 -0
- package/dist/modes/interactive/status-bar.js +139 -0
- package/dist/modes/interactive/tool-view.d.ts +95 -0
- package/dist/modes/interactive/tool-view.js +344 -0
- package/dist/modes/print/json-event.d.ts +15 -0
- package/dist/modes/print/json-event.js +39 -0
- package/dist/modes/print/print-mode.d.ts +17 -0
- package/dist/modes/print/print-mode.js +107 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +263 -0
- package/dist/modes/rpc/jsonl.d.ts +16 -0
- package/dist/modes/rpc/jsonl.js +55 -0
- package/dist/modes/rpc/rpc-mode.d.ts +22 -0
- package/dist/modes/rpc/rpc-mode.js +153 -0
- package/dist/modes/session-report.d.ts +55 -0
- package/dist/modes/session-report.js +278 -0
- package/dist/modes/shared.d.ts +13 -0
- package/dist/modes/shared.js +52 -0
- package/dist/modes/startup-ui-text.d.ts +16 -0
- package/dist/modes/startup-ui-text.js +103 -0
- package/dist/permissions/broker.d.ts +47 -0
- package/dist/permissions/broker.js +107 -0
- package/dist/permissions/dangerous.d.ts +47 -0
- package/dist/permissions/dangerous.js +370 -0
- package/dist/permissions/pipeline.d.ts +51 -0
- package/dist/permissions/pipeline.js +181 -0
- package/dist/permissions/preview.d.ts +43 -0
- package/dist/permissions/preview.js +463 -0
- package/dist/permissions/rules.d.ts +58 -0
- package/dist/permissions/rules.js +249 -0
- package/dist/permissions/types.d.ts +100 -0
- package/dist/permissions/types.js +18 -0
- package/dist/rpc.d.ts +173 -0
- package/dist/rpc.js +13 -0
- package/dist/sdk.d.ts +98 -0
- package/dist/sdk.js +195 -0
- package/dist/session/manager.d.ts +70 -0
- package/dist/session/manager.js +278 -0
- package/dist/session/migrate.d.ts +19 -0
- package/dist/session/migrate.js +53 -0
- package/dist/session/projection.d.ts +50 -0
- package/dist/session/projection.js +184 -0
- package/dist/session/store.d.ts +47 -0
- package/dist/session/store.js +210 -0
- package/dist/session/tree.d.ts +21 -0
- package/dist/session/tree.js +88 -0
- package/dist/session/types.d.ts +201 -0
- package/dist/session/types.js +18 -0
- package/dist/skills/discover.d.ts +60 -0
- package/dist/skills/discover.js +160 -0
- package/dist/skills/expand.d.ts +35 -0
- package/dist/skills/expand.js +46 -0
- package/dist/skills/frontmatter.d.ts +27 -0
- package/dist/skills/frontmatter.js +157 -0
- package/dist/skills/index-prompt.d.ts +13 -0
- package/dist/skills/index-prompt.js +42 -0
- package/dist/skills/templates.d.ts +52 -0
- package/dist/skills/templates.js +177 -0
- package/dist/tools/bash.d.ts +49 -0
- package/dist/tools/bash.js +222 -0
- package/dist/tools/edit-fuzzy.d.ts +36 -0
- package/dist/tools/edit-fuzzy.js +129 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +241 -0
- package/dist/tools/file-mutex.d.ts +8 -0
- package/dist/tools/file-mutex.js +29 -0
- package/dist/tools/glob.d.ts +44 -0
- package/dist/tools/glob.js +217 -0
- package/dist/tools/grep.d.ts +39 -0
- package/dist/tools/grep.js +179 -0
- package/dist/tools/ignore.d.ts +40 -0
- package/dist/tools/ignore.js +174 -0
- package/dist/tools/image-file.d.ts +31 -0
- package/dist/tools/image-file.js +114 -0
- package/dist/tools/ls.d.ts +21 -0
- package/dist/tools/ls.js +96 -0
- package/dist/tools/output-accumulator.d.ts +48 -0
- package/dist/tools/output-accumulator.js +106 -0
- package/dist/tools/paths.d.ts +20 -0
- package/dist/tools/paths.js +58 -0
- package/dist/tools/presets.d.ts +71 -0
- package/dist/tools/presets.js +128 -0
- package/dist/tools/process-tree.d.ts +35 -0
- package/dist/tools/process-tree.js +125 -0
- package/dist/tools/read.d.ts +24 -0
- package/dist/tools/read.js +132 -0
- package/dist/tools/registry.d.ts +43 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/shell.d.ts +38 -0
- package/dist/tools/shell.js +91 -0
- package/dist/tools/task.d.ts +29 -0
- package/dist/tools/task.js +107 -0
- package/dist/tools/todo.d.ts +28 -0
- package/dist/tools/todo.js +107 -0
- package/dist/tools/truncate.d.ts +44 -0
- package/dist/tools/truncate.js +175 -0
- package/dist/tools/types.d.ts +135 -0
- package/dist/tools/types.js +19 -0
- package/dist/tools/write.d.ts +20 -0
- package/dist/tools/write.js +84 -0
- package/dist/tui/ansi.d.ts +29 -0
- package/dist/tui/ansi.js +420 -0
- package/dist/tui/component.d.ts +38 -0
- package/dist/tui/component.js +13 -0
- package/dist/tui/components/box.d.ts +26 -0
- package/dist/tui/components/box.js +65 -0
- package/dist/tui/components/container.d.ts +14 -0
- package/dist/tui/components/container.js +39 -0
- package/dist/tui/components/editor-buffer.d.ts +67 -0
- package/dist/tui/components/editor-buffer.js +300 -0
- package/dist/tui/components/editor-paste.d.ts +29 -0
- package/dist/tui/components/editor-paste.js +64 -0
- package/dist/tui/components/editor.d.ts +114 -0
- package/dist/tui/components/editor.js +530 -0
- package/dist/tui/components/key-value.d.ts +29 -0
- package/dist/tui/components/key-value.js +48 -0
- package/dist/tui/components/loader.d.ts +35 -0
- package/dist/tui/components/loader.js +71 -0
- package/dist/tui/components/markdown.d.ts +90 -0
- package/dist/tui/components/markdown.js +462 -0
- package/dist/tui/components/meter.d.ts +29 -0
- package/dist/tui/components/meter.js +47 -0
- package/dist/tui/components/overlay.d.ts +24 -0
- package/dist/tui/components/overlay.js +52 -0
- package/dist/tui/components/select-list.d.ts +57 -0
- package/dist/tui/components/select-list.js +179 -0
- package/dist/tui/components/spacer.d.ts +11 -0
- package/dist/tui/components/spacer.js +16 -0
- package/dist/tui/components/text.d.ts +30 -0
- package/dist/tui/components/text.js +65 -0
- package/dist/tui/keybindings.d.ts +66 -0
- package/dist/tui/keybindings.js +120 -0
- package/dist/tui/keys.d.ts +38 -0
- package/dist/tui/keys.js +275 -0
- package/dist/tui/stdin-buffer.d.ts +41 -0
- package/dist/tui/stdin-buffer.js +206 -0
- package/dist/tui/terminal.d.ts +79 -0
- package/dist/tui/terminal.js +153 -0
- package/dist/tui/theme.d.ts +31 -0
- package/dist/tui/theme.js +199 -0
- package/dist/tui/tui.d.ts +103 -0
- package/dist/tui/tui.js +363 -0
- package/dist/tui/vt-screen.d.ts +46 -0
- package/dist/tui/vt-screen.js +251 -0
- package/dist/tui.d.ts +30 -0
- package/dist/tui.js +31 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +24 -0
- package/docs/codemode.md +102 -0
- package/docs/hooks.md +196 -0
- package/docs/host-api.md +161 -0
- package/docs/providers.md +531 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +170 -0
- package/package.json +101 -4
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 单行余量表(第三波 A8):`ctx ▮▮▮▮▮▮▮▯▯▯ 72%`。[W3-B9a-2]
|
|
3
|
+
*
|
|
4
|
+
* - `value` 是 0–1 的比例(越界夹紧);undefined 显示 `—`,格子全空;
|
|
5
|
+
* - 阈值着色(需要 theme):≥ `dangerAt`(缺省 0.9)error,≥ `warnAt`(缺省 0.7)warning,否则 success;
|
|
6
|
+
* 空格子 dim;
|
|
7
|
+
* - 宽度不够时先去掉格子,只剩「标签 百分比」,再不够就截断。
|
|
8
|
+
*/
|
|
9
|
+
import { truncateToWidth, visibleWidth } from "../ansi.js";
|
|
10
|
+
export const METER_FULL = "▮";
|
|
11
|
+
export const METER_EMPTY = "▯";
|
|
12
|
+
export class Meter {
|
|
13
|
+
value;
|
|
14
|
+
options;
|
|
15
|
+
constructor(value = undefined, options = {}) {
|
|
16
|
+
this.value = value;
|
|
17
|
+
this.options = options;
|
|
18
|
+
}
|
|
19
|
+
setValue(value) {
|
|
20
|
+
this.value = value;
|
|
21
|
+
}
|
|
22
|
+
/** 当前阈值档位对应的语义色。 */
|
|
23
|
+
level() {
|
|
24
|
+
const v = this.value ?? 0;
|
|
25
|
+
if (v >= (this.options.dangerAt ?? 0.9))
|
|
26
|
+
return "error";
|
|
27
|
+
if (v >= (this.options.warnAt ?? 0.7))
|
|
28
|
+
return "warning";
|
|
29
|
+
return "success";
|
|
30
|
+
}
|
|
31
|
+
render(width) {
|
|
32
|
+
const { theme, label, cells = 10 } = this.options;
|
|
33
|
+
const ratio = this.value === undefined ? undefined : Math.min(1, Math.max(0, this.value));
|
|
34
|
+
const percent = ratio === undefined ? "—" : `${Math.round(ratio * 100)}%`;
|
|
35
|
+
const color = (c, text) => (theme ? theme.fg(c, text) : text);
|
|
36
|
+
const head = label === undefined || label === "" ? "" : `${label} `;
|
|
37
|
+
const filled = ratio === undefined ? 0 : Math.round(ratio * cells);
|
|
38
|
+
const bar = color(this.level(), METER_FULL.repeat(filled)) +
|
|
39
|
+
color("dim", METER_EMPTY.repeat(cells - filled));
|
|
40
|
+
const tail = color(ratio === undefined ? "dim" : this.level(), percent);
|
|
41
|
+
const full = `${head}${bar} ${tail}`;
|
|
42
|
+
if (visibleWidth(full) <= width)
|
|
43
|
+
return [full];
|
|
44
|
+
return [truncateToWidth(`${head}${tail}`, width)];
|
|
45
|
+
}
|
|
46
|
+
invalidate() { }
|
|
47
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 覆盖层合成(设计 §12.1、§12.2 第 2 步):只支持 `center` 与 `bottom` 两种锚点。[B4]
|
|
3
|
+
*
|
|
4
|
+
* 覆盖层画在「终端视口」内(内容的最后 height 行);内容不足覆盖层高度时在末尾补空行。
|
|
5
|
+
* 合成发生在差分之前,所以覆盖层的出现 / 消失与普通内容变化一样只重写变化行。
|
|
6
|
+
*/
|
|
7
|
+
import type { Component } from "../component.js";
|
|
8
|
+
export type OverlayAnchor = "center" | "bottom";
|
|
9
|
+
export interface OverlayOptions {
|
|
10
|
+
/** 缺省 `center`。 */
|
|
11
|
+
anchor?: OverlayAnchor;
|
|
12
|
+
/** 覆盖层列宽;缺省 center 为 min(width - 4, 80),bottom 为整宽。 */
|
|
13
|
+
width?: number;
|
|
14
|
+
/** 最大行数;缺省为终端高度。 */
|
|
15
|
+
maxHeight?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface OverlayLayer {
|
|
18
|
+
readonly component: Component;
|
|
19
|
+
readonly options: OverlayOptions;
|
|
20
|
+
}
|
|
21
|
+
/** 把一行 base 的 [col, col + w) 列替换为 overlay 行。 */
|
|
22
|
+
export declare function spliceLine(base: string, overlay: string, col: number, w: number, width: number): string;
|
|
23
|
+
/** 按顺序合成全部覆盖层(后加入的在上)。 */
|
|
24
|
+
export declare function compositeOverlays(lines: readonly string[], layers: readonly OverlayLayer[], width: number, height: number): string[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 覆盖层合成(设计 §12.1、§12.2 第 2 步):只支持 `center` 与 `bottom` 两种锚点。[B4]
|
|
3
|
+
*
|
|
4
|
+
* 覆盖层画在「终端视口」内(内容的最后 height 行);内容不足覆盖层高度时在末尾补空行。
|
|
5
|
+
* 合成发生在差分之前,所以覆盖层的出现 / 消失与普通内容变化一样只重写变化行。
|
|
6
|
+
*/
|
|
7
|
+
import { SGR_RESET, padToWidth, sliceByColumn, truncateToWidth } from "../ansi.js";
|
|
8
|
+
function overlayWidth(options, width) {
|
|
9
|
+
const anchor = options.anchor ?? "center";
|
|
10
|
+
const wanted = options.width ?? (anchor === "bottom" ? width : Math.min(width - 4, 80));
|
|
11
|
+
return Math.max(1, Math.min(width, wanted));
|
|
12
|
+
}
|
|
13
|
+
/** 把一行 base 的 [col, col + w) 列替换为 overlay 行。 */
|
|
14
|
+
export function spliceLine(base, overlay, col, w, width) {
|
|
15
|
+
const left = padToWidth(sliceByColumn(base, 0, col), col);
|
|
16
|
+
const mid = padToWidth(truncateToWidth(overlay, w), w);
|
|
17
|
+
const right = sliceByColumn(base, col + w, width);
|
|
18
|
+
const midReset = mid.includes("\x1b") ? SGR_RESET : "";
|
|
19
|
+
return left + mid + midReset + right;
|
|
20
|
+
}
|
|
21
|
+
/** 按顺序合成全部覆盖层(后加入的在上)。 */
|
|
22
|
+
export function compositeOverlays(lines, layers, width, height) {
|
|
23
|
+
let result = [...lines];
|
|
24
|
+
for (const layer of layers)
|
|
25
|
+
result = compositeOne(result, layer, width, height);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function compositeOne(lines, layer, width, height) {
|
|
29
|
+
const ow = overlayWidth(layer.options, width);
|
|
30
|
+
const maxHeight = Math.max(1, Math.min(height, layer.options.maxHeight ?? height));
|
|
31
|
+
let body = layer.component.render(ow);
|
|
32
|
+
if (body.length > maxHeight)
|
|
33
|
+
body = body.slice(body.length - maxHeight);
|
|
34
|
+
const oh = body.length;
|
|
35
|
+
if (oh === 0)
|
|
36
|
+
return lines;
|
|
37
|
+
const result = [...lines];
|
|
38
|
+
while (result.length < oh)
|
|
39
|
+
result.push("");
|
|
40
|
+
const viewportTop = Math.max(0, result.length - height);
|
|
41
|
+
const visible = result.length - viewportTop;
|
|
42
|
+
const anchor = layer.options.anchor ?? "center";
|
|
43
|
+
const startRow = anchor === "bottom"
|
|
44
|
+
? result.length - oh
|
|
45
|
+
: viewportTop + Math.max(0, Math.floor((visible - oh) / 2));
|
|
46
|
+
const col = anchor === "bottom" && ow === width ? 0 : Math.floor((width - ow) / 2);
|
|
47
|
+
for (let i = 0; i < oh; i++) {
|
|
48
|
+
const row = startRow + i;
|
|
49
|
+
result[row] = spliceLine(result[row] ?? "", body[i] ?? "", col, ow, width);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 选择列表(设计 §12.6):过滤、分页(视窗跟随选中项)、描述列、分组标题。[B4]
|
|
3
|
+
*
|
|
4
|
+
* 键位走 `tui.select.*`:上下移动(循环)、翻页、确认(Enter / Tab)、取消(Esc / Ctrl+C)。
|
|
5
|
+
* `filterable` 时可打印字符追加到过滤词、退格删除;过滤为大小写不敏感的分词子串匹配
|
|
6
|
+
* (每个词都要出现在 label / value / group / description 之一中)。
|
|
7
|
+
*/
|
|
8
|
+
import type { Component, Focusable, Theme } from "../component.js";
|
|
9
|
+
import { type Keybindings } from "../keybindings.js";
|
|
10
|
+
export interface SelectItem {
|
|
11
|
+
value: string;
|
|
12
|
+
label: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
/** 相邻项 group 不同时插入分组标题行。 */
|
|
15
|
+
group?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SelectListOptions {
|
|
18
|
+
/** 一屏最多显示的条目数,缺省 10。 */
|
|
19
|
+
maxVisible?: number;
|
|
20
|
+
theme?: Theme;
|
|
21
|
+
keybindings?: Keybindings;
|
|
22
|
+
filterable?: boolean;
|
|
23
|
+
emptyText?: string;
|
|
24
|
+
onSelect?(item: SelectItem): void;
|
|
25
|
+
onCancel?(): void;
|
|
26
|
+
onSelectionChange?(item: SelectItem | undefined): void;
|
|
27
|
+
}
|
|
28
|
+
export declare function filterItems(items: readonly SelectItem[], filter: string): SelectItem[];
|
|
29
|
+
export declare class SelectList implements Component, Focusable {
|
|
30
|
+
private readonly options;
|
|
31
|
+
focused: boolean;
|
|
32
|
+
private items;
|
|
33
|
+
private filtered;
|
|
34
|
+
private filter;
|
|
35
|
+
private selected;
|
|
36
|
+
private scrollTop;
|
|
37
|
+
private readonly keys;
|
|
38
|
+
constructor(items: readonly SelectItem[], options?: SelectListOptions);
|
|
39
|
+
private get maxVisible();
|
|
40
|
+
setItems(items: readonly SelectItem[]): void;
|
|
41
|
+
getItems(): readonly SelectItem[];
|
|
42
|
+
getFilter(): string;
|
|
43
|
+
setFilter(filter: string): void;
|
|
44
|
+
getSelected(): SelectItem | undefined;
|
|
45
|
+
getSelectedIndex(): number;
|
|
46
|
+
setSelectedIndex(index: number): void;
|
|
47
|
+
/** 按 value 选中。 */
|
|
48
|
+
selectValue(value: string): boolean;
|
|
49
|
+
moveSelection(delta: number, wrap?: boolean): void;
|
|
50
|
+
handleInput(data: string): void;
|
|
51
|
+
render(width: number): string[];
|
|
52
|
+
invalidate(): void;
|
|
53
|
+
private renderItem;
|
|
54
|
+
private labelColumnWidth;
|
|
55
|
+
private applyFilter;
|
|
56
|
+
private ensureVisible;
|
|
57
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 选择列表(设计 §12.6):过滤、分页(视窗跟随选中项)、描述列、分组标题。[B4]
|
|
3
|
+
*
|
|
4
|
+
* 键位走 `tui.select.*`:上下移动(循环)、翻页、确认(Enter / Tab)、取消(Esc / Ctrl+C)。
|
|
5
|
+
* `filterable` 时可打印字符追加到过滤词、退格删除;过滤为大小写不敏感的分词子串匹配
|
|
6
|
+
* (每个词都要出现在 label / value / group / description 之一中)。
|
|
7
|
+
*/
|
|
8
|
+
import { padToWidth, truncateToWidth, visibleWidth } from "../ansi.js";
|
|
9
|
+
import { defaultKeybindings } from "../keybindings.js";
|
|
10
|
+
import { isPrintableText, matchesKey } from "../keys.js";
|
|
11
|
+
export function filterItems(items, filter) {
|
|
12
|
+
const terms = filter.toLowerCase().split(/\s+/).filter(Boolean);
|
|
13
|
+
if (terms.length === 0)
|
|
14
|
+
return [...items];
|
|
15
|
+
return items.filter((item) => {
|
|
16
|
+
const hay = [item.label, item.value, item.group ?? "", item.description ?? ""]
|
|
17
|
+
.join("\n")
|
|
18
|
+
.toLowerCase();
|
|
19
|
+
return terms.every((term) => hay.includes(term));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export class SelectList {
|
|
23
|
+
options;
|
|
24
|
+
focused = false;
|
|
25
|
+
items;
|
|
26
|
+
filtered;
|
|
27
|
+
filter = "";
|
|
28
|
+
selected = 0;
|
|
29
|
+
scrollTop = 0;
|
|
30
|
+
keys;
|
|
31
|
+
constructor(items, options = {}) {
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.items = [...items];
|
|
34
|
+
this.filtered = [...items];
|
|
35
|
+
this.keys = options.keybindings ?? defaultKeybindings;
|
|
36
|
+
}
|
|
37
|
+
get maxVisible() {
|
|
38
|
+
return Math.max(1, this.options.maxVisible ?? 10);
|
|
39
|
+
}
|
|
40
|
+
setItems(items) {
|
|
41
|
+
this.items = [...items];
|
|
42
|
+
this.applyFilter();
|
|
43
|
+
}
|
|
44
|
+
getItems() {
|
|
45
|
+
return this.filtered;
|
|
46
|
+
}
|
|
47
|
+
getFilter() {
|
|
48
|
+
return this.filter;
|
|
49
|
+
}
|
|
50
|
+
setFilter(filter) {
|
|
51
|
+
this.filter = filter;
|
|
52
|
+
this.applyFilter();
|
|
53
|
+
}
|
|
54
|
+
getSelected() {
|
|
55
|
+
return this.filtered[this.selected];
|
|
56
|
+
}
|
|
57
|
+
getSelectedIndex() {
|
|
58
|
+
return this.selected;
|
|
59
|
+
}
|
|
60
|
+
setSelectedIndex(index) {
|
|
61
|
+
if (this.filtered.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
this.selected = Math.max(0, Math.min(this.filtered.length - 1, index));
|
|
64
|
+
this.ensureVisible();
|
|
65
|
+
this.options.onSelectionChange?.(this.getSelected());
|
|
66
|
+
}
|
|
67
|
+
/** 按 value 选中。 */
|
|
68
|
+
selectValue(value) {
|
|
69
|
+
const index = this.filtered.findIndex((item) => item.value === value);
|
|
70
|
+
if (index === -1)
|
|
71
|
+
return false;
|
|
72
|
+
this.setSelectedIndex(index);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
moveSelection(delta, wrap = true) {
|
|
76
|
+
const n = this.filtered.length;
|
|
77
|
+
if (n === 0)
|
|
78
|
+
return;
|
|
79
|
+
let next = this.selected + delta;
|
|
80
|
+
if (wrap && Math.abs(delta) === 1)
|
|
81
|
+
next = (next + n) % n;
|
|
82
|
+
this.setSelectedIndex(next);
|
|
83
|
+
}
|
|
84
|
+
handleInput(data) {
|
|
85
|
+
const k = this.keys;
|
|
86
|
+
if (k.matches(data, "tui.select.up"))
|
|
87
|
+
this.moveSelection(-1);
|
|
88
|
+
else if (k.matches(data, "tui.select.down"))
|
|
89
|
+
this.moveSelection(1);
|
|
90
|
+
else if (k.matches(data, "tui.select.pageUp"))
|
|
91
|
+
this.moveSelection(-this.maxVisible, false);
|
|
92
|
+
else if (k.matches(data, "tui.select.pageDown"))
|
|
93
|
+
this.moveSelection(this.maxVisible, false);
|
|
94
|
+
else if (k.matches(data, "tui.select.confirm")) {
|
|
95
|
+
const item = this.getSelected();
|
|
96
|
+
if (item)
|
|
97
|
+
this.options.onSelect?.(item);
|
|
98
|
+
}
|
|
99
|
+
else if (k.matches(data, "tui.select.cancel"))
|
|
100
|
+
this.options.onCancel?.();
|
|
101
|
+
else if (this.options.filterable) {
|
|
102
|
+
if (matchesKey(data, "backspace")) {
|
|
103
|
+
if (this.filter.length > 0)
|
|
104
|
+
this.setFilter([...this.filter].slice(0, -1).join(""));
|
|
105
|
+
}
|
|
106
|
+
else if (isPrintableText(data)) {
|
|
107
|
+
this.setFilter(this.filter + data);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
render(width) {
|
|
112
|
+
const theme = this.options.theme;
|
|
113
|
+
const dim = (s) => (theme ? theme.fg("dim", s) : s);
|
|
114
|
+
const lines = [];
|
|
115
|
+
if (this.options.filterable) {
|
|
116
|
+
const prompt = theme ? theme.fg("accent", "> ") : "> ";
|
|
117
|
+
lines.push(truncateToWidth(prompt + this.filter + (this.focused ? "▏" : ""), width));
|
|
118
|
+
}
|
|
119
|
+
if (this.filtered.length === 0) {
|
|
120
|
+
lines.push(truncateToWidth(dim(` ${this.options.emptyText ?? "(no matches)"}`), width));
|
|
121
|
+
return lines;
|
|
122
|
+
}
|
|
123
|
+
const end = Math.min(this.filtered.length, this.scrollTop + this.maxVisible);
|
|
124
|
+
const labelWidth = this.labelColumnWidth(end);
|
|
125
|
+
let lastGroup;
|
|
126
|
+
for (let i = this.scrollTop; i < end; i++) {
|
|
127
|
+
const item = this.filtered[i];
|
|
128
|
+
if (item.group !== undefined && item.group !== lastGroup) {
|
|
129
|
+
lines.push(truncateToWidth(dim(item.group), width));
|
|
130
|
+
}
|
|
131
|
+
lastGroup = item.group;
|
|
132
|
+
lines.push(this.renderItem(item, i === this.selected, labelWidth, width));
|
|
133
|
+
}
|
|
134
|
+
if (this.filtered.length > this.maxVisible) {
|
|
135
|
+
lines.push(truncateToWidth(dim(` (${this.selected + 1}/${this.filtered.length})`), width));
|
|
136
|
+
}
|
|
137
|
+
return lines;
|
|
138
|
+
}
|
|
139
|
+
invalidate() { }
|
|
140
|
+
renderItem(item, selected, labelWidth, width) {
|
|
141
|
+
const theme = this.options.theme;
|
|
142
|
+
const prefix = selected ? "› " : " ";
|
|
143
|
+
const label = truncateToWidth(item.label, Math.max(1, width - 2));
|
|
144
|
+
let line = prefix + padToWidth(label, Math.min(labelWidth, width - 2));
|
|
145
|
+
if (selected && theme)
|
|
146
|
+
line = theme.fg("accent", theme.bold(line));
|
|
147
|
+
const room = width - visibleWidth(line) - 2;
|
|
148
|
+
if (item.description && room > 4) {
|
|
149
|
+
const desc = truncateToWidth(item.description, room);
|
|
150
|
+
line += " " + (theme ? theme.fg("dim", desc) : desc);
|
|
151
|
+
}
|
|
152
|
+
return truncateToWidth(line, width);
|
|
153
|
+
}
|
|
154
|
+
labelColumnWidth(end) {
|
|
155
|
+
let max = 0;
|
|
156
|
+
for (let i = this.scrollTop; i < end; i++) {
|
|
157
|
+
max = Math.max(max, visibleWidth(this.filtered[i].label));
|
|
158
|
+
}
|
|
159
|
+
return max;
|
|
160
|
+
}
|
|
161
|
+
applyFilter() {
|
|
162
|
+
const previous = this.getSelected()?.value;
|
|
163
|
+
this.filtered = filterItems(this.items, this.filter);
|
|
164
|
+
const keep = previous === undefined ? -1 : this.filtered.findIndex((i) => i.value === previous);
|
|
165
|
+
this.selected = keep === -1 ? 0 : keep;
|
|
166
|
+
this.scrollTop = 0;
|
|
167
|
+
this.ensureVisible();
|
|
168
|
+
this.options.onSelectionChange?.(this.getSelected());
|
|
169
|
+
}
|
|
170
|
+
ensureVisible() {
|
|
171
|
+
if (this.selected < this.scrollTop)
|
|
172
|
+
this.scrollTop = this.selected;
|
|
173
|
+
else if (this.selected >= this.scrollTop + this.maxVisible) {
|
|
174
|
+
this.scrollTop = this.selected - this.maxVisible + 1;
|
|
175
|
+
}
|
|
176
|
+
this.scrollTop = Math.max(0, Math.min(this.scrollTop, this.filtered.length - this.maxVisible));
|
|
177
|
+
this.scrollTop = Math.max(0, this.scrollTop);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 占位空行(设计 §12.1)。[B4]
|
|
3
|
+
*/
|
|
4
|
+
import type { Component } from "../component.js";
|
|
5
|
+
export declare class Spacer implements Component {
|
|
6
|
+
private lines;
|
|
7
|
+
constructor(lines?: number);
|
|
8
|
+
setLines(lines: number): void;
|
|
9
|
+
render(_width: number): string[];
|
|
10
|
+
invalidate(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 占位空行(设计 §12.1)。[B4]
|
|
3
|
+
*/
|
|
4
|
+
export class Spacer {
|
|
5
|
+
lines;
|
|
6
|
+
constructor(lines = 1) {
|
|
7
|
+
this.lines = lines;
|
|
8
|
+
}
|
|
9
|
+
setLines(lines) {
|
|
10
|
+
this.lines = Math.max(0, lines);
|
|
11
|
+
}
|
|
12
|
+
render(_width) {
|
|
13
|
+
return new Array(this.lines).fill("");
|
|
14
|
+
}
|
|
15
|
+
invalidate() { }
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文本组件(设计 §12.1)。[B4]
|
|
3
|
+
*
|
|
4
|
+
* - `Text`:自动换行(保留 ANSI 样式)、可选左右 / 上下内边距;按 (width, text) 缓存。
|
|
5
|
+
* - `TruncatedText`:只取第一行并截断到宽度(状态栏、标题行)。
|
|
6
|
+
*/
|
|
7
|
+
import type { Component } from "../component.js";
|
|
8
|
+
export interface TextOptions {
|
|
9
|
+
paddingX?: number;
|
|
10
|
+
paddingY?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class Text implements Component {
|
|
13
|
+
private text;
|
|
14
|
+
private cache;
|
|
15
|
+
private readonly paddingX;
|
|
16
|
+
private readonly paddingY;
|
|
17
|
+
constructor(text?: string, options?: TextOptions);
|
|
18
|
+
getText(): string;
|
|
19
|
+
setText(text: string): void;
|
|
20
|
+
render(width: number): string[];
|
|
21
|
+
invalidate(): void;
|
|
22
|
+
}
|
|
23
|
+
export declare class TruncatedText implements Component {
|
|
24
|
+
private text;
|
|
25
|
+
private readonly paddingX;
|
|
26
|
+
constructor(text?: string, paddingX?: number);
|
|
27
|
+
setText(text: string): void;
|
|
28
|
+
render(width: number): string[];
|
|
29
|
+
invalidate(): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文本组件(设计 §12.1)。[B4]
|
|
3
|
+
*
|
|
4
|
+
* - `Text`:自动换行(保留 ANSI 样式)、可选左右 / 上下内边距;按 (width, text) 缓存。
|
|
5
|
+
* - `TruncatedText`:只取第一行并截断到宽度(状态栏、标题行)。
|
|
6
|
+
*/
|
|
7
|
+
import { truncateToWidth, wrapTextWithAnsi } from "../ansi.js";
|
|
8
|
+
export class Text {
|
|
9
|
+
text;
|
|
10
|
+
cache = null;
|
|
11
|
+
paddingX;
|
|
12
|
+
paddingY;
|
|
13
|
+
constructor(text = "", options = {}) {
|
|
14
|
+
this.text = text;
|
|
15
|
+
this.paddingX = options.paddingX ?? 0;
|
|
16
|
+
this.paddingY = options.paddingY ?? 0;
|
|
17
|
+
}
|
|
18
|
+
getText() {
|
|
19
|
+
return this.text;
|
|
20
|
+
}
|
|
21
|
+
setText(text) {
|
|
22
|
+
if (text === this.text)
|
|
23
|
+
return;
|
|
24
|
+
this.text = text;
|
|
25
|
+
this.cache = null;
|
|
26
|
+
}
|
|
27
|
+
render(width) {
|
|
28
|
+
if (this.cache && this.cache.width === width && this.cache.text === this.text) {
|
|
29
|
+
return this.cache.lines;
|
|
30
|
+
}
|
|
31
|
+
const inner = Math.max(1, width - this.paddingX * 2);
|
|
32
|
+
const pad = " ".repeat(this.paddingX);
|
|
33
|
+
const lines = [];
|
|
34
|
+
for (let i = 0; i < this.paddingY; i++)
|
|
35
|
+
lines.push("");
|
|
36
|
+
if (this.text !== "") {
|
|
37
|
+
for (const line of wrapTextWithAnsi(this.text, inner))
|
|
38
|
+
lines.push(pad + line);
|
|
39
|
+
}
|
|
40
|
+
for (let i = 0; i < this.paddingY; i++)
|
|
41
|
+
lines.push("");
|
|
42
|
+
this.cache = { width, text: this.text, lines };
|
|
43
|
+
return lines;
|
|
44
|
+
}
|
|
45
|
+
invalidate() {
|
|
46
|
+
this.cache = null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export class TruncatedText {
|
|
50
|
+
text;
|
|
51
|
+
paddingX;
|
|
52
|
+
constructor(text = "", paddingX = 0) {
|
|
53
|
+
this.text = text;
|
|
54
|
+
this.paddingX = paddingX;
|
|
55
|
+
}
|
|
56
|
+
setText(text) {
|
|
57
|
+
this.text = text;
|
|
58
|
+
}
|
|
59
|
+
render(width) {
|
|
60
|
+
const firstLine = this.text.split("\n", 1)[0] ?? "";
|
|
61
|
+
const pad = " ".repeat(this.paddingX);
|
|
62
|
+
return [pad + truncateToWidth(firstLine, Math.max(0, width - this.paddingX * 2))];
|
|
63
|
+
}
|
|
64
|
+
invalidate() { }
|
|
65
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 键位表(设计 §12.3):动作 id → KeyId 列表;固定缺省表 + `keybindings.json` 覆盖。[B4]
|
|
3
|
+
*
|
|
4
|
+
* - `tui.*` 由组件库自己消费(编辑器、选择列表);`app.*` 供交互模式(B7)分派。
|
|
5
|
+
* - 覆盖时整组替换;空数组表示禁用该动作。未知动作 id 与非字符串键会被报告并忽略。
|
|
6
|
+
* - 文件位置由调用方决定(配置目录归 B5),这里只负责解析与合并。
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_KEYBINDINGS: {
|
|
9
|
+
readonly "tui.editor.submit": readonly ["enter"];
|
|
10
|
+
readonly "tui.editor.newLine": readonly ["shift+enter", "ctrl+j"];
|
|
11
|
+
readonly "tui.editor.cursorLeft": readonly ["left", "ctrl+b"];
|
|
12
|
+
readonly "tui.editor.cursorRight": readonly ["right", "ctrl+f"];
|
|
13
|
+
readonly "tui.editor.cursorUp": readonly ["up", "ctrl+p"];
|
|
14
|
+
readonly "tui.editor.cursorDown": readonly ["down", "ctrl+n"];
|
|
15
|
+
readonly "tui.editor.wordLeft": readonly ["alt+left", "ctrl+left", "alt+b"];
|
|
16
|
+
readonly "tui.editor.wordRight": readonly ["alt+right", "ctrl+right", "alt+f"];
|
|
17
|
+
readonly "tui.editor.lineStart": readonly ["home", "ctrl+a"];
|
|
18
|
+
readonly "tui.editor.lineEnd": readonly ["end", "ctrl+e"];
|
|
19
|
+
readonly "tui.editor.deleteBackward": readonly ["backspace", "shift+backspace"];
|
|
20
|
+
readonly "tui.editor.deleteForward": readonly ["delete"];
|
|
21
|
+
readonly "tui.editor.deleteWordBackward": readonly ["ctrl+w", "alt+backspace", "ctrl+backspace"];
|
|
22
|
+
readonly "tui.editor.deleteWordForward": readonly ["alt+d", "alt+delete", "ctrl+delete"];
|
|
23
|
+
readonly "tui.editor.deleteToLineStart": readonly ["ctrl+u"];
|
|
24
|
+
readonly "tui.editor.deleteToLineEnd": readonly ["ctrl+k"];
|
|
25
|
+
readonly "tui.editor.undo": readonly ["ctrl+z", "ctrl+-"];
|
|
26
|
+
readonly "tui.editor.complete": readonly ["tab"];
|
|
27
|
+
readonly "tui.select.up": readonly ["up", "ctrl+p"];
|
|
28
|
+
readonly "tui.select.down": readonly ["down", "ctrl+n"];
|
|
29
|
+
readonly "tui.select.pageUp": readonly ["pageup"];
|
|
30
|
+
readonly "tui.select.pageDown": readonly ["pagedown"];
|
|
31
|
+
readonly "tui.select.confirm": readonly ["enter", "tab"];
|
|
32
|
+
readonly "tui.select.cancel": readonly ["escape", "ctrl+c"];
|
|
33
|
+
readonly "app.interrupt": readonly ["escape"];
|
|
34
|
+
readonly "app.clear": readonly ["ctrl+c"];
|
|
35
|
+
readonly "app.exit": readonly ["ctrl+d"];
|
|
36
|
+
readonly "app.message.followUp": readonly ["alt+enter"];
|
|
37
|
+
readonly "app.message.dequeue": readonly ["alt+up"];
|
|
38
|
+
readonly "app.permission.cycle": readonly ["shift+tab"];
|
|
39
|
+
readonly "app.tools.expand": readonly ["ctrl+o"];
|
|
40
|
+
readonly "app.model.select": readonly ["ctrl+l"];
|
|
41
|
+
readonly "app.thinking.select": readonly ["ctrl+t"];
|
|
42
|
+
};
|
|
43
|
+
export type ActionId = keyof typeof DEFAULT_KEYBINDINGS;
|
|
44
|
+
export type KeybindingOverrides = Partial<Record<ActionId, readonly string[]>>;
|
|
45
|
+
export interface ParsedKeybindings {
|
|
46
|
+
overrides: KeybindingOverrides;
|
|
47
|
+
/** 被忽略的条目说明(未知动作、类型错误)。 */
|
|
48
|
+
warnings: string[];
|
|
49
|
+
}
|
|
50
|
+
export declare function isActionId(id: string): id is ActionId;
|
|
51
|
+
/** 解析 keybindings.json 的对象形状:`{ "<action>": "key" | ["key", ...] }`。 */
|
|
52
|
+
export declare function parseKeybindings(value: unknown): ParsedKeybindings;
|
|
53
|
+
/** 读取并解析 keybindings.json;文件不存在返回空覆盖,JSON 错误记入 warnings。 */
|
|
54
|
+
export declare function loadKeybindingsFile(path: string): ParsedKeybindings;
|
|
55
|
+
export declare class Keybindings {
|
|
56
|
+
private readonly table;
|
|
57
|
+
constructor(overrides?: KeybindingOverrides);
|
|
58
|
+
/** 动作绑定的 KeyId 列表(已规范化)。 */
|
|
59
|
+
keys(action: ActionId): readonly string[];
|
|
60
|
+
/** 原始输入数据是否触发该动作。 */
|
|
61
|
+
matches(data: string, action: ActionId): boolean;
|
|
62
|
+
/** 输入数据触发的全部动作(按缺省表顺序),用于分派。 */
|
|
63
|
+
actionsFor(data: string): ActionId[];
|
|
64
|
+
}
|
|
65
|
+
/** 进程内共享的缺省键位(组件未显式传入时使用)。 */
|
|
66
|
+
export declare const defaultKeybindings: Keybindings;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 键位表(设计 §12.3):动作 id → KeyId 列表;固定缺省表 + `keybindings.json` 覆盖。[B4]
|
|
3
|
+
*
|
|
4
|
+
* - `tui.*` 由组件库自己消费(编辑器、选择列表);`app.*` 供交互模式(B7)分派。
|
|
5
|
+
* - 覆盖时整组替换;空数组表示禁用该动作。未知动作 id 与非字符串键会被报告并忽略。
|
|
6
|
+
* - 文件位置由调用方决定(配置目录归 B5),这里只负责解析与合并。
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { normalizeKeyId, parseKey } from "./keys.js";
|
|
10
|
+
export const DEFAULT_KEYBINDINGS = {
|
|
11
|
+
"tui.editor.submit": ["enter"],
|
|
12
|
+
"tui.editor.newLine": ["shift+enter", "ctrl+j"],
|
|
13
|
+
"tui.editor.cursorLeft": ["left", "ctrl+b"],
|
|
14
|
+
"tui.editor.cursorRight": ["right", "ctrl+f"],
|
|
15
|
+
"tui.editor.cursorUp": ["up", "ctrl+p"],
|
|
16
|
+
"tui.editor.cursorDown": ["down", "ctrl+n"],
|
|
17
|
+
"tui.editor.wordLeft": ["alt+left", "ctrl+left", "alt+b"],
|
|
18
|
+
"tui.editor.wordRight": ["alt+right", "ctrl+right", "alt+f"],
|
|
19
|
+
"tui.editor.lineStart": ["home", "ctrl+a"],
|
|
20
|
+
"tui.editor.lineEnd": ["end", "ctrl+e"],
|
|
21
|
+
"tui.editor.deleteBackward": ["backspace", "shift+backspace"],
|
|
22
|
+
"tui.editor.deleteForward": ["delete"],
|
|
23
|
+
"tui.editor.deleteWordBackward": ["ctrl+w", "alt+backspace", "ctrl+backspace"],
|
|
24
|
+
"tui.editor.deleteWordForward": ["alt+d", "alt+delete", "ctrl+delete"],
|
|
25
|
+
"tui.editor.deleteToLineStart": ["ctrl+u"],
|
|
26
|
+
"tui.editor.deleteToLineEnd": ["ctrl+k"],
|
|
27
|
+
"tui.editor.undo": ["ctrl+z", "ctrl+-"],
|
|
28
|
+
"tui.editor.complete": ["tab"],
|
|
29
|
+
"tui.select.up": ["up", "ctrl+p"],
|
|
30
|
+
"tui.select.down": ["down", "ctrl+n"],
|
|
31
|
+
"tui.select.pageUp": ["pageup"],
|
|
32
|
+
"tui.select.pageDown": ["pagedown"],
|
|
33
|
+
"tui.select.confirm": ["enter", "tab"],
|
|
34
|
+
"tui.select.cancel": ["escape", "ctrl+c"],
|
|
35
|
+
"app.interrupt": ["escape"],
|
|
36
|
+
"app.clear": ["ctrl+c"],
|
|
37
|
+
"app.exit": ["ctrl+d"],
|
|
38
|
+
"app.message.followUp": ["alt+enter"],
|
|
39
|
+
"app.message.dequeue": ["alt+up"],
|
|
40
|
+
"app.permission.cycle": ["shift+tab"],
|
|
41
|
+
"app.tools.expand": ["ctrl+o"],
|
|
42
|
+
"app.model.select": ["ctrl+l"],
|
|
43
|
+
"app.thinking.select": ["ctrl+t"],
|
|
44
|
+
};
|
|
45
|
+
export function isActionId(id) {
|
|
46
|
+
return Object.prototype.hasOwnProperty.call(DEFAULT_KEYBINDINGS, id);
|
|
47
|
+
}
|
|
48
|
+
/** 解析 keybindings.json 的对象形状:`{ "<action>": "key" | ["key", ...] }`。 */
|
|
49
|
+
export function parseKeybindings(value) {
|
|
50
|
+
const overrides = {};
|
|
51
|
+
const warnings = [];
|
|
52
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
53
|
+
return { overrides: {}, warnings: ["keybindings 必须是对象"] };
|
|
54
|
+
}
|
|
55
|
+
for (const [action, keys] of Object.entries(value)) {
|
|
56
|
+
if (!isActionId(action)) {
|
|
57
|
+
warnings.push(`未知动作:${action}`);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const list = typeof keys === "string" ? [keys] : keys;
|
|
61
|
+
if (!Array.isArray(list) || !list.every((k) => typeof k === "string")) {
|
|
62
|
+
warnings.push(`${action}:键必须是字符串或字符串数组`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
overrides[action] = list.map((k) => normalizeKeyId(k));
|
|
66
|
+
}
|
|
67
|
+
return { overrides: overrides, warnings };
|
|
68
|
+
}
|
|
69
|
+
/** 读取并解析 keybindings.json;文件不存在返回空覆盖,JSON 错误记入 warnings。 */
|
|
70
|
+
export function loadKeybindingsFile(path) {
|
|
71
|
+
let text;
|
|
72
|
+
try {
|
|
73
|
+
text = readFileSync(path, "utf8");
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error.code === "ENOENT")
|
|
77
|
+
return { overrides: {}, warnings: [] };
|
|
78
|
+
return { overrides: {}, warnings: [`读取 ${path} 失败:${error.message}`] };
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
return parseKeybindings(JSON.parse(text));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return { overrides: {}, warnings: [`${path} 不是合法 JSON:${error.message}`] };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export class Keybindings {
|
|
88
|
+
table;
|
|
89
|
+
constructor(overrides = {}) {
|
|
90
|
+
this.table = new Map();
|
|
91
|
+
for (const action of Object.keys(DEFAULT_KEYBINDINGS)) {
|
|
92
|
+
const keys = overrides[action] ?? DEFAULT_KEYBINDINGS[action];
|
|
93
|
+
this.table.set(action, keys.map((k) => normalizeKeyId(k)));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** 动作绑定的 KeyId 列表(已规范化)。 */
|
|
97
|
+
keys(action) {
|
|
98
|
+
return this.table.get(action) ?? [];
|
|
99
|
+
}
|
|
100
|
+
/** 原始输入数据是否触发该动作。 */
|
|
101
|
+
matches(data, action) {
|
|
102
|
+
const key = parseKey(data);
|
|
103
|
+
if (key === undefined)
|
|
104
|
+
return false;
|
|
105
|
+
return this.keys(action).includes(key.id);
|
|
106
|
+
}
|
|
107
|
+
/** 输入数据触发的全部动作(按缺省表顺序),用于分派。 */
|
|
108
|
+
actionsFor(data) {
|
|
109
|
+
const key = parseKey(data);
|
|
110
|
+
if (key === undefined)
|
|
111
|
+
return [];
|
|
112
|
+
const out = [];
|
|
113
|
+
for (const [action, keys] of this.table)
|
|
114
|
+
if (keys.includes(key.id))
|
|
115
|
+
out.push(action);
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** 进程内共享的缺省键位(组件未显式传入时使用)。 */
|
|
120
|
+
export const defaultKeybindings = new Keybindings();
|