@jecruz/merc-tui 0.1.3 → 0.1.6

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 (77) hide show
  1. package/README.md +14 -1
  2. package/dist/autocomplete.d.ts +1 -0
  3. package/dist/autocomplete.d.ts.map +1 -1
  4. package/dist/autocomplete.js +85 -24
  5. package/dist/autocomplete.js.map +1 -1
  6. package/dist/components/box.d.ts +1 -0
  7. package/dist/components/box.d.ts.map +1 -1
  8. package/dist/components/box.js +6 -1
  9. package/dist/components/box.js.map +1 -1
  10. package/dist/components/editor.d.ts.map +1 -1
  11. package/dist/components/editor.js +80 -115
  12. package/dist/components/editor.js.map +1 -1
  13. package/dist/components/image.d.ts +12 -0
  14. package/dist/components/image.d.ts.map +1 -1
  15. package/dist/components/image.js +45 -6
  16. package/dist/components/image.js.map +1 -1
  17. package/dist/components/input.d.ts.map +1 -1
  18. package/dist/components/input.js +7 -55
  19. package/dist/components/input.js.map +1 -1
  20. package/dist/components/markdown.d.ts +1 -0
  21. package/dist/components/markdown.d.ts.map +1 -1
  22. package/dist/components/markdown.js +18 -6
  23. package/dist/components/markdown.js.map +1 -1
  24. package/dist/components/settings-list.d.ts.map +1 -1
  25. package/dist/components/settings-list.js +4 -7
  26. package/dist/components/settings-list.js.map +1 -1
  27. package/dist/components/text.d.ts +1 -0
  28. package/dist/components/text.d.ts.map +1 -1
  29. package/dist/components/text.js +11 -5
  30. package/dist/components/text.js.map +1 -1
  31. package/dist/fuzzy.d.ts.map +1 -1
  32. package/dist/fuzzy.js +23 -22
  33. package/dist/fuzzy.js.map +1 -1
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/keys.d.ts.map +1 -1
  39. package/dist/keys.js +9 -1
  40. package/dist/keys.js.map +1 -1
  41. package/dist/native-modifier-digests.d.ts +6 -0
  42. package/dist/native-modifier-digests.d.ts.map +1 -0
  43. package/dist/native-modifier-digests.js +6 -0
  44. package/dist/native-modifier-digests.js.map +1 -0
  45. package/dist/native-modifiers.d.ts +11 -0
  46. package/dist/native-modifiers.d.ts.map +1 -0
  47. package/dist/native-modifiers.js +98 -0
  48. package/dist/native-modifiers.js.map +1 -0
  49. package/dist/stdin-buffer.d.ts +25 -2
  50. package/dist/stdin-buffer.d.ts.map +1 -1
  51. package/dist/stdin-buffer.js +176 -3
  52. package/dist/stdin-buffer.js.map +1 -1
  53. package/dist/terminal-image.d.ts +10 -0
  54. package/dist/terminal-image.d.ts.map +1 -1
  55. package/dist/terminal-image.js +102 -17
  56. package/dist/terminal-image.js.map +1 -1
  57. package/dist/terminal.d.ts +17 -0
  58. package/dist/terminal.d.ts.map +1 -1
  59. package/dist/terminal.js +165 -56
  60. package/dist/terminal.js.map +1 -1
  61. package/dist/tui.d.ts +28 -7
  62. package/dist/tui.d.ts.map +1 -1
  63. package/dist/tui.js +322 -125
  64. package/dist/tui.js.map +1 -1
  65. package/dist/utils.d.ts +16 -1
  66. package/dist/utils.d.ts.map +1 -1
  67. package/dist/utils.js +115 -29
  68. package/dist/utils.js.map +1 -1
  69. package/dist/word-navigation.d.ts +25 -0
  70. package/dist/word-navigation.d.ts.map +1 -0
  71. package/dist/word-navigation.js +96 -0
  72. package/dist/word-navigation.js.map +1 -0
  73. package/native/darwin/README.md +13 -0
  74. package/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  75. package/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  76. package/native/darwin/prebuilds/manifest.json +24 -0
  77. package/package.json +4 -1
@@ -1,3 +1,11 @@
1
+ export declare function isAppleTerminalSession(): boolean;
2
+ export declare function normalizeAppleTerminalInput(data: string, isAppleTerminal: boolean, isShiftPressed: boolean): string;
3
+ /**
4
+ * Refresh terminal dimensions on POSIX platforms by sending SIGWINCH to this process.
5
+ * Best-effort: some environments (restricted seccomp or LSM policies) return EACCES
6
+ * for `kill(2)`; in that case the dimensions refresh is skipped rather than crashing.
7
+ */
8
+ export declare function refreshTerminalDimensions(): void;
1
9
  /**
2
10
  * Minimal terminal interface for TUI
3
11
  */
@@ -24,6 +32,12 @@ export interface Terminal {
24
32
  setTitle(title: string): void;
25
33
  setProgress(active: boolean): void;
26
34
  }
35
+ /**
36
+ * Resolve how long to wait for the rest of an escape sequence before
37
+ * dispatching a lone ESC as the Escape key. Legacy Alt+key input is ESC plus
38
+ * another byte, so high-latency transports need a longer reassembly window.
39
+ */
40
+ export declare function resolveEscapeTimeoutMs(env?: NodeJS.ProcessEnv): number;
27
41
  /**
28
42
  * Real terminal using process.stdin/stdout
29
43
  */
