@google/gemini-cli 0.12.0-nightly.20251023.c4c0c0d1 → 0.12.0-preview.0

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