chenbhao Claude Opus 4.6 commited on
Commit
00d812d
·
1 Parent(s): f73174f

fix: add Ghostty env-var detection for Kitty graphics in ink-picture

Browse files

- Add TERM_PROGRAM=ghostty fallback to enable Kitty graphics protocol
- Add COLORTERM=truecolor fallback for color detection
- Create TerminalDetection.test.tsx diagnostic tool for terminal capability debugging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

src/ink-picture/InkPictureProvider.tsx CHANGED
@@ -257,6 +257,16 @@ export function InkPictureProvider({
257
  info.supportsKittyGraphics = result.supportsKittyGraphics;
258
  info.supportsITerm2Graphics = supportsITerm2Graphics;
259
 
 
 
 
 
 
 
 
 
 
 
260
  // Apply iTerm2 cell size override for HiDPI scaling
261
  // See https://iterm2.com/documentation-escape-codes.html#report-cell-size
262
  if (
 
257
  info.supportsKittyGraphics = result.supportsKittyGraphics;
258
  info.supportsITerm2Graphics = supportsITerm2Graphics;
259
 
260
+ // Ghostty supports Kitty graphics protocol natively
261
+ if (process.env.TERM_PROGRAM === 'ghostty') {
262
+ info.supportsKittyGraphics = true;
263
+ }
264
+
265
+ // Fallback color detection if supports-color fails
266
+ if (!info.supportsColor && process.env.COLORTERM === 'truecolor') {
267
+ info.supportsColor = true;
268
+ }
269
+
270
  // Apply iTerm2 cell size override for HiDPI scaling
271
  // See https://iterm2.com/documentation-escape-codes.html#report-cell-size
272
  if (
src/ink-picture/__tests__/TerminalDetection.test.tsx ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Terminal capability detection diagnostic test.
4
+ *
5
+ * Shows what ink-picture detects about the current terminal:
6
+ * env vars, escape sequence query results, and the final protocol.
7
+ *
8
+ * Usage:
9
+ * bun run src/ink-picture/__tests__/TerminalDetection.test.tsx
10
+ */
11
+
12
+ import React, { useEffect, useState } from 'react'
13
+ import { render, Box, Text, useApp } from 'ink'
14
+ import Image, {
15
+ InkPictureProvider,
16
+ type TerminalInfo,
17
+ useTerminalInfo,
18
+ } from '../index.ts'
19
+
20
+ /**
21
+ * Probe Kitty graphics protocol directly via raw escape sequences.
22
+ * Uses fs.writeSync to stdout and intercepts stdin responses,
23
+ * bypassing Ink's input pipeline.
24
+ */
25
+ async function probeKittyDirect(): Promise<{ protocol: string }> {
26
+ return new Promise(resolve => {
27
+ const result = { protocol: 'no-response' }
28
+
29
+ if (!process.stdout.isTTY || !process.stdin.isTTY) {
30
+ resolve(result)
31
+ return
32
+ }
33
+
34
+ const origPush = process.stdin.push.bind(process.stdin)
35
+ let resolved = false
36
+
37
+ const finish = (label: string) => {
38
+ if (resolved) return
39
+ resolved = true
40
+ process.stdin.push = origPush as any
41
+ result.protocol = label
42
+ resolve(result)
43
+ }
44
+
45
+ process.stdin.push = ((chunk: any) => {
46
+ const str = Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk)
47
+
48
+ // Kitty OK response
49
+ if (/\x1b_Gi=31;OK\x1b\\/.test(str)) {
50
+ finish('kitty-ok')
51
+ return origPush(chunk)
52
+ }
53
+
54
+ // DA1 sentinel — kitty response should arrive before or with DA1
55
+ if (/\x1b\[\?(\d+(?:;\d+)*)c/.test(str)) {
56
+ // Give kitty response a chance if it arrived in same chunk after DA
57
+ setTimeout(() => finish('no-kitty'), 200)
58
+ }
59
+
60
+ return origPush(chunk)
61
+ }) as any
62
+
63
+ const query =
64
+ '\x1b[8m' +
65
+ '\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\' +
66
+ '\x1b[c' +
67
+ '\x1b[2K\r' +
68
+ '\x1b[0m'
69
+
70
+ try {
71
+ process.stdin.setRawMode?.(true)
72
+ require('fs').writeSync(process.stdout.fd, query)
73
+ } catch {}
74
+
75
+ setTimeout(() => finish('timeout'), 2000)
76
+ })
77
+ }
78
+
79
+ function DetectionApp() {
80
+ const { exit } = useApp()
81
+ const terminalInfo = useTerminalInfo()
82
+ const [probeResult, setProbeResult] = useState<string>('probing...')
83
+
84
+ useEffect(() => {
85
+ probeKittyDirect().then(r => {
86
+ setProbeResult(JSON.stringify(r))
87
+ })
88
+ const timer = setTimeout(() => exit(), 5000)
89
+ return () => clearTimeout(timer)
90
+ }, [exit])
91
+
92
+ const env: Record<string, string | undefined> = {
93
+ TERM_PROGRAM: process.env.TERM_PROGRAM,
94
+ TERM: process.env.TERM,
95
+ KITTY_WINDOW_ID: process.env.KITTY_WINDOW_ID,
96
+ GHOSTTY_RESOURCES_DIR: process.env.GHOSTTY_RESOURCES_DIR,
97
+ TERM_PROGRAM_VERSION: process.env.TERM_PROGRAM_VERSION,
98
+ COLORTERM: process.env.COLORTERM,
99
+ WEZTERM_PANE: process.env.WEZTERM_PANE,
100
+ KONSOLE_VERSION: process.env.KONSOLE_VERSION,
101
+ XTERM_VERSION: process.env.XTERM_VERSION,
102
+ VTE_VERSION: process.env.VTE_VERSION,
103
+ }
104
+
105
+ return (
106
+ <Box flexDirection="column" paddingX={1}>
107
+ <Text bold underline color="cyan">
108
+ Terminal Detection Diagnostic
109
+ </Text>
110
+ <Text>{' '}</Text>
111
+
112
+ <Text bold color="yellow">
113
+ Environment Variables:
114
+ </Text>
115
+ {Object.entries(env).map(([k, v]) => (
116
+ <Text key={k}>
117
+ {' '}
118
+ {k}: {v ?? '(not set)'}
119
+ </Text>
120
+ ))}
121
+ <Text>{' '}</Text>
122
+
123
+ <Text bold color="yellow">
124
+ Direct Kitty Probe (raw escape sequence):
125
+ </Text>
126
+ <Text> {probeResult}</Text>
127
+ <Text>{' '}</Text>
128
+
129
+ <Text bold color="yellow">
130
+ InkPictureProvider TerminalInfo:
131
+ </Text>
132
+ <Text>
133
+ {' '}supportsKittyGraphics: {String(terminalInfo.supportsKittyGraphics)}
134
+ </Text>
135
+ <Text>
136
+ {' '}supportsSixelGraphics: {String(terminalInfo.supportsSixelGraphics)}
137
+ </Text>
138
+ <Text>
139
+ {' '}supportsITerm2Graphics: {String(terminalInfo.supportsITerm2Graphics)}
140
+ </Text>
141
+ <Text>
142
+ {' '}supportsUnicode: {String(terminalInfo.supportsUnicode)}
143
+ </Text>
144
+ <Text>
145
+ {' '}supportsColor: {String(terminalInfo.supportsColor)}
146
+ </Text>
147
+ <Text>
148
+ {' '}cell: {terminalInfo.cellWidth}x{terminalInfo.cellHeight}
149
+ </Text>
150
+ <Text>
151
+ {' '}terminal: {terminalInfo.terminalWidth}x{terminalInfo.terminalHeight}
152
+ </Text>
153
+ </Box>
154
+ )
155
+ }
156
+
157
+ const { waitUntilExit } = render(
158
+ <InkPictureProvider>
159
+ <DetectionApp />
160
+ </InkPictureProvider>,
161
+ )
162
+ await waitUntilExit()