@@ -33,6 +47,8 @@ export declare class ProcessTerminal implements Terminal {
33
47
  private resizeHandler?;
34
48
  private _kittyProtocolActive;
35
49
  private _modifyOtherKeysActive;
50
+ private bracketedPasteActive;
51
+ private progressActive;
36
52
  private stdinBuffer?;
37
53
  private stdinDataHandler?;
38
54
  private progressInterval?;
@@ -70,6 +86,7 @@ export declare class ProcessTerminal implements Terminal {
70
86
  private enableWindowsVTInput;
71
87
  drainInput(maxMs?: number, idleMs?: number): Promise<void>;
72
88
  stop(): void;
89
+ private cleanup;
73
90
  write(data: string): void;
74
91
  get columns(): number;
75
92
  get rows(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAcA;;GAEG;AACH,MAAM,WAAW,QAAQ;IAExB,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAGnE,IAAI,IAAI,IAAI,CAAC;IAEb;;;;;OAKG;IACH,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG3D,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAG1B,IAAI,OAAO,IAAI,MAAM,CAAC;IACtB,IAAI,IAAI,IAAI,MAAM,CAAC;IAGnB,IAAI,mBAAmB,IAAI,OAAO,CAAC;IAGnC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAG5B,UAAU,IAAI,IAAI,CAAC;IACnB,UAAU,IAAI,IAAI,CAAC;IAGnB,SAAS,IAAI,IAAI,CAAC;IAClB,eAAe,IAAI,IAAI,CAAC;IACxB,WAAW,IAAI,IAAI,CAAC;IAGpB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAG9B,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC/C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAC,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAC,CAAa;IACnC,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,WAAW,CAAC,CAAc;IAClC,OAAO,CAAC,gBAAgB,CAAC,CAAyB;IAClD,OAAO,CAAC,gBAAgB,CAAC,CAAiC;IAC1D,OAAO,CAAC,YAAY,CAaf;IAEL,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAED,IAAI,qBAAqB,IAAI,OAAO,CAEnC;IAED,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAkCjE;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAwCxB;;;;;;;OAOG;IACH,OAAO,CAAC,2BAA2B;IAMnC,OAAO,CAAC,2BAA2B;IAenC,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,sBAAsB;IAQ9B;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAuBtB,UAAU,CAAC,KAAK,SAAO,EAAE,MAAM,SAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCzD;IAED,IAAI,IAAI,IAAI,CA0CX;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CASxB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAS1B;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,SAAS,IAAI,IAAI,CAEhB;IAED,eAAe,IAAI,IAAI,CAEtB;IAED,WAAW,IAAI,IAAI,CAElB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5B;IAED,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAcjC;IAED,OAAO,CAAC,qBAAqB;CAM7B","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { setKittyProtocolActive } from \"./keys.js\";\nimport { StdinBuffer } from \"./stdin-buffer.js\";\n\nconst cjsRequire = createRequire(import.meta.url);\n\nconst TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;\nconst TERMINAL_PROGRESS_ACTIVE_SEQUENCE = \"\\x1b]9;4;3\\x07\";\nconst TERMINAL_PROGRESS_CLEAR_SEQUENCE = \"\\x1b]9;4;0;\\x07\";\nconst DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS = 7;\nconst KITTY_KEYBOARD_PROTOCOL_QUERY = `\\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u\\x1b[?u\\x1b[c`;\n\n/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t/**\n\t * Drain stdin before exiting to prevent Kitty key release events from\n\t * leaking to the parent shell over slow SSH connections.\n\t * @param maxMs - Maximum time to drain (default: 1000ms)\n\t * @param idleMs - Exit early if no input arrives within this time (default: 50ms)\n\t */\n\tdrainInput(maxMs?: number, idleMs?: number): Promise<void>;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Whether Kitty keyboard protocol is active\n\tget kittyProtocolActive(): boolean;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n\n\t// Title operations\n\tsetTitle(title: string): void; // Set terminal window title\n\n\t// Progress indicator (OSC 9;4)\n\tsetProgress(active: boolean): void;\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\tprivate _kittyProtocolActive = false;\n\tprivate _modifyOtherKeysActive = false;\n\tprivate stdinBuffer?: StdinBuffer;\n\tprivate stdinDataHandler?: (data: string) => void;\n\tprivate progressInterval?: ReturnType<typeof setInterval>;\n\tprivate writeLogPath = (() => {\n\t\tconst env = process.env.PI_TUI_WRITE_LOG || \"\";\n\t\tif (!env) return \"\";\n\t\ttry {\n\t\t\tif (fs.statSync(env).isDirectory()) {\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}_${String(now.getHours()).padStart(2, \"0\")}-${String(now.getMinutes()).padStart(2, \"0\")}-${String(now.getSeconds()).padStart(2, \"0\")}`;\n\t\t\t\treturn path.join(env, `tui-${ts}-${process.pid}.log`);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not an existing directory - use as-is (file path)\n\t\t}\n\t\treturn env;\n\t})();\n\n\tget kittyProtocolActive(): boolean {\n\t\treturn this._kittyProtocolActive;\n\t}\n\n\tget modifyOtherKeysActive(): boolean {\n\t\treturn this._modifyOtherKeysActive;\n\t}\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\n\t\t// Set up resize handler immediately\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\n\t\t// Refresh terminal dimensions - they may be stale after suspend/resume\n\t\t// (SIGWINCH is lost while process is stopped). Unix only.\n\t\tif (process.platform !== \"win32\") {\n\t\t\tprocess.kill(process.pid, \"SIGWINCH\");\n\t\t}\n\n\t\t// On Windows, enable ENABLE_VIRTUAL_TERMINAL_INPUT so the console sends\n\t\t// VT escape sequences (e.g. \\x1b[Z for Shift+Tab) instead of raw console\n\t\t// events that lose modifier information. Must run AFTER setRawMode(true)\n\t\t// since that resets console mode flags.\n\t\tthis.enableWindowsVTInput();\n\n\t\t// Query Kitty keyboard protocol and fall back to modifyOtherKeys when a\n\t\t// device-attributes response indicates Kitty is unavailable.\n\t\t// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n\t\tthis.queryAndEnableKittyProtocol();\n\t}\n\n\t/**\n\t * Set up StdinBuffer to split batched input into individual sequences.\n\t * This ensures components receive single events, making matchesKey/isKeyRelease work correctly.\n\t *\n\t * Also watches for Kitty protocol response and enables it when detected.\n\t * This is done here (after stdinBuffer parsing) rather than on raw stdin\n\t * to handle the case where the response arrives split across multiple events.\n\t */\n\tprivate setupStdinBuffer(): void {\n\t\tthis.stdinBuffer = new StdinBuffer({ timeout: 10 });\n\n\t\t// Forward individual sequences to the input handler\n\t\tthis.stdinBuffer.on(\"data\", (sequence) => {\n\t\t\tif (!this._kittyProtocolActive) {\n\t\t\t\tconst kittyFlags = sequence.match(/^\\x1b\\[\\?(\\d+)u$/);\n\t\t\t\tif (kittyFlags) {\n\t\t\t\t\tif (Number.parseInt(kittyFlags[1]!, 10) !== 0) {\n\t\t\t\t\t\tthis.enableKittyKeyboardProtocol();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.enableModifyOtherKeys();\n\t\t\t\t\t}\n\t\t\t\t\treturn; // Don't forward protocol response to TUI\n\t\t\t\t}\n\n\t\t\t\tif (/^\\x1b\\[\\?[\\d;]*c$/.test(sequence)) {\n\t\t\t\t\tthis.enableModifyOtherKeys();\n\t\t\t\t\treturn; // Don't forward protocol response to TUI\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.inputHandler) {\n\t\t\t\tthis.inputHandler(sequence);\n\t\t\t}\n\t\t});\n\n\t\t// Re-wrap paste content with bracketed paste markers for existing editor handling\n\t\tthis.stdinBuffer.on(\"paste\", (content) => {\n\t\t\tif (this.inputHandler) {\n\t\t\t\tthis.inputHandler(`\\x1b[200~${content}\\x1b[201~`);\n\t\t\t}\n\t\t});\n\n\t\t// Handler that pipes stdin data through the buffer\n\t\tthis.stdinDataHandler = (data: string) => {\n\t\t\tthis.stdinBuffer!.process(data);\n\t\t};\n\t}\n\n\t/**\n\t * Query terminal for Kitty keyboard protocol support and enable if available.\n\t *\n\t * Sends both the Kitty capability query and the device-attributes query.\n\t * If the terminal responds with Kitty flags, we enable Kitty keyboard\n\t * protocol. Otherwise a device-attributes response activates xterm\n\t * modifyOtherKeys mode 2.\n\t */\n\tprivate queryAndEnableKittyProtocol(): void {\n\t\tthis.setupStdinBuffer();\n\t\tprocess.stdin.on(\"data\", this.stdinDataHandler!);\n\t\tprocess.stdout.write(KITTY_KEYBOARD_PROTOCOL_QUERY);\n\t}\n\n\tprivate enableKittyKeyboardProtocol(): void {\n\t\tif (this._kittyProtocolActive) {\n\t\t\treturn;\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\t\tthis._kittyProtocolActive = true;\n\t\tsetKittyProtocolActive(true);\n\t\t// Enable Kitty keyboard protocol (push flags)\n\t\t// Flag 1 = disambiguate escape codes\n\t\t// Flag 2 = report event types (press/repeat/release)\n\t\t// Flag 4 = report alternate keys (shifted key, base layout key)\n\t\t// Base layout key enables shortcuts to work with non-Latin keyboard layouts\n\t\tprocess.stdout.write(`\\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u`);\n\t}\n\n\tprivate enableModifyOtherKeys(): void {\n\t\tif (this._kittyProtocolActive || this._modifyOtherKeysActive) {\n\t\t\treturn;\n\t\t}\n\t\tprocess.stdout.write(\"\\x1b[>4;2m\");\n\t\tthis._modifyOtherKeysActive = true;\n\t}\n\n\tprivate disableModifyOtherKeys(): void {\n\t\tif (!this._modifyOtherKeysActive) {\n\t\t\treturn;\n\t\t}\n\t\tprocess.stdout.write(\"\\x1b[>4;0m\");\n\t\tthis._modifyOtherKeysActive = false;\n\t}\n\n\t/**\n\t * On Windows, add ENABLE_VIRTUAL_TERMINAL_INPUT (0x0200) to the stdin\n\t * console handle so the terminal sends VT sequences for modified keys\n\t * (e.g. \\x1b[Z for Shift+Tab). Without this, libuv's ReadConsoleInputW\n\t * discards modifier state and Shift+Tab arrives as plain \\t.\n\t */\n\tprivate enableWindowsVTInput(): void {\n\t\tif (process.platform !== \"win32\") return;\n\t\ttry {\n\t\t\t// Dynamic require to avoid bundling koffi's 74MB of cross-platform\n\t\t\t// native binaries into every compiled binary. Koffi is only needed\n\t\t\t// on Windows for VT input support.\n\t\t\tconst koffi = cjsRequire(\"koffi\");\n\t\t\tconst k32 = koffi.load(\"kernel32.dll\");\n\t\t\tconst GetStdHandle = k32.func(\"void* __stdcall GetStdHandle(int)\");\n\t\t\tconst GetConsoleMode = k32.func(\"bool __stdcall GetConsoleMode(void*, _Out_ uint32_t*)\");\n\t\t\tconst SetConsoleMode = k32.func(\"bool __stdcall SetConsoleMode(void*, uint32_t)\");\n\n\t\t\tconst STD_INPUT_HANDLE = -10;\n\t\t\tconst ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;\n\t\t\tconst handle = GetStdHandle(STD_INPUT_HANDLE);\n\t\t\tconst mode = new Uint32Array(1);\n\t\t\tGetConsoleMode(handle, mode);\n\t\t\tSetConsoleMode(handle, mode[0]! | ENABLE_VIRTUAL_TERMINAL_INPUT);\n\t\t} catch {\n\t\t\t// koffi not available — Shift+Tab won't be distinguishable from Tab\n\t\t}\n\t}\n\n\tasync drainInput(maxMs = 1000, idleMs = 50): Promise<void> {\n\t\tif (this._kittyProtocolActive) {\n\t\t\t// Disable Kitty keyboard protocol first so any late key releases\n\t\t\t// do not generate new Kitty escape sequences.\n\t\t\tprocess.stdout.write(\"\\x1b[<u\");\n\t\t\tthis._kittyProtocolActive = false;\n\t\t\tsetKittyProtocolActive(false);\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\n\t\tconst previousHandler = this.inputHandler;\n\t\tthis.inputHandler = undefined;\n\n\t\tlet lastDataTime = Date.now();\n\t\tconst onData = () => {\n\t\t\tlastDataTime = Date.now();\n\t\t};\n\n\t\tprocess.stdin.on(\"data\", onData);\n\t\tconst endTime = Date.now() + maxMs;\n\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tconst timeLeft = endTime - now;\n\t\t\t\tif (timeLeft <= 0) break;\n\t\t\t\tif (now - lastDataTime >= idleMs) break;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, Math.min(idleMs, timeLeft)));\n\t\t\t}\n\t\t} finally {\n\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\tthis.inputHandler = previousHandler;\n\t\t}\n\t}\n\n\tstop(): void {\n\t\tif (this.clearProgressInterval()) {\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);\n\t\t}\n\n\t\t// Disable bracketed paste mode\n\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\n\t\t// Disable Kitty keyboard protocol if not already done by drainInput()\n\t\tif (this._kittyProtocolActive) {\n\t\t\tprocess.stdout.write(\"\\x1b[<u\");\n\t\t\tthis._kittyProtocolActive = false;\n\t\t\tsetKittyProtocolActive(false);\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\n\t\t// Clean up StdinBuffer\n\t\tif (this.stdinBuffer) {\n\t\t\tthis.stdinBuffer.destroy();\n\t\t\tthis.stdinBuffer = undefined;\n\t\t}\n\n\t\t// Remove event handlers\n\t\tif (this.stdinDataHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.stdinDataHandler);\n\t\t\tthis.stdinDataHandler = undefined;\n\t\t}\n\t\tthis.inputHandler = undefined;\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Pause stdin to prevent any buffered input (e.g., Ctrl+D) from being\n\t\t// re-interpreted after raw mode is disabled. This fixes a race condition\n\t\t// where Ctrl+D could close the parent shell over SSH.\n\t\tprocess.stdin.pause();\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t\tif (this.writeLogPath) {\n\t\t\ttry {\n\t\t\t\tfs.appendFileSync(this.writeLogPath, data, { encoding: \"utf8\" });\n\t\t\t} catch {\n\t\t\t\t// Ignore logging errors\n\t\t\t}\n\t\t}\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || Number(process.env.COLUMNS) || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || Number(process.env.LINES) || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n\n\tsetTitle(title: string): void {\n\t\t// OSC 0;title BEL - set terminal window title\n\t\tprocess.stdout.write(`\\x1b]0;${title}\\x07`);\n\t}\n\n\tsetProgress(active: boolean): void {\n\t\tif (active) {\n\t\t\t// OSC 9;4;3 - indeterminate progress\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);\n\t\t\tif (!this.progressInterval) {\n\t\t\t\tthis.progressInterval = setInterval(() => {\n\t\t\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);\n\t\t\t\t}, TERMINAL_PROGRESS_KEEPALIVE_MS);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.clearProgressInterval();\n\t\t\t// OSC 9;4;0 - clear progress\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);\n\t\t}\n\t}\n\n\tprivate clearProgressInterval(): boolean {\n\t\tif (!this.progressInterval) return false;\n\t\tclearInterval(this.progressInterval);\n\t\tthis.progressInterval = undefined;\n\t\treturn true;\n\t}\n}\n"]}
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { setKittyProtocolActive } from \"./keys.js\";\nimport { isNativeModifierPressed } from \"./native-modifiers.js\";\nimport { StdinBuffer } from \"./stdin-buffer.js\";\n\nconst cjsRequire = createRequire(import.meta.url);\n\nconst TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;\nconst TERMINAL_PROGRESS_ACTIVE_SEQUENCE = \"\\x1b]9;4;3\\x07\";\nconst TERMINAL_PROGRESS_CLEAR_SEQUENCE = \"\\x1b]9;4;0\\x07\";\nconst DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS = 7;\nconst KITTY_KEYBOARD_PROTOCOL_QUERY = `\\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u\\x1b[?u\\x1b[c`;\nconst APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE = \"\\x1b[13;2u\";\n\nexport function isAppleTerminalSession(): boolean {\n\treturn (\n\t\tprocess.platform === \"darwin\" &&\n\t\tprocess.env.TERM_PROGRAM === \"Apple_Terminal\" &&\n\t\tprocess.env.SSH_CONNECTION === undefined &&\n\t\tprocess.env.SSH_TTY === undefined\n\t);\n}\n\nexport function normalizeAppleTerminalInput(data: string, isAppleTerminal: boolean, isShiftPressed: boolean): string {\n\tif (isAppleTerminal && data === \"\\r\" && isShiftPressed) return APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE;\n\treturn data;\n}\n\n/**\n * Refresh terminal dimensions on POSIX platforms by sending SIGWINCH to this process.\n * Best-effort: some environments (restricted seccomp or LSM policies) return EACCES\n * for `kill(2)`; in that case the dimensions refresh is skipped rather than crashing.\n */\nexport function refreshTerminalDimensions(): void {\n\tif (process.platform === \"win32\" || process.pid <= 0) return;\n\ttry {\n\t\tprocess.kill(process.pid, \"SIGWINCH\");\n\t} catch {\n\t\t// Signal delivery not permitted in this environment; ignore.\n\t}\n}\n\n/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t/**\n\t * Drain stdin before exiting to prevent Kitty key release events from\n\t * leaking to the parent shell over slow SSH connections.\n\t * @param maxMs - Maximum time to drain (default: 1000ms)\n\t * @param idleMs - Exit early if no input arrives within this time (default: 50ms)\n\t */\n\tdrainInput(maxMs?: number, idleMs?: number): Promise<void>;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Whether Kitty keyboard protocol is active\n\tget kittyProtocolActive(): boolean;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n\n\t// Title operations\n\tsetTitle(title: string): void; // Set terminal window title\n\n\t// Progress indicator (OSC 9;4)\n\tsetProgress(active: boolean): void;\n}\n\nconst DEFAULT_ESCAPE_TIMEOUT_MS = 10;\nconst DEFAULT_SSH_ESCAPE_TIMEOUT_MS = 100;\n\n/**\n * Resolve how long to wait for the rest of an escape sequence before\n * dispatching a lone ESC as the Escape key. Legacy Alt+key input is ESC plus\n * another byte, so high-latency transports need a longer reassembly window.\n */\nexport function resolveEscapeTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {\n\tconst configured = Number(env.PI_TUI_ESC_TIMEOUT);\n\tif (Number.isFinite(configured) && configured > 0) {\n\t\treturn configured;\n\t}\n\tif (env.SSH_CONNECTION || env.SSH_TTY) {\n\t\treturn DEFAULT_SSH_ESCAPE_TIMEOUT_MS;\n\t}\n\treturn DEFAULT_ESCAPE_TIMEOUT_MS;\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\tprivate _kittyProtocolActive = false;\n\tprivate _modifyOtherKeysActive = false;\n\tprivate bracketedPasteActive = false;\n\tprivate progressActive = false;\n\tprivate stdinBuffer?: StdinBuffer;\n\tprivate stdinDataHandler?: (data: string) => void;\n\tprivate progressInterval?: ReturnType<typeof setInterval>;\n\tprivate writeLogPath = (() => {\n\t\tconst env = process.env.PI_TUI_WRITE_LOG || \"\";\n\t\tif (!env) return \"\";\n\t\ttry {\n\t\t\tif (fs.statSync(env).isDirectory()) {\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}_${String(now.getHours()).padStart(2, \"0\")}-${String(now.getMinutes()).padStart(2, \"0\")}-${String(now.getSeconds()).padStart(2, \"0\")}`;\n\t\t\t\treturn path.join(env, `tui-${ts}-${process.pid}.log`);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not an existing directory - use as-is (file path)\n\t\t}\n\t\treturn env;\n\t})();\n\n\tget kittyProtocolActive(): boolean {\n\t\treturn this._kittyProtocolActive;\n\t}\n\n\tget modifyOtherKeysActive(): boolean {\n\t\treturn this._modifyOtherKeysActive;\n\t}\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\ttry {\n\t\t\t// Save previous state and enable raw mode\n\t\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\t\tif (process.stdin.setRawMode) {\n\t\t\t\tprocess.stdin.setRawMode(true);\n\t\t\t}\n\t\t\tprocess.stdin.setEncoding(\"utf8\");\n\n\t\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\t\t\tthis.bracketedPasteActive = true;\n\n\t\t\t// Set up resize handler immediately\n\t\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\n\t\t\t// Refresh terminal dimensions - they may be stale after suspend/resume\n\t\t\t// (SIGWINCH is lost while process is stopped). Unix only, best-effort.\n\t\t\trefreshTerminalDimensions();\n\n\t\t\t// On Windows, enable ENABLE_VIRTUAL_TERMINAL_INPUT so the console sends\n\t\t\t// VT escape sequences (e.g. \\x1b[Z for Shift+Tab) instead of raw console\n\t\t\t// events that lose modifier information. Must run AFTER setRawMode(true)\n\t\t\t// since that resets console mode flags.\n\t\t\tthis.enableWindowsVTInput();\n\n\t\t\t// Query Kitty keyboard protocol and fall back to modifyOtherKeys when a\n\t\t\t// device-attributes response indicates Kitty is unavailable.\n\t\t\t// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n\t\t\tthis.queryAndEnableKittyProtocol();\n\t\t\tprocess.stdin.resume();\n\t\t} catch (error) {\n\t\t\tlet cleanupError: unknown;\n\t\t\ttry {\n\t\t\t\tthis.cleanup(false);\n\t\t\t} catch (cleanupFailure) {\n\t\t\t\tcleanupError = cleanupFailure;\n\t\t\t}\n\t\t\tif (cleanupError !== undefined) {\n\t\t\t\tthrow new AggregateError([error, cleanupError], \"Terminal startup and rollback both failed\");\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Set up StdinBuffer to split batched input into individual sequences.\n\t * This ensures components receive single events, making matchesKey/isKeyRelease work correctly.\n\t *\n\t * Also watches for Kitty protocol response and enables it when detected.\n\t * This is done here (after stdinBuffer parsing) rather than on raw stdin\n\t * to handle the case where the response arrives split across multiple events.\n\t */\n\tprivate setupStdinBuffer(): void {\n\t\tthis.stdinBuffer = new StdinBuffer({ escapeTimeout: resolveEscapeTimeoutMs() });\n\n\t\t// Forward individual sequences to the input handler\n\t\tthis.stdinBuffer.on(\"data\", (sequence) => {\n\t\t\tif (!this._kittyProtocolActive) {\n\t\t\t\tconst kittyFlags = sequence.match(/^\\x1b\\[\\?(\\d+)u$/);\n\t\t\t\tif (kittyFlags) {\n\t\t\t\t\tif (Number.parseInt(kittyFlags[1]!, 10) !== 0) {\n\t\t\t\t\t\tthis.enableKittyKeyboardProtocol();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.enableModifyOtherKeys();\n\t\t\t\t\t}\n\t\t\t\t\treturn; // Don't forward protocol response to TUI\n\t\t\t\t}\n\n\t\t\t\tif (/^\\x1b\\[\\?[\\d;]*c$/.test(sequence)) {\n\t\t\t\t\tthis.enableModifyOtherKeys();\n\t\t\t\t\treturn; // Don't forward protocol response to TUI\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.inputHandler) {\n\t\t\t\tconst isAppleTerminal = sequence === \"\\r\" && isAppleTerminalSession();\n\t\t\t\tthis.inputHandler(\n\t\t\t\t\tnormalizeAppleTerminalInput(\n\t\t\t\t\t\tsequence,\n\t\t\t\t\t\tisAppleTerminal,\n\t\t\t\t\t\tisAppleTerminal && isNativeModifierPressed(\"shift\"),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\t// Re-wrap paste content with bracketed paste markers for existing editor handling\n\t\tthis.stdinBuffer.on(\"paste\", (content) => {\n\t\t\tif (this.inputHandler) {\n\t\t\t\tthis.inputHandler(`\\x1b[200~${content}\\x1b[201~`);\n\t\t\t}\n\t\t});\n\n\t\t// Handler that pipes stdin data through the buffer\n\t\tthis.stdinDataHandler = (data: string) => {\n\t\t\tthis.stdinBuffer!.process(data);\n\t\t};\n\t}\n\n\t/**\n\t * Query terminal for Kitty keyboard protocol support and enable if available.\n\t *\n\t * Sends both the Kitty capability query and the device-attributes query.\n\t * If the terminal responds with Kitty flags, we enable Kitty keyboard\n\t * protocol. Otherwise a device-attributes response activates xterm\n\t * modifyOtherKeys mode 2.\n\t */\n\tprivate queryAndEnableKittyProtocol(): void {\n\t\tthis.setupStdinBuffer();\n\t\tprocess.stdin.on(\"data\", this.stdinDataHandler!);\n\t\tprocess.stdout.write(KITTY_KEYBOARD_PROTOCOL_QUERY);\n\t}\n\n\tprivate enableKittyKeyboardProtocol(): void {\n\t\tif (this._kittyProtocolActive) {\n\t\t\treturn;\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\t\tthis._kittyProtocolActive = true;\n\t\tsetKittyProtocolActive(true);\n\t\t// Enable Kitty keyboard protocol (push flags)\n\t\t// Flag 1 = disambiguate escape codes\n\t\t// Flag 2 = report event types (press/repeat/release)\n\t\t// Flag 4 = report alternate keys (shifted key, base layout key)\n\t\t// Base layout key enables shortcuts to work with non-Latin keyboard layouts\n\t\tprocess.stdout.write(`\\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u`);\n\t}\n\n\tprivate enableModifyOtherKeys(): void {\n\t\tif (this._kittyProtocolActive || this._modifyOtherKeysActive) {\n\t\t\treturn;\n\t\t}\n\t\tprocess.stdout.write(\"\\x1b[>4;2m\");\n\t\tthis._modifyOtherKeysActive = true;\n\t}\n\n\tprivate disableModifyOtherKeys(): void {\n\t\tif (!this._modifyOtherKeysActive) {\n\t\t\treturn;\n\t\t}\n\t\tprocess.stdout.write(\"\\x1b[>4;0m\");\n\t\tthis._modifyOtherKeysActive = false;\n\t}\n\n\t/**\n\t * On Windows, add ENABLE_VIRTUAL_TERMINAL_INPUT (0x0200) to the stdin\n\t * console handle so the terminal sends VT sequences for modified keys\n\t * (e.g. \\x1b[Z for Shift+Tab). Without this, libuv's ReadConsoleInputW\n\t * discards modifier state and Shift+Tab arrives as plain \\t.\n\t */\n\tprivate enableWindowsVTInput(): void {\n\t\tif (process.platform !== \"win32\") return;\n\t\ttry {\n\t\t\t// Dynamic require to avoid bundling koffi's 74MB of cross-platform\n\t\t\t// native binaries into every compiled binary. Koffi is only needed\n\t\t\t// on Windows for VT input support.\n\t\t\tconst koffi = cjsRequire(\"koffi\");\n\t\t\tconst k32 = koffi.load(\"kernel32.dll\");\n\t\t\tconst GetStdHandle = k32.func(\"void* __stdcall GetStdHandle(int)\");\n\t\t\tconst GetConsoleMode = k32.func(\"bool __stdcall GetConsoleMode(void*, _Out_ uint32_t*)\");\n\t\t\tconst SetConsoleMode = k32.func(\"bool __stdcall SetConsoleMode(void*, uint32_t)\");\n\n\t\t\tconst STD_INPUT_HANDLE = -10;\n\t\t\tconst ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;\n\t\t\tconst handle = GetStdHandle(STD_INPUT_HANDLE);\n\t\t\tconst mode = new Uint32Array(1);\n\t\t\tGetConsoleMode(handle, mode);\n\t\t\tSetConsoleMode(handle, mode[0]! | ENABLE_VIRTUAL_TERMINAL_INPUT);\n\t\t} catch {\n\t\t\t// koffi not available — Shift+Tab won't be distinguishable from Tab\n\t\t}\n\t}\n\n\tasync drainInput(maxMs = 1000, idleMs = 50): Promise<void> {\n\t\tif (this._kittyProtocolActive) {\n\t\t\t// Disable Kitty keyboard protocol first so any late key releases\n\t\t\t// do not generate new Kitty escape sequences.\n\t\t\tprocess.stdout.write(\"\\x1b[<u\");\n\t\t\tthis._kittyProtocolActive = false;\n\t\t\tsetKittyProtocolActive(false);\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\n\t\tconst previousHandler = this.inputHandler;\n\t\tthis.inputHandler = undefined;\n\n\t\tlet lastDataTime = Date.now();\n\t\tconst onData = () => {\n\t\t\tlastDataTime = Date.now();\n\t\t};\n\n\t\tprocess.stdin.on(\"data\", onData);\n\t\tconst endTime = Date.now() + maxMs;\n\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tconst timeLeft = endTime - now;\n\t\t\t\tif (timeLeft <= 0) break;\n\t\t\t\tif (now - lastDataTime >= idleMs) break;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, Math.min(idleMs, timeLeft)));\n\t\t\t}\n\t\t} finally {\n\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\tthis.inputHandler = previousHandler;\n\t\t}\n\t}\n\n\tstop(): void {\n\t\tthis.cleanup(true);\n\t}\n\n\tprivate cleanup(pauseStdin: boolean): void {\n\t\tlet firstError: unknown;\n\t\tlet hasError = false;\n\t\tconst attempt = (operation: () => void): void => {\n\t\t\ttry {\n\t\t\t\toperation();\n\t\t\t} catch (error) {\n\t\t\t\tif (!hasError) {\n\t\t\t\t\tfirstError = error;\n\t\t\t\t\thasError = true;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tattempt(() => this.clearProgressInterval());\n\t\tif (this.progressActive) {\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);\n\t\t\t\tthis.progressActive = false;\n\t\t\t});\n\t\t}\n\t\tif (this.bracketedPasteActive) {\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\t\t\t\tthis.bracketedPasteActive = false;\n\t\t\t});\n\t\t}\n\n\t\tif (this._kittyProtocolActive) {\n\t\t\tlet kittyProtocolDisabled = false;\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.write(\"\\x1b[<u\");\n\t\t\t\tkittyProtocolDisabled = true;\n\t\t\t});\n\t\t\tif (kittyProtocolDisabled) {\n\t\t\t\tthis._kittyProtocolActive = false;\n\t\t\t\tattempt(() => setKittyProtocolActive(false));\n\t\t\t}\n\t\t}\n\t\tif (this._modifyOtherKeysActive) {\n\t\t\tlet modifyOtherKeysDisabled = false;\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.write(\"\\x1b[>4;0m\");\n\t\t\t\tmodifyOtherKeysDisabled = true;\n\t\t\t});\n\t\t\tif (modifyOtherKeysDisabled) {\n\t\t\t\tthis._modifyOtherKeysActive = false;\n\t\t\t}\n\t\t}\n\n\t\tconst stdinBuffer = this.stdinBuffer;\n\t\tif (stdinBuffer) {\n\t\t\tattempt(() => {\n\t\t\t\tstdinBuffer.destroy();\n\t\t\t\tif (this.stdinBuffer === stdinBuffer) this.stdinBuffer = undefined;\n\t\t\t});\n\t\t}\n\n\t\tconst stdinDataHandler = this.stdinDataHandler;\n\t\tif (stdinDataHandler) {\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdin.removeListener(\"data\", stdinDataHandler);\n\t\t\t\tif (this.stdinDataHandler === stdinDataHandler) this.stdinDataHandler = undefined;\n\t\t\t});\n\t\t}\n\t\tthis.inputHandler = undefined;\n\t\tconst resizeHandler = this.resizeHandler;\n\t\tif (resizeHandler) {\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.removeListener(\"resize\", resizeHandler);\n\t\t\t\tif (this.resizeHandler === resizeHandler) this.resizeHandler = undefined;\n\t\t\t});\n\t\t}\n\n\t\tif (pauseStdin) attempt(() => process.stdin.pause());\n\t\tif (process.stdin.setRawMode) attempt(() => process.stdin.setRawMode!(this.wasRaw));\n\t\tif (hasError) throw firstError;\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t\tif (this.writeLogPath) {\n\t\t\ttry {\n\t\t\t\tfs.appendFileSync(this.writeLogPath, data, { encoding: \"utf8\" });\n\t\t\t} catch {\n\t\t\t\t// Ignore logging errors\n\t\t\t}\n\t\t}\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || Number(process.env.COLUMNS) || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || Number(process.env.LINES) || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n\n\tsetTitle(title: string): void {\n\t\t// OSC 0;title BEL - set terminal window title\n\t\tprocess.stdout.write(`\\x1b]0;${title}\\x07`);\n\t}\n\n\tsetProgress(active: boolean): void {\n\t\tif (active) {\n\t\t\t// OSC 9;4;3 - indeterminate progress\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);\n\t\t\tthis.progressActive = true;\n\t\t\tif (!this.progressInterval) {\n\t\t\t\tthis.progressInterval = setInterval(() => {\n\t\t\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);\n\t\t\t\t}, TERMINAL_PROGRESS_KEEPALIVE_MS);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.clearProgressInterval();\n\t\t\t// OSC 9;4;0 - clear progress\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);\n\t\t\tthis.progressActive = false;\n\t\t}\n\t}\n\n\tprivate clearProgressInterval(): void {\n\t\tif (!this.progressInterval) return;\n\t\tclearInterval(this.progressInterval);\n\t\tthis.progressInterval = undefined;\n\t}\n}\n"]}
package/dist/terminal.js CHANGED
@@ -2,13 +2,58 @@ import * as fs from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as path from "node:path";
4
4
  import { setKittyProtocolActive } from "./keys.js";
