@dungle-scrubs/tallow 0.9.4 → 0.9.7

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.
Files changed (212) hide show
  1. package/dist/cli.js +8 -5
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config.d.ts +1 -1
  4. package/dist/config.js +1 -1
  5. package/dist/interactive-mode-patch.d.ts +24 -12
  6. package/dist/interactive-mode-patch.d.ts.map +1 -1
  7. package/dist/interactive-mode-patch.js +229 -146
  8. package/dist/interactive-mode-patch.js.map +1 -1
  9. package/dist/interactive-reset.d.ts +49 -0
  10. package/dist/interactive-reset.d.ts.map +1 -0
  11. package/dist/interactive-reset.js +40 -0
  12. package/dist/interactive-reset.js.map +1 -0
  13. package/dist/pi-tui-editor-patch.d.ts +10 -0
  14. package/dist/pi-tui-editor-patch.d.ts.map +1 -0
  15. package/dist/pi-tui-editor-patch.js +159 -0
  16. package/dist/pi-tui-editor-patch.js.map +1 -0
  17. package/dist/pi-tui-patch.d.ts +2 -0
  18. package/dist/pi-tui-patch.d.ts.map +1 -0
  19. package/dist/pi-tui-patch.js +563 -0
  20. package/dist/pi-tui-patch.js.map +1 -0
  21. package/dist/pi-tui-settings-list-patch.d.ts +11 -0
  22. package/dist/pi-tui-settings-list-patch.d.ts.map +1 -0
  23. package/dist/pi-tui-settings-list-patch.js +38 -0
  24. package/dist/pi-tui-settings-list-patch.js.map +1 -0
  25. package/dist/process-cleanup.js +1 -1
  26. package/dist/process-cleanup.js.map +1 -1
  27. package/dist/reset-diagnostics.d.ts +69 -0
  28. package/dist/reset-diagnostics.d.ts.map +1 -0
  29. package/dist/reset-diagnostics.js +41 -0
  30. package/dist/reset-diagnostics.js.map +1 -0
  31. package/dist/sdk.d.ts +7 -23
  32. package/dist/sdk.d.ts.map +1 -1
  33. package/dist/sdk.js +211 -174
  34. package/dist/sdk.js.map +1 -1
  35. package/dist/workspace-transition-interactive.d.ts +1 -0
  36. package/dist/workspace-transition-interactive.d.ts.map +1 -1
  37. package/dist/workspace-transition-interactive.js +8 -18
  38. package/dist/workspace-transition-interactive.js.map +1 -1
  39. package/extensions/__integration__/audit-findings.test.ts +4 -5
  40. package/extensions/_icons/index.ts +2 -4
  41. package/extensions/_shared/__tests__/image-metadata.test.ts +33 -0
  42. package/extensions/_shared/__tests__/shell-policy.test.ts +19 -0
  43. package/extensions/_shared/__tests__/terminal-links.test.ts +18 -0
  44. package/extensions/_shared/image-metadata.ts +99 -0
  45. package/extensions/_shared/inline-preview.ts +1 -1
  46. package/extensions/_shared/shell-policy.ts +121 -1
  47. package/extensions/_shared/terminal-links.ts +22 -0
  48. package/extensions/ask-user-question-tool/index.ts +0 -3
  49. package/extensions/clear/__tests__/clear.test.ts +269 -2
  50. package/extensions/command-expansion/index.ts +9 -3
  51. package/extensions/context-files/index.ts +5 -1
  52. package/extensions/context-fork/__tests__/context-fork.test.ts +94 -1
  53. package/extensions/context-fork/extension.json +1 -1
  54. package/extensions/context-fork/frontmatter-index.ts +6 -1
  55. package/extensions/context-fork/index.ts +32 -0
  56. package/extensions/edit-tool-enhanced/index.ts +2 -1
  57. package/extensions/git-status/__tests__/git-status.test.ts +65 -2
  58. package/extensions/git-status/index.ts +268 -98
  59. package/extensions/hooks/index.ts +33 -11
  60. package/extensions/loop/index.ts +14 -1
  61. package/extensions/lsp/index.ts +64 -13
  62. package/extensions/lsp/package.json +2 -2
  63. package/extensions/minimal-skill-display/index.ts +7 -1
  64. package/extensions/random-spinner/index.ts +7 -642
  65. package/extensions/read-tool-enhanced/index.ts +13 -10
  66. package/extensions/render-stabilizer/__tests__/render-stabilizer.test.ts +2 -3
  67. package/extensions/render-stabilizer/index.ts +6 -6
  68. package/extensions/rewind/__tests__/session-files.test.ts +115 -0
  69. package/extensions/rewind/__tests__/snapshots.test.ts +23 -0
  70. package/extensions/rewind/index.ts +5 -0
  71. package/extensions/rewind/session-files.ts +138 -0
  72. package/extensions/rewind/snapshots.ts +104 -5
  73. package/extensions/skill-commands/index.ts +6 -1
  74. package/extensions/slash-command-bridge/__tests__/slash-command-bridge.test.ts +26 -0
  75. package/extensions/slash-command-bridge/index.ts +14 -2
  76. package/extensions/subagent-tool/model-resolver.ts +274 -7
  77. package/extensions/subagent-tool/schema.ts +1 -2
  78. package/extensions/tasks/commands/register-tasks-extension.ts +9 -9
  79. package/extensions/teams-tool/tools/register-extension.ts +1 -3
  80. package/extensions/teams-tool/tools/teammate-tools.ts +1 -2
  81. package/extensions/web-search-tool/index.ts +2 -1
  82. package/extensions/wezterm-pane-control/index.ts +1 -2
  83. package/extensions/write-tool-enhanced/index.ts +2 -1
  84. package/node_modules/@mariozechner/pi-tui/README.md +56 -34
  85. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts +18 -13
  86. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.d.ts.map +1 -1
  87. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js +182 -113
  88. package/node_modules/@mariozechner/pi-tui/dist/autocomplete.js.map +1 -1
  89. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js +3 -3
  90. package/node_modules/@mariozechner/pi-tui/dist/components/cancellable-loader.js.map +1 -1
  91. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts +45 -36
  92. package/node_modules/@mariozechner/pi-tui/dist/components/editor.d.ts.map +1 -1
  93. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js +489 -325
  94. package/node_modules/@mariozechner/pi-tui/dist/components/editor.js.map +1 -1
  95. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts +1 -99
  96. package/node_modules/@mariozechner/pi-tui/dist/components/image.d.ts.map +1 -1
  97. package/node_modules/@mariozechner/pi-tui/dist/components/image.js +17 -192
  98. package/node_modules/@mariozechner/pi-tui/dist/components/image.js.map +1 -1
  99. package/node_modules/@mariozechner/pi-tui/dist/components/input.d.ts.map +1 -1
  100. package/node_modules/@mariozechner/pi-tui/dist/components/input.js +57 -60
  101. package/node_modules/@mariozechner/pi-tui/dist/components/input.js.map +1 -1
  102. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts +2 -69
  103. package/node_modules/@mariozechner/pi-tui/dist/components/loader.d.ts.map +1 -1
  104. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js +5 -102
  105. package/node_modules/@mariozechner/pi-tui/dist/components/loader.js.map +1 -1
  106. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.d.ts.map +1 -1
  107. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js +111 -53
  108. package/node_modules/@mariozechner/pi-tui/dist/components/markdown.js.map +1 -1
  109. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts +19 -1
  110. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.d.ts.map +1 -1
  111. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js +78 -67
  112. package/node_modules/@mariozechner/pi-tui/dist/components/select-list.js.map +1 -1
  113. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts +1 -25
  114. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.d.ts.map +1 -1
  115. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js +13 -50
  116. package/node_modules/@mariozechner/pi-tui/dist/components/settings-list.js.map +1 -1
  117. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts +8 -10
  118. package/node_modules/@mariozechner/pi-tui/dist/index.d.ts.map +1 -1
  119. package/node_modules/@mariozechner/pi-tui/dist/index.js +6 -9
  120. package/node_modules/@mariozechner/pi-tui/dist/index.js.map +1 -1
  121. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts +108 -238
  122. package/node_modules/@mariozechner/pi-tui/dist/keybindings.d.ts.map +1 -1
  123. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js +108 -365
  124. package/node_modules/@mariozechner/pi-tui/dist/keybindings.js.map +1 -1
  125. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts +33 -48
  126. package/node_modules/@mariozechner/pi-tui/dist/keys.d.ts.map +1 -1
  127. package/node_modules/@mariozechner/pi-tui/dist/keys.js +239 -155
  128. package/node_modules/@mariozechner/pi-tui/dist/keys.js.map +1 -1
  129. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts +14 -94
  130. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.d.ts.map +1 -1
  131. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js +44 -186
  132. package/node_modules/@mariozechner/pi-tui/dist/terminal-image.js.map +1 -1
  133. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts +13 -58
  134. package/node_modules/@mariozechner/pi-tui/dist/terminal.d.ts.map +1 -1
  135. package/node_modules/@mariozechner/pi-tui/dist/terminal.js +78 -111
  136. package/node_modules/@mariozechner/pi-tui/dist/terminal.js.map +1 -1
  137. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts +24 -110
  138. package/node_modules/@mariozechner/pi-tui/dist/tui.d.ts.map +1 -1
  139. package/node_modules/@mariozechner/pi-tui/dist/tui.js +188 -435
  140. package/node_modules/@mariozechner/pi-tui/dist/tui.js.map +1 -1
  141. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts +0 -18
  142. package/node_modules/@mariozechner/pi-tui/dist/utils.d.ts.map +1 -1
  143. package/node_modules/@mariozechner/pi-tui/dist/utils.js +251 -119
  144. package/node_modules/@mariozechner/pi-tui/dist/utils.js.map +1 -1
  145. package/node_modules/@mariozechner/pi-tui/package.json +6 -6
  146. package/node_modules/@mariozechner/pi-tui/src/__tests__/__snapshots__/render.test.ts.snap +3 -40
  147. package/node_modules/@mariozechner/pi-tui/src/__tests__/image-component.test.ts +71 -81
  148. package/node_modules/@mariozechner/pi-tui/src/__tests__/render.test.ts +0 -33
  149. package/node_modules/@mariozechner/pi-tui/src/__tests__/terminal-image.test.ts +93 -334
  150. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-render-scheduling.test.ts +1 -1
  151. package/node_modules/@mariozechner/pi-tui/src/__tests__/utils.test.ts +11 -196
  152. package/node_modules/@mariozechner/pi-tui/src/autocomplete.ts +228 -142
  153. package/node_modules/@mariozechner/pi-tui/src/components/cancellable-loader.ts +3 -3
  154. package/node_modules/@mariozechner/pi-tui/src/components/editor.ts +624 -390
  155. package/node_modules/@mariozechner/pi-tui/src/components/image.ts +17 -227
  156. package/node_modules/@mariozechner/pi-tui/src/components/input.ts +71 -63
  157. package/node_modules/@mariozechner/pi-tui/src/components/loader.ts +5 -137
  158. package/node_modules/@mariozechner/pi-tui/src/components/markdown.ts +143 -52
  159. package/node_modules/@mariozechner/pi-tui/src/components/select-list.ts +136 -70
  160. package/node_modules/@mariozechner/pi-tui/src/components/settings-list.ts +12 -51
  161. package/node_modules/@mariozechner/pi-tui/src/index.ts +17 -36
  162. package/node_modules/@mariozechner/pi-tui/src/keybindings.ts +148 -421
  163. package/node_modules/@mariozechner/pi-tui/src/keys.ts +253 -181
  164. package/node_modules/@mariozechner/pi-tui/src/terminal-image.ts +51 -252
  165. package/node_modules/@mariozechner/pi-tui/src/terminal.ts +78 -133
  166. package/node_modules/@mariozechner/pi-tui/src/tui.ts +202 -478
  167. package/node_modules/@mariozechner/pi-tui/src/utils.ts +289 -125
  168. package/node_modules/@mariozechner/pi-tui/tsconfig.build.json +1 -0
  169. package/package.json +13 -13
  170. package/packages/tallow-tui/node_modules/@types/mime-types/README.md +8 -2
  171. package/packages/tallow-tui/node_modules/@types/mime-types/index.d.ts +6 -0
  172. package/packages/tallow-tui/node_modules/@types/mime-types/package.json +9 -3
  173. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup-data.js +18 -0
  174. package/packages/tallow-tui/node_modules/get-east-asian-width/lookup.js +116 -384
  175. package/packages/tallow-tui/node_modules/get-east-asian-width/package.json +5 -4
  176. package/packages/tallow-tui/node_modules/get-east-asian-width/utilities.js +24 -0
  177. package/packages/tallow-tui/node_modules/marked/README.md +5 -4
  178. package/packages/tallow-tui/node_modules/marked/bin/main.js +10 -8
  179. package/packages/tallow-tui/node_modules/marked/bin/marked.js +2 -1
  180. package/packages/tallow-tui/node_modules/marked/lib/marked.d.ts +156 -125
  181. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js +67 -2179
  182. package/packages/tallow-tui/node_modules/marked/lib/marked.esm.js.map +3 -3
  183. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js +67 -2201
  184. package/packages/tallow-tui/node_modules/marked/lib/marked.umd.js.map +3 -3
  185. package/packages/tallow-tui/node_modules/marked/man/marked.1 +4 -2
  186. package/packages/tallow-tui/node_modules/marked/man/marked.1.md +2 -1
  187. package/packages/tallow-tui/node_modules/marked/package.json +26 -34
  188. package/skills/tallow-expert/SKILL.md +3 -5
  189. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts +0 -32
  190. package/node_modules/@mariozechner/pi-tui/dist/border-styles.d.ts.map +0 -1
  191. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js +0 -46
  192. package/node_modules/@mariozechner/pi-tui/dist/border-styles.js.map +0 -1
  193. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts +0 -52
  194. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.d.ts.map +0 -1
  195. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js +0 -89
  196. package/node_modules/@mariozechner/pi-tui/dist/components/bordered-box.js.map +0 -1
  197. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts +0 -14
  198. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.d.ts.map +0 -1
  199. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js +0 -55
  200. package/node_modules/@mariozechner/pi-tui/dist/test-utils/capability-env.js.map +0 -1
  201. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-change-listener.test.ts +0 -121
  202. package/node_modules/@mariozechner/pi-tui/src/__tests__/editor-ghost-text.test.ts +0 -112
  203. package/node_modules/@mariozechner/pi-tui/src/__tests__/mouse-events.test.ts +0 -134
  204. package/node_modules/@mariozechner/pi-tui/src/__tests__/settings-list.test.ts +0 -81
  205. package/node_modules/@mariozechner/pi-tui/src/__tests__/tui-diff-regression.test.ts +0 -555
  206. package/node_modules/@mariozechner/pi-tui/src/border-styles.ts +0 -60
  207. package/node_modules/@mariozechner/pi-tui/src/components/bordered-box.ts +0 -113
  208. package/node_modules/@mariozechner/pi-tui/src/test-utils/capability-env.ts +0 -56
  209. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs +0 -2211
  210. package/packages/tallow-tui/node_modules/marked/lib/marked.cjs.map +0 -7
  211. package/packages/tallow-tui/node_modules/marked/lib/marked.d.cts +0 -728
  212. package/packages/tallow-tui/node_modules/marked/marked.min.js +0 -69
