one-code-extension 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -219
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +32 -23
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +27 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +66 -34
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +123 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +515 -0
- package/extensions/background/index.ts +514 -236
- package/extensions/background/loop-fire.ts +136 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +52 -0
- package/extensions/background/wakeup.ts +159 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +88 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +229 -71
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +446 -58
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +216 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/index.ts +24 -5
- package/extensions/context-management/index.ts +25 -0
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +72 -19
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +337 -26
- package/extensions/file-tracker/replay.ts +96 -10
- package/extensions/file-tracker/restore.ts +63 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +32 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +198 -31
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +13 -5
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/lib/agent-cron.ts +130 -0
- package/extensions/lib/agent-loader.ts +15 -1
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +42 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +78 -0
- package/extensions/lib/context-stack.ts +81 -0
- package/extensions/lib/deferred.ts +219 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/local-command.ts +47 -13
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +174 -0
- package/extensions/lib/model-catalog/models-dev.json +18297 -0
- package/extensions/lib/model-catalog/openrouter.json +5252 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +308 -64
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +21 -2
- package/extensions/lib/plan-mode-channels.ts +20 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/process-tree.ts +131 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +364 -58
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +439 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +13 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +113 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +149 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +143 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +70 -4
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +87 -5
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +209 -71
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +151 -67
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1300 -381
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +6 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +156 -69
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +61 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +12 -7
- package/extensions/recap/index.ts +65 -60
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +421 -101
- package/extensions/skill/invoke.ts +128 -18
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +116 -23
- package/extensions/subagents/announce.ts +69 -0
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +76 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1493 -824
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +2 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/runner.ts +108 -80
- package/extensions/subagents/runs.ts +39 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +131 -47
- package/extensions/system-prompt/template.ts +24 -50
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +175 -13
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/announce.ts +16 -0
- package/extensions/tool-search/index.ts +189 -41
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +105 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +145 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +29 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +121 -39
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +19 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal key decoding for One Code's panels, dialogs and input listeners.
|
|
3
|
+
*
|
|
4
|
+
* A key reaches an extension as raw bytes, and one key has several encodings.
|
|
5
|
+
* pi-tui asks the terminal for the kitty keyboard protocol (flags 7:
|
|
6
|
+
* disambiguate, event types, alternate keys), and when the terminal does not
|
|
7
|
+
* answer it turns on xterm's modifyOtherKeys. So Esc is `\x1b` in a legacy
|
|
8
|
+
* terminal and `\x1b[27u` in kitty, Ghostty, WezTerm or VS Code; ctrl+c is
|
|
9
|
+
* `\x03`, `\x1b[99;5u`, or `\x1b[27;5;99~` under tmux with `extended-keys on`;
|
|
10
|
+
* and with event types on, every press is followed by a release such as
|
|
11
|
+
* `\x1b[1;1:3B`. A decoder that compares legacy bytes misses all of those.
|
|
12
|
+
*
|
|
13
|
+
* `parseKey` is pi-tui's own parser (`dist/keys.js`, MIT; unchanged from
|
|
14
|
+
* 0.87.1 to 1.1.0, then 1.2.0's logical shifted-symbol reading), ported
|
|
15
|
+
* verbatim so every panel names a key exactly as pi-tui and pi's
|
|
16
|
+
* keybindings do. It stays here because `lib/` modules
|
|
17
|
+
* import nothing from pi, and the bare `@earendil-works/pi-tui` specifier
|
|
18
|
+
* resolves to a different pi-tui in tests than at runtime.
|
|
19
|
+
* `key-input.test.ts` compares this port with the installed pi-tui over
|
|
20
|
+
* every encoding, so a pi bump that changes the parser fails the suite.
|
|
21
|
+
*
|
|
22
|
+
* Decoders switch on `keyId(data)` (pi-tui's key ids: `escape`, `ctrl+c`,
|
|
23
|
+
* `shift+tab`, `up`, `pageDown`, `x`) and read typed text with `keyText`.
|
|
24
|
+
* An input listener, which pi-tui calls before it filters releases, checks
|
|
25
|
+
* `isKeyRelease` first.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const MODIFIERS = { shift: 1, alt: 2, ctrl: 4, super: 8 } as const;
|
|
29
|
+
/** Caps lock and num lock, which the kitty protocol reports as modifier bits. */
|
|
30
|
+
const LOCK_MASK = 64 + 128;
|
|
31
|
+
|
|
32
|
+
const CODEPOINTS = { escape: 27, tab: 9, enter: 13, space: 32, backspace: 127, kpEnter: 57414 } as const;
|
|
33
|
+
const ARROW_CODEPOINTS = { up: -1, down: -2, right: -3, left: -4 } as const;
|
|
34
|
+
const FUNCTIONAL_CODEPOINTS = { delete: -10, insert: -11, pageUp: -12, pageDown: -13, home: -14, end: -15 } as const;
|
|
35
|
+
|
|
36
|
+
/** Kitty keypad codes and the keys they stand for. */
|
|
37
|
+
const KITTY_FUNCTIONAL_KEY_EQUIVALENTS = new Map<number, number>([
|
|
38
|
+
[57399, 48],
|
|
39
|
+
[57400, 49],
|
|
40
|
+
[57401, 50],
|
|
41
|
+
[57402, 51],
|
|
42
|
+
[57403, 52],
|
|
43
|
+
[57404, 53],
|
|
44
|
+
[57405, 54],
|
|
45
|
+
[57406, 55],
|
|
46
|
+
[57407, 56],
|
|
47
|
+
[57408, 57],
|
|
48
|
+
[57409, 46],
|
|
49
|
+
[57410, 47],
|
|
50
|
+
[57411, 42],
|
|
51
|
+
[57412, 45],
|
|
52
|
+
[57413, 43],
|
|
53
|
+
[57415, 61],
|
|
54
|
+
[57416, 44],
|
|
55
|
+
[57417, ARROW_CODEPOINTS.left],
|
|
56
|
+
[57418, ARROW_CODEPOINTS.right],
|
|
57
|
+
[57419, ARROW_CODEPOINTS.up],
|
|
58
|
+
[57420, ARROW_CODEPOINTS.down],
|
|
59
|
+
[57421, FUNCTIONAL_CODEPOINTS.pageUp],
|
|
60
|
+
[57422, FUNCTIONAL_CODEPOINTS.pageDown],
|
|
61
|
+
[57423, FUNCTIONAL_CODEPOINTS.home],
|
|
62
|
+
[57424, FUNCTIONAL_CODEPOINTS.end],
|
|
63
|
+
[57425, FUNCTIONAL_CODEPOINTS.insert],
|
|
64
|
+
[57426, FUNCTIONAL_CODEPOINTS.delete],
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
const SYMBOL_KEYS = new Set([
|
|
68
|
+
"`", "-", "=", "[", "]", "\\", ";", "'", ",", ".", "/", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_",
|
|
69
|
+
"+", "|", "~", "{", "}", ":", "<", ">", "?",
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
const LEGACY_SEQUENCE_KEY_IDS: Record<string, string> = {
|
|
73
|
+
"\x1bOA": "up",
|
|
74
|
+
"\x1bOB": "down",
|
|
75
|
+
"\x1bOC": "right",
|
|
76
|
+
"\x1bOD": "left",
|
|
77
|
+
"\x1bOH": "home",
|
|
78
|
+
"\x1bOF": "end",
|
|
79
|
+
"\x1b[E": "clear",
|
|
80
|
+
"\x1bOE": "clear",
|
|
81
|
+
"\x1bOe": "ctrl+clear",
|
|
82
|
+
"\x1b[e": "shift+clear",
|
|
83
|
+
"\x1b[2~": "insert",
|
|
84
|
+
"\x1b[2$": "shift+insert",
|
|
85
|
+
"\x1b[2^": "ctrl+insert",
|
|
86
|
+
"\x1b[3$": "shift+delete",
|
|
87
|
+
"\x1b[3^": "ctrl+delete",
|
|
88
|
+
"\x1b[[5~": "pageUp",
|
|
89
|
+
"\x1b[[6~": "pageDown",
|
|
90
|
+
"\x1b[a": "shift+up",
|
|
91
|
+
"\x1b[b": "shift+down",
|
|
92
|
+
"\x1b[c": "shift+right",
|
|
93
|
+
"\x1b[d": "shift+left",
|
|
94
|
+
"\x1bOa": "ctrl+up",
|
|
95
|
+
"\x1bOb": "ctrl+down",
|
|
96
|
+
"\x1bOc": "ctrl+right",
|
|
97
|
+
"\x1bOd": "ctrl+left",
|
|
98
|
+
"\x1b[5$": "shift+pageUp",
|
|
99
|
+
"\x1b[6$": "shift+pageDown",
|
|
100
|
+
"\x1b[7$": "shift+home",
|
|
101
|
+
"\x1b[8$": "shift+end",
|
|
102
|
+
"\x1b[5^": "ctrl+pageUp",
|
|
103
|
+
"\x1b[6^": "ctrl+pageDown",
|
|
104
|
+
"\x1b[7^": "ctrl+home",
|
|
105
|
+
"\x1b[8^": "ctrl+end",
|
|
106
|
+
"\x1bOP": "f1",
|
|
107
|
+
"\x1bOQ": "f2",
|
|
108
|
+
"\x1bOR": "f3",
|
|
109
|
+
"\x1bOS": "f4",
|
|
110
|
+
"\x1b[11~": "f1",
|
|
111
|
+
"\x1b[12~": "f2",
|
|
112
|
+
"\x1b[13~": "f3",
|
|
113
|
+
"\x1b[14~": "f4",
|
|
114
|
+
"\x1b[[A": "f1",
|
|
115
|
+
"\x1b[[B": "f2",
|
|
116
|
+
"\x1b[[C": "f3",
|
|
117
|
+
"\x1b[[D": "f4",
|
|
118
|
+
"\x1b[[E": "f5",
|
|
119
|
+
"\x1b[15~": "f5",
|
|
120
|
+
"\x1b[17~": "f6",
|
|
121
|
+
"\x1b[18~": "f7",
|
|
122
|
+
"\x1b[19~": "f8",
|
|
123
|
+
"\x1b[20~": "f9",
|
|
124
|
+
"\x1b[21~": "f10",
|
|
125
|
+
"\x1b[23~": "f11",
|
|
126
|
+
"\x1b[24~": "f12",
|
|
127
|
+
"\x1bb": "alt+left",
|
|
128
|
+
"\x1bf": "alt+right",
|
|
129
|
+
"\x1bp": "alt+up",
|
|
130
|
+
"\x1bn": "alt+down",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type KeyEventType = "press" | "repeat" | "release";
|
|
134
|
+
|
|
135
|
+
interface ParsedSequence {
|
|
136
|
+
codepoint: number;
|
|
137
|
+
shiftedKey?: number;
|
|
138
|
+
baseLayoutKey?: number;
|
|
139
|
+
modifier: number;
|
|
140
|
+
eventType: KeyEventType;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseEventType(field: string | undefined): KeyEventType {
|
|
144
|
+
if (!field) return "press";
|
|
145
|
+
const eventType = Number.parseInt(field, 10);
|
|
146
|
+
if (eventType === 2) return "repeat";
|
|
147
|
+
if (eventType === 3) return "release";
|
|
148
|
+
return "press";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const KITTY_CSI_U = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/;
|
|
152
|
+
|
|
153
|
+
function parseKittySequence(data: string): ParsedSequence | undefined {
|
|
154
|
+
const csiU = data.match(KITTY_CSI_U);
|
|
155
|
+
if (csiU) {
|
|
156
|
+
return {
|
|
157
|
+
codepoint: Number.parseInt(csiU[1], 10),
|
|
158
|
+
shiftedKey: csiU[2] && csiU[2].length > 0 ? Number.parseInt(csiU[2], 10) : undefined,
|
|
159
|
+
baseLayoutKey: csiU[3] ? Number.parseInt(csiU[3], 10) : undefined,
|
|
160
|
+
modifier: (csiU[4] ? Number.parseInt(csiU[4], 10) : 1) - 1,
|
|
161
|
+
eventType: parseEventType(csiU[5]),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const arrow = data.match(/^\x1b\[1;(\d+)(?::(\d+))?([ABCD])$/);
|
|
165
|
+
if (arrow) {
|
|
166
|
+
const codes: Record<string, number> = { A: -1, B: -2, C: -3, D: -4 };
|
|
167
|
+
return { codepoint: codes[arrow[3]], modifier: Number.parseInt(arrow[1], 10) - 1, eventType: parseEventType(arrow[2]) };
|
|
168
|
+
}
|
|
169
|
+
const func = data.match(/^\x1b\[(\d+)(?:;(\d+))?(?::(\d+))?~$/);
|
|
170
|
+
if (func) {
|
|
171
|
+
const funcCodes: Record<number, number> = {
|
|
172
|
+
2: FUNCTIONAL_CODEPOINTS.insert,
|
|
173
|
+
3: FUNCTIONAL_CODEPOINTS.delete,
|
|
174
|
+
5: FUNCTIONAL_CODEPOINTS.pageUp,
|
|
175
|
+
6: FUNCTIONAL_CODEPOINTS.pageDown,
|
|
176
|
+
7: FUNCTIONAL_CODEPOINTS.home,
|
|
177
|
+
8: FUNCTIONAL_CODEPOINTS.end,
|
|
178
|
+
};
|
|
179
|
+
const codepoint = funcCodes[Number.parseInt(func[1], 10)];
|
|
180
|
+
if (codepoint !== undefined) {
|
|
181
|
+
return { codepoint, modifier: (func[2] ? Number.parseInt(func[2], 10) : 1) - 1, eventType: parseEventType(func[3]) };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const homeEnd = data.match(/^\x1b\[1;(\d+)(?::(\d+))?([HF])$/);
|
|
185
|
+
if (homeEnd) {
|
|
186
|
+
return {
|
|
187
|
+
codepoint: homeEnd[3] === "H" ? FUNCTIONAL_CODEPOINTS.home : FUNCTIONAL_CODEPOINTS.end,
|
|
188
|
+
modifier: Number.parseInt(homeEnd[1], 10) - 1,
|
|
189
|
+
eventType: parseEventType(homeEnd[2]),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseModifyOtherKeysSequence(data: string): { codepoint: number; modifier: number } | undefined {
|
|
196
|
+
const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
|
|
197
|
+
if (!match) return undefined;
|
|
198
|
+
return { codepoint: Number.parseInt(match[2], 10), modifier: Number.parseInt(match[1], 10) - 1 };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function normalizeKittyFunctionalCodepoint(codepoint: number): number {
|
|
202
|
+
return KITTY_FUNCTIONAL_KEY_EQUIVALENTS.get(codepoint) ?? codepoint;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizeShiftedLetterIdentityCodepoint(codepoint: number, modifier: number): number {
|
|
206
|
+
const effective = modifier & ~LOCK_MASK;
|
|
207
|
+
if ((effective & MODIFIERS.shift) !== 0 && codepoint >= 65 && codepoint <= 90) return codepoint + 32;
|
|
208
|
+
return codepoint;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function formatKeyNameWithModifiers(keyName: string, modifier: number): string | undefined {
|
|
212
|
+
const effective = modifier & ~LOCK_MASK;
|
|
213
|
+
const supported = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt | MODIFIERS.super;
|
|
214
|
+
if ((effective & ~supported) !== 0) return undefined;
|
|
215
|
+
const mods: string[] = [];
|
|
216
|
+
if (effective & MODIFIERS.shift) mods.push("shift");
|
|
217
|
+
if (effective & MODIFIERS.ctrl) mods.push("ctrl");
|
|
218
|
+
if (effective & MODIFIERS.alt) mods.push("alt");
|
|
219
|
+
if (effective & MODIFIERS.super) mods.push("super");
|
|
220
|
+
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const NAMED_CODEPOINTS = new Map<number, string>([
|
|
224
|
+
[CODEPOINTS.escape, "escape"],
|
|
225
|
+
[CODEPOINTS.tab, "tab"],
|
|
226
|
+
[CODEPOINTS.enter, "enter"],
|
|
227
|
+
[CODEPOINTS.kpEnter, "enter"],
|
|
228
|
+
[CODEPOINTS.space, "space"],
|
|
229
|
+
[CODEPOINTS.backspace, "backspace"],
|
|
230
|
+
[FUNCTIONAL_CODEPOINTS.delete, "delete"],
|
|
231
|
+
[FUNCTIONAL_CODEPOINTS.insert, "insert"],
|
|
232
|
+
[FUNCTIONAL_CODEPOINTS.home, "home"],
|
|
233
|
+
[FUNCTIONAL_CODEPOINTS.end, "end"],
|
|
234
|
+
[FUNCTIONAL_CODEPOINTS.pageUp, "pageUp"],
|
|
235
|
+
[FUNCTIONAL_CODEPOINTS.pageDown, "pageDown"],
|
|
236
|
+
[ARROW_CODEPOINTS.up, "up"],
|
|
237
|
+
[ARROW_CODEPOINTS.down, "down"],
|
|
238
|
+
[ARROW_CODEPOINTS.left, "left"],
|
|
239
|
+
[ARROW_CODEPOINTS.right, "right"],
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
/** Symbols a modifyOtherKeys terminal reports as Shift plus the physical key (pi-tui 1.2.0). */
|
|
243
|
+
const SHIFTED_SYMBOL_KEYS = new Set("~!@#$%^&*()_+|{}:<>?");
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* pi-tui 1.2.0's logical reading of a shifted symbol: kitty reports "+" as
|
|
247
|
+
* Shift plus the physical "=" key (the produced symbol in the shifted-key
|
|
248
|
+
* field), and bindings name the produced symbol, so the implicit Shift is
|
|
249
|
+
* consumed while Ctrl, Alt and Super stay.
|
|
250
|
+
*/
|
|
251
|
+
function logicalShiftedSymbol(codepoint: number | undefined, modifier: number, allowed: ReadonlySet<string>): { codepoint: number; modifier: number } | undefined {
|
|
252
|
+
if (codepoint === undefined || (modifier & MODIFIERS.shift) === 0) return undefined;
|
|
253
|
+
const normalized = normalizeKittyFunctionalCodepoint(codepoint);
|
|
254
|
+
if (!allowed.has(String.fromCharCode(normalized))) return undefined;
|
|
255
|
+
return { codepoint: normalized, modifier: modifier & ~MODIFIERS.shift };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function formatParsedKey(codepoint: number, modifier: number, baseLayoutKey?: number): string | undefined {
|
|
259
|
+
const identity = normalizeShiftedLetterIdentityCodepoint(normalizeKittyFunctionalCodepoint(codepoint), modifier);
|
|
260
|
+
// The codepoint is authoritative for a Latin letter, digit or known symbol
|
|
261
|
+
// (remapped layouts); otherwise the base-layout key names it.
|
|
262
|
+
const isLatinLetter = identity >= 97 && identity <= 122;
|
|
263
|
+
const isDigit = identity >= 48 && identity <= 57;
|
|
264
|
+
const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(identity));
|
|
265
|
+
const effective = isLatinLetter || isDigit || isKnownSymbol ? identity : (baseLayoutKey ?? identity);
|
|
266
|
+
let keyName = NAMED_CODEPOINTS.get(effective);
|
|
267
|
+
if (!keyName) {
|
|
268
|
+
if ((effective >= 48 && effective <= 57) || (effective >= 97 && effective <= 122)) keyName = String.fromCharCode(effective);
|
|
269
|
+
else if (SYMBOL_KEYS.has(String.fromCharCode(effective))) keyName = String.fromCharCode(effective);
|
|
270
|
+
}
|
|
271
|
+
if (!keyName) return undefined;
|
|
272
|
+
return formatKeyNameWithModifiers(keyName, modifier);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface ParseKeyOptions {
|
|
276
|
+
/** pi-tui's kitty-protocol state: `\n` and `\x1b\r` then mean shift+enter. Default false. */
|
|
277
|
+
kittyActive?: boolean;
|
|
278
|
+
/** Windows Terminal outside SSH, where a raw `\x08` is ctrl+backspace. Default read from the environment. */
|
|
279
|
+
windowsTerminal?: boolean;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function isWindowsTerminalSession(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
283
|
+
return Boolean(env.WT_SESSION) && !env.SSH_CONNECTION && !env.SSH_CLIENT && !env.SSH_TTY;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** pi-tui's `parseKey` (1.2.0): the key id of one input chunk, or undefined. Releases parse like presses. */
|
|
287
|
+
export function parseKey(data: string, options: ParseKeyOptions = {}): string | undefined {
|
|
288
|
+
const kitty = options.kittyActive ?? false;
|
|
289
|
+
const kittySeq = parseKittySequence(data);
|
|
290
|
+
if (kittySeq) {
|
|
291
|
+
const symbol = logicalShiftedSymbol(kittySeq.shiftedKey, kittySeq.modifier, SYMBOL_KEYS);
|
|
292
|
+
return formatParsedKey(symbol?.codepoint ?? kittySeq.codepoint, symbol?.modifier ?? kittySeq.modifier, kittySeq.baseLayoutKey);
|
|
293
|
+
}
|
|
294
|
+
const mok = parseModifyOtherKeysSequence(data);
|
|
295
|
+
if (mok) {
|
|
296
|
+
const symbol = logicalShiftedSymbol(mok.codepoint, mok.modifier, SHIFTED_SYMBOL_KEYS);
|
|
297
|
+
return formatParsedKey(symbol?.codepoint ?? mok.codepoint, symbol?.modifier ?? mok.modifier);
|
|
298
|
+
}
|
|
299
|
+
if (kitty && (data === "\x1b\r" || data === "\n")) return "shift+enter";
|
|
300
|
+
const legacy = LEGACY_SEQUENCE_KEY_IDS[data];
|
|
301
|
+
if (legacy) return legacy;
|
|
302
|
+
switch (data) {
|
|
303
|
+
case "\x1b":
|
|
304
|
+
return "escape";
|
|
305
|
+
case "\x1c":
|
|
306
|
+
return "ctrl+\\";
|
|
307
|
+
case "\x1d":
|
|
308
|
+
return "ctrl+]";
|
|
309
|
+
case "\x1f":
|
|
310
|
+
return "ctrl+-";
|
|
311
|
+
case "\x1b\x1b":
|
|
312
|
+
return "ctrl+alt+[";
|
|
313
|
+
case "\x1b\x1c":
|
|
314
|
+
return "ctrl+alt+\\";
|
|
315
|
+
case "\x1b\x1d":
|
|
316
|
+
return "ctrl+alt+]";
|
|
317
|
+
case "\x1b\x1f":
|
|
318
|
+
return "ctrl+alt+-";
|
|
319
|
+
case "\t":
|
|
320
|
+
return "tab";
|
|
321
|
+
case "\r":
|
|
322
|
+
case "\x1bOM":
|
|
323
|
+
return "enter";
|
|
324
|
+
case "\x00":
|
|
325
|
+
return "ctrl+space";
|
|
326
|
+
case " ":
|
|
327
|
+
return "space";
|
|
328
|
+
case "\x7f":
|
|
329
|
+
return "backspace";
|
|
330
|
+
case "\x08":
|
|
331
|
+
return (options.windowsTerminal ?? isWindowsTerminalSession()) ? "ctrl+backspace" : "backspace";
|
|
332
|
+
case "\x1b[Z":
|
|
333
|
+
return "shift+tab";
|
|
334
|
+
case "\x1b\x7f":
|
|
335
|
+
case "\x1b\b":
|
|
336
|
+
return "alt+backspace";
|
|
337
|
+
}
|
|
338
|
+
if (!kitty) {
|
|
339
|
+
if (data === "\n") return "enter";
|
|
340
|
+
if (data === "\x1b\r") return "alt+enter";
|
|
341
|
+
if (data === "\x1b ") return "alt+space";
|
|
342
|
+
if (data === "\x1bB") return "alt+left";
|
|
343
|
+
if (data === "\x1bF") return "alt+right";
|
|
344
|
+
if (data.length === 2 && data[0] === "\x1b") {
|
|
345
|
+
const code = data.charCodeAt(1);
|
|
346
|
+
if (code >= 1 && code <= 26) return `ctrl+alt+${String.fromCharCode(code + 96)}`;
|
|
347
|
+
const key = String.fromCharCode(code);
|
|
348
|
+
if ((code >= 97 && code <= 122) || (code >= 48 && code <= 57) || SYMBOL_KEYS.has(key)) return `alt+${key}`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
switch (data) {
|
|
352
|
+
case "\x1b[A":
|
|
353
|
+
return "up";
|
|
354
|
+
case "\x1b[B":
|
|
355
|
+
return "down";
|
|
356
|
+
case "\x1b[C":
|
|
357
|
+
return "right";
|
|
358
|
+
case "\x1b[D":
|
|
359
|
+
return "left";
|
|
360
|
+
case "\x1b[H":
|
|
361
|
+
case "\x1bOH":
|
|
362
|
+
return "home";
|
|
363
|
+
case "\x1b[F":
|
|
364
|
+
case "\x1bOF":
|
|
365
|
+
return "end";
|
|
366
|
+
case "\x1b[3~":
|
|
367
|
+
return "delete";
|
|
368
|
+
case "\x1b[5~":
|
|
369
|
+
return "pageUp";
|
|
370
|
+
case "\x1b[6~":
|
|
371
|
+
return "pageDown";
|
|
372
|
+
}
|
|
373
|
+
if (data.length === 1) {
|
|
374
|
+
const code = data.charCodeAt(0);
|
|
375
|
+
if (code >= 1 && code <= 26) return `ctrl+${String.fromCharCode(code + 96)}`;
|
|
376
|
+
if (code >= 32 && code <= 126) return data;
|
|
377
|
+
}
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Legacy Home and End forms that pi-tui's `matchesKey` accepts but its
|
|
383
|
+
* `parseKey` does not name (rxvt and the Linux console send them).
|
|
384
|
+
*/
|
|
385
|
+
const EXTRA_KEY_IDS: Record<string, string> = { "\x1b[1~": "home", "\x1b[4~": "end" };
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* The key id for a panel decoder: pi-tui's `parseKey`, plus the legacy Home
|
|
389
|
+
* and End forms. A release or repeat names the same key as its press; pi-tui
|
|
390
|
+
* drops releases before a focused component sees them.
|
|
391
|
+
*/
|
|
392
|
+
export function keyId(data: string, options?: ParseKeyOptions): string | undefined {
|
|
393
|
+
return parseKey(data, options) ?? EXTRA_KEY_IDS[data];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** pi-tui's `isKeyRelease`: the kitty protocol's release event (flag 2). Pasted text never counts. */
|
|
397
|
+
export function isKeyRelease(data: string): boolean {
|
|
398
|
+
if (data.includes("\x1b[200~")) return false;
|
|
399
|
+
return /:3[u~ABCDHF]/.test(data);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** pi-tui's `isKeyRepeat`: the kitty protocol's auto-repeat event (flag 2). */
|
|
403
|
+
export function isKeyRepeat(data: string): boolean {
|
|
404
|
+
if (data.includes("\x1b[200~")) return false;
|
|
405
|
+
return /:2[u~ABCDHF]/.test(data);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const KITTY_PRINTABLE_ALLOWED_MODIFIERS = MODIFIERS.shift | LOCK_MASK;
|
|
409
|
+
|
|
410
|
+
/** pi-tui's `decodePrintableKey`: the character a kitty CSI u or modifyOtherKeys sequence types, if any. */
|
|
411
|
+
export function decodePrintableKey(data: string): string | undefined {
|
|
412
|
+
const csiU = data.match(KITTY_CSI_U);
|
|
413
|
+
if (csiU) {
|
|
414
|
+
const codepoint = Number.parseInt(csiU[1] ?? "", 10);
|
|
415
|
+
if (!Number.isFinite(codepoint)) return undefined;
|
|
416
|
+
const shiftedKey = csiU[2] && csiU[2].length > 0 ? Number.parseInt(csiU[2], 10) : undefined;
|
|
417
|
+
const modValue = csiU[4] ? Number.parseInt(csiU[4], 10) : 1;
|
|
418
|
+
const modifier = Number.isFinite(modValue) ? modValue - 1 : 0;
|
|
419
|
+
if ((modifier & ~KITTY_PRINTABLE_ALLOWED_MODIFIERS) !== 0) return undefined;
|
|
420
|
+
if (modifier & (MODIFIERS.alt | MODIFIERS.ctrl)) return undefined;
|
|
421
|
+
let effective = codepoint;
|
|
422
|
+
if (modifier & MODIFIERS.shift && typeof shiftedKey === "number") effective = shiftedKey;
|
|
423
|
+
effective = normalizeKittyFunctionalCodepoint(effective);
|
|
424
|
+
if (!Number.isFinite(effective) || effective < 32) return undefined;
|
|
425
|
+
try {
|
|
426
|
+
return String.fromCodePoint(effective);
|
|
427
|
+
} catch {
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
const mok = parseModifyOtherKeysSequence(data);
|
|
432
|
+
if (!mok) return undefined;
|
|
433
|
+
if (((mok.modifier & ~LOCK_MASK) & ~MODIFIERS.shift) !== 0) return undefined;
|
|
434
|
+
if (!Number.isFinite(mok.codepoint) || mok.codepoint < 32) return undefined;
|
|
435
|
+
try {
|
|
436
|
+
return String.fromCodePoint(mok.codepoint);
|
|
437
|
+
} catch {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* The text a chunk types, for a search box, a draft or a letter shortcut:
|
|
444
|
+
* plain input (a key or a paste) with control characters removed, or the
|
|
445
|
+
* character an encoded printable key stands for. A control sequence types
|
|
446
|
+
* nothing, so its tail never leaks into a draft.
|
|
447
|
+
*/
|
|
448
|
+
export function keyText(data: string): string | undefined {
|
|
449
|
+
if (data.startsWith("\x1b")) {
|
|
450
|
+
if (isKeyRelease(data)) return undefined;
|
|
451
|
+
return decodePrintableKey(data);
|
|
452
|
+
}
|
|
453
|
+
const text = [...data].filter((ch) => ch >= " " && ch !== "\x7f").join("");
|
|
454
|
+
return text.length > 0 ? text : undefined;
|
|
455
|
+
}
|
package/extensions/lib/keys.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* warning, and mode cycling had no key at all on Windows (seen on the Vultr
|
|
12
12
|
* VM, 2026-09-19). No ctrl+letter is free on Windows once pi's app and editor
|
|
13
13
|
* bindings are counted, and pi itself reaches for alt there, so mode cycling
|
|
14
|
-
* is alt+m on Windows and WSL — docs/decisions/windows.md.
|
|
14
|
+
* is alt+m on Windows and WSL — working-docs/decisions/windows.md.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/** pi's rule for its Windows key table: native Windows, or a WSL distro. */
|
|
@@ -24,6 +24,19 @@ export function modeCycleKey(platform: string = process.platform, env: NodeJS.Pr
|
|
|
24
24
|
return useWindowsKeybindings(platform, env) ? "alt+m" : "ctrl+q";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* The key that shows or hides the pinned task list. Claude Code uses ctrl+t,
|
|
29
|
+
* which pi reserves for its thinking-block toggle (an extension shortcut on a
|
|
30
|
+
* reserved key is skipped), so One Code uses alt+t, free in pi's key tables
|
|
31
|
+
* on every platform. macOS terminals send option+t as the typed character †
|
|
32
|
+
* unless "Use Option as Meta" is on, so alt+t never arrives there; macOS gets
|
|
33
|
+
* ctrl+\ instead, a control byte every terminal passes through, free in pi's
|
|
34
|
+
* tables (ctrl+] and ctrl+- are taken, and no ctrl+letter is left).
|
|
35
|
+
*/
|
|
36
|
+
export function taskToggleKey(platform: string = process.platform): "alt+t" | "ctrl+\\" {
|
|
37
|
+
return platform === "darwin" ? "ctrl+\\" : "alt+t";
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
/**
|
|
28
41
|
* The footer badge's pause icon. Claude Code's is U+23F8 (⏸); Windows Terminal
|
|
29
42
|
* draws that code point with its emoji font, two cells wide and coloured,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* of the next user message, after the reminder stack and before the user's own
|
|
8
8
|
* text —
|
|
9
9
|
*
|
|
10
|
-
* <local-command-caveat>
|
|
10
|
+
* <local-command-caveat>The command below was run directly in …</local-command-caveat>\n
|
|
11
11
|
* <command-name>/model</command-name>\n
|
|
12
12
|
* <command-message>model</command-message>\n
|
|
13
13
|
* <command-args></command-args>\n
|
|
@@ -16,19 +16,23 @@
|
|
|
16
16
|
* (twelve-space indent on the second and third command lines, verbatim from
|
|
17
17
|
* CC's `formatCommandInputTags`; stdout XML-escaped like CC's `escapeXml`, `&
|
|
18
18
|
* < >` only). `/clear` carries an EMPTY stdout block and opens the new
|
|
19
|
-
* conversation. The caveat tells the model
|
|
19
|
+
* conversation. The caveat tells the model the command was the user's, not a
|
|
20
|
+
* request to it, kept as context.
|
|
20
21
|
*
|
|
21
22
|
* One Code emits these through the reminder queue's `user-prepend` placement
|
|
22
23
|
* (`lib/reminders.ts`): pi dispatches an extension command BEFORE its `input`
|
|
23
24
|
* event fires and handles its own built-ins (`/model`, `/name`, …) before
|
|
24
25
|
* either, so there is no single interception point — every One Code local
|
|
25
26
|
* command is registered through `registerLocalCommand`, which announces before
|
|
26
|
-
* the handler runs,
|
|
27
|
-
*
|
|
27
|
+
* the handler runs (or, with `reportsResult`, after it, carrying the handler's
|
|
28
|
+
* returned text as the stdout, as CC's `onDone(result)` does), and `/model` is
|
|
29
|
+
* announced from the `model_select` event by the extension that persists the
|
|
30
|
+
* choice. pi built-ins we cannot see
|
|
28
31
|
* (`/compact`, `/settings`, …) stay silent. The blocks are pinned in process
|
|
29
32
|
* memory, not written to the session file as CC does — a `--resume` drops them.
|
|
30
33
|
*/
|
|
31
34
|
|
|
35
|
+
import { announceArgumentHint, type HintEmitter, withoutExactMatch } from "./argument-hints.ts";
|
|
32
36
|
import { REMINDER_CHANNEL, type ReminderPayload } from "./reminders.ts";
|
|
33
37
|
|
|
34
38
|
/** The slice of pi's ExtensionAPI the announcer needs (its event bus). */
|
|
@@ -43,8 +47,9 @@ export interface ReminderEmitter {
|
|
|
43
47
|
*/
|
|
44
48
|
export const LOCAL_COMMAND_TAG_PREFIXES = ["<local-command-caveat>", "<command-name>", "<command-message>", "<local-command-stdout>"] as const;
|
|
45
49
|
|
|
50
|
+
/** Claude Code 2.1.288's caveat, naming the harness the command ran in. */
|
|
46
51
|
export const LOCAL_COMMAND_CAVEAT =
|
|
47
|
-
"<local-command-caveat>
|
|
52
|
+
"<local-command-caveat>The command below was run directly in One Code, not sent to you as a request, and its output goes straight to the user. It's recorded here as context for later messages.</local-command-caveat>\n";
|
|
48
53
|
|
|
49
54
|
/** CC's `escapeXml` (utils/xml.ts): `& < >` only. */
|
|
50
55
|
export function escapeXml(s: string): string {
|
|
@@ -91,22 +96,51 @@ export function announceLocalCommand(pi: ReminderEmitter, command: LocalCommand)
|
|
|
91
96
|
|
|
92
97
|
type CommandOptions = { handler: (args: string, ctx: any) => Promise<void> | void } & Record<string, unknown>;
|
|
93
98
|
|
|
99
|
+
/**
|
|
100
|
+
* What a local command passes: pi's options, plus `reportsResult` when its
|
|
101
|
+
* handler returns the stdout (a returned string is never silently dropped).
|
|
102
|
+
*/
|
|
103
|
+
type LocalCommandOptions<O extends CommandOptions> = Omit<O, "handler"> & { argumentHint?: string } & (
|
|
104
|
+
| { reportsResult: true; handler: (args: string, ctx: Parameters<O["handler"]>[1]) => Promise<string | void> | string | void }
|
|
105
|
+
| { reportsResult?: false; handler: (args: string, ctx: Parameters<O["handler"]>[1]) => Promise<void> | void }
|
|
106
|
+
);
|
|
107
|
+
|
|
94
108
|
/**
|
|
95
109
|
* `pi.registerCommand` for a One Code local command: the breadcrumb is
|
|
96
|
-
* announced before the handler runs, so no command can forget it.
|
|
110
|
+
* announced before the handler runs, so no command can forget it. With
|
|
111
|
+
* `reportsResult`, it is announced once the handler settles instead, its
|
|
112
|
+
* stdout the string the handler returned (CC's `onDone(result)`; "" for none
|
|
113
|
+
* or a throw). Only a command that never starts a turn of its own may opt in:
|
|
114
|
+
* a breadcrumb queued after `sendUserMessage` would miss that message. `/clear`
|
|
97
115
|
* is the one exception — it announces from the NEW session's `session_start`
|
|
98
|
-
* (see clear/index.ts) and registers plainly.
|
|
116
|
+
* (see clear/index.ts) and registers plainly. `argumentHint` is the prompt's
|
|
117
|
+
* dim placeholder for the command's argument (lib/argument-hints.ts). Its
|
|
118
|
+
* completions leave out an exact match, so a fully typed argument runs on the
|
|
119
|
+
* first Enter (`withoutExactMatch`).
|
|
99
120
|
*/
|
|
100
121
|
export function registerLocalCommand<O extends CommandOptions>(
|
|
101
|
-
pi: ReminderEmitter & { registerCommand(name: string, options: O): void },
|
|
122
|
+
pi: ReminderEmitter & HintEmitter & { registerCommand(name: string, options: O): void },
|
|
102
123
|
name: string,
|
|
103
|
-
options: O
|
|
124
|
+
options: LocalCommandOptions<O>,
|
|
104
125
|
): void {
|
|
126
|
+
const { argumentHint, reportsResult, ...rest } = options;
|
|
127
|
+
if (argumentHint) announceArgumentHint(pi, name, argumentHint);
|
|
128
|
+
const complete = (rest as { getArgumentCompletions?: Parameters<typeof withoutExactMatch>[0] }).getArgumentCompletions;
|
|
105
129
|
pi.registerCommand(name, {
|
|
106
|
-
...
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
...(rest as unknown as O),
|
|
131
|
+
...(complete ? { getArgumentCompletions: withoutExactMatch(complete) } : {}),
|
|
132
|
+
handler: async (args: string, ctx: Parameters<O["handler"]>[1]) => {
|
|
133
|
+
if (!reportsResult) {
|
|
134
|
+
announceLocalCommand(pi, { name, args });
|
|
135
|
+
await options.handler(args, ctx);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let stdout = "";
|
|
139
|
+
try {
|
|
140
|
+
stdout = (await options.handler(args, ctx)) ?? "";
|
|
141
|
+
} finally {
|
|
142
|
+
announceLocalCommand(pi, { name, args, stdout });
|
|
143
|
+
}
|
|
110
144
|
},
|
|
111
145
|
});
|
|
112
146
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The /mcp panel's Disable/Enable action persists across sessions here rather
|
|
5
5
|
* than by mutating the user's `~/.claude.json` or a plugin's `.mcp.json`: those
|
|
6
6
|
* are Claude Code's / a plugin's config, which One Code only ever reads (the
|
|
7
|
-
* "own state, borrowed config" rule — docs/decisions/memory-state.md). A
|
|
7
|
+
* "own state, borrowed config" rule — working-docs/decisions/memory-state.md). A
|
|
8
8
|
* disabled server is still discovered and listed; the mcp extension just skips
|
|
9
9
|
* connecting it, and the panel offers Enable to bring it back.
|
|
10
10
|
*
|
|
@@ -23,3 +23,49 @@ export interface McpToolsPayload {
|
|
|
23
23
|
*/
|
|
24
24
|
settled?: boolean;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
/** How long a child spawned while MCP is still connecting waits for the settled set. */
|
|
28
|
+
export const MCP_SETTLE_CAP_MS = 10_000;
|
|
29
|
+
|
|
30
|
+
/** The events slice `watchMcpTools` needs. */
|
|
31
|
+
export interface McpToolsEvents {
|
|
32
|
+
on(channel: string, handler: (data: unknown) => void): unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Track the parent's live MCP tool definitions for in-process children (the
|
|
37
|
+
* subagent runner and the workflow runner). Subscribe at extension load; the
|
|
38
|
+
* getter resolves to the current set. The parent's MCP connect runs in the
|
|
39
|
+
* background, so a child spawned in the first seconds of a session waits for
|
|
40
|
+
* the settled publish, capped so a hung server cannot stall spawns, instead of
|
|
41
|
+
* baking in a still-connecting (possibly empty) snapshot. Empty when no MCP
|
|
42
|
+
* servers are configured.
|
|
43
|
+
*/
|
|
44
|
+
export function watchMcpTools(events: McpToolsEvents, settleCapMs = MCP_SETTLE_CAP_MS): () => Promise<ToolDefinition[]> {
|
|
45
|
+
let tools: ToolDefinition[] = [];
|
|
46
|
+
let settled = false;
|
|
47
|
+
let resolveSettled: (() => void) | undefined;
|
|
48
|
+
const settledPromise = new Promise<void>((resolve) => {
|
|
49
|
+
resolveSettled = resolve;
|
|
50
|
+
});
|
|
51
|
+
events.on(MCP_TOOLS_CHANNEL, (data) => {
|
|
52
|
+
const payload = data as McpToolsPayload | undefined;
|
|
53
|
+
tools = payload?.tools ?? [];
|
|
54
|
+
if (payload?.settled) {
|
|
55
|
+
settled = true;
|
|
56
|
+
resolveSettled?.();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return async () => {
|
|
60
|
+
if (!settled) {
|
|
61
|
+
await Promise.race([
|
|
62
|
+
settledPromise,
|
|
63
|
+
new Promise<void>((resolve) => {
|
|
64
|
+
const timer = setTimeout(resolve, settleCapMs);
|
|
65
|
+
timer.unref?.();
|
|
66
|
+
}),
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
return tools;
|
|
70
|
+
};
|
|
71
|
+
}
|