5
+ import { isNativeModifierPressed } from "./native-modifiers.js";
5
6
  import { StdinBuffer } from "./stdin-buffer.js";
6
7
  const cjsRequire = createRequire(import.meta.url);
7
8
  const TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;
8
9
  const TERMINAL_PROGRESS_ACTIVE_SEQUENCE = "\x1b]9;4;3\x07";
9
- const TERMINAL_PROGRESS_CLEAR_SEQUENCE = "\x1b]9;4;0;\x07";
10
+ const TERMINAL_PROGRESS_CLEAR_SEQUENCE = "\x1b]9;4;0\x07";
10
11
  const DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS = 7;
11
12
  const KITTY_KEYBOARD_PROTOCOL_QUERY = `\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u\x1b[?u\x1b[c`;
13
+ const APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE = "\x1b[13;2u";
14
+ export function isAppleTerminalSession() {
15
+ return (process.platform === "darwin" &&
16
+ process.env.TERM_PROGRAM === "Apple_Terminal" &&
17
+ process.env.SSH_CONNECTION === undefined &&
18
+ process.env.SSH_TTY === undefined);
19
+ }
20
+ export function normalizeAppleTerminalInput(data, isAppleTerminal, isShiftPressed) {
21
+ if (isAppleTerminal && data === "\r" && isShiftPressed)
22
+ return APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE;
23
+ return data;
24
+ }
25
+ /**
26
+ * Refresh terminal dimensions on POSIX platforms by sending SIGWINCH to this process.
27
+ * Best-effort: some environments (restricted seccomp or LSM policies) return EACCES
28
+ * for `kill(2)`; in that case the dimensions refresh is skipped rather than crashing.
29
+ */
30
+ export function refreshTerminalDimensions() {
31
+ if (process.platform === "win32" || process.pid <= 0)
32
+ return;
33
+ try {
34
+ process.kill(process.pid, "SIGWINCH");
35
+ }
36
+ catch {
37
+ // Signal delivery not permitted in this environment; ignore.
38
+ }
39
+ }
40
+ const DEFAULT_ESCAPE_TIMEOUT_MS = 10;
41
+ const DEFAULT_SSH_ESCAPE_TIMEOUT_MS = 100;
42
+ /**
43
+ * Resolve how long to wait for the rest of an escape sequence before
44
+ * dispatching a lone ESC as the Escape key. Legacy Alt+key input is ESC plus
45
+ * another byte, so high-latency transports need a longer reassembly window.
46
+ */
47
+ export function resolveEscapeTimeoutMs(env = process.env) {
48
+ const configured = Number(env.PI_TUI_ESC_TIMEOUT);
49
+ if (Number.isFinite(configured) && configured > 0) {
50
+ return configured;
51
+ }
52
+ if (env.SSH_CONNECTION || env.SSH_TTY) {
53
+ return DEFAULT_SSH_ESCAPE_TIMEOUT_MS;
54
+ }
55
+ return DEFAULT_ESCAPE_TIMEOUT_MS;
56
+ }
12
57
  /**
13
58
  * Real terminal using process.stdin/stdout
14
59
  */