@@ -71,6 +71,8 @@ type Letter =
71
71
  | "y"
72
72
  | "z";
73
73
 
74
+ type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
75
+
74
76
  type SymbolKey =
75
77
  | "`"
76
78
  | "-"
@@ -136,29 +138,20 @@ type SpecialKey =
136
138
  | "f11"
137
139
  | "f12";
138
140
 
139
- type BaseKey = Letter | SymbolKey | SpecialKey;
141
+ type BaseKey = Letter | Digit | SymbolKey | SpecialKey;
142
+ type ModifierName = "ctrl" | "shift" | "alt" | "super";
143
+
144
+ type ModifiedKeyId<Key extends string, RemainingModifiers extends ModifierName = ModifierName> = {
145
+ [M in RemainingModifiers]:
146
+ | `${M}+${Key}`
147
+ | `${M}+${ModifiedKeyId<Key, Exclude<RemainingModifiers, M>>}`;
148
+ }[RemainingModifiers];
140
149
 
141
150
  /**
142
151
  * Union type of all valid key identifiers.
143
152
  * Provides autocomplete and catches typos at compile time.
144
153
  */
145
- export type KeyId =
146
- | BaseKey
147
- | `ctrl+${BaseKey}`
148
- | `shift+${BaseKey}`
149
- | `alt+${BaseKey}`
150
- | `ctrl+shift+${BaseKey}`
151
- | `shift+ctrl+${BaseKey}`
152
- | `ctrl+alt+${BaseKey}`
153
- | `alt+ctrl+${BaseKey}`
154
- | `shift+alt+${BaseKey}`
155
- | `alt+shift+${BaseKey}`
156
- | `ctrl+shift+alt+${BaseKey}`
157
- | `ctrl+alt+shift+${BaseKey}`
158
- | `shift+ctrl+alt+${BaseKey}`
159
- | `shift+alt+ctrl+${BaseKey}`
160
- | `alt+ctrl+shift+${BaseKey}`
161
- | `alt+shift+ctrl+${BaseKey}`;
154
+ export type KeyId = BaseKey | ModifiedKeyId<BaseKey>;
162
155
 
