claudescreenfix-hardwicksoftware 2.3.1 → 2.4.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 (2) hide show
  1. package/bin/claude-fixed.js +143 -22
  2. package/package.json +2 -2
@@ -2,25 +2,60 @@
2
2
  'use strict';
3
3
 
4
4
  /**
5
- * wrapper script - runs claude with the terminal fix loaded
5
+ * claude-fixed wrapper - runs Claude in a PTY and filters background colors
6
6
  *
7
- * finds your claude binary and runs it with our fix injected
8
- * no manual setup needed, just run claude-fixed instead of claude
7
+ * Claude is a compiled binary that needs a TTY for its Ink-based TUI.
8
+ * We spawn it in a pseudo-terminal (PTY) and filter its output in real-time,
9
+ * stripping background colors that cause VTE rendering glitches on Xvfb/VNC.
10
+ *
11
+ * Just run `claude-fixed` instead of `claude`
9
12
  */
10
13
 
11
- const { spawn, execSync } = require('child_process');
14
+ const { execSync } = require('child_process');
12
15
  const path = require('path');
13
- const fs = require('fs');
14
16
 
15
- // find the loader path
16
- const loaderPath = path.join(__dirname, '..', 'loader.cjs');
17
+ // ANSI background color patterns that cause VTE glitches on Xvfb/VNC
18
+ const ANSI_BG_PATTERNS = [
19
+ /\x1b\[48;5;\d+m/g, // 256-color background
20
+ /\x1b\[48;2;\d+;\d+;\d+m/g, // true color background (RGB)
21
+ /\x1b\[4[0-7]m/g, // standard background colors 40-47
22
+ /\x1b\[10[0-7]m/g, // bright background colors 100-107
23
+ /\x1b\[7m/g, // inverse video (swaps FG/BG)
24
+ /\x1b\[27m/g, // inverse off
25
+ /\x1b\[49m/g, // default background
26
+ ];
17
27
 
18
- if (!fs.existsSync(loaderPath)) {
19
- console.error('loader not found at ' + loaderPath);
20
- process.exit(1);
28
+ function stripBackgroundColors(data) {
29
+ let str = data;
30
+ for (const pattern of ANSI_BG_PATTERNS) {
31
+ str = str.replace(pattern, '');
32
+ }
33
+ return str;
34
+ }
35
+
36
+ // Check if we should strip colors (headless/VNC environment)
37
+ function isHeadless() {
38
+ if (process.env.CLAUDE_HEADLESS_MODE === '1') return true;
39
+ if (process.env.CLAUDE_HEADLESS_MODE === '0') return false;
40
+
41
+ const display = process.env.DISPLAY;
42
+ if (!display) return true;
43
+
44
+ try {
45
+ const xdpyinfo = execSync(`xdpyinfo -display ${display} 2>/dev/null`, { encoding: 'utf8', timeout: 2000 });
46
+ if (xdpyinfo.toLowerCase().includes('xvfb') || xdpyinfo.toLowerCase().includes('virtual')) {
47
+ return true;
48
+ }
49
+ const vnc = execSync(`pgrep -a "x11vnc|Xvnc|vncserver" 2>/dev/null || true`, { encoding: 'utf8', timeout: 2000 });
50
+ if (vnc.includes(display) || vnc.includes('x11vnc')) {
51
+ return true;
52
+ }
53
+ } catch (e) {}
54
+
55
+ return false;
21
56
  }
22
57
 
23
- // find claude binary
58
+ // Find claude binary
24
59
  let claudeBin;
25
60
  try {
26
61
  claudeBin = execSync('which claude', { encoding: 'utf8' }).trim();
@@ -29,16 +64,102 @@ try {
29
64
  process.exit(1);
30
65
  }
31
66
 
32
- // run claude with our fix loaded via NODE_OPTIONS
33
- const env = Object.assign({}, process.env, {
34
- NODE_OPTIONS: '--require ' + loaderPath + ' ' + (process.env.NODE_OPTIONS || '')
35
- });
67
+ const headless = isHeadless();
68
+ const debug = process.env.CLAUDE_TERMINAL_FIX_DEBUG === '1';
69
+
70
+ if (debug) {
71
+ console.error('[claude-fixed] headless mode:', headless);
72
+ console.error('[claude-fixed] claude binary:', claudeBin);
73
+ }
74
+
75
+ if (!headless) {
76
+ // Not headless, just exec claude directly (replaces this process)
77
+ const { spawn } = require('child_process');
78
+ const child = spawn(claudeBin, process.argv.slice(2), {
79
+ stdio: 'inherit'
80
+ });
81
+ child.on('exit', (code) => process.exit(code || 0));
82
+ } else {
83
+ // Headless - use PTY to preserve TTY behavior while filtering output
84
+ let pty;
85
+ try {
86
+ // Try to find node-pty (might be in different locations)
87
+ const possiblePaths = [
88
+ 'node-pty',
89
+ path.join('/usr/lib/node_modules/specmem-hardwicksoftware/node_modules/node-pty'),
90
+ ];
36
91
 
37
- const child = spawn(claudeBin, process.argv.slice(2), {
38
- stdio: 'inherit',
39
- env: env
40
- });
92
+ for (const p of possiblePaths) {
93
+ try {
94
+ pty = require(p);
95
+ if (debug) console.error('[claude-fixed] loaded node-pty from:', p);
96
+ break;
97
+ } catch (e) {}
98
+ }
41
99
 
42
- child.on('exit', (code) => {
43
- process.exit(code || 0);
44
- });
100
+ if (!pty) throw new Error('node-pty not found');
101
+ } catch (e) {
102
+ console.error('[claude-fixed] node-pty not available, falling back to direct spawn');
103
+ console.error('[claude-fixed] Install with: npm install -g node-pty');
104
+ // Fallback to direct spawn (may have TTY issues but better than nothing)
105
+ const { spawn } = require('child_process');
106
+ const child = spawn(claudeBin, process.argv.slice(2), {
107
+ stdio: 'inherit'
108
+ });
109
+ child.on('exit', (code) => process.exit(code || 0));
110
+ return;
111
+ }
112
+
113
+ // Get terminal size
114
+ const cols = process.stdout.columns || 80;
115
+ const rows = process.stdout.rows || 24;
116
+
117
+ if (debug) {
118
+ console.error('[claude-fixed] terminal size:', cols, 'x', rows);
119
+ }
120
+
121
+ // Spawn Claude in a PTY
122
+ const ptyProcess = pty.spawn(claudeBin, process.argv.slice(2), {
123
+ name: 'xterm-256color',
124
+ cols: cols,
125
+ rows: rows,
126
+ cwd: process.cwd(),
127
+ env: process.env
128
+ });
129
+
130
+ // Filter PTY output and write to real stdout
131
+ ptyProcess.onData((data) => {
132
+ const filtered = stripBackgroundColors(data);
133
+ process.stdout.write(filtered);
134
+ });
135
+
136
+ // Forward stdin to PTY
137
+ if (process.stdin.isTTY) {
138
+ process.stdin.setRawMode(true);
139
+ }
140
+ process.stdin.resume();
141
+ process.stdin.on('data', (data) => {
142
+ ptyProcess.write(data);
143
+ });
144
+
145
+ // Handle terminal resize
146
+ process.stdout.on('resize', () => {
147
+ ptyProcess.resize(process.stdout.columns || 80, process.stdout.rows || 24);
148
+ });
149
+
150
+ // Handle exit
151
+ ptyProcess.onExit(({ exitCode }) => {
152
+ if (process.stdin.isTTY) {
153
+ process.stdin.setRawMode(false);
154
+ }
155
+ process.exit(exitCode);
156
+ });
157
+
158
+ // Handle signals
159
+ process.on('SIGINT', () => ptyProcess.kill('SIGINT'));
160
+ process.on('SIGTERM', () => ptyProcess.kill('SIGTERM'));
161
+ process.on('SIGHUP', () => ptyProcess.kill('SIGHUP'));
162
+ process.on('SIGWINCH', () => {
163
+ ptyProcess.resize(process.stdout.columns || 80, process.stdout.rows || 24);
164
+ });
165
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claudescreenfix-hardwicksoftware",
3
- "version": "2.3.1",
4
- "description": "fixes scroll glitch + VNC/headless rendering in claude code cli - auto-installs hook, strips BG colors on Xvfb",
3
+ "version": "2.4.0",
4
+ "description": "fixes scroll glitch + VNC/headless rendering in claude code cli - use claude-fixed command to strip BG colors that break VTE on Xvfb",
5
5
  "main": "index.cjs",
6
6
  "bin": {
7
7
  "claude-fixed": "./bin/claude-fixed.js"