@@ -18,6 +63,8 @@ export class ProcessTerminal {
18
63
  resizeHandler;
19
64
  _kittyProtocolActive = false;
20
65
  _modifyOtherKeysActive = false;
66
+ bracketedPasteActive = false;
67
+ progressActive = false;
21
68
  stdinBuffer;
22
69
  stdinDataHandler;
23
70
  progressInterval;
@@ -46,31 +93,45 @@ export class ProcessTerminal {
46
93
  start(onInput, onResize) {
47
94
  this.inputHandler = onInput;
48
95
  this.resizeHandler = onResize;
49
- // Save previous state and enable raw mode
50
- this.wasRaw = process.stdin.isRaw || false;
51
- if (process.stdin.setRawMode) {
52
- process.stdin.setRawMode(true);
96
+ try {
97
+ // Save previous state and enable raw mode
98
+ this.wasRaw = process.stdin.isRaw || false;
99
+ if (process.stdin.setRawMode) {
100
+ process.stdin.setRawMode(true);
101
+ }
102
+ process.stdin.setEncoding("utf8");
103
+ // Enable bracketed paste mode - terminal will wrap pastes in \x1b[200~ ... \x1b[201~
104
+ process.stdout.write("\x1b[?2004h");
105
+ this.bracketedPasteActive = true;
106
+ // Set up resize handler immediately
107
+ process.stdout.on("resize", this.resizeHandler);
108
+ // Refresh terminal dimensions - they may be stale after suspend/resume
109
+ // (SIGWINCH is lost while process is stopped). Unix only, best-effort.
110
+ refreshTerminalDimensions();
111
+ // On Windows, enable ENABLE_VIRTUAL_TERMINAL_INPUT so the console sends
112
+ // VT escape sequences (e.g. \x1b[Z for Shift+Tab) instead of raw console
113
+ // events that lose modifier information. Must run AFTER setRawMode(true)
114
+ // since that resets console mode flags.
115
+ this.enableWindowsVTInput();
116
+ // Query Kitty keyboard protocol and fall back to modifyOtherKeys when a
117
+ // device-attributes response indicates Kitty is unavailable.
118
+ // See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
119
+ this.queryAndEnableKittyProtocol();
120
+ process.stdin.resume();
53
121
  }
54
- process.stdin.setEncoding("utf8");
55
- process.stdin.resume();
56
- // Enable bracketed paste mode - terminal will wrap pastes in \x1b[200~ ... \x1b[201~
57
- process.stdout.write("\x1b[?2004h");
58
- // Set up resize handler immediately
59
- process.stdout.on("resize", this.resizeHandler);
60
- // Refresh terminal dimensions - they may be stale after suspend/resume
61
- // (SIGWINCH is lost while process is stopped). Unix only.
62
- if (process.platform !== "win32") {
63
- process.kill(process.pid, "SIGWINCH");
122
+ catch (error) {
123
+ let cleanupError;
124
+ try {
125
+ this.cleanup(false);
126
+ }
127
+ catch (cleanupFailure) {
128
+ cleanupError = cleanupFailure;
129
+ }
130
+ if (cleanupError !== undefined) {
131
+ throw new AggregateError([error, cleanupError], "Terminal startup and rollback both failed");
132
+ }
133
+ throw error;
64
134
  }
65
- // On Windows, enable ENABLE_VIRTUAL_TERMINAL_INPUT so the console sends
66
- // VT escape sequences (e.g. \x1b[Z for Shift+Tab) instead of raw console
67
- // events that lose modifier information. Must run AFTER setRawMode(true)
68
- // since that resets console mode flags.
69
- this.enableWindowsVTInput();
70
- // Query Kitty keyboard protocol and fall back to modifyOtherKeys when a
71
- // device-attributes response indicates Kitty is unavailable.
72
- // See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
73
- this.queryAndEnableKittyProtocol();
74
135
  }
75
136
  /**
76
137
  * Set up StdinBuffer to split batched input into individual sequences.
@@ -81,7 +142,7 @@ export class ProcessTerminal {
81
142
  * to handle the case where the response arrives split across multiple events.
82
143
  */
83
144
  setupStdinBuffer() {
84
- this.stdinBuffer = new StdinBuffer({ timeout: 10 });
145
+ this.stdinBuffer = new StdinBuffer({ escapeTimeout: resolveEscapeTimeoutMs() });
85
146
  // Forward individual sequences to the input handler
86
147
  this.stdinBuffer.on("data", (sequence) => {
87
148
  if (!this._kittyProtocolActive) {
@@ -101,7 +162,8 @@ export class ProcessTerminal {
101
162
  }
102
163
  }
103
164
  if (this.inputHandler) {
104
- this.inputHandler(sequence);
165
+ const isAppleTerminal = sequence === "\r" && isAppleTerminalSession();
166
+ this.inputHandler(normalizeAppleTerminalInput(sequence, isAppleTerminal, isAppleTerminal && isNativeModifierPressed("shift")));
105
167
  }
106
168
  });
107
169
  // Re-wrap paste content with bracketed paste markers for existing editor handling
@@ -219,41 +281,87 @@ export class ProcessTerminal {
219
281
  }
220
282
  }
221
283
  stop() {
222
- if (this.clearProgressInterval()) {
223
- process.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);
284
+ this.cleanup(true);
285
+ }
286
+ cleanup(pauseStdin) {
287
+ let firstError;
288
+ let hasError = false;
289
+ const attempt = (operation) => {
290
+ try {
291
+ operation();
292
+ }
293
+ catch (error) {
294
+ if (!hasError) {
295
+ firstError = error;
296
+ hasError = true;
297
+ }
298
+ }
299
+ };
300
+ attempt(() => this.clearProgressInterval());
301
+ if (this.progressActive) {
302
+ attempt(() => {
303
+ process.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);
304
+ this.progressActive = false;
305
+ });
306
+ }
307
+ if (this.bracketedPasteActive) {
308
+ attempt(() => {
309
+ process.stdout.write("\x1b[?2004l");
310
+ this.bracketedPasteActive = false;
311
+ });
224
312
  }
225
- // Disable bracketed paste mode
226
- process.stdout.write("\x1b[?2004l");
227
- // Disable Kitty keyboard protocol if not already done by drainInput()
228
313
  if (this._kittyProtocolActive) {
229
- process.stdout.write("\x1b[<u");
230
- this._kittyProtocolActive = false;
231
- setKittyProtocolActive(false);
314
+ let kittyProtocolDisabled = false;
315
+ attempt(() => {
316
+ process.stdout.write("\x1b[<u");
317
+ kittyProtocolDisabled = true;
318
+ });
319
+ if (kittyProtocolDisabled) {
320
+ this._kittyProtocolActive = false;
321
+ attempt(() => setKittyProtocolActive(false));
322
+ }
232
323
  }
233
- this.disableModifyOtherKeys();
234
- // Clean up StdinBuffer
235
- if (this.stdinBuffer) {
236
- this.stdinBuffer.destroy();
237
- this.stdinBuffer = undefined;
324
+ if (this._modifyOtherKeysActive) {
325
+ let modifyOtherKeysDisabled = false;
326
+ attempt(() => {
327
+ process.stdout.write("\x1b[>4;0m");
328
+ modifyOtherKeysDisabled = true;
329
+ });
330
+ if (modifyOtherKeysDisabled) {
331
+ this._modifyOtherKeysActive = false;
332
+ }
238
333
  }
239
- // Remove event handlers
240
- if (this.stdinDataHandler) {
241
- process.stdin.removeListener("data", this.stdinDataHandler);
242
- this.stdinDataHandler = undefined;
334
+ const stdinBuffer = this.stdinBuffer;
335
+ if (stdinBuffer) {
336
+ attempt(() => {
337
+ stdinBuffer.destroy();
338
+ if (this.stdinBuffer === stdinBuffer)
339
+ this.stdinBuffer = undefined;
340
+ });
243
341
  }
244
- this.inputHandler = undefined;
245
- if (this.resizeHandler) {
246
- process.stdout.removeListener("resize", this.resizeHandler);
247
- this.resizeHandler = undefined;
342
+ const stdinDataHandler = this.stdinDataHandler;
343
+ if (stdinDataHandler) {
344
+ attempt(() => {
345
+ process.stdin.removeListener("data", stdinDataHandler);
346
+ if (this.stdinDataHandler === stdinDataHandler)
347
+ this.stdinDataHandler = undefined;
348
+ });
248
349
  }
249
- // Pause stdin to prevent any buffered input (e.g., Ctrl+D) from being
250
- // re-interpreted after raw mode is disabled. This fixes a race condition
251
- // where Ctrl+D could close the parent shell over SSH.
252
- process.stdin.pause();
253
- // Restore raw mode state
254
- if (process.stdin.setRawMode) {
255
- process.stdin.setRawMode(this.wasRaw);
350
+ this.inputHandler = undefined;
351
+ const resizeHandler = this.resizeHandler;
352
+ if (resizeHandler) {
353
+ attempt(() => {
354
+ process.stdout.removeListener("resize", resizeHandler);
355
+ if (this.resizeHandler === resizeHandler)
356
+ this.resizeHandler = undefined;
357
+ });
256
358
  }
359
+ if (pauseStdin)
360
+ attempt(() => process.stdin.pause());
361
+ if (process.stdin.setRawMode)
362
+ attempt(() => process.stdin.setRawMode(this.wasRaw));
363
+ if (hasError)
364
+ throw firstError;
257
365
  }
258
366
  write(data) {
259
367
  process.stdout.write(data);
@@ -306,6 +414,7 @@ export class ProcessTerminal {
306
414
  if (active) {
307
415
  // OSC 9;4;3 - indeterminate progress
308
416
  process.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);
417
+ this.progressActive = true;
309
418
  if (!this.progressInterval) {
310
419
  this.progressInterval = setInterval(() => {
311
420
  process.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);
@@ -316,14 +425,14 @@ export class ProcessTerminal {
316
425
  this.clearProgressInterval();
317
426
  // OSC 9;4;0 - clear progress
318
427
  process.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);
428
+ this.progressActive = false;
319
429
  }
320
430
  }
321
431
  clearProgressInterval() {
322
432
  if (!this.progressInterval)
323
- return false;
433
+ return;
324
434
  clearInterval(this.progressInterval);
325
435
  this.progressInterval = undefined;
326
- return true;
327
436
  }
328
437
  }
329
438
  //# sourceMappingURL=terminal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { setKittyProtocolActive } from \"./keys.js\";\nimport { StdinBuffer } from \"./stdin-buffer.js\";\n\nconst cjsRequire = createRequire(import.meta.url);\n\nconst TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;\nconst TERMINAL_PROGRESS_ACTIVE_SEQUENCE = \"\\x1b]9;4;3\\x07\";\nconst TERMINAL_PROGRESS_CLEAR_SEQUENCE = \"\\x1b]9;4;0;\\x07\";\nconst DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS = 7;\nconst KITTY_KEYBOARD_PROTOCOL_QUERY = `\\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u\\x1b[?u\\x1b[c`;\n\n/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t/**\n\t * Drain stdin before exiting to prevent Kitty key release events from\n\t * leaking to the parent shell over slow SSH connections.\n\t * @param maxMs - Maximum time to drain (default: 1000ms)\n\t * @param idleMs - Exit early if no input arrives within this time (default: 50ms)\n\t */\n\tdrainInput(maxMs?: number, idleMs?: number): Promise<void>;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Whether Kitty keyboard protocol is active\n\tget kittyProtocolActive(): boolean;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n\n\t// Title operations\n\tsetTitle(title: string): void; // Set terminal window title\n\n\t// Progress indicator (OSC 9;4)\n\tsetProgress(active: boolean): void;\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\tprivate _kittyProtocolActive = false;\n\tprivate _modifyOtherKeysActive = false;\n\tprivate stdinBuffer?: StdinBuffer;\n\tprivate stdinDataHandler?: (data: string) => void;\n\tprivate progressInterval?: ReturnType<typeof setInterval>;\n\tprivate writeLogPath = (() => {\n\t\tconst env = process.env.PI_TUI_WRITE_LOG || \"\";\n\t\tif (!env) return \"\";\n\t\ttry {\n\t\t\tif (fs.statSync(env).isDirectory()) {\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}_${String(now.getHours()).padStart(2, \"0\")}-${String(now.getMinutes()).padStart(2, \"0\")}-${String(now.getSeconds()).padStart(2, \"0\")}`;\n\t\t\t\treturn path.join(env, `tui-${ts}-${process.pid}.log`);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not an existing directory - use as-is (file path)\n\t\t}\n\t\treturn env;\n\t})();\n\n\tget kittyProtocolActive(): boolean {\n\t\treturn this._kittyProtocolActive;\n\t}\n\n\tget modifyOtherKeysActive(): boolean {\n\t\treturn this._modifyOtherKeysActive;\n\t}\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\t// Save previous state and enable raw mode\n\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(true);\n\t\t}\n\t\tprocess.stdin.setEncoding(\"utf8\");\n\t\tprocess.stdin.resume();\n\n\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\n\t\t// Set up resize handler immediately\n\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\n\t\t// Refresh terminal dimensions - they may be stale after suspend/resume\n\t\t// (SIGWINCH is lost while process is stopped). Unix only.\n\t\tif (process.platform !== \"win32\") {\n\t\t\tprocess.kill(process.pid, \"SIGWINCH\");\n\t\t}\n\n\t\t// On Windows, enable ENABLE_VIRTUAL_TERMINAL_INPUT so the console sends\n\t\t// VT escape sequences (e.g. \\x1b[Z for Shift+Tab) instead of raw console\n\t\t// events that lose modifier information. Must run AFTER setRawMode(true)\n\t\t// since that resets console mode flags.\n\t\tthis.enableWindowsVTInput();\n\n\t\t// Query Kitty keyboard protocol and fall back to modifyOtherKeys when a\n\t\t// device-attributes response indicates Kitty is unavailable.\n\t\t// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n\t\tthis.queryAndEnableKittyProtocol();\n\t}\n\n\t/**\n\t * Set up StdinBuffer to split batched input into individual sequences.\n\t * This ensures components receive single events, making matchesKey/isKeyRelease work correctly.\n\t *\n\t * Also watches for Kitty protocol response and enables it when detected.\n\t * This is done here (after stdinBuffer parsing) rather than on raw stdin\n\t * to handle the case where the response arrives split across multiple events.\n\t */\n\tprivate setupStdinBuffer(): void {\n\t\tthis.stdinBuffer = new StdinBuffer({ timeout: 10 });\n\n\t\t// Forward individual sequences to the input handler\n\t\tthis.stdinBuffer.on(\"data\", (sequence) => {\n\t\t\tif (!this._kittyProtocolActive) {\n\t\t\t\tconst kittyFlags = sequence.match(/^\\x1b\\[\\?(\\d+)u$/);\n\t\t\t\tif (kittyFlags) {\n\t\t\t\t\tif (Number.parseInt(kittyFlags[1]!, 10) !== 0) {\n\t\t\t\t\t\tthis.enableKittyKeyboardProtocol();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.enableModifyOtherKeys();\n\t\t\t\t\t}\n\t\t\t\t\treturn; // Don't forward protocol response to TUI\n\t\t\t\t}\n\n\t\t\t\tif (/^\\x1b\\[\\?[\\d;]*c$/.test(sequence)) {\n\t\t\t\t\tthis.enableModifyOtherKeys();\n\t\t\t\t\treturn; // Don't forward protocol response to TUI\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.inputHandler) {\n\t\t\t\tthis.inputHandler(sequence);\n\t\t\t}\n\t\t});\n\n\t\t// Re-wrap paste content with bracketed paste markers for existing editor handling\n\t\tthis.stdinBuffer.on(\"paste\", (content) => {\n\t\t\tif (this.inputHandler) {\n\t\t\t\tthis.inputHandler(`\\x1b[200~${content}\\x1b[201~`);\n\t\t\t}\n\t\t});\n\n\t\t// Handler that pipes stdin data through the buffer\n\t\tthis.stdinDataHandler = (data: string) => {\n\t\t\tthis.stdinBuffer!.process(data);\n\t\t};\n\t}\n\n\t/**\n\t * Query terminal for Kitty keyboard protocol support and enable if available.\n\t *\n\t * Sends both the Kitty capability query and the device-attributes query.\n\t * If the terminal responds with Kitty flags, we enable Kitty keyboard\n\t * protocol. Otherwise a device-attributes response activates xterm\n\t * modifyOtherKeys mode 2.\n\t */\n\tprivate queryAndEnableKittyProtocol(): void {\n\t\tthis.setupStdinBuffer();\n\t\tprocess.stdin.on(\"data\", this.stdinDataHandler!);\n\t\tprocess.stdout.write(KITTY_KEYBOARD_PROTOCOL_QUERY);\n\t}\n\n\tprivate enableKittyKeyboardProtocol(): void {\n\t\tif (this._kittyProtocolActive) {\n\t\t\treturn;\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\t\tthis._kittyProtocolActive = true;\n\t\tsetKittyProtocolActive(true);\n\t\t// Enable Kitty keyboard protocol (push flags)\n\t\t// Flag 1 = disambiguate escape codes\n\t\t// Flag 2 = report event types (press/repeat/release)\n\t\t// Flag 4 = report alternate keys (shifted key, base layout key)\n\t\t// Base layout key enables shortcuts to work with non-Latin keyboard layouts\n\t\tprocess.stdout.write(`\\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u`);\n\t}\n\n\tprivate enableModifyOtherKeys(): void {\n\t\tif (this._kittyProtocolActive || this._modifyOtherKeysActive) {\n\t\t\treturn;\n\t\t}\n\t\tprocess.stdout.write(\"\\x1b[>4;2m\");\n\t\tthis._modifyOtherKeysActive = true;\n\t}\n\n\tprivate disableModifyOtherKeys(): void {\n\t\tif (!this._modifyOtherKeysActive) {\n\t\t\treturn;\n\t\t}\n\t\tprocess.stdout.write(\"\\x1b[>4;0m\");\n\t\tthis._modifyOtherKeysActive = false;\n\t}\n\n\t/**\n\t * On Windows, add ENABLE_VIRTUAL_TERMINAL_INPUT (0x0200) to the stdin\n\t * console handle so the terminal sends VT sequences for modified keys\n\t * (e.g. \\x1b[Z for Shift+Tab). Without this, libuv's ReadConsoleInputW\n\t * discards modifier state and Shift+Tab arrives as plain \\t.\n\t */\n\tprivate enableWindowsVTInput(): void {\n\t\tif (process.platform !== \"win32\") return;\n\t\ttry {\n\t\t\t// Dynamic require to avoid bundling koffi's 74MB of cross-platform\n\t\t\t// native binaries into every compiled binary. Koffi is only needed\n\t\t\t// on Windows for VT input support.\n\t\t\tconst koffi = cjsRequire(\"koffi\");\n\t\t\tconst k32 = koffi.load(\"kernel32.dll\");\n\t\t\tconst GetStdHandle = k32.func(\"void* __stdcall GetStdHandle(int)\");\n\t\t\tconst GetConsoleMode = k32.func(\"bool __stdcall GetConsoleMode(void*, _Out_ uint32_t*)\");\n\t\t\tconst SetConsoleMode = k32.func(\"bool __stdcall SetConsoleMode(void*, uint32_t)\");\n\n\t\t\tconst STD_INPUT_HANDLE = -10;\n\t\t\tconst ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;\n\t\t\tconst handle = GetStdHandle(STD_INPUT_HANDLE);\n\t\t\tconst mode = new Uint32Array(1);\n\t\t\tGetConsoleMode(handle, mode);\n\t\t\tSetConsoleMode(handle, mode[0]! | ENABLE_VIRTUAL_TERMINAL_INPUT);\n\t\t} catch {\n\t\t\t// koffi not available — Shift+Tab won't be distinguishable from Tab\n\t\t}\n\t}\n\n\tasync drainInput(maxMs = 1000, idleMs = 50): Promise<void> {\n\t\tif (this._kittyProtocolActive) {\n\t\t\t// Disable Kitty keyboard protocol first so any late key releases\n\t\t\t// do not generate new Kitty escape sequences.\n\t\t\tprocess.stdout.write(\"\\x1b[<u\");\n\t\t\tthis._kittyProtocolActive = false;\n\t\t\tsetKittyProtocolActive(false);\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\n\t\tconst previousHandler = this.inputHandler;\n\t\tthis.inputHandler = undefined;\n\n\t\tlet lastDataTime = Date.now();\n\t\tconst onData = () => {\n\t\t\tlastDataTime = Date.now();\n\t\t};\n\n\t\tprocess.stdin.on(\"data\", onData);\n\t\tconst endTime = Date.now() + maxMs;\n\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tconst timeLeft = endTime - now;\n\t\t\t\tif (timeLeft <= 0) break;\n\t\t\t\tif (now - lastDataTime >= idleMs) break;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, Math.min(idleMs, timeLeft)));\n\t\t\t}\n\t\t} finally {\n\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\tthis.inputHandler = previousHandler;\n\t\t}\n\t}\n\n\tstop(): void {\n\t\tif (this.clearProgressInterval()) {\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);\n\t\t}\n\n\t\t// Disable bracketed paste mode\n\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\n\t\t// Disable Kitty keyboard protocol if not already done by drainInput()\n\t\tif (this._kittyProtocolActive) {\n\t\t\tprocess.stdout.write(\"\\x1b[<u\");\n\t\t\tthis._kittyProtocolActive = false;\n\t\t\tsetKittyProtocolActive(false);\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\n\t\t// Clean up StdinBuffer\n\t\tif (this.stdinBuffer) {\n\t\t\tthis.stdinBuffer.destroy();\n\t\t\tthis.stdinBuffer = undefined;\n\t\t}\n\n\t\t// Remove event handlers\n\t\tif (this.stdinDataHandler) {\n\t\t\tprocess.stdin.removeListener(\"data\", this.stdinDataHandler);\n\t\t\tthis.stdinDataHandler = undefined;\n\t\t}\n\t\tthis.inputHandler = undefined;\n\t\tif (this.resizeHandler) {\n\t\t\tprocess.stdout.removeListener(\"resize\", this.resizeHandler);\n\t\t\tthis.resizeHandler = undefined;\n\t\t}\n\n\t\t// Pause stdin to prevent any buffered input (e.g., Ctrl+D) from being\n\t\t// re-interpreted after raw mode is disabled. This fixes a race condition\n\t\t// where Ctrl+D could close the parent shell over SSH.\n\t\tprocess.stdin.pause();\n\n\t\t// Restore raw mode state\n\t\tif (process.stdin.setRawMode) {\n\t\t\tprocess.stdin.setRawMode(this.wasRaw);\n\t\t}\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t\tif (this.writeLogPath) {\n\t\t\ttry {\n\t\t\t\tfs.appendFileSync(this.writeLogPath, data, { encoding: \"utf8\" });\n\t\t\t} catch {\n\t\t\t\t// Ignore logging errors\n\t\t\t}\n\t\t}\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || Number(process.env.COLUMNS) || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || Number(process.env.LINES) || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n\n\tsetTitle(title: string): void {\n\t\t// OSC 0;title BEL - set terminal window title\n\t\tprocess.stdout.write(`\\x1b]0;${title}\\x07`);\n\t}\n\n\tsetProgress(active: boolean): void {\n\t\tif (active) {\n\t\t\t// OSC 9;4;3 - indeterminate progress\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);\n\t\t\tif (!this.progressInterval) {\n\t\t\t\tthis.progressInterval = setInterval(() => {\n\t\t\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);\n\t\t\t\t}, TERMINAL_PROGRESS_KEEPALIVE_MS);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.clearProgressInterval();\n\t\t\t// OSC 9;4;0 - clear progress\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);\n\t\t}\n\t}\n\n\tprivate clearProgressInterval(): boolean {\n\t\tif (!this.progressInterval) return false;\n\t\tclearInterval(this.progressInterval);\n\t\tthis.progressInterval = undefined;\n\t\treturn true;\n\t}\n}\n"]}
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { setKittyProtocolActive } from \"./keys.js\";\nimport { isNativeModifierPressed } from \"./native-modifiers.js\";\nimport { StdinBuffer } from \"./stdin-buffer.js\";\n\nconst cjsRequire = createRequire(import.meta.url);\n\nconst TERMINAL_PROGRESS_KEEPALIVE_MS = 1000;\nconst TERMINAL_PROGRESS_ACTIVE_SEQUENCE = \"\\x1b]9;4;3\\x07\";\nconst TERMINAL_PROGRESS_CLEAR_SEQUENCE = \"\\x1b]9;4;0\\x07\";\nconst DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS = 7;\nconst KITTY_KEYBOARD_PROTOCOL_QUERY = `\\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u\\x1b[?u\\x1b[c`;\nconst APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE = \"\\x1b[13;2u\";\n\nexport function isAppleTerminalSession(): boolean {\n\treturn (\n\t\tprocess.platform === \"darwin\" &&\n\t\tprocess.env.TERM_PROGRAM === \"Apple_Terminal\" &&\n\t\tprocess.env.SSH_CONNECTION === undefined &&\n\t\tprocess.env.SSH_TTY === undefined\n\t);\n}\n\nexport function normalizeAppleTerminalInput(data: string, isAppleTerminal: boolean, isShiftPressed: boolean): string {\n\tif (isAppleTerminal && data === \"\\r\" && isShiftPressed) return APPLE_TERMINAL_SHIFT_ENTER_SEQUENCE;\n\treturn data;\n}\n\n/**\n * Refresh terminal dimensions on POSIX platforms by sending SIGWINCH to this process.\n * Best-effort: some environments (restricted seccomp or LSM policies) return EACCES\n * for `kill(2)`; in that case the dimensions refresh is skipped rather than crashing.\n */\nexport function refreshTerminalDimensions(): void {\n\tif (process.platform === \"win32\" || process.pid <= 0) return;\n\ttry {\n\t\tprocess.kill(process.pid, \"SIGWINCH\");\n\t} catch {\n\t\t// Signal delivery not permitted in this environment; ignore.\n\t}\n}\n\n/**\n * Minimal terminal interface for TUI\n */\nexport interface Terminal {\n\t// Start the terminal with input and resize handlers\n\tstart(onInput: (data: string) => void, onResize: () => void): void;\n\n\t// Stop the terminal and restore state\n\tstop(): void;\n\n\t/**\n\t * Drain stdin before exiting to prevent Kitty key release events from\n\t * leaking to the parent shell over slow SSH connections.\n\t * @param maxMs - Maximum time to drain (default: 1000ms)\n\t * @param idleMs - Exit early if no input arrives within this time (default: 50ms)\n\t */\n\tdrainInput(maxMs?: number, idleMs?: number): Promise<void>;\n\n\t// Write output to terminal\n\twrite(data: string): void;\n\n\t// Get terminal dimensions\n\tget columns(): number;\n\tget rows(): number;\n\n\t// Whether Kitty keyboard protocol is active\n\tget kittyProtocolActive(): boolean;\n\n\t// Cursor positioning (relative to current position)\n\tmoveBy(lines: number): void; // Move cursor up (negative) or down (positive) by N lines\n\n\t// Cursor visibility\n\thideCursor(): void; // Hide the cursor\n\tshowCursor(): void; // Show the cursor\n\n\t// Clear operations\n\tclearLine(): void; // Clear current line\n\tclearFromCursor(): void; // Clear from cursor to end of screen\n\tclearScreen(): void; // Clear entire screen and move cursor to (0,0)\n\n\t// Title operations\n\tsetTitle(title: string): void; // Set terminal window title\n\n\t// Progress indicator (OSC 9;4)\n\tsetProgress(active: boolean): void;\n}\n\nconst DEFAULT_ESCAPE_TIMEOUT_MS = 10;\nconst DEFAULT_SSH_ESCAPE_TIMEOUT_MS = 100;\n\n/**\n * Resolve how long to wait for the rest of an escape sequence before\n * dispatching a lone ESC as the Escape key. Legacy Alt+key input is ESC plus\n * another byte, so high-latency transports need a longer reassembly window.\n */\nexport function resolveEscapeTimeoutMs(env: NodeJS.ProcessEnv = process.env): number {\n\tconst configured = Number(env.PI_TUI_ESC_TIMEOUT);\n\tif (Number.isFinite(configured) && configured > 0) {\n\t\treturn configured;\n\t}\n\tif (env.SSH_CONNECTION || env.SSH_TTY) {\n\t\treturn DEFAULT_SSH_ESCAPE_TIMEOUT_MS;\n\t}\n\treturn DEFAULT_ESCAPE_TIMEOUT_MS;\n}\n\n/**\n * Real terminal using process.stdin/stdout\n */\nexport class ProcessTerminal implements Terminal {\n\tprivate wasRaw = false;\n\tprivate inputHandler?: (data: string) => void;\n\tprivate resizeHandler?: () => void;\n\tprivate _kittyProtocolActive = false;\n\tprivate _modifyOtherKeysActive = false;\n\tprivate bracketedPasteActive = false;\n\tprivate progressActive = false;\n\tprivate stdinBuffer?: StdinBuffer;\n\tprivate stdinDataHandler?: (data: string) => void;\n\tprivate progressInterval?: ReturnType<typeof setInterval>;\n\tprivate writeLogPath = (() => {\n\t\tconst env = process.env.PI_TUI_WRITE_LOG || \"\";\n\t\tif (!env) return \"\";\n\t\ttry {\n\t\t\tif (fs.statSync(env).isDirectory()) {\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst ts = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}_${String(now.getHours()).padStart(2, \"0\")}-${String(now.getMinutes()).padStart(2, \"0\")}-${String(now.getSeconds()).padStart(2, \"0\")}`;\n\t\t\t\treturn path.join(env, `tui-${ts}-${process.pid}.log`);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not an existing directory - use as-is (file path)\n\t\t}\n\t\treturn env;\n\t})();\n\n\tget kittyProtocolActive(): boolean {\n\t\treturn this._kittyProtocolActive;\n\t}\n\n\tget modifyOtherKeysActive(): boolean {\n\t\treturn this._modifyOtherKeysActive;\n\t}\n\n\tstart(onInput: (data: string) => void, onResize: () => void): void {\n\t\tthis.inputHandler = onInput;\n\t\tthis.resizeHandler = onResize;\n\n\t\ttry {\n\t\t\t// Save previous state and enable raw mode\n\t\t\tthis.wasRaw = process.stdin.isRaw || false;\n\t\t\tif (process.stdin.setRawMode) {\n\t\t\t\tprocess.stdin.setRawMode(true);\n\t\t\t}\n\t\t\tprocess.stdin.setEncoding(\"utf8\");\n\n\t\t\t// Enable bracketed paste mode - terminal will wrap pastes in \\x1b[200~ ... \\x1b[201~\n\t\t\tprocess.stdout.write(\"\\x1b[?2004h\");\n\t\t\tthis.bracketedPasteActive = true;\n\n\t\t\t// Set up resize handler immediately\n\t\t\tprocess.stdout.on(\"resize\", this.resizeHandler);\n\n\t\t\t// Refresh terminal dimensions - they may be stale after suspend/resume\n\t\t\t// (SIGWINCH is lost while process is stopped). Unix only, best-effort.\n\t\t\trefreshTerminalDimensions();\n\n\t\t\t// On Windows, enable ENABLE_VIRTUAL_TERMINAL_INPUT so the console sends\n\t\t\t// VT escape sequences (e.g. \\x1b[Z for Shift+Tab) instead of raw console\n\t\t\t// events that lose modifier information. Must run AFTER setRawMode(true)\n\t\t\t// since that resets console mode flags.\n\t\t\tthis.enableWindowsVTInput();\n\n\t\t\t// Query Kitty keyboard protocol and fall back to modifyOtherKeys when a\n\t\t\t// device-attributes response indicates Kitty is unavailable.\n\t\t\t// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/\n\t\t\tthis.queryAndEnableKittyProtocol();\n\t\t\tprocess.stdin.resume();\n\t\t} catch (error) {\n\t\t\tlet cleanupError: unknown;\n\t\t\ttry {\n\t\t\t\tthis.cleanup(false);\n\t\t\t} catch (cleanupFailure) {\n\t\t\t\tcleanupError = cleanupFailure;\n\t\t\t}\n\t\t\tif (cleanupError !== undefined) {\n\t\t\t\tthrow new AggregateError([error, cleanupError], \"Terminal startup and rollback both failed\");\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Set up StdinBuffer to split batched input into individual sequences.\n\t * This ensures components receive single events, making matchesKey/isKeyRelease work correctly.\n\t *\n\t * Also watches for Kitty protocol response and enables it when detected.\n\t * This is done here (after stdinBuffer parsing) rather than on raw stdin\n\t * to handle the case where the response arrives split across multiple events.\n\t */\n\tprivate setupStdinBuffer(): void {\n\t\tthis.stdinBuffer = new StdinBuffer({ escapeTimeout: resolveEscapeTimeoutMs() });\n\n\t\t// Forward individual sequences to the input handler\n\t\tthis.stdinBuffer.on(\"data\", (sequence) => {\n\t\t\tif (!this._kittyProtocolActive) {\n\t\t\t\tconst kittyFlags = sequence.match(/^\\x1b\\[\\?(\\d+)u$/);\n\t\t\t\tif (kittyFlags) {\n\t\t\t\t\tif (Number.parseInt(kittyFlags[1]!, 10) !== 0) {\n\t\t\t\t\t\tthis.enableKittyKeyboardProtocol();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.enableModifyOtherKeys();\n\t\t\t\t\t}\n\t\t\t\t\treturn; // Don't forward protocol response to TUI\n\t\t\t\t}\n\n\t\t\t\tif (/^\\x1b\\[\\?[\\d;]*c$/.test(sequence)) {\n\t\t\t\t\tthis.enableModifyOtherKeys();\n\t\t\t\t\treturn; // Don't forward protocol response to TUI\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.inputHandler) {\n\t\t\t\tconst isAppleTerminal = sequence === \"\\r\" && isAppleTerminalSession();\n\t\t\t\tthis.inputHandler(\n\t\t\t\t\tnormalizeAppleTerminalInput(\n\t\t\t\t\t\tsequence,\n\t\t\t\t\t\tisAppleTerminal,\n\t\t\t\t\t\tisAppleTerminal && isNativeModifierPressed(\"shift\"),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\t\t// Re-wrap paste content with bracketed paste markers for existing editor handling\n\t\tthis.stdinBuffer.on(\"paste\", (content) => {\n\t\t\tif (this.inputHandler) {\n\t\t\t\tthis.inputHandler(`\\x1b[200~${content}\\x1b[201~`);\n\t\t\t}\n\t\t});\n\n\t\t// Handler that pipes stdin data through the buffer\n\t\tthis.stdinDataHandler = (data: string) => {\n\t\t\tthis.stdinBuffer!.process(data);\n\t\t};\n\t}\n\n\t/**\n\t * Query terminal for Kitty keyboard protocol support and enable if available.\n\t *\n\t * Sends both the Kitty capability query and the device-attributes query.\n\t * If the terminal responds with Kitty flags, we enable Kitty keyboard\n\t * protocol. Otherwise a device-attributes response activates xterm\n\t * modifyOtherKeys mode 2.\n\t */\n\tprivate queryAndEnableKittyProtocol(): void {\n\t\tthis.setupStdinBuffer();\n\t\tprocess.stdin.on(\"data\", this.stdinDataHandler!);\n\t\tprocess.stdout.write(KITTY_KEYBOARD_PROTOCOL_QUERY);\n\t}\n\n\tprivate enableKittyKeyboardProtocol(): void {\n\t\tif (this._kittyProtocolActive) {\n\t\t\treturn;\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\t\tthis._kittyProtocolActive = true;\n\t\tsetKittyProtocolActive(true);\n\t\t// Enable Kitty keyboard protocol (push flags)\n\t\t// Flag 1 = disambiguate escape codes\n\t\t// Flag 2 = report event types (press/repeat/release)\n\t\t// Flag 4 = report alternate keys (shifted key, base layout key)\n\t\t// Base layout key enables shortcuts to work with non-Latin keyboard layouts\n\t\tprocess.stdout.write(`\\x1b[>${DESIRED_KITTY_KEYBOARD_PROTOCOL_FLAGS}u`);\n\t}\n\n\tprivate enableModifyOtherKeys(): void {\n\t\tif (this._kittyProtocolActive || this._modifyOtherKeysActive) {\n\t\t\treturn;\n\t\t}\n\t\tprocess.stdout.write(\"\\x1b[>4;2m\");\n\t\tthis._modifyOtherKeysActive = true;\n\t}\n\n\tprivate disableModifyOtherKeys(): void {\n\t\tif (!this._modifyOtherKeysActive) {\n\t\t\treturn;\n\t\t}\n\t\tprocess.stdout.write(\"\\x1b[>4;0m\");\n\t\tthis._modifyOtherKeysActive = false;\n\t}\n\n\t/**\n\t * On Windows, add ENABLE_VIRTUAL_TERMINAL_INPUT (0x0200) to the stdin\n\t * console handle so the terminal sends VT sequences for modified keys\n\t * (e.g. \\x1b[Z for Shift+Tab). Without this, libuv's ReadConsoleInputW\n\t * discards modifier state and Shift+Tab arrives as plain \\t.\n\t */\n\tprivate enableWindowsVTInput(): void {\n\t\tif (process.platform !== \"win32\") return;\n\t\ttry {\n\t\t\t// Dynamic require to avoid bundling koffi's 74MB of cross-platform\n\t\t\t// native binaries into every compiled binary. Koffi is only needed\n\t\t\t// on Windows for VT input support.\n\t\t\tconst koffi = cjsRequire(\"koffi\");\n\t\t\tconst k32 = koffi.load(\"kernel32.dll\");\n\t\t\tconst GetStdHandle = k32.func(\"void* __stdcall GetStdHandle(int)\");\n\t\t\tconst GetConsoleMode = k32.func(\"bool __stdcall GetConsoleMode(void*, _Out_ uint32_t*)\");\n\t\t\tconst SetConsoleMode = k32.func(\"bool __stdcall SetConsoleMode(void*, uint32_t)\");\n\n\t\t\tconst STD_INPUT_HANDLE = -10;\n\t\t\tconst ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;\n\t\t\tconst handle = GetStdHandle(STD_INPUT_HANDLE);\n\t\t\tconst mode = new Uint32Array(1);\n\t\t\tGetConsoleMode(handle, mode);\n\t\t\tSetConsoleMode(handle, mode[0]! | ENABLE_VIRTUAL_TERMINAL_INPUT);\n\t\t} catch {\n\t\t\t// koffi not available — Shift+Tab won't be distinguishable from Tab\n\t\t}\n\t}\n\n\tasync drainInput(maxMs = 1000, idleMs = 50): Promise<void> {\n\t\tif (this._kittyProtocolActive) {\n\t\t\t// Disable Kitty keyboard protocol first so any late key releases\n\t\t\t// do not generate new Kitty escape sequences.\n\t\t\tprocess.stdout.write(\"\\x1b[<u\");\n\t\t\tthis._kittyProtocolActive = false;\n\t\t\tsetKittyProtocolActive(false);\n\t\t}\n\t\tthis.disableModifyOtherKeys();\n\n\t\tconst previousHandler = this.inputHandler;\n\t\tthis.inputHandler = undefined;\n\n\t\tlet lastDataTime = Date.now();\n\t\tconst onData = () => {\n\t\t\tlastDataTime = Date.now();\n\t\t};\n\n\t\tprocess.stdin.on(\"data\", onData);\n\t\tconst endTime = Date.now() + maxMs;\n\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\tconst now = Date.now();\n\t\t\t\tconst timeLeft = endTime - now;\n\t\t\t\tif (timeLeft <= 0) break;\n\t\t\t\tif (now - lastDataTime >= idleMs) break;\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, Math.min(idleMs, timeLeft)));\n\t\t\t}\n\t\t} finally {\n\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\tthis.inputHandler = previousHandler;\n\t\t}\n\t}\n\n\tstop(): void {\n\t\tthis.cleanup(true);\n\t}\n\n\tprivate cleanup(pauseStdin: boolean): void {\n\t\tlet firstError: unknown;\n\t\tlet hasError = false;\n\t\tconst attempt = (operation: () => void): void => {\n\t\t\ttry {\n\t\t\t\toperation();\n\t\t\t} catch (error) {\n\t\t\t\tif (!hasError) {\n\t\t\t\t\tfirstError = error;\n\t\t\t\t\thasError = true;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tattempt(() => this.clearProgressInterval());\n\t\tif (this.progressActive) {\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);\n\t\t\t\tthis.progressActive = false;\n\t\t\t});\n\t\t}\n\t\tif (this.bracketedPasteActive) {\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.write(\"\\x1b[?2004l\");\n\t\t\t\tthis.bracketedPasteActive = false;\n\t\t\t});\n\t\t}\n\n\t\tif (this._kittyProtocolActive) {\n\t\t\tlet kittyProtocolDisabled = false;\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.write(\"\\x1b[<u\");\n\t\t\t\tkittyProtocolDisabled = true;\n\t\t\t});\n\t\t\tif (kittyProtocolDisabled) {\n\t\t\t\tthis._kittyProtocolActive = false;\n\t\t\t\tattempt(() => setKittyProtocolActive(false));\n\t\t\t}\n\t\t}\n\t\tif (this._modifyOtherKeysActive) {\n\t\t\tlet modifyOtherKeysDisabled = false;\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.write(\"\\x1b[>4;0m\");\n\t\t\t\tmodifyOtherKeysDisabled = true;\n\t\t\t});\n\t\t\tif (modifyOtherKeysDisabled) {\n\t\t\t\tthis._modifyOtherKeysActive = false;\n\t\t\t}\n\t\t}\n\n\t\tconst stdinBuffer = this.stdinBuffer;\n\t\tif (stdinBuffer) {\n\t\t\tattempt(() => {\n\t\t\t\tstdinBuffer.destroy();\n\t\t\t\tif (this.stdinBuffer === stdinBuffer) this.stdinBuffer = undefined;\n\t\t\t});\n\t\t}\n\n\t\tconst stdinDataHandler = this.stdinDataHandler;\n\t\tif (stdinDataHandler) {\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdin.removeListener(\"data\", stdinDataHandler);\n\t\t\t\tif (this.stdinDataHandler === stdinDataHandler) this.stdinDataHandler = undefined;\n\t\t\t});\n\t\t}\n\t\tthis.inputHandler = undefined;\n\t\tconst resizeHandler = this.resizeHandler;\n\t\tif (resizeHandler) {\n\t\t\tattempt(() => {\n\t\t\t\tprocess.stdout.removeListener(\"resize\", resizeHandler);\n\t\t\t\tif (this.resizeHandler === resizeHandler) this.resizeHandler = undefined;\n\t\t\t});\n\t\t}\n\n\t\tif (pauseStdin) attempt(() => process.stdin.pause());\n\t\tif (process.stdin.setRawMode) attempt(() => process.stdin.setRawMode!(this.wasRaw));\n\t\tif (hasError) throw firstError;\n\t}\n\n\twrite(data: string): void {\n\t\tprocess.stdout.write(data);\n\t\tif (this.writeLogPath) {\n\t\t\ttry {\n\t\t\t\tfs.appendFileSync(this.writeLogPath, data, { encoding: \"utf8\" });\n\t\t\t} catch {\n\t\t\t\t// Ignore logging errors\n\t\t\t}\n\t\t}\n\t}\n\n\tget columns(): number {\n\t\treturn process.stdout.columns || Number(process.env.COLUMNS) || 80;\n\t}\n\n\tget rows(): number {\n\t\treturn process.stdout.rows || Number(process.env.LINES) || 24;\n\t}\n\n\tmoveBy(lines: number): void {\n\t\tif (lines > 0) {\n\t\t\t// Move down\n\t\t\tprocess.stdout.write(`\\x1b[${lines}B`);\n\t\t} else if (lines < 0) {\n\t\t\t// Move up\n\t\t\tprocess.stdout.write(`\\x1b[${-lines}A`);\n\t\t}\n\t\t// lines === 0: no movement\n\t}\n\n\thideCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25l\");\n\t}\n\n\tshowCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[?25h\");\n\t}\n\n\tclearLine(): void {\n\t\tprocess.stdout.write(\"\\x1b[K\");\n\t}\n\n\tclearFromCursor(): void {\n\t\tprocess.stdout.write(\"\\x1b[J\");\n\t}\n\n\tclearScreen(): void {\n\t\tprocess.stdout.write(\"\\x1b[2J\\x1b[H\"); // Clear screen and move to home (1,1)\n\t}\n\n\tsetTitle(title: string): void {\n\t\t// OSC 0;title BEL - set terminal window title\n\t\tprocess.stdout.write(`\\x1b]0;${title}\\x07`);\n\t}\n\n\tsetProgress(active: boolean): void {\n\t\tif (active) {\n\t\t\t// OSC 9;4;3 - indeterminate progress\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);\n\t\t\tthis.progressActive = true;\n\t\t\tif (!this.progressInterval) {\n\t\t\t\tthis.progressInterval = setInterval(() => {\n\t\t\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_ACTIVE_SEQUENCE);\n\t\t\t\t}, TERMINAL_PROGRESS_KEEPALIVE_MS);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.clearProgressInterval();\n\t\t\t// OSC 9;4;0 - clear progress\n\t\t\tprocess.stdout.write(TERMINAL_PROGRESS_CLEAR_SEQUENCE);\n\t\t\tthis.progressActive = false;\n\t\t}\n\t}\n\n\tprivate clearProgressInterval(): void {\n\t\tif (!this.progressInterval) return;\n\t\tclearInterval(this.progressInterval);\n\t\tthis.progressInterval = undefined;\n\t}\n}\n"]}