163
156
  /**
164
157
  * Helper object for creating typed key identifiers with autocomplete.
@@ -166,8 +159,8 @@ export type KeyId =
166
159
  * Usage:
167
160
  * - Key.escape, Key.enter, Key.tab, etc. for special keys
168
161
  * - Key.backtick, Key.comma, Key.period, etc. for symbol keys
169
- * - Key.ctrl("c"), Key.alt("x") for single modifier
170
- * - Key.ctrlShift("p"), Key.ctrlAlt("x") for combined modifiers
162
+ * - Key.ctrl("c"), Key.alt("x"), Key.super("k") for single modifiers
163
+ * - Key.ctrlShift("p"), Key.ctrlAlt("x"), Key.ctrlSuper("k") for combined modifiers
171
164
  */
172
165
  export const Key = {
173
166
  // Special keys
@@ -239,6 +232,7 @@ export const Key = {
239
232
  ctrl: <K extends BaseKey>(key: K): `ctrl+${K}` => `ctrl+${key}`,
240
233
  shift: <K extends BaseKey>(key: K): `shift+${K}` => `shift+${key}`,
241
234
  alt: <K extends BaseKey>(key: K): `alt+${K}` => `alt+${key}`,
235
+ super: <K extends BaseKey>(key: K): `super+${K}` => `super+${key}`,
242
236
 
243
237
  // Combined modifiers
244
238
  ctrlShift: <K extends BaseKey>(key: K): `ctrl+shift+${K}` => `ctrl+shift+${key}`,
@@ -247,9 +241,16 @@ export const Key = {
247
241
  altCtrl: <K extends BaseKey>(key: K): `alt+ctrl+${K}` => `alt+ctrl+${key}`,
248
242
  shiftAlt: <K extends BaseKey>(key: K): `shift+alt+${K}` => `shift+alt+${key}`,
249
243
  altShift: <K extends BaseKey>(key: K): `alt+shift+${K}` => `alt+shift+${key}`,
244
+ ctrlSuper: <K extends BaseKey>(key: K): `ctrl+super+${K}` => `ctrl+super+${key}`,
245
+ superCtrl: <K extends BaseKey>(key: K): `super+ctrl+${K}` => `super+ctrl+${key}`,
246
+ shiftSuper: <K extends BaseKey>(key: K): `shift+super+${K}` => `shift+super+${key}`,
247
+ superShift: <K extends BaseKey>(key: K): `super+shift+${K}` => `super+shift+${key}`,
248
+ altSuper: <K extends BaseKey>(key: K): `alt+super+${K}` => `alt+super+${key}`,
249
+ superAlt: <K extends BaseKey>(key: K): `super+alt+${K}` => `super+alt+${key}`,
250
250
 
251
251
  // Triple modifiers
252
252
  ctrlShiftAlt: <K extends BaseKey>(key: K): `ctrl+shift+alt+${K}` => `ctrl+shift+alt+${key}`,
253
+ ctrlShiftSuper: <K extends BaseKey>(key: K): `ctrl+shift+super+${K}` => `ctrl+shift+super+${key}`,
253
254
  } as const;
254
255
 
255
256
  // =============================================================================
@@ -294,6 +295,7 @@ const MODIFIERS = {
294
295
  shift: 1,
295
296
  alt: 2,
296
297
  ctrl: 4,
298
+ super: 8,
297
299
  } as const;
298
300
 
299
301
  const LOCK_MASK = 64 + 128; // Caps Lock + Num Lock
@@ -323,6 +325,40 @@ const FUNCTIONAL_CODEPOINTS = {
323
325
  end: -15,
324
326
  } as const;
325
327
 
328
+ const KITTY_FUNCTIONAL_KEY_EQUIVALENTS = new Map<number, number>([
329
+ [57399, 48], // KP_0 -> 0
330
+ [57400, 49], // KP_1 -> 1
331
+ [57401, 50], // KP_2 -> 2
332
+ [57402, 51], // KP_3 -> 3
333
+ [57403, 52], // KP_4 -> 4
334
+ [57404, 53], // KP_5 -> 5
335
+ [57405, 54], // KP_6 -> 6
336
+ [57406, 55], // KP_7 -> 7
337
+ [57407, 56], // KP_8 -> 8
338
+ [57408, 57], // KP_9 -> 9
339
+ [57409, 46], // KP_DECIMAL -> .
340
+ [57410, 47], // KP_DIVIDE -> /
341
+ [57411, 42], // KP_MULTIPLY -> *
342
+ [57412, 45], // KP_SUBTRACT -> -
343
+ [57413, 43], // KP_ADD -> +
344
+ [57415, 61], // KP_EQUAL -> =
345
+ [57416, 44], // KP_SEPARATOR -> ,
346
+ [57417, ARROW_CODEPOINTS.left],
347
+ [57418, ARROW_CODEPOINTS.right],
348
+ [57419, ARROW_CODEPOINTS.up],
349
+ [57420, ARROW_CODEPOINTS.down],
350
+ [57421, FUNCTIONAL_CODEPOINTS.pageUp],
351
+ [57422, FUNCTIONAL_CODEPOINTS.pageDown],
352
+ [57423, FUNCTIONAL_CODEPOINTS.home],
353
+ [57424, FUNCTIONAL_CODEPOINTS.end],
354
+ [57425, FUNCTIONAL_CODEPOINTS.insert],
355
+ [57426, FUNCTIONAL_CODEPOINTS.delete],
356
+ ]);
357
+
358
+ function normalizeKittyFunctionalCodepoint(codepoint: number): number {
359
+ return KITTY_FUNCTIONAL_KEY_EQUIVALENTS.get(codepoint) ?? codepoint;
360
+ }
361
+
326
362
  const LEGACY_KEY_SEQUENCES = {
327
363
  up: ["\x1b[A", "\x1bOA"],
328
364
  down: ["\x1b[B", "\x1bOB"],
@@ -475,16 +511,17 @@ interface ParsedKittySequence {
475
511
  eventType: KeyEventType;
476
512
  }
477
513
 
478
- // Tracks the event type from the most recent parseKey() call.
514
+ interface ParsedModifyOtherKeysSequence {
515
+ codepoint: number;
516
+ modifier: number;
517
+ }
518
+
519
+ // Store the last parsed event type for isKeyRelease() to query
479
520
  let _lastEventType: KeyEventType = "press";
480
521
 
481
522
  /**
482
- * Check if the given input data represents a key release event.
483
- * Performs fast pattern matching on raw terminal escape sequences.
523
+ * Check if the last parsed key event was a key release.
484
524
  * Only meaningful when Kitty keyboard protocol with flag 2 is active.
485
- *
486
- * @param data - Raw terminal input string
487
- * @returns true if the data contains a key release escape sequence
488
525
  */
489
526
  export function isKeyRelease(data: string): boolean {
490
527
  // Don't treat bracketed paste content as key release, even if it contains
@@ -513,12 +550,8 @@ export function isKeyRelease(data: string): boolean {
513
550
  }
514
551
 
515
552
  /**
516
- * Check if the given input data represents a key repeat event.
517
- * Performs fast pattern matching on raw terminal escape sequences.
553
+ * Check if the last parsed key event was a key repeat.
518
554
  * Only meaningful when Kitty keyboard protocol with flag 2 is active.
519
- *
520
- * @param data - Raw terminal input string
521
- * @returns true if the data contains a key repeat escape sequence
522
555
  */
523
556
  export function isKeyRepeat(data: string): boolean {
524
557
  // Don't treat bracketed paste content as key repeat, even if it contains
@@ -631,8 +664,11 @@ function matchesKittySequence(
631
664
  // Check if modifiers match
632
665
  if (actualMod !== expectedMod) return false;
633
666
 
634
- // Primary match: codepoint matches directly
635
- if (parsed.codepoint === expectedCodepoint) return true;
667
+ const normalizedCodepoint = normalizeKittyFunctionalCodepoint(parsed.codepoint);
668
+ const normalizedExpectedCodepoint = normalizeKittyFunctionalCodepoint(expectedCodepoint);
669
+
670
+ // Primary match: codepoint matches directly after normalizing functional keys
671
+ if (normalizedCodepoint === normalizedExpectedCodepoint) return true;
636
672
 
637
673
  // Alternate match: use base layout key for non-Latin keyboard layouts.
638
674
  // This allows Ctrl+С (Cyrillic) to match Ctrl+c (Latin) when terminal reports
@@ -647,7 +683,7 @@ function matchesKittySequence(
647
683
  // (letter remapping) and Ctrl+/ could falsely match Ctrl+[ (symbol remapping)
648
684
  // if the base layout key were always considered.
649
685
  if (parsed.baseLayoutKey !== undefined && parsed.baseLayoutKey === expectedCodepoint) {
650
- const cp = parsed.codepoint;
686
+ const cp = normalizedCodepoint;
651
687
  const isLatinLetter = cp >= 97 && cp <= 122; // a-z
652
688
  const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(cp));
653
689
  if (!isLatinLetter && !isKnownSymbol) return true;
@@ -656,6 +692,14 @@ function matchesKittySequence(
656
692
  return false;
657
693
  }
658
694
 
695
+ function parseModifyOtherKeysSequence(data: string): ParsedModifyOtherKeysSequence | null {
696
+ const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
697
+ if (!match) return null;
698
+ const modValue = parseInt(match[1]!, 10);
699
+ const codepoint = parseInt(match[2]!, 10);
700
+ return { codepoint, modifier: modValue - 1 };
701
+ }
702
+
659
703
  /**
660
704
  * Match xterm modifyOtherKeys format: CSI 27 ; modifiers ; keycode ~
661
705
  * This is used by terminals when Kitty protocol is not enabled.
@@ -666,21 +710,11 @@ function matchesModifyOtherKeys(
666
710
  expectedKeycode: number,
667
711
  expectedModifier: number
668
712
  ): boolean {
669
- const match = data.match(/^\x1b\[27;(\d+);(\d+)~$/);
670
- if (!match) return false;
671
- const modValue = parseInt(match[1]!, 10);
672
- const keycode = parseInt(match[2]!, 10);
673
- // Convert from 1-indexed xterm format to our 0-indexed format
674
- const actualMod = modValue - 1;
675
- return keycode === expectedKeycode && actualMod === expectedModifier;
713
+ const parsed = parseModifyOtherKeysSequence(data);
714
+ if (!parsed) return false;
715
+ return parsed.codepoint === expectedKeycode && parsed.modifier === expectedModifier;
676
716
  }
677
717
 
678
- /**
679
- * Detect Windows Terminal by session environment variable.
680
- * Excludes SSH sessions where WT_SESSION may leak from the host.
681
- *
682
- * @returns true when running directly inside Windows Terminal
683
- */
684
718
  function isWindowsTerminalSession(): boolean {
685
719
  return (
686
720
  Boolean(process.env.WT_SESSION) &&
@@ -698,10 +732,6 @@ function isWindowsTerminalSession(): boolean {
698
732
  *
699
733
  * Prefer explicit Kitty / CSI-u / modifyOtherKeys sequences whenever they are
700
734
  * available. Fall back to a Windows Terminal heuristic only for raw BS bytes.
701
- *
702
- * @param data - Raw terminal input byte
703
- * @param expectedModifier - Modifier bitmask to match against
704
- * @returns true if the data matches backspace with the given modifier
705
735
  */
706
736
  function matchesRawBackspace(data: string, expectedModifier: number): boolean {
707
737
  if (data === "\x7f") return expectedModifier === 0;
@@ -741,9 +771,34 @@ function rawCtrlChar(key: string): string | null {
741
771
  return null;
742
772
  }
743
773
 
774
+ function isDigitKey(key: string): boolean {
775
+ return key >= "0" && key <= "9";
776
+ }
777
+
778
+ function matchesPrintableModifyOtherKeys(
779
+ data: string,
780
+ expectedKeycode: number,
781
+ expectedModifier: number
782
+ ): boolean {
783
+ if (expectedModifier === 0) return false;
784
+ return matchesModifyOtherKeys(data, expectedKeycode, expectedModifier);
785
+ }
786
+
787
+ function formatKeyNameWithModifiers(keyName: string, modifier: number): string | undefined {
788
+ const mods: string[] = [];
789
+ const effectiveMod = modifier & ~LOCK_MASK;
790
+ const supportedModifierMask = MODIFIERS.shift | MODIFIERS.ctrl | MODIFIERS.alt | MODIFIERS.super;
791
+ if ((effectiveMod & ~supportedModifierMask) !== 0) return undefined;
792
+ if (effectiveMod & MODIFIERS.shift) mods.push("shift");
793
+ if (effectiveMod & MODIFIERS.ctrl) mods.push("ctrl");
794
+ if (effectiveMod & MODIFIERS.alt) mods.push("alt");
795
+ if (effectiveMod & MODIFIERS.super) mods.push("super");
796
+ return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
797
+ }
798
+
744
799
  function parseKeyId(
745
800
  keyId: string
746
- ): { key: string; ctrl: boolean; shift: boolean; alt: boolean } | null {
801
+ ): { key: string; ctrl: boolean; shift: boolean; alt: boolean; super: boolean } | null {
747
802
  const parts = keyId.toLowerCase().split("+");
748
803
  const key = parts[parts.length - 1];
749
804
  if (!key) return null;
@@ -752,6 +807,7 @@ function parseKeyId(
752
807
  ctrl: parts.includes("ctrl"),
753
808
  shift: parts.includes("shift"),
754
809
  alt: parts.includes("alt"),
810
+ super: parts.includes("super"),
755
811
  };
756
812
  }
757
813
 
@@ -764,9 +820,10 @@ function parseKeyId(
764
820
  * - Ctrl combinations: "ctrl+c", "ctrl+z", etc.
765
821
  * - Shift combinations: "shift+tab", "shift+enter"
766
822
  * - Alt combinations: "alt+enter", "alt+backspace"
767
- * - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x"
823
+ * - Super combinations: "super+k", "super+enter"
824
+ * - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x", "ctrl+super+k"
768
825
  *
769
- * Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p")
826
+ * Use the Key helper for autocomplete: Key.ctrl("c"), Key.escape, Key.ctrlShift("p"), Key.super("k")
770
827
  *
771
828
  * @param data - Raw input data from terminal
772
829
  * @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c"))
@@ -775,11 +832,12 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
775
832
  const parsed = parseKeyId(keyId);
776
833
  if (!parsed) return false;
777
834
 
778
- const { key, ctrl, shift, alt } = parsed;
835
+ const { key, ctrl, shift, alt, super: superModifier } = parsed;
779
836
  let modifier = 0;
780
837
  if (shift) modifier |= MODIFIERS.shift;
781
838
  if (alt) modifier |= MODIFIERS.alt;
782
839
  if (ctrl) modifier |= MODIFIERS.ctrl;
840
+ if (superModifier) modifier |= MODIFIERS.super;
783
841
 
784
842
  switch (key) {
785
843
  case "escape":
@@ -793,10 +851,10 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
793
851
 
794
852
  case "space":
795
853
  if (!_kittyProtocolActive) {
796
- if (ctrl && !alt && !shift && data === "\x00") {
854
+ if (modifier === MODIFIERS.ctrl && data === "\x00") {
797
855
  return true;
798
856
  }
799
- if (alt && !ctrl && !shift && data === "\x1b ") {
857
+ if (modifier === MODIFIERS.alt && data === "\x1b ") {
800
858
  return true;
801
859
  }
802
860
  }
@@ -813,8 +871,12 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
813
871
  );
814
872
 
815
873
  case "tab":
816
- if (shift && !ctrl && !alt) {
817
- return data === "\x1b[Z" || matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift);
874
+ if (modifier === MODIFIERS.shift) {
875
+ return (
876
+ data === "\x1b[Z" ||
877
+ matchesKittySequence(data, CODEPOINTS.tab, MODIFIERS.shift) ||
878
+ matchesModifyOtherKeys(data, CODEPOINTS.tab, MODIFIERS.shift)
879
+ );
818
880
  }
819
881
  if (modifier === 0) {
820
882
  return data === "\t" || matchesKittySequence(data, CODEPOINTS.tab, 0);
@@ -826,7 +888,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
826
888
 
827
889
  case "enter":
828
890
  case "return":
829
- if (shift && !ctrl && !alt) {
891
+ if (modifier === MODIFIERS.shift) {
830
892
  // CSI u sequences (standard Kitty protocol)
831
893
  if (
832
894
  matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.shift) ||
@@ -846,7 +908,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
846
908
  }
847
909
  return false;
848
910
  }
849
- if (alt && !ctrl && !shift) {
911
+ if (modifier === MODIFIERS.alt) {
850
912
  // CSI u sequences (standard Kitty protocol)
851
913
  if (
852
914
  matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt) ||
@@ -881,7 +943,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
881
943
  );
882
944
 
883
945
  case "backspace":
884
- if (alt && !ctrl && !shift) {
946
+ if (modifier === MODIFIERS.alt) {
885
947
  if (data === "\x1b\x7f" || data === "\x1b\b") {
886
948
  return true;
887
949
  }
@@ -890,7 +952,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
890
952
  matchesModifyOtherKeys(data, CODEPOINTS.backspace, MODIFIERS.alt)
891
953
  );
892
954
  }
893
- if (ctrl && !alt && !shift) {
955
+ if (modifier === MODIFIERS.ctrl) {
894
956
  // Legacy raw 0x08 is ambiguous: it can be Ctrl+Backspace on Windows
895
957
  // Terminal or plain Backspace on other terminals, while also
896
958
  // overlapping with Ctrl+H.
@@ -991,7 +1053,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
991
1053
  return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.pageDown, modifier);
992
1054
 
993
1055
  case "up":
994
- if (alt && !ctrl && !shift) {
1056
+ if (modifier === MODIFIERS.alt) {
995
1057
  return data === "\x1bp" || matchesKittySequence(data, ARROW_CODEPOINTS.up, MODIFIERS.alt);
996
1058
  }
997
1059
  if (modifier === 0) {
@@ -1006,7 +1068,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
1006
1068
  return matchesKittySequence(data, ARROW_CODEPOINTS.up, modifier);
1007
1069
 
1008
1070
  case "down":
1009
- if (alt && !ctrl && !shift) {
1071
+ if (modifier === MODIFIERS.alt) {
1010
1072
  return data === "\x1bn" || matchesKittySequence(data, ARROW_CODEPOINTS.down, MODIFIERS.alt);
1011
1073
  }
1012
1074
  if (modifier === 0) {
@@ -1021,7 +1083,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
1021
1083
  return matchesKittySequence(data, ARROW_CODEPOINTS.down, modifier);
1022
1084
 
1023
1085
  case "left":
1024
- if (alt && !ctrl && !shift) {
1086
+ if (modifier === MODIFIERS.alt) {
1025
1087
  return (
1026
1088
  data === "\x1b[1;3D" ||
1027
1089
  (!_kittyProtocolActive && data === "\x1bB") ||
@@ -1029,7 +1091,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
1029
1091
  matchesKittySequence(data, ARROW_CODEPOINTS.left, MODIFIERS.alt)
1030
1092
  );
1031
1093
  }
1032
- if (ctrl && !alt && !shift) {
1094
+ if (modifier === MODIFIERS.ctrl) {
1033
1095
  return (
1034
1096
  data === "\x1b[1;5D" ||
1035
1097
  matchesLegacyModifierSequence(data, "left", MODIFIERS.ctrl) ||
@@ -1048,7 +1110,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
1048
1110
  return matchesKittySequence(data, ARROW_CODEPOINTS.left, modifier);
1049
1111
 
1050
1112
  case "right":
1051
- if (alt && !ctrl && !shift) {
1113
+ if (modifier === MODIFIERS.alt) {
1052
1114
  return (
1053
1115
  data === "\x1b[1;3C" ||
1054
1116
  (!_kittyProtocolActive && data === "\x1bF") ||
@@ -1056,7 +1118,7 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
1056
1118
  matchesKittySequence(data, ARROW_CODEPOINTS.right, MODIFIERS.alt)
1057
1119
  );
1058
1120
  }
1059
- if (ctrl && !alt && !shift) {
1121
+ if (modifier === MODIFIERS.ctrl) {
1060
1122
  return (
1061
1123
  data === "\x1b[1;5C" ||
1062
1124
  matchesLegacyModifierSequence(data, "right", MODIFIERS.ctrl) ||
@@ -1094,50 +1156,54 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
1094
1156
  }
1095
1157
  }
1096
1158
 
1097
- // Handle single letter keys (a-z) and some symbols
1098
- if (key.length === 1 && ((key >= "a" && key <= "z") || SYMBOL_KEYS.has(key))) {
1159
+ // Handle single letter/digit keys and symbols
1160
+ if (key.length === 1 && ((key >= "a" && key <= "z") || isDigitKey(key) || SYMBOL_KEYS.has(key))) {
1099
1161
  const codepoint = key.charCodeAt(0);
1100
1162
  const rawCtrl = rawCtrlChar(key);
1101
-
1102
- if (ctrl && alt && !shift && !_kittyProtocolActive && rawCtrl) {
1103
- // Legacy: ctrl+alt+key is ESC followed by the control character
1104
- return data === `\x1b${rawCtrl}`;
1163
+ const isLetter = key >= "a" && key <= "z";
1164
+ const isDigit = isDigitKey(key);
1165
+
1166
+ if (modifier === MODIFIERS.ctrl + MODIFIERS.alt && !_kittyProtocolActive && rawCtrl) {
1167
+ // Legacy: ctrl+alt+key is ESC followed by the control character.
1168
+ // If that legacy form does not match, continue so CSI-u and
1169
+ // modifyOtherKeys sequences from tmux can still be recognized.
1170
+ if (data === `\x1b${rawCtrl}`) return true;
1105
1171
  }
1106
1172
 
1107
- if (alt && !ctrl && !shift && !_kittyProtocolActive && key >= "a" && key <= "z") {
1108
- // Legacy: alt+letter is ESC followed by the letter
1173
+ if (modifier === MODIFIERS.alt && !_kittyProtocolActive && (isLetter || isDigit)) {
1174
+ // Legacy: alt+letter/digit is ESC followed by the key
1109
1175
  if (data === `\x1b${key}`) return true;
1110
1176
  }
1111
1177
 
1112
- if (ctrl && !shift && !alt) {
1178
+ if (modifier === MODIFIERS.ctrl) {
1113
1179
  // Legacy: ctrl+key sends the control character
1114
1180
  if (rawCtrl && data === rawCtrl) return true;
1115
1181
  return (
1116
1182
  matchesKittySequence(data, codepoint, MODIFIERS.ctrl) ||
1117
- matchesModifyOtherKeys(data, codepoint, MODIFIERS.ctrl)
1183
+ matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.ctrl)
1118
1184
  );
1119
1185
  }
1120
1186
 
1121
- if (ctrl && shift && !alt) {
1187
+ if (modifier === MODIFIERS.shift + MODIFIERS.ctrl) {
1122
1188
  return (
1123
1189
  matchesKittySequence(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl) ||
1124
- matchesModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl)
1190
+ matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift + MODIFIERS.ctrl)
1125
1191
  );
1126
1192
  }
1127
1193
 
1128
- if (shift && !ctrl && !alt) {
1194
+ if (modifier === MODIFIERS.shift) {
1129
1195
  // Legacy: shift+letter produces uppercase
1130
- if (data === key.toUpperCase()) return true;
1196
+ if (isLetter && data === key.toUpperCase()) return true;
1131
1197
  return (
1132
1198
  matchesKittySequence(data, codepoint, MODIFIERS.shift) ||
1133
- matchesModifyOtherKeys(data, codepoint, MODIFIERS.shift)
1199
+ matchesPrintableModifyOtherKeys(data, codepoint, MODIFIERS.shift)
1134
1200
  );
1135
1201
  }
1136
1202
 
1137
1203
  if (modifier !== 0) {
1138
1204
  return (
1139
1205
  matchesKittySequence(data, codepoint, modifier) ||
1140
- matchesModifyOtherKeys(data, codepoint, modifier)
1206
+ matchesPrintableModifyOtherKeys(data, codepoint, modifier)
1141
1207
  );
1142
1208
  }
1143
1209
 
@@ -1154,51 +1220,63 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
1154
1220
  * @param data - Raw input data from terminal
1155
1221
  * @returns Key identifier string (e.g., "ctrl+c") or undefined
1156
1222
  */
1223
+ function formatParsedKey(
1224
+ codepoint: number,
1225
+ modifier: number,
1226
+ baseLayoutKey?: number
1227
+ ): string | undefined {
1228
+ const normalizedCodepoint = normalizeKittyFunctionalCodepoint(codepoint);
1229
+
1230
+ // Use base layout key only when codepoint is not a recognized Latin
1231
+ // letter (a-z), digit (0-9), or symbol (/, -, [, ;, etc.). For those,
1232
+ // the codepoint is authoritative regardless of physical key position.
1233
+ // This prevents remapped layouts (Dvorak, Colemak, xremap, etc.) from
1234
+ // reporting the wrong key name based on the QWERTY physical position.
1235
+ const isLatinLetter = normalizedCodepoint >= 97 && normalizedCodepoint <= 122; // a-z
1236
+ const isDigit = normalizedCodepoint >= 48 && normalizedCodepoint <= 57; // 0-9
1237
+ const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(normalizedCodepoint));
1238
+ const effectiveCodepoint =
1239
+ isLatinLetter || isDigit || isKnownSymbol
1240
+ ? normalizedCodepoint
1241
+ : (baseLayoutKey ?? normalizedCodepoint);
1242
+
1243
+ let keyName: string | undefined;
1244
+ if (effectiveCodepoint === CODEPOINTS.escape) keyName = "escape";
1245
+ else if (effectiveCodepoint === CODEPOINTS.tab) keyName = "tab";
1246
+ else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
1247
+ keyName = "enter";
1248
+ else if (effectiveCodepoint === CODEPOINTS.space) keyName = "space";
1249
+ else if (effectiveCodepoint === CODEPOINTS.backspace) keyName = "backspace";
1250
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = "delete";
1251
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert) keyName = "insert";
1252
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home) keyName = "home";
1253
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end) keyName = "end";
1254
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp) keyName = "pageUp";
1255
+ else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown) keyName = "pageDown";
1256
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.up) keyName = "up";
1257
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.down) keyName = "down";
1258
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.left) keyName = "left";
1259
+ else if (effectiveCodepoint === ARROW_CODEPOINTS.right) keyName = "right";
1260
+ else if (effectiveCodepoint >= 48 && effectiveCodepoint <= 57)
1261
+ keyName = String.fromCharCode(effectiveCodepoint);
1262
+ else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
1263
+ keyName = String.fromCharCode(effectiveCodepoint);
1264
+ else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
1265
+ keyName = String.fromCharCode(effectiveCodepoint);
1266
+
1267
+ if (!keyName) return undefined;
1268
+ return formatKeyNameWithModifiers(keyName, modifier);
1269
+ }
1270
+
1157
1271
  export function parseKey(data: string): string | undefined {
1158
1272
  const kitty = parseKittySequence(data);
1159
1273
  if (kitty) {
1160
- const { codepoint, baseLayoutKey, modifier } = kitty;
1161
- const mods: string[] = [];
1162
- const effectiveMod = modifier & ~LOCK_MASK;
1163
- if (effectiveMod & MODIFIERS.shift) mods.push("shift");
1164
- if (effectiveMod & MODIFIERS.ctrl) mods.push("ctrl");
1165
- if (effectiveMod & MODIFIERS.alt) mods.push("alt");
1166
-
1167
- // Use base layout key only when codepoint is not a recognized Latin
1168
- // letter (a-z) or symbol (/, -, [, ;, etc.). For those, the codepoint
1169
- // is authoritative regardless of physical key position. This prevents
1170
- // remapped layouts (Dvorak, Colemak, xremap, etc.) from reporting the
1171
- // wrong key name based on the QWERTY physical position.
1172
- const isLatinLetter = codepoint >= 97 && codepoint <= 122; // a-z
1173
- const isKnownSymbol = SYMBOL_KEYS.has(String.fromCharCode(codepoint));
1174
- const effectiveCodepoint =
1175
- isLatinLetter || isKnownSymbol ? codepoint : (baseLayoutKey ?? codepoint);
1176
-
1177
- let keyName: string | undefined;
1178
- if (effectiveCodepoint === CODEPOINTS.escape) keyName = "escape";
1179
- else if (effectiveCodepoint === CODEPOINTS.tab) keyName = "tab";
1180
- else if (effectiveCodepoint === CODEPOINTS.enter || effectiveCodepoint === CODEPOINTS.kpEnter)
1181
- keyName = "enter";
1182
- else if (effectiveCodepoint === CODEPOINTS.space) keyName = "space";
1183
- else if (effectiveCodepoint === CODEPOINTS.backspace) keyName = "backspace";
1184
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.delete) keyName = "delete";
1185
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.insert) keyName = "insert";
1186
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.home) keyName = "home";
1187
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.end) keyName = "end";
1188
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageUp) keyName = "pageUp";
1189
- else if (effectiveCodepoint === FUNCTIONAL_CODEPOINTS.pageDown) keyName = "pageDown";
1190
- else if (effectiveCodepoint === ARROW_CODEPOINTS.up) keyName = "up";
1191
- else if (effectiveCodepoint === ARROW_CODEPOINTS.down) keyName = "down";
1192
- else if (effectiveCodepoint === ARROW_CODEPOINTS.left) keyName = "left";
1193
- else if (effectiveCodepoint === ARROW_CODEPOINTS.right) keyName = "right";
1194
- else if (effectiveCodepoint >= 97 && effectiveCodepoint <= 122)
1195
- keyName = String.fromCharCode(effectiveCodepoint);
1196
- else if (SYMBOL_KEYS.has(String.fromCharCode(effectiveCodepoint)))
1197
- keyName = String.fromCharCode(effectiveCodepoint);
1198
-
1199
- if (keyName) {
1200
- return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
1201
- }
1274
+ return formatParsedKey(kitty.codepoint, kitty.modifier, kitty.baseLayoutKey);
1275
+ }
1276
+
1277
+ const modifyOtherKeys = parseModifyOtherKeysSequence(data);
1278
+ if (modifyOtherKeys) {
1279
+ return formatParsedKey(modifyOtherKeys.codepoint, modifyOtherKeys.modifier);
1202
1280
  }
1203
1281
 
1204
1282
  // Mode-aware legacy sequences
@@ -1239,8 +1317,8 @@ export function parseKey(data: string): string | undefined {
1239
1317
  if (code >= 1 && code <= 26) {
1240
1318
  return `ctrl+alt+${String.fromCharCode(code + 96)}`;
1241
1319
  }
1242
- // Legacy alt+letter (ESC followed by letter a-z)
1243
- if (code >= 97 && code <= 122) {
1320
+ // Legacy alt+letter/digit (ESC followed by the key)
1321
+ if ((code >= 97 && code <= 122) || (code >= 48 && code <= 57)) {
1244
1322
  return `alt+${String.fromCharCode(code)}`;
1245
1323
  }
1246
1324
  }
@@ -1269,63 +1347,57 @@ export function parseKey(data: string): string | undefined {
1269
1347
  }
1270
1348
 
1271
1349
  // =============================================================================
1272
- // Mouse Event Parsing
1350
+ // Kitty CSI-u Printable Decoding
1273
1351
  // =============================================================================
1274
1352
 
1275
- /**
1276
- * Parsed mouse event from SGR extended format.
1277
- * SGR format: \x1b[<button;column;row[Mm]
1278
- * M = press, m = release
1279
- */
1280
- export interface MouseEvent {
1281
- /** Event type */
1282
- type: "scroll-up" | "scroll-down" | "press" | "release" | "drag";
1283
- /** 0=left, 1=middle, 2=right */
1284
- button: number;
1285
- /** 1-indexed column */
1286
- x: number;
1287
- /** 1-indexed row */
1288
- y: number;
1289
- }
1290
-
1291
- /** SGR extended mouse format: \x1b[<button;x;y[Mm] */
1292
- const SGR_MOUSE_RE = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/;
1353
+ const KITTY_CSI_U_REGEX = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u$/;
1354
+ const KITTY_PRINTABLE_ALLOWED_MODIFIERS = MODIFIERS.shift | LOCK_MASK;
1293
1355
 
1294
1356
  /**
1295
- * Parse an SGR mouse event from raw terminal input.
1357
+ * Decode a Kitty CSI-u sequence into a printable character, if applicable.
1296
1358
  *
1297
- * @param data - Raw terminal input
1298
- * @returns Parsed mouse event, or null if not a mouse sequence
1299
- */
1300
- export function parseMouseEvent(data: string): MouseEvent | null {
1301
- const match = data.match(SGR_MOUSE_RE);
1302
- if (!match) return null;
1303
-
1304
- const code = parseInt(match[1]!, 10);
1305
- const x = parseInt(match[2]!, 10);
1306
- const y = parseInt(match[3]!, 10);
1307
- const isRelease = match[4] === "m";
1308
-
1309
- // Scroll wheel: codes 64 (up) and 65 (down)
1310
- if (code === 64) return { type: "scroll-up", button: 0, x, y };
1311
- if (code === 65) return { type: "scroll-down", button: 0, x, y };
1312
-
1313
- // Button number is in the low 2 bits
1314
- const button = code & 0x03;
1315
-
1316
- // Bit 5 (32) = motion/drag
1317
- if (code & 32) return { type: "drag", button, x, y };
1318
-
1319
- return { type: isRelease ? "release" : "press", button, x, y };
1320
- }
1321
-
1322
- /**
1323
- * Fast check: is this data an SGR mouse event?
1324
- * Avoids regex for non-mouse input.
1359
+ * When Kitty keyboard protocol flag 1 (disambiguate) is active, terminals send
1360
+ * CSI-u sequences for all keys, including plain printable characters. This
1361
+ * function extracts the printable character from such sequences.
1362
+ *
1363
+ * Only accepts plain or Shift-modified keys. Rejects Ctrl, Alt, and unsupported
1364
+ * modifier combinations (those are handled by keybinding matching instead).
1365
+ * Prefers the shifted keycode when Shift is held and a shifted key is reported.
1325
1366
  *
1326
- * @param data - Raw terminal input
1327
- * @returns true if the input is an SGR mouse sequence
1367
+ * @param data - Raw input data from terminal
1368
+ * @returns The printable character, or undefined if not a printable CSI-u sequence
1328
1369
  */
1329
- export function isMouseEvent(data: string): boolean {
1330
- return data.length >= 9 && data.startsWith("\x1b[<");
1370
+ export function decodeKittyPrintable(data: string): string | undefined {
1371
+ const match = data.match(KITTY_CSI_U_REGEX);
1372
+ if (!match) return undefined;
1373
+
1374
+ // CSI-u groups: <codepoint>[:<shifted>[:<base>]];<mod>[:<event>]u
1375
+ const codepoint = Number.parseInt(match[1] ?? "", 10);
1376
+ if (!Number.isFinite(codepoint)) return undefined;
1377
+
1378
+ const shiftedKey = match[2] && match[2].length > 0 ? Number.parseInt(match[2], 10) : undefined;
1379
+ const modValue = match[4] ? Number.parseInt(match[4], 10) : 1;
1380
+ // Modifiers are 1-indexed in CSI-u; normalize to our bitmask.
1381
+ const modifier = Number.isFinite(modValue) ? modValue - 1 : 0;
1382
+
1383
+ // Only accept printable CSI-u input for plain or Shift-modified text keys.
1384
+ // Reject unsupported modifier bits (e.g. Super/Meta) to avoid inserting
1385
+ // characters from modifier-only terminal events.
1386
+ if ((modifier & ~KITTY_PRINTABLE_ALLOWED_MODIFIERS) !== 0) return undefined;
1387
+ if (modifier & (MODIFIERS.alt | MODIFIERS.ctrl)) return undefined;
1388
+
1389
+ // Prefer the shifted keycode when Shift is held.
1390
+ let effectiveCodepoint = codepoint;
1391
+ if (modifier & MODIFIERS.shift && typeof shiftedKey === "number") {
1392
+ effectiveCodepoint = shiftedKey;
1393
+ }
1394
+ effectiveCodepoint = normalizeKittyFunctionalCodepoint(effectiveCodepoint);
1395
+ // Drop control characters or invalid codepoints.
1396
+ if (!Number.isFinite(effectiveCodepoint) || effectiveCodepoint < 32) return undefined;
1397
+
1398
+ try {
1399
+ return String.fromCodePoint(effectiveCodepoint);
1400
+ } catch {
1401
+ return undefined;
1402
+ }
1331
1403
  }