@google/gemini-cli 0.12.0-nightly.20251023.a7faa208 → 0.12.0-nightly.20251027.cb0947c5

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 (233) hide show
  1. package/dist/package.json +5 -4
  2. package/dist/src/commands/extensions/disable.js +13 -6
  3. package/dist/src/commands/extensions/disable.js.map +1 -1
  4. package/dist/src/commands/extensions/enable.js +13 -6
  5. package/dist/src/commands/extensions/enable.js.map +1 -1
  6. package/dist/src/commands/extensions/install.js +12 -2
  7. package/dist/src/commands/extensions/install.js.map +1 -1
  8. package/dist/src/commands/extensions/install.test.js +11 -3
  9. package/dist/src/commands/extensions/install.test.js.map +1 -1
  10. package/dist/src/commands/extensions/link.js +12 -2
  11. package/dist/src/commands/extensions/link.js.map +1 -1
  12. package/dist/src/commands/extensions/list.js +13 -4
  13. package/dist/src/commands/extensions/list.js.map +1 -1
  14. package/dist/src/commands/extensions/uninstall.js +12 -2
  15. package/dist/src/commands/extensions/uninstall.js.map +1 -1
  16. package/dist/src/commands/extensions/update.js +17 -13
  17. package/dist/src/commands/extensions/update.js.map +1 -1
  18. package/dist/src/commands/mcp/list.js +10 -3
  19. package/dist/src/commands/mcp/list.js.map +1 -1
  20. package/dist/src/commands/mcp/list.test.js +12 -6
  21. package/dist/src/commands/mcp/list.test.js.map +1 -1
  22. package/dist/src/config/config.d.ts +1 -0
  23. package/dist/src/config/config.js +15 -3
  24. package/dist/src/config/config.js.map +1 -1
  25. package/dist/src/config/config.test.js +23 -15
  26. package/dist/src/config/config.test.js.map +1 -1
  27. package/dist/src/config/extension-manager.d.ts +38 -0
  28. package/dist/src/config/extension-manager.js +411 -0
  29. package/dist/src/config/extension-manager.js.map +1 -0
  30. package/dist/src/config/extension.d.ts +4 -51
  31. package/dist/src/config/extension.js +1 -535
  32. package/dist/src/config/extension.js.map +1 -1
  33. package/dist/src/config/extension.test.js +343 -163
  34. package/dist/src/config/extension.test.js.map +1 -1
  35. package/dist/src/config/extensions/consent.d.ts +38 -0
  36. package/dist/src/config/extensions/consent.js +123 -0
  37. package/dist/src/config/extensions/consent.js.map +1 -0
  38. package/dist/src/config/extensions/extensionEnablement.d.ts +1 -1
  39. package/dist/src/config/extensions/extensionEnablement.js +4 -3
  40. package/dist/src/config/extensions/extensionEnablement.js.map +1 -1
  41. package/dist/src/config/extensions/extensionEnablement.test.js +10 -10
  42. package/dist/src/config/extensions/extensionEnablement.test.js.map +1 -1
  43. package/dist/src/config/extensions/extensionSettings.d.ts +15 -0
  44. package/dist/src/config/extensions/extensionSettings.js +63 -0
  45. package/dist/src/config/extensions/extensionSettings.js.map +1 -0
  46. package/dist/src/config/extensions/extensionSettings.test.d.ts +6 -0
  47. package/dist/src/config/extensions/extensionSettings.test.js +137 -0
  48. package/dist/src/config/extensions/extensionSettings.test.js.map +1 -0
  49. package/dist/src/config/extensions/github.d.ts +2 -2
  50. package/dist/src/config/extensions/github.js +3 -8
  51. package/dist/src/config/extensions/github.js.map +1 -1
  52. package/dist/src/config/extensions/github.test.js +25 -7
  53. package/dist/src/config/extensions/github.test.js.map +1 -1
  54. package/dist/src/config/extensions/github_fetch.d.ts +1 -1
  55. package/dist/src/config/extensions/github_fetch.js +13 -1
  56. package/dist/src/config/extensions/github_fetch.js.map +1 -1
  57. package/dist/src/config/extensions/github_fetch.test.d.ts +6 -0
  58. package/dist/src/config/extensions/github_fetch.test.js +169 -0
  59. package/dist/src/config/extensions/github_fetch.test.js.map +1 -0
  60. package/dist/src/config/extensions/storage.d.ts +14 -0
  61. package/dist/src/config/extensions/storage.js +32 -0
  62. package/dist/src/config/extensions/storage.js.map +1 -0
  63. package/dist/src/config/extensions/update.d.ts +4 -4
  64. package/dist/src/config/extensions/update.js +11 -18
  65. package/dist/src/config/extensions/update.js.map +1 -1
  66. package/dist/src/config/extensions/update.test.js +33 -58
  67. package/dist/src/config/extensions/update.test.js.map +1 -1
  68. package/dist/src/config/extensions/variableSchema.d.ts +0 -6
  69. package/dist/src/config/extensions/variableSchema.js.map +1 -1
  70. package/dist/src/config/extensions/variables.d.ts +4 -0
  71. package/dist/src/config/extensions/variables.js +6 -0
  72. package/dist/src/config/extensions/variables.js.map +1 -1
  73. package/dist/src/config/settings.d.ts +2 -1
  74. package/dist/src/config/settings.js +4 -7
  75. package/dist/src/config/settings.js.map +1 -1
  76. package/dist/src/config/settings.test.js +86 -16
  77. package/dist/src/config/settings.test.js.map +1 -1
  78. package/dist/src/core/initializer.js +2 -1
  79. package/dist/src/core/initializer.js.map +1 -1
  80. package/dist/src/gemini.js +22 -5
  81. package/dist/src/gemini.js.map +1 -1
  82. package/dist/src/gemini.test.js +26 -2
  83. package/dist/src/gemini.test.js.map +1 -1
  84. package/dist/src/generated/git-commit.d.ts +2 -2
  85. package/dist/src/generated/git-commit.js +2 -2
  86. package/dist/src/nonInteractiveCli.js +30 -5
  87. package/dist/src/nonInteractiveCli.js.map +1 -1
  88. package/dist/src/nonInteractiveCli.test.js +151 -14
  89. package/dist/src/nonInteractiveCli.test.js.map +1 -1
  90. package/dist/src/test-utils/createExtension.d.ts +3 -1
  91. package/dist/src/test-utils/createExtension.js +3 -3
  92. package/dist/src/test-utils/createExtension.js.map +1 -1
  93. package/dist/src/ui/AppContainer.js +102 -48
  94. package/dist/src/ui/AppContainer.js.map +1 -1
  95. package/dist/src/ui/AppContainer.test.js +138 -79
  96. package/dist/src/ui/AppContainer.test.js.map +1 -1
  97. package/dist/src/ui/commands/extensionsCommand.js +19 -10
  98. package/dist/src/ui/commands/extensionsCommand.js.map +1 -1
  99. package/dist/src/ui/commands/extensionsCommand.test.js +8 -0
  100. package/dist/src/ui/commands/extensionsCommand.test.js.map +1 -1
  101. package/dist/src/ui/components/FolderTrustDialog.test.js +1 -0
  102. package/dist/src/ui/components/FolderTrustDialog.test.js.map +1 -1
  103. package/dist/src/ui/components/HistoryItemDisplay.js +1 -1
  104. package/dist/src/ui/components/HistoryItemDisplay.js.map +1 -1
  105. package/dist/src/ui/components/InputPrompt.js +5 -6
  106. package/dist/src/ui/components/InputPrompt.js.map +1 -1
  107. package/dist/src/ui/components/InputPrompt.test.js +668 -712
  108. package/dist/src/ui/components/InputPrompt.test.js.map +1 -1
  109. package/dist/src/ui/components/ModelDialog.test.js +1 -0
  110. package/dist/src/ui/components/ModelDialog.test.js.map +1 -1
  111. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js +1 -0
  112. package/dist/src/ui/components/PermissionsModifyTrustDialog.test.js.map +1 -1
  113. package/dist/src/ui/components/SettingsDialog.test.js +2 -30
  114. package/dist/src/ui/components/SettingsDialog.test.js.map +1 -1
  115. package/dist/src/ui/components/ThemeDialog.test.js +1 -2
  116. package/dist/src/ui/components/ThemeDialog.test.js.map +1 -1
  117. package/dist/src/ui/components/shared/BaseSelectionList.test.js +1 -0
  118. package/dist/src/ui/components/shared/BaseSelectionList.test.js.map +1 -1
  119. package/dist/src/ui/components/shared/text-buffer.test.js +1 -0
  120. package/dist/src/ui/components/shared/text-buffer.test.js.map +1 -1
  121. package/dist/src/ui/components/views/ExtensionsList.d.ts +7 -1
  122. package/dist/src/ui/components/views/ExtensionsList.js +8 -11
  123. package/dist/src/ui/components/views/ExtensionsList.js.map +1 -1
  124. package/dist/src/ui/components/views/ExtensionsList.test.js +34 -21
  125. package/dist/src/ui/components/views/ExtensionsList.test.js.map +1 -1
  126. package/dist/src/ui/contexts/KeypressContext.d.ts +3 -2
  127. package/dist/src/ui/contexts/KeypressContext.js +429 -386
  128. package/dist/src/ui/contexts/KeypressContext.js.map +1 -1
  129. package/dist/src/ui/contexts/KeypressContext.test.js +162 -479
  130. package/dist/src/ui/contexts/KeypressContext.test.js.map +1 -1
  131. package/dist/src/ui/contexts/SessionContext.test.js +1 -0
  132. package/dist/src/ui/contexts/SessionContext.test.js.map +1 -1
  133. package/dist/src/ui/hooks/shellCommandProcessor.test.js +18 -2
  134. package/dist/src/ui/hooks/shellCommandProcessor.test.js.map +1 -1
  135. package/dist/src/ui/hooks/slashCommandProcessor.test.js +74 -80
  136. package/dist/src/ui/hooks/slashCommandProcessor.test.js.map +1 -1
  137. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js +1 -0
  138. package/dist/src/ui/hooks/useAutoAcceptIndicator.test.js.map +1 -1
  139. package/dist/src/ui/hooks/useCommandCompletion.test.js +79 -78
  140. package/dist/src/ui/hooks/useCommandCompletion.test.js.map +1 -1
  141. package/dist/src/ui/hooks/useConsoleMessages.test.js +26 -9
  142. package/dist/src/ui/hooks/useConsoleMessages.test.js.map +1 -1
  143. package/dist/src/ui/hooks/useEditorSettings.test.js +40 -34
  144. package/dist/src/ui/hooks/useEditorSettings.test.js.map +1 -1
  145. package/dist/src/ui/hooks/useExtensionUpdates.d.ts +15 -5
  146. package/dist/src/ui/hooks/useExtensionUpdates.js +16 -18
  147. package/dist/src/ui/hooks/useExtensionUpdates.js.map +1 -1
  148. package/dist/src/ui/hooks/useExtensionUpdates.test.js +46 -36
  149. package/dist/src/ui/hooks/useExtensionUpdates.test.js.map +1 -1
  150. package/dist/src/ui/hooks/useFlickerDetector.test.js +1 -0
  151. package/dist/src/ui/hooks/useFlickerDetector.test.js.map +1 -1
  152. package/dist/src/ui/hooks/useFocus.test.js +25 -9
  153. package/dist/src/ui/hooks/useFocus.test.js.map +1 -1
  154. package/dist/src/ui/hooks/useFolderTrust.test.js +1 -0
  155. package/dist/src/ui/hooks/useFolderTrust.test.js.map +1 -1
  156. package/dist/src/ui/hooks/useGeminiStream.js +49 -14
  157. package/dist/src/ui/hooks/useGeminiStream.js.map +1 -1
  158. package/dist/src/ui/hooks/useGeminiStream.test.js +114 -34
  159. package/dist/src/ui/hooks/useGeminiStream.test.js.map +1 -1
  160. package/dist/src/ui/hooks/useGitBranchName.js +4 -0
  161. package/dist/src/ui/hooks/useGitBranchName.js.map +1 -1
  162. package/dist/src/ui/hooks/useGitBranchName.test.js +46 -35
  163. package/dist/src/ui/hooks/useGitBranchName.test.js.map +1 -1
  164. package/dist/src/ui/hooks/useHistoryManager.test.js +1 -0
  165. package/dist/src/ui/hooks/useHistoryManager.test.js.map +1 -1
  166. package/dist/src/ui/hooks/useIdeTrustListener.test.js +24 -7
  167. package/dist/src/ui/hooks/useIdeTrustListener.test.js.map +1 -1
  168. package/dist/src/ui/hooks/useInputHistory.test.js +1 -0
  169. package/dist/src/ui/hooks/useInputHistory.test.js.map +1 -1
  170. package/dist/src/ui/hooks/useInputHistoryStore.test.js +1 -0
  171. package/dist/src/ui/hooks/useInputHistoryStore.test.js.map +1 -1
  172. package/dist/src/ui/hooks/useKeypress.test.js +94 -113
  173. package/dist/src/ui/hooks/useKeypress.test.js.map +1 -1
  174. package/dist/src/ui/hooks/useLoadingIndicator.test.js +24 -6
  175. package/dist/src/ui/hooks/useLoadingIndicator.test.js.map +1 -1
  176. package/dist/src/ui/hooks/useMemoryMonitor.test.js +10 -5
  177. package/dist/src/ui/hooks/useMemoryMonitor.test.js.map +1 -1
  178. package/dist/src/ui/hooks/useMessageQueue.test.js +61 -45
  179. package/dist/src/ui/hooks/useMessageQueue.test.js.map +1 -1
  180. package/dist/src/ui/hooks/useModelCommand.test.js +18 -11
  181. package/dist/src/ui/hooks/useModelCommand.test.js.map +1 -1
  182. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js +1 -0
  183. package/dist/src/ui/hooks/usePermissionsModifyTrust.test.js.map +1 -1
  184. package/dist/src/ui/hooks/usePhraseCycler.test.js +1 -0
  185. package/dist/src/ui/hooks/usePhraseCycler.test.js.map +1 -1
  186. package/dist/src/ui/hooks/usePrivacySettings.test.js +26 -10
  187. package/dist/src/ui/hooks/usePrivacySettings.test.js.map +1 -1
  188. package/dist/src/ui/hooks/useQuotaAndFallback.js +13 -14
  189. package/dist/src/ui/hooks/useQuotaAndFallback.js.map +1 -1
  190. package/dist/src/ui/hooks/useQuotaAndFallback.test.js +32 -39
  191. package/dist/src/ui/hooks/useQuotaAndFallback.test.js.map +1 -1
  192. package/dist/src/ui/hooks/useReactToolScheduler.d.ts +8 -1
  193. package/dist/src/ui/hooks/useReactToolScheduler.js +59 -34
  194. package/dist/src/ui/hooks/useReactToolScheduler.js.map +1 -1
  195. package/dist/src/ui/hooks/useReactToolScheduler.test.d.ts +6 -0
  196. package/dist/src/ui/hooks/useReactToolScheduler.test.js +66 -0
  197. package/dist/src/ui/hooks/useReactToolScheduler.test.js.map +1 -0
  198. package/dist/src/ui/hooks/useSelectionList.test.js +193 -132
  199. package/dist/src/ui/hooks/useSelectionList.test.js.map +1 -1
  200. package/dist/src/ui/hooks/useShellHistory.test.js +1 -0
  201. package/dist/src/ui/hooks/useShellHistory.test.js.map +1 -1
  202. package/dist/src/ui/hooks/useTimer.test.js +43 -14
  203. package/dist/src/ui/hooks/useTimer.test.js.map +1 -1
  204. package/dist/src/ui/hooks/useToolScheduler.test.js +122 -39
  205. package/dist/src/ui/hooks/useToolScheduler.test.js.map +1 -1
  206. package/dist/src/ui/hooks/vim.test.js +251 -356
  207. package/dist/src/ui/hooks/vim.test.js.map +1 -1
  208. package/dist/src/ui/types.d.ts +2 -1
  209. package/dist/src/ui/types.js.map +1 -1
  210. package/dist/src/ui/utils/CodeColorizer.js +2 -1
  211. package/dist/src/ui/utils/CodeColorizer.js.map +1 -1
  212. package/dist/src/ui/utils/textOutput.d.ts +25 -0
  213. package/dist/src/ui/utils/textOutput.js +49 -0
  214. package/dist/src/ui/utils/textOutput.js.map +1 -0
  215. package/dist/src/ui/utils/textOutput.test.d.ts +6 -0
  216. package/dist/src/ui/utils/textOutput.test.js +79 -0
  217. package/dist/src/ui/utils/textOutput.test.js.map +1 -0
  218. package/dist/src/ui/utils/updateCheck.d.ts +7 -1
  219. package/dist/src/ui/utils/updateCheck.js +27 -26
  220. package/dist/src/ui/utils/updateCheck.js.map +1 -1
  221. package/dist/src/ui/utils/updateCheck.test.js +19 -49
  222. package/dist/src/ui/utils/updateCheck.test.js.map +1 -1
  223. package/dist/src/utils/envVarResolver.d.ts +2 -2
  224. package/dist/src/utils/envVarResolver.js +10 -7
  225. package/dist/src/utils/envVarResolver.js.map +1 -1
  226. package/dist/src/utils/handleAutoUpdate.js +9 -3
  227. package/dist/src/utils/handleAutoUpdate.js.map +1 -1
  228. package/dist/src/zed-integration/schema.d.ts +4 -4
  229. package/dist/src/zed-integration/zedIntegration.js +8 -13
  230. package/dist/src/zed-integration/zedIntegration.js.map +1 -1
  231. package/dist/tsconfig.tsbuildinfo +1 -1
  232. package/package.json +5 -4
  233. package/dist/google-gemini-cli-0.12.0-nightly.20251022.0542de95.tgz +0 -0
@@ -15,29 +15,20 @@ vi.mock('ink', async (importOriginal) => {
15
15
  useStdin: vi.fn(),
16
16
  };
17
17
  });
18
+ const PASTE_START = '\x1B[200~';
19
+ const PASTE_END = '\x1B[201~';
20
+ // readline will not emit most incomplete kitty sequences but it will give
21
+ // up on sequences like this where the modifier (135) has more than two digits.
22
+ const INCOMPLETE_KITTY_SEQUENCE = '\x1b[97;135';
18
23
  class MockStdin extends EventEmitter {
19
24
  isTTY = true;
20
25
  setRawMode = vi.fn();
21
26
  on = this.addListener;
22
27
  removeListener = super.removeListener;
23
- write = vi.fn();
24
28
  resume = vi.fn();
25
29
  pause = vi.fn();
26
- // Helper to simulate a keypress event
27
- pressKey(key) {
28
- this.emit('keypress', null, key);
29
- }
30
- // Helper to simulate a kitty protocol sequence
31
- sendKittySequence(sequence) {
32
- this.emit('data', Buffer.from(sequence));
33
- }
34
- // Helper to simulate a paste event
35
- sendPaste(text) {
36
- const PASTE_MODE_PREFIX = `\x1b[200~`;
37
- const PASTE_MODE_SUFFIX = `\x1b[201~`;
38
- this.emit('data', Buffer.from(PASTE_MODE_PREFIX));
39
- this.emit('data', Buffer.from(text));
40
- this.emit('data', Buffer.from(PASTE_MODE_SUFFIX));
30
+ write(text) {
31
+ this.emit('data', text);
41
32
  }
42
33
  }
43
34
  describe('KeypressContext - Kitty Protocol', () => {
@@ -58,12 +49,10 @@ describe('KeypressContext - Kitty Protocol', () => {
58
49
  const { result } = renderHook(() => useKeypressContext(), {
59
50
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
60
51
  });
61
- act(() => {
62
- result.current.subscribe(keyHandler);
63
- });
52
+ act(() => result.current.subscribe(keyHandler));
64
53
  // Send kitty protocol sequence for regular enter: ESC[13u
65
54
  act(() => {
66
- stdin.sendKittySequence(`\x1b[13u`);
55
+ stdin.write(`\x1b[13u`);
67
56
  });
68
57
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
69
58
  name: 'return',
@@ -78,12 +67,10 @@ describe('KeypressContext - Kitty Protocol', () => {
78
67
  const { result } = renderHook(() => useKeypressContext(), {
79
68
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
80
69
  });
81
- act(() => {
82
- result.current.subscribe(keyHandler);
83
- });
70
+ act(() => result.current.subscribe(keyHandler));
84
71
  // Send kitty protocol sequence for numpad enter: ESC[57414u
85
72
  act(() => {
86
- stdin.sendKittySequence(`\x1b[57414u`);
73
+ stdin.write(`\x1b[57414u`);
87
74
  });
88
75
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
89
76
  name: 'return',
@@ -98,12 +85,10 @@ describe('KeypressContext - Kitty Protocol', () => {
98
85
  const { result } = renderHook(() => useKeypressContext(), {
99
86
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
100
87
  });
101
- act(() => {
102
- result.current.subscribe(keyHandler);
103
- });
88
+ act(() => result.current.subscribe(keyHandler));
104
89
  // Send kitty protocol sequence for numpad enter with Shift (modifier 2): ESC[57414;2u
105
90
  act(() => {
106
- stdin.sendKittySequence(`\x1b[57414;2u`);
91
+ stdin.write(`\x1b[57414;2u`);
107
92
  });
108
93
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
109
94
  name: 'return',
@@ -118,13 +103,9 @@ describe('KeypressContext - Kitty Protocol', () => {
118
103
  const { result } = renderHook(() => useKeypressContext(), {
119
104
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
120
105
  });
121
- act(() => {
122
- result.current.subscribe(keyHandler);
123
- });
106
+ act(() => result.current.subscribe(keyHandler));
124
107
  // Send kitty protocol sequence for numpad enter with Ctrl (modifier 5): ESC[57414;5u
125
- act(() => {
126
- stdin.sendKittySequence(`\x1b[57414;5u`);
127
- });
108
+ act(() => stdin.write(`\x1b[57414;5u`));
128
109
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
129
110
  name: 'return',
130
111
  kittyProtocol: true,
@@ -138,12 +119,10 @@ describe('KeypressContext - Kitty Protocol', () => {
138
119
  const { result } = renderHook(() => useKeypressContext(), {
139
120
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
140
121
  });
141
- act(() => {
142
- result.current.subscribe(keyHandler);
143
- });
122
+ act(() => result.current.subscribe(keyHandler));
144
123
  // Send kitty protocol sequence for numpad enter with Alt (modifier 3): ESC[57414;3u
145
124
  act(() => {
146
- stdin.sendKittySequence(`\x1b[57414;3u`);
125
+ stdin.write(`\x1b[57414;3u`);
147
126
  });
148
127
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
149
128
  name: 'return',
@@ -158,12 +137,10 @@ describe('KeypressContext - Kitty Protocol', () => {
158
137
  const { result } = renderHook(() => useKeypressContext(), {
159
138
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
160
139
  });
161
- act(() => {
162
- result.current.subscribe(keyHandler);
163
- });
140
+ act(() => result.current.subscribe(keyHandler));
164
141
  // Send kitty protocol sequence for numpad enter
165
142
  act(() => {
166
- stdin.sendKittySequence(`\x1b[57414u`);
143
+ stdin.write(`\x1b[57414u`);
167
144
  });
168
145
  // When kitty protocol is disabled, the sequence should be passed through
169
146
  // as individual keypresses, not recognized as a single enter key
@@ -179,12 +156,10 @@ describe('KeypressContext - Kitty Protocol', () => {
179
156
  const { result } = renderHook(() => useKeypressContext(), {
180
157
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: true }),
181
158
  });
182
- act(() => {
183
- result.current.subscribe(keyHandler);
184
- });
159
+ act(() => result.current.subscribe(keyHandler));
185
160
  // Send kitty protocol sequence for escape: ESC[27u
186
161
  act(() => {
187
- stdin.sendKittySequence('\x1b[27u');
162
+ stdin.write('\x1b[27u');
188
163
  });
189
164
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
190
165
  name: 'escape',
@@ -198,7 +173,7 @@ describe('KeypressContext - Kitty Protocol', () => {
198
173
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
199
174
  act(() => result.current.subscribe(keyHandler));
200
175
  act(() => {
201
- stdin.sendKittySequence(`\x1b[9u`);
176
+ stdin.write(`\x1b[9u`);
202
177
  });
203
178
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
204
179
  name: 'tab',
@@ -212,7 +187,7 @@ describe('KeypressContext - Kitty Protocol', () => {
212
187
  act(() => result.current.subscribe(keyHandler));
213
188
  // Modifier 2 is Shift
214
189
  act(() => {
215
- stdin.sendKittySequence(`\x1b[9;2u`);
190
+ stdin.write(`\x1b[9;2u`);
216
191
  });
217
192
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
218
193
  name: 'tab',
@@ -225,7 +200,7 @@ describe('KeypressContext - Kitty Protocol', () => {
225
200
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
226
201
  act(() => result.current.subscribe(keyHandler));
227
202
  act(() => {
228
- stdin.sendKittySequence(`\x1b[127u`);
203
+ stdin.write(`\x1b[127u`);
229
204
  });
230
205
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
231
206
  name: 'backspace',
@@ -239,7 +214,7 @@ describe('KeypressContext - Kitty Protocol', () => {
239
214
  act(() => result.current.subscribe(keyHandler));
240
215
  // Modifier 3 is Alt/Option
241
216
  act(() => {
242
- stdin.sendKittySequence(`\x1b[127;3u`);
217
+ stdin.write(`\x1b[127;3u`);
243
218
  });
244
219
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
245
220
  name: 'backspace',
@@ -253,7 +228,7 @@ describe('KeypressContext - Kitty Protocol', () => {
253
228
  act(() => result.current.subscribe(keyHandler));
254
229
  // Modifier 5 is Ctrl
255
230
  act(() => {
256
- stdin.sendKittySequence(`\x1b[127;5u`);
231
+ stdin.write(`\x1b[127;5u`);
257
232
  });
258
233
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
259
234
  name: 'backspace',
@@ -269,12 +244,12 @@ describe('KeypressContext - Kitty Protocol', () => {
269
244
  const { result } = renderHook(() => useKeypressContext(), {
270
245
  wrapper,
271
246
  });
272
- act(() => {
273
- result.current.subscribe(keyHandler);
274
- });
247
+ act(() => result.current.subscribe(keyHandler));
275
248
  // Simulate a bracketed paste event
276
249
  act(() => {
277
- stdin.sendPaste(pastedText);
250
+ stdin.write(PASTE_START);
251
+ stdin.write(pastedText);
252
+ stdin.write(PASTE_END);
278
253
  });
279
254
  await waitFor(() => {
280
255
  // Expect the handler to be called exactly once for the entire paste
@@ -286,6 +261,46 @@ describe('KeypressContext - Kitty Protocol', () => {
286
261
  sequence: pastedText,
287
262
  }));
288
263
  });
264
+ it('should paste start code split over multiple writes', async () => {
265
+ const keyHandler = vi.fn();
266
+ const pastedText = 'pasted content';
267
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
268
+ act(() => result.current.subscribe(keyHandler));
269
+ act(() => {
270
+ // Split PASTE_START into two parts
271
+ stdin.write(PASTE_START.slice(0, 3));
272
+ stdin.write(PASTE_START.slice(3));
273
+ stdin.write(pastedText);
274
+ stdin.write(PASTE_END);
275
+ });
276
+ await waitFor(() => {
277
+ expect(keyHandler).toHaveBeenCalledTimes(1);
278
+ });
279
+ expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
280
+ paste: true,
281
+ sequence: pastedText,
282
+ }));
283
+ });
284
+ it('should paste end code split over multiple writes', async () => {
285
+ const keyHandler = vi.fn();
286
+ const pastedText = 'pasted content';
287
+ const { result } = renderHook(() => useKeypressContext(), { wrapper });
288
+ act(() => result.current.subscribe(keyHandler));
289
+ act(() => {
290
+ stdin.write(PASTE_START);
291
+ stdin.write(pastedText);
292
+ // Split PASTE_END into two parts
293
+ stdin.write(PASTE_END.slice(0, 3));
294
+ stdin.write(PASTE_END.slice(3));
295
+ });
296
+ await waitFor(() => {
297
+ expect(keyHandler).toHaveBeenCalledTimes(1);
298
+ });
299
+ expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
300
+ paste: true,
301
+ sequence: pastedText,
302
+ }));
303
+ });
289
304
  });
290
305
  describe('debug keystroke logging', () => {
291
306
  let consoleLogSpy;
@@ -302,12 +317,10 @@ describe('KeypressContext - Kitty Protocol', () => {
302
317
  const keyHandler = vi.fn();
303
318
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: false, children: children }));
304
319
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
305
- act(() => {
306
- result.current.subscribe(keyHandler);
307
- });
320
+ act(() => result.current.subscribe(keyHandler));
308
321
  // Send a kitty sequence
309
322
  act(() => {
310
- stdin.sendKittySequence('\x1b[27u');
323
+ stdin.write('\x1b[27u');
311
324
  });
312
325
  expect(keyHandler).toHaveBeenCalled();
313
326
  expect(consoleLogSpy).not.toHaveBeenCalledWith(expect.stringContaining('[DEBUG] Kitty'));
@@ -316,13 +329,9 @@ describe('KeypressContext - Kitty Protocol', () => {
316
329
  const keyHandler = vi.fn();
317
330
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
318
331
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
319
- act(() => {
320
- result.current.subscribe(keyHandler);
321
- });
332
+ act(() => result.current.subscribe(keyHandler));
322
333
  // Send a complete kitty sequence for escape
323
- act(() => {
324
- stdin.sendKittySequence('\x1b[27u');
325
- });
334
+ act(() => stdin.write('\x1b[27u'));
326
335
  expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', expect.stringContaining('"\\u001b[27u"'));
327
336
  const parsedCall = consoleLogSpy.mock.calls.find((args) => typeof args[0] === 'string' &&
328
337
  args[0].includes('[DEBUG] Kitty sequence parsed successfully'));
@@ -333,44 +342,21 @@ describe('KeypressContext - Kitty Protocol', () => {
333
342
  const keyHandler = vi.fn();
334
343
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
335
344
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
336
- act(() => {
337
- result.current.subscribe(keyHandler);
338
- });
345
+ act(() => result.current.subscribe(keyHandler));
339
346
  // Send a long sequence starting with a valid kitty prefix to trigger overflow
340
347
  const longSequence = '\x1b[1;' + '1'.repeat(100);
341
- act(() => {
342
- stdin.sendKittySequence(longSequence);
343
- });
348
+ act(() => stdin.write(longSequence));
344
349
  expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer overflow, clearing:', expect.any(String));
345
350
  });
346
351
  it('should log kitty buffer clear on Ctrl+C when debugKeystrokeLogging is true', async () => {
347
352
  const keyHandler = vi.fn();
348
353
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
349
354
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
350
- act(() => {
351
- result.current.subscribe(keyHandler);
352
- });
353
- // Send incomplete kitty sequence
354
- act(() => {
355
- stdin.pressKey({
356
- name: undefined,
357
- ctrl: false,
358
- meta: false,
359
- shift: false,
360
- sequence: '\x1b[1',
361
- });
362
- });
355
+ act(() => result.current.subscribe(keyHandler));
356
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
363
357
  // Send Ctrl+C
364
- act(() => {
365
- stdin.pressKey({
366
- name: 'c',
367
- ctrl: true,
368
- meta: false,
369
- shift: false,
370
- sequence: '\x03',
371
- });
372
- });
373
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer cleared on Ctrl+C:', '\x1b[1');
358
+ act(() => stdin.write('\x03'));
359
+ expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer cleared on Ctrl+C:', INCOMPLETE_KITTY_SEQUENCE);
374
360
  // Verify Ctrl+C was handled
375
361
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
376
362
  name: 'c',
@@ -381,24 +367,13 @@ describe('KeypressContext - Kitty Protocol', () => {
381
367
  const keyHandler = vi.fn();
382
368
  const wrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: true, debugKeystrokeLogging: true, children: children }));
383
369
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
384
- act(() => {
385
- result.current.subscribe(keyHandler);
386
- });
370
+ act(() => result.current.subscribe(keyHandler));
387
371
  // Send incomplete kitty sequence
388
- const sequence = '\x1b[12';
389
- act(() => {
390
- stdin.pressKey({
391
- name: undefined,
392
- ctrl: false,
393
- meta: false,
394
- shift: false,
395
- sequence,
396
- });
397
- });
372
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
398
373
  // Verify debug logging for accumulation
399
- expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', JSON.stringify(sequence));
374
+ expect(consoleLogSpy).toHaveBeenCalledWith('[DEBUG] Kitty buffer accumulating:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
400
375
  // Verify warning for char codes
401
- expect(consoleWarnSpy).toHaveBeenCalledWith('Kitty sequence buffer has content:', JSON.stringify(sequence));
376
+ expect(consoleWarnSpy).toHaveBeenCalledWith('Kitty sequence buffer has content:', JSON.stringify(INCOMPLETE_KITTY_SEQUENCE));
402
377
  });
403
378
  });
404
379
  describe('Parameterized functional keys', () => {
@@ -414,6 +389,9 @@ describe('KeypressContext - Kitty Protocol', () => {
414
389
  { sequence: `\x1b[1~`, expected: { name: 'home' } },
415
390
  { sequence: `\x1b[4~`, expected: { name: 'end' } },
416
391
  { sequence: `\x1b[2~`, expected: { name: 'insert' } },
392
+ // Reverse tabs
393
+ { sequence: `\x1b[Z`, expected: { name: 'tab', shift: true } },
394
+ { sequence: `\x1b[1;2Z`, expected: { name: 'tab', shift: true } },
417
395
  // Legacy Arrows
418
396
  {
419
397
  sequence: `\x1b[A`,
@@ -444,29 +422,17 @@ describe('KeypressContext - Kitty Protocol', () => {
444
422
  const keyHandler = vi.fn();
445
423
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
446
424
  act(() => result.current.subscribe(keyHandler));
447
- act(() => stdin.sendKittySequence(sequence));
425
+ act(() => stdin.write(sequence));
448
426
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
449
427
  });
450
428
  });
451
- describe('Shift+Tab forms', () => {
452
- it.each([
453
- { sequence: `\x1b[Z`, description: 'legacy reverse Tab' },
454
- { sequence: `\x1b[1;2Z`, description: 'parameterized reverse Tab' },
455
- ])('should recognize $description "$sequence" as Shift+Tab', ({ sequence }) => {
456
- const keyHandler = vi.fn();
457
- const { result } = renderHook(() => useKeypressContext(), { wrapper });
458
- act(() => result.current.subscribe(keyHandler));
459
- act(() => stdin.sendKittySequence(sequence));
460
- expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'tab', shift: true }));
461
- });
462
- });
463
429
  describe('Double-tap and batching', () => {
464
430
  it('should emit two delete events for double-tap CSI[3~', async () => {
465
431
  const keyHandler = vi.fn();
466
432
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
467
433
  act(() => result.current.subscribe(keyHandler));
468
- act(() => stdin.sendKittySequence(`\x1b[3~`));
469
- act(() => stdin.sendKittySequence(`\x1b[3~`));
434
+ act(() => stdin.write(`\x1b[3~`));
435
+ act(() => stdin.write(`\x1b[3~`));
470
436
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({ name: 'delete' }));
471
437
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({ name: 'delete' }));
472
438
  });
@@ -474,7 +440,7 @@ describe('KeypressContext - Kitty Protocol', () => {
474
440
  const keyHandler = vi.fn();
475
441
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
476
442
  act(() => result.current.subscribe(keyHandler));
477
- act(() => stdin.sendKittySequence(`\x1b[3~\x1b[5~`));
443
+ act(() => stdin.write(`\x1b[3~\x1b[5~`));
478
444
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
479
445
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'pageup' }));
480
446
  });
@@ -485,15 +451,9 @@ describe('KeypressContext - Kitty Protocol', () => {
485
451
  // Incomplete ESC sequence then a complete Delete
486
452
  act(() => {
487
453
  // Provide an incomplete ESC sequence chunk with a real ESC character
488
- stdin.pressKey({
489
- name: undefined,
490
- ctrl: false,
491
- meta: false,
492
- shift: false,
493
- sequence: '\x1b[1;',
494
- });
454
+ stdin.write('\x1b[1;');
495
455
  });
496
- act(() => stdin.sendKittySequence(`\x1b[3~`));
456
+ act(() => stdin.write(`\x1b[3~`));
497
457
  expect(keyHandler).toHaveBeenCalledTimes(1);
498
458
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({ name: 'delete' }));
499
459
  });
@@ -519,37 +479,15 @@ describe('Drag and Drop Handling', () => {
519
479
  it('should start collecting when single quote arrives and not broadcast immediately', async () => {
520
480
  const keyHandler = vi.fn();
521
481
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
522
- act(() => {
523
- result.current.subscribe(keyHandler);
524
- });
525
- act(() => {
526
- stdin.pressKey({
527
- name: undefined,
528
- ctrl: false,
529
- meta: false,
530
- shift: false,
531
- paste: false,
532
- sequence: SINGLE_QUOTE,
533
- });
534
- });
482
+ act(() => result.current.subscribe(keyHandler));
483
+ act(() => stdin.write(SINGLE_QUOTE));
535
484
  expect(keyHandler).not.toHaveBeenCalled();
536
485
  });
537
486
  it('should start collecting when double quote arrives and not broadcast immediately', async () => {
538
487
  const keyHandler = vi.fn();
539
488
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
540
- act(() => {
541
- result.current.subscribe(keyHandler);
542
- });
543
- act(() => {
544
- stdin.pressKey({
545
- name: undefined,
546
- ctrl: false,
547
- meta: false,
548
- shift: false,
549
- paste: false,
550
- sequence: DOUBLE_QUOTE,
551
- });
552
- });
489
+ act(() => result.current.subscribe(keyHandler));
490
+ act(() => stdin.write(DOUBLE_QUOTE));
553
491
  expect(keyHandler).not.toHaveBeenCalled();
554
492
  });
555
493
  });
@@ -557,31 +495,11 @@ describe('Drag and Drop Handling', () => {
557
495
  it('should collect single character inputs during drag mode', async () => {
558
496
  const keyHandler = vi.fn();
559
497
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
560
- act(() => {
561
- result.current.subscribe(keyHandler);
562
- });
498
+ act(() => result.current.subscribe(keyHandler));
563
499
  // Start by single quote
564
- act(() => {
565
- stdin.pressKey({
566
- name: undefined,
567
- ctrl: false,
568
- meta: false,
569
- shift: false,
570
- paste: false,
571
- sequence: SINGLE_QUOTE,
572
- });
573
- });
500
+ act(() => stdin.write(SINGLE_QUOTE));
574
501
  // Send single character
575
- act(() => {
576
- stdin.pressKey({
577
- name: undefined,
578
- ctrl: false,
579
- meta: false,
580
- shift: false,
581
- paste: false,
582
- sequence: 'a',
583
- });
584
- });
502
+ act(() => stdin.write('a'));
585
503
  // Character should not be immediately broadcast
586
504
  expect(keyHandler).not.toHaveBeenCalled();
587
505
  // Fast-forward to completion timeout
@@ -598,61 +516,14 @@ describe('Drag and Drop Handling', () => {
598
516
  it('should collect multiple characters and complete on timeout', async () => {
599
517
  const keyHandler = vi.fn();
600
518
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
601
- act(() => {
602
- result.current.subscribe(keyHandler);
603
- });
519
+ act(() => result.current.subscribe(keyHandler));
604
520
  // Start by single quote
605
- act(() => {
606
- stdin.pressKey({
607
- name: undefined,
608
- ctrl: false,
609
- meta: false,
610
- shift: false,
611
- paste: false,
612
- sequence: SINGLE_QUOTE,
613
- });
614
- });
521
+ act(() => stdin.write(SINGLE_QUOTE));
615
522
  // Send multiple characters
616
- act(() => {
617
- stdin.pressKey({
618
- name: undefined,
619
- ctrl: false,
620
- meta: false,
621
- shift: false,
622
- paste: false,
623
- sequence: 'p',
624
- });
625
- });
626
- act(() => {
627
- stdin.pressKey({
628
- name: undefined,
629
- ctrl: false,
630
- meta: false,
631
- shift: false,
632
- paste: false,
633
- sequence: 'a',
634
- });
635
- });
636
- act(() => {
637
- stdin.pressKey({
638
- name: undefined,
639
- ctrl: false,
640
- meta: false,
641
- shift: false,
642
- paste: false,
643
- sequence: 't',
644
- });
645
- });
646
- act(() => {
647
- stdin.pressKey({
648
- name: undefined,
649
- ctrl: false,
650
- meta: false,
651
- shift: false,
652
- paste: false,
653
- sequence: 'h',
654
- });
655
- });
523
+ act(() => stdin.write('p'));
524
+ act(() => stdin.write('a'));
525
+ act(() => stdin.write('t'));
526
+ act(() => stdin.write('h'));
656
527
  // Characters should not be immediately broadcast
657
528
  expect(keyHandler).not.toHaveBeenCalled();
658
529
  // Fast-forward to completion timeout
@@ -686,20 +557,19 @@ describe('Kitty Sequence Parsing', () => {
686
557
  });
687
558
  // Terminals to test
688
559
  const terminals = ['iTerm2', 'Ghostty', 'MacTerminal', 'VSCodeTerminal'];
689
- // Key mappings: letter -> [keycode, accented character, shouldHaveMeta]
690
- // Note: µ (mu) is sent with meta:false on iTerm2/VSCode
560
+ // Key mappings: letter -> [keycode, accented character]
691
561
  const keys = {
692
- a: [97, 'å', true],
693
- o: [111, 'ø', true],
694
- m: [109, 'µ', false],
562
+ a: [97, 'å'],
563
+ o: [111, 'ø'],
564
+ m: [109, 'µ'],
695
565
  };
696
- it.each(terminals.flatMap((terminal) => Object.entries(keys).map(([key, [keycode, accentedChar, shouldHaveMeta]]) => {
566
+ it.each(terminals.flatMap((terminal) => Object.entries(keys).map(([key, [keycode, accentedChar]]) => {
697
567
  if (terminal === 'Ghostty') {
698
568
  // Ghostty uses kitty protocol sequences
699
569
  return {
700
570
  terminal,
701
571
  key,
702
- kittySequence: `\x1b[${keycode};3u`,
572
+ chunk: `\x1b[${keycode};3u`,
703
573
  expected: {
704
574
  name: key,
705
575
  ctrl: false,
@@ -716,14 +586,7 @@ describe('Kitty Sequence Parsing', () => {
716
586
  terminal,
717
587
  key,
718
588
  kitty: false,
719
- input: {
720
- sequence: `\x1b${key}`,
721
- name: key,
722
- ctrl: false,
723
- meta: true,
724
- shift: false,
725
- paste: false,
726
- },
589
+ chunk: `\x1b${key}`,
727
590
  expected: {
728
591
  sequence: `\x1b${key}`,
729
592
  name: key,
@@ -736,18 +599,12 @@ describe('Kitty Sequence Parsing', () => {
736
599
  }
737
600
  else {
738
601
  // iTerm2 and VSCode send accented characters (å, ø, µ)
739
- // Note: µ comes with meta:false but gets converted to m with meta:true
602
+ // Note: µ (mu) is sent with meta:false on iTerm2/VSCode but
603
+ // gets converted to m with meta:true
740
604
  return {
741
605
  terminal,
742
606
  key,
743
- input: {
744
- name: key,
745
- ctrl: false,
746
- meta: shouldHaveMeta,
747
- shift: false,
748
- paste: false,
749
- sequence: accentedChar,
750
- },
607
+ chunk: accentedChar,
751
608
  expected: {
752
609
  name: key,
753
610
  ctrl: false,
@@ -758,19 +615,14 @@ describe('Kitty Sequence Parsing', () => {
758
615
  },
759
616
  };
760
617
  }
761
- })))('should handle Alt+$key in $terminal', ({ kittySequence, input, expected, kitty = true, }) => {
618
+ })))('should handle Alt+$key in $terminal', ({ chunk, expected, kitty = true, }) => {
762
619
  const keyHandler = vi.fn();
763
620
  const testWrapper = ({ children }) => (_jsx(KeypressProvider, { kittyProtocolEnabled: kitty, children: children }));
764
621
  const { result } = renderHook(() => useKeypressContext(), {
765
622
  wrapper: testWrapper,
766
623
  });
767
624
  act(() => result.current.subscribe(keyHandler));
768
- if (kittySequence) {
769
- act(() => stdin.sendKittySequence(kittySequence));
770
- }
771
- else if (input) {
772
- act(() => stdin.pressKey(input));
773
- }
625
+ act(() => stdin.write(chunk));
774
626
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining(expected));
775
627
  });
776
628
  describe('Backslash key handling', () => {
@@ -784,14 +636,7 @@ describe('Kitty Sequence Parsing', () => {
784
636
  const keyHandler = vi.fn();
785
637
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
786
638
  act(() => result.current.subscribe(keyHandler));
787
- act(() => stdin.pressKey({
788
- name: undefined,
789
- ctrl: false,
790
- meta: false,
791
- shift: false,
792
- paste: false,
793
- sequence: '\\',
794
- }));
639
+ act(() => stdin.write('\\'));
795
640
  // Advance timers to trigger the backslash timeout
796
641
  act(() => {
797
642
  vi.runAllTimers();
@@ -805,57 +650,30 @@ describe('Kitty Sequence Parsing', () => {
805
650
  it('should timeout and flush incomplete kitty sequences after 50ms', async () => {
806
651
  const keyHandler = vi.fn();
807
652
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
808
- act(() => {
809
- result.current.subscribe(keyHandler);
810
- });
811
- // Send incomplete kitty sequence
812
- act(() => {
813
- stdin.pressKey({
814
- name: undefined,
815
- ctrl: false,
816
- meta: false,
817
- shift: false,
818
- paste: false,
819
- sequence: '\x1b[1;',
820
- });
821
- });
653
+ act(() => result.current.subscribe(keyHandler));
654
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
822
655
  // Should not broadcast immediately
823
656
  expect(keyHandler).not.toHaveBeenCalled();
824
657
  // Advance time just before timeout
825
- act(() => {
826
- vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5);
827
- });
658
+ act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS - 5));
828
659
  // Still shouldn't broadcast
829
660
  expect(keyHandler).not.toHaveBeenCalled();
830
661
  // Advance past timeout
831
- act(() => {
832
- vi.advanceTimersByTime(10);
833
- });
662
+ act(() => vi.advanceTimersByTime(10));
834
663
  // Should now broadcast the incomplete sequence as regular input
835
664
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
836
665
  name: '',
837
- sequence: '\x1b[1;',
666
+ sequence: INCOMPLETE_KITTY_SEQUENCE,
838
667
  paste: false,
839
668
  }));
840
669
  });
841
670
  it('should immediately flush non-kitty CSI sequences', async () => {
842
671
  const keyHandler = vi.fn();
843
672
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
844
- act(() => {
845
- result.current.subscribe(keyHandler);
846
- });
673
+ act(() => result.current.subscribe(keyHandler));
847
674
  // Send a CSI sequence that doesn't match kitty patterns
848
675
  // ESC[m is SGR reset, not a kitty sequence
849
- act(() => {
850
- stdin.pressKey({
851
- name: undefined,
852
- ctrl: false,
853
- meta: false,
854
- shift: false,
855
- paste: false,
856
- sequence: '\x1b[m',
857
- });
858
- });
676
+ act(() => stdin.write('\x1b[m'));
859
677
  // Should broadcast immediately as it's not a valid kitty pattern
860
678
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
861
679
  name: '',
@@ -866,20 +684,9 @@ describe('Kitty Sequence Parsing', () => {
866
684
  it('should parse valid kitty sequences immediately when complete', async () => {
867
685
  const keyHandler = vi.fn();
868
686
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
869
- act(() => {
870
- result.current.subscribe(keyHandler);
871
- });
687
+ act(() => result.current.subscribe(keyHandler));
872
688
  // Send complete kitty sequence for Ctrl+A
873
- act(() => {
874
- stdin.pressKey({
875
- name: undefined,
876
- ctrl: false,
877
- meta: false,
878
- shift: false,
879
- paste: false,
880
- sequence: '\x1b[97;5u',
881
- });
882
- });
689
+ act(() => stdin.write('\x1b[97;5u'));
883
690
  // Should parse and broadcast immediately
884
691
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
885
692
  name: 'a',
@@ -890,20 +697,9 @@ describe('Kitty Sequence Parsing', () => {
890
697
  it('should handle batched kitty sequences correctly', async () => {
891
698
  const keyHandler = vi.fn();
892
699
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
893
- act(() => {
894
- result.current.subscribe(keyHandler);
895
- });
896
- // Send multiple kitty sequences at once
897
- act(() => {
898
- stdin.pressKey({
899
- name: undefined,
900
- ctrl: false,
901
- meta: false,
902
- shift: false,
903
- paste: false,
904
- sequence: '\x1b[97;5u\x1b[98;5u', // Ctrl+a followed by Ctrl+b
905
- });
906
- });
700
+ act(() => result.current.subscribe(keyHandler));
701
+ // Send Ctrl+a followed by Ctrl+b
702
+ act(() => stdin.write('\x1b[97;5u\x1b[98;5u'));
907
703
  // Should parse both sequences
908
704
  expect(keyHandler).toHaveBeenCalledTimes(2);
909
705
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
@@ -920,35 +716,12 @@ describe('Kitty Sequence Parsing', () => {
920
716
  it('should clear kitty buffer and timeout on Ctrl+C', async () => {
921
717
  const keyHandler = vi.fn();
922
718
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
923
- act(() => {
924
- result.current.subscribe(keyHandler);
925
- });
926
- // Send incomplete kitty sequence
927
- act(() => {
928
- stdin.pressKey({
929
- name: undefined,
930
- ctrl: false,
931
- meta: false,
932
- shift: false,
933
- paste: false,
934
- sequence: '\x1b[1;',
935
- });
936
- });
719
+ act(() => result.current.subscribe(keyHandler));
720
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
937
721
  // Press Ctrl+C
938
- act(() => {
939
- stdin.pressKey({
940
- name: 'c',
941
- ctrl: true,
942
- meta: false,
943
- shift: false,
944
- paste: false,
945
- sequence: '\x03',
946
- });
947
- });
722
+ act(() => stdin.write('\x03'));
948
723
  // Advance past timeout
949
- act(() => {
950
- vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10);
951
- });
724
+ act(() => vi.advanceTimersByTime(KITTY_SEQUENCE_TIMEOUT_MS + 10));
952
725
  // Should only have received Ctrl+C, not the incomplete sequence
953
726
  expect(keyHandler).toHaveBeenCalledTimes(1);
954
727
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
@@ -959,20 +732,10 @@ describe('Kitty Sequence Parsing', () => {
959
732
  it('should handle mixed valid and invalid sequences', async () => {
960
733
  const keyHandler = vi.fn();
961
734
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
962
- act(() => {
963
- result.current.subscribe(keyHandler);
964
- });
735
+ act(() => result.current.subscribe(keyHandler));
965
736
  // Send valid kitty sequence followed by invalid CSI
966
- act(() => {
967
- stdin.pressKey({
968
- name: undefined,
969
- ctrl: false,
970
- meta: false,
971
- shift: false,
972
- paste: false,
973
- sequence: '\x1b[13u\x1b[!', // Valid enter, then invalid sequence
974
- });
975
- });
737
+ // Valid enter, then invalid sequence
738
+ act(() => stdin.write('\x1b[13u\x1b[!'));
976
739
  // Should parse valid sequence and flush invalid immediately
977
740
  expect(keyHandler).toHaveBeenCalledTimes(2);
978
741
  expect(keyHandler).toHaveBeenNthCalledWith(1, expect.objectContaining({
@@ -989,20 +752,9 @@ describe('Kitty Sequence Parsing', () => {
989
752
  const { result } = renderHook(() => useKeypressContext(), {
990
753
  wrapper: ({ children }) => wrapper({ children, kittyProtocolEnabled: false }),
991
754
  });
992
- act(() => {
993
- result.current.subscribe(keyHandler);
994
- });
755
+ act(() => result.current.subscribe(keyHandler));
995
756
  // Send what would be a kitty sequence
996
- act(() => {
997
- stdin.pressKey({
998
- name: undefined,
999
- ctrl: false,
1000
- meta: false,
1001
- shift: false,
1002
- paste: false,
1003
- sequence: '\x1b[13u',
1004
- });
1005
- });
757
+ act(() => stdin.write('\x1b[13u'));
1006
758
  // Should pass through without parsing
1007
759
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1008
760
  sequence: '\x1b[13u',
@@ -1038,113 +790,56 @@ describe('Kitty Sequence Parsing', () => {
1038
790
  it('should reset timeout when new input arrives', async () => {
1039
791
  const keyHandler = vi.fn();
1040
792
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
1041
- act(() => {
1042
- result.current.subscribe(keyHandler);
1043
- });
793
+ act(() => result.current.subscribe(keyHandler));
1044
794
  // Start incomplete sequence
1045
- act(() => {
1046
- stdin.pressKey({
1047
- name: undefined,
1048
- ctrl: false,
1049
- meta: false,
1050
- shift: false,
1051
- paste: false,
1052
- sequence: '\x1b[1',
1053
- });
1054
- });
795
+ act(() => stdin.write('\x1b[97;13'));
1055
796
  // Advance time partway
1056
- act(() => {
1057
- vi.advanceTimersByTime(30);
1058
- });
797
+ act(() => vi.advanceTimersByTime(30));
1059
798
  // Add more to sequence
1060
- act(() => {
1061
- stdin.pressKey({
1062
- name: undefined,
1063
- ctrl: false,
1064
- meta: false,
1065
- shift: false,
1066
- paste: false,
1067
- sequence: '3',
1068
- });
1069
- });
799
+ act(() => stdin.write('5'));
1070
800
  // Advance time from the first timeout point
1071
- act(() => {
1072
- vi.advanceTimersByTime(25);
1073
- });
801
+ act(() => vi.advanceTimersByTime(25));
1074
802
  // Should not have timed out yet (timeout restarted)
1075
803
  expect(keyHandler).not.toHaveBeenCalled();
1076
804
  // Complete the sequence
1077
- act(() => {
1078
- stdin.pressKey({
1079
- name: undefined,
1080
- ctrl: false,
1081
- meta: false,
1082
- shift: false,
1083
- paste: false,
1084
- sequence: 'u',
1085
- });
1086
- });
805
+ act(() => stdin.write('u'));
1087
806
  // Should now parse as complete enter key
1088
807
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1089
- name: 'return',
808
+ name: 'a',
1090
809
  kittyProtocol: true,
1091
810
  }));
1092
811
  });
1093
812
  it('should flush incomplete kitty sequence on FOCUS_IN event', async () => {
1094
813
  const keyHandler = vi.fn();
1095
814
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
1096
- act(() => {
1097
- result.current.subscribe(keyHandler);
1098
- });
1099
- // Send incomplete kitty sequence
1100
- act(() => {
1101
- stdin.pressKey({
1102
- sequence: '\x1b[1;',
1103
- });
1104
- });
815
+ act(() => result.current.subscribe(keyHandler));
816
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
1105
817
  // Incomplete sequence should be buffered, not broadcast
1106
818
  expect(keyHandler).not.toHaveBeenCalled();
1107
819
  // Send FOCUS_IN event
1108
- const FOCUS_IN = '\x1b[I';
1109
- act(() => {
1110
- stdin.pressKey({
1111
- sequence: FOCUS_IN,
1112
- });
1113
- });
820
+ act(() => stdin.write('\x1b[I'));
1114
821
  // The buffered sequence should be flushed
1115
822
  expect(keyHandler).toHaveBeenCalledTimes(1);
1116
823
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1117
824
  name: '',
1118
- sequence: '\x1b[1;',
825
+ sequence: INCOMPLETE_KITTY_SEQUENCE,
1119
826
  paste: false,
1120
827
  }));
1121
828
  });
1122
829
  it('should flush incomplete kitty sequence on FOCUS_OUT event', async () => {
1123
830
  const keyHandler = vi.fn();
1124
831
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
1125
- act(() => {
1126
- result.current.subscribe(keyHandler);
1127
- });
1128
- // Send incomplete kitty sequence
1129
- act(() => {
1130
- stdin.pressKey({
1131
- sequence: '\x1b[1;',
1132
- });
1133
- });
832
+ act(() => result.current.subscribe(keyHandler));
833
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
1134
834
  // Incomplete sequence should be buffered, not broadcast
1135
835
  expect(keyHandler).not.toHaveBeenCalled();
1136
836
  // Send FOCUS_OUT event
1137
- const FOCUS_OUT = '\x1b[O';
1138
- act(() => {
1139
- stdin.pressKey({
1140
- sequence: FOCUS_OUT,
1141
- });
1142
- });
837
+ act(() => stdin.write('\x1b[O'));
1143
838
  // The buffered sequence should be flushed
1144
839
  expect(keyHandler).toHaveBeenCalledTimes(1);
1145
840
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1146
841
  name: '',
1147
- sequence: '\x1b[1;',
842
+ sequence: INCOMPLETE_KITTY_SEQUENCE,
1148
843
  paste: false,
1149
844
  }));
1150
845
  });
@@ -1152,39 +847,27 @@ describe('Kitty Sequence Parsing', () => {
1152
847
  vi.useFakeTimers();
1153
848
  const keyHandler = vi.fn();
1154
849
  const { result } = renderHook(() => useKeypressContext(), { wrapper });
1155
- act(() => {
1156
- result.current.subscribe(keyHandler);
1157
- });
1158
- // Send incomplete kitty sequence
1159
- act(() => {
1160
- stdin.pressKey({
1161
- sequence: '\x1b[1;',
1162
- });
1163
- });
850
+ act(() => result.current.subscribe(keyHandler));
851
+ act(() => stdin.write(INCOMPLETE_KITTY_SEQUENCE));
1164
852
  // Incomplete sequence should be buffered, not broadcast
1165
853
  expect(keyHandler).not.toHaveBeenCalled();
1166
854
  // Send paste start sequence
1167
- const PASTE_MODE_PREFIX = `\x1b[200~`;
1168
- act(() => {
1169
- stdin.emit('data', Buffer.from(PASTE_MODE_PREFIX));
1170
- });
855
+ act(() => stdin.write(`\x1b[200~`));
1171
856
  // The buffered sequence should be flushed
1172
857
  expect(keyHandler).toHaveBeenCalledTimes(1);
1173
858
  expect(keyHandler).toHaveBeenCalledWith(expect.objectContaining({
1174
859
  name: '',
1175
- sequence: '\x1b[1;',
860
+ sequence: INCOMPLETE_KITTY_SEQUENCE,
1176
861
  paste: false,
1177
862
  }));
1178
863
  // Now send some paste content and end paste to make sure paste still works
1179
864
  const pastedText = 'hello';
1180
865
  const PASTE_MODE_SUFFIX = `\x1b[201~`;
1181
866
  act(() => {
1182
- stdin.emit('data', Buffer.from(pastedText));
1183
- stdin.emit('data', Buffer.from(PASTE_MODE_SUFFIX));
1184
- });
1185
- act(() => {
1186
- vi.runAllTimers();
867
+ stdin.write(pastedText);
868
+ stdin.write(PASTE_MODE_SUFFIX);
1187
869
  });
870
+ act(() => vi.runAllTimers());
1188
871
  // The paste event should be broadcast
1189
872
  expect(keyHandler).toHaveBeenCalledTimes(2);
1190
873
  expect(keyHandler).toHaveBeenNthCalledWith(2, expect.objectContaining({