claudescreenfix-hardwicksoftware 1.0.1 → 2.0.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.
@@ -5,14 +5,15 @@
5
5
  * wrapper script - runs claude with the terminal fix loaded
6
6
  *
7
7
  * finds your claude binary and runs it with our fix injected
8
- * no manual setup needed, just run claude-fixed instead of claude
8
+ * you don't need any manual setup, just run claude-fixed instead of claude
9
+ * it'll handle the rest
9
10
  */
10
11
 
11
12
  const { spawn, execSync } = require('child_process');
12
13
  const path = require('path');
13
14
  const fs = require('fs');
14
15
 
15
- // find the loader path
16
+ // find the loader path - it's in the parent dir
16
17
  const loaderPath = path.join(__dirname, '..', 'loader.cjs');
17
18
 
18
19
  if (!fs.existsSync(loaderPath)) {
@@ -25,11 +26,11 @@ let claudeBin;
25
26
  try {
26
27
  claudeBin = execSync('which claude', { encoding: 'utf8' }).trim();
27
28
  } catch (e) {
28
- console.error('claude not found in PATH - make sure its installed');
29
+ console.error('claude not found in PATH - make sure it\'s installed');
29
30
  process.exit(1);
30
31
  }
31
32
 
32
- // run claude with our fix loaded via NODE_OPTIONS
33
+ // run claude with our fix loaded via NODE_OPTIONS - it's the cleanest way
33
34
  const env = Object.assign({}, process.env, {
34
35
  NODE_OPTIONS: '--require ' + loaderPath + ' ' + (process.env.NODE_OPTIONS || '')
35
36
  });
@@ -0,0 +1,351 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * glitch detector - catches when the terminal's cooked
5
+ *
6
+ * watches for these signals:
7
+ * - stdin goes quiet while stdout's still busy (input blocked)
8
+ * - too many resize events too fast (sigwinch spam)
9
+ * - render rate going crazy (ink thrashing)
10
+ *
11
+ * when 2+ signals fire we know shit's broken and try to recover
12
+ */
13
+
14
+ const EventEmitter = require('events');
15
+ const { execSync, spawn } = require('child_process');
16
+ const path = require('path');
17
+ const os = require('os');
18
+
19
+ // Default configuration
20
+ const DEFAULT_CONFIG = {
21
+ stdinTimeoutMs: 2000, // stdin silence threshold
22
+ sigwinchThresholdMs: 10, // minimum ms between resize events
23
+ sigwinchStormCount: 5, // resize events/sec to trigger storm alert
24
+ renderRateLimit: 500, // max renders per minute before alert
25
+ lineLimitMax: 120, // max terminal lines before trim
26
+ checkIntervalMs: 500, // how often to check for glitch state
27
+ recoveryDelayMs: 1000, // delay before recovery actions
28
+ debug: process.env.CLAUDE_GLITCH_DETECTOR_DEBUG === '1'
29
+ };
30
+
31
+ class GlitchDetector extends EventEmitter {
32
+ constructor(config = {}) {
33
+ super();
34
+ this.config = { ...DEFAULT_CONFIG, ...config };
35
+
36
+ // State tracking
37
+ this.lastStdinTime = Date.now();
38
+ this.lastStdoutTime = 0;
39
+ this.lastSigwinchTime = 0;
40
+ this.sigwinchCount = 0;
41
+ this.renderTimes = [];
42
+ this.isGlitched = false;
43
+ this.glitchStartTime = null;
44
+
45
+ // Metrics
46
+ this.metrics = {
47
+ glitchesDetected: 0,
48
+ recoveriesAttempted: 0,
49
+ stdinSilenceEvents: 0,
50
+ sigwinchStorms: 0,
51
+ renderSpikes: 0
52
+ };
53
+
54
+ // Check interval handle
55
+ this.checkInterval = null;
56
+ this.installed = false;
57
+ }
58
+
59
+ log(...args) {
60
+ if (this.config.debug) {
61
+ process.stderr.write('[glitch-detector] ' + args.join(' ') + '\n');
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Install the glitch detector - it hooks into process events
67
+ */
68
+ install() {
69
+ if (this.installed) return;
70
+
71
+ // Track stdin activity
72
+ if (process.stdin.isTTY) {
73
+ process.stdin.on('data', () => {
74
+ this.lastStdinTime = Date.now();
75
+ });
76
+ }
77
+
78
+ // Track SIGWINCH events
79
+ const originalOn = process.on.bind(process);
80
+ process.on = (event, handler) => {
81
+ if (event === 'SIGWINCH') {
82
+ return originalOn(event, (...args) => {
83
+ this.onSigwinch();
84
+ handler(...args);
85
+ });
86
+ }
87
+ return originalOn(event, handler);
88
+ };
89
+
90
+ // Start periodic glitch check
91
+ this.checkInterval = setInterval(() => {
92
+ this.checkGlitchState();
93
+ }, this.config.checkIntervalMs);
94
+
95
+ this.installed = true;
96
+ this.log('installed successfully');
97
+ }
98
+
99
+ /**
100
+ * Track stdout write for activity monitoring
101
+ * Call this from the stdout.write hook in index.cjs, it won't slow anything down
102
+ */
103
+ trackStdout() {
104
+ this.lastStdoutTime = Date.now();
105
+
106
+ // Track render times for rate limiting
107
+ const now = Date.now();
108
+ this.renderTimes.push(now);
109
+
110
+ // Keep only last minute of renders
111
+ this.renderTimes = this.renderTimes.filter(t => now - t < 60000);
112
+ }
113
+
114
+ /**
115
+ * Handle SIGWINCH (resize) events
116
+ */
117
+ onSigwinch() {
118
+ const now = Date.now();
119
+ const interval = now - this.lastSigwinchTime;
120
+ this.lastSigwinchTime = now;
121
+
122
+ // Detect resize storm
123
+ if (interval < this.config.sigwinchThresholdMs) {
124
+ this.sigwinchCount++;
125
+ this.log('rapid SIGWINCH detected, interval:', interval, 'ms');
126
+ }
127
+
128
+ // Decay counter over time
129
+ setTimeout(() => {
130
+ if (this.sigwinchCount > 0) this.sigwinchCount--;
131
+ }, 1000);
132
+ }
133
+
134
+ /**
135
+ * SIGNAL 1: Check for stdin silence during output activity
136
+ * This is the main glitch signal - if stdin's dead but stdout's busy, we're cooked
137
+ */
138
+ checkStdinSilence() {
139
+ const now = Date.now();
140
+ const stdinSilence = now - this.lastStdinTime;
141
+ const outputActive = (now - this.lastStdoutTime) < 5000; // output in last 5s
142
+
143
+ // stdin's been quiet for 2+ sec while output's still going = we're glitched
144
+ if (stdinSilence > this.config.stdinTimeoutMs && outputActive) {
145
+ this.log('stdin silence detected:', stdinSilence, 'ms');
146
+ this.metrics.stdinSilenceEvents++;
147
+ return true;
148
+ }
149
+ return false;
150
+ }
151
+
152
+ /**
153
+ * SIGNAL 2: Check for SIGWINCH storm
154
+ */
155
+ checkSigwinchStorm() {
156
+ const isStorm = this.sigwinchCount >= this.config.sigwinchStormCount;
157
+ if (isStorm) {
158
+ this.log('SIGWINCH storm detected, count:', this.sigwinchCount);
159
+ this.metrics.sigwinchStorms++;
160
+ }
161
+ return isStorm;
162
+ }
163
+
164
+ /**
165
+ * SIGNAL 3: Check for render rate spike
166
+ */
167
+ checkRenderSpike() {
168
+ const rendersPerMinute = this.renderTimes.length;
169
+ const isSpike = rendersPerMinute > this.config.renderRateLimit;
170
+ if (isSpike) {
171
+ this.log('render spike detected:', rendersPerMinute, '/min');
172
+ this.metrics.renderSpikes++;
173
+ }
174
+ return isSpike;
175
+ }
176
+
177
+ /**
178
+ * Main glitch detection - it combines all signals
179
+ * Uses 2-of-3 voting so we don't get false positives
180
+ */
181
+ checkGlitchState() {
182
+ const stdinBlocked = this.checkStdinSilence();
183
+ const sigwinchStorm = this.checkSigwinchStorm();
184
+ const renderSpike = this.checkRenderSpike();
185
+
186
+ const signals = [stdinBlocked, sigwinchStorm, renderSpike];
187
+ const activeSignals = signals.filter(Boolean).length;
188
+
189
+ // 2 of 3 signals = we're definitely glitched
190
+ // OR stdin blocked alone (that's the most reliable one)
191
+ const glitched = activeSignals >= 2 || stdinBlocked;
192
+
193
+ if (glitched && !this.isGlitched) {
194
+ this.isGlitched = true;
195
+ this.glitchStartTime = Date.now();
196
+ this.metrics.glitchesDetected++;
197
+
198
+ this.log('GLITCH DETECTED!', {
199
+ stdinBlocked,
200
+ sigwinchStorm,
201
+ renderSpike
202
+ });
203
+
204
+ this.emit('glitch-detected', {
205
+ timestamp: Date.now(),
206
+ signals: { stdinBlocked, sigwinchStorm, renderSpike },
207
+ metrics: { ...this.metrics }
208
+ });
209
+ } else if (!glitched && this.isGlitched) {
210
+ const duration = Date.now() - this.glitchStartTime;
211
+ this.log('glitch resolved after', duration, 'ms');
212
+ this.isGlitched = false;
213
+ this.glitchStartTime = null;
214
+
215
+ this.emit('glitch-resolved', { duration });
216
+ }
217
+
218
+ return glitched;
219
+ }
220
+
221
+ /**
222
+ * Check if we're currently in glitched state
223
+ */
224
+ isInGlitchState() {
225
+ return this.isGlitched;
226
+ }
227
+
228
+ /**
229
+ * Get glitch duration in ms (0 if we aren't glitched)
230
+ */
231
+ getGlitchDuration() {
232
+ if (!this.isGlitched || !this.glitchStartTime) return 0;
233
+ return Date.now() - this.glitchStartTime;
234
+ }
235
+
236
+ /**
237
+ * Force recovery attempt - it'll try screen commands and scrollback clears
238
+ */
239
+ async attemptRecovery() {
240
+ if (!this.isGlitched) return false;
241
+
242
+ this.log('attempting recovery...');
243
+ this.metrics.recoveriesAttempted++;
244
+
245
+ this.emit('recovery-started');
246
+
247
+ try {
248
+ // Method 1: Send Enter via screen (if we've got a session)
249
+ const screenSession = process.env.STY || process.env.SPECMEM_SCREEN_SESSION;
250
+ if (screenSession) {
251
+ this.log('sending Enter via screen session:', screenSession);
252
+ execSync(`screen -S "${screenSession}" -X stuff $'\\r'`, {
253
+ stdio: 'ignore',
254
+ timeout: 5000
255
+ });
256
+
257
+ await this.sleep(this.config.recoveryDelayMs);
258
+
259
+ // Check if recovered
260
+ if (!this.checkGlitchState()) {
261
+ this.log('recovery successful via screen');
262
+ this.emit('recovery-success', { method: 'screen' });
263
+ return true;
264
+ }
265
+ }
266
+
267
+ // Method 2: Force scrollback clear
268
+ this.log('forcing scrollback clear');
269
+ if (process.stdout.isTTY) {
270
+ process.stdout.write('\x1b[3J'); // CLEAR_SCROLLBACK
271
+ }
272
+
273
+ await this.sleep(this.config.recoveryDelayMs);
274
+
275
+ if (!this.checkGlitchState()) {
276
+ this.log('recovery successful via scrollback clear');
277
+ this.emit('recovery-success', { method: 'scrollback-clear' });
278
+ return true;
279
+ }
280
+
281
+ this.log('recovery failed');
282
+ this.emit('recovery-failed');
283
+ return false;
284
+
285
+ } catch (err) {
286
+ this.log('recovery error:', err.message);
287
+ this.emit('recovery-error', { error: err });
288
+ return false;
289
+ }
290
+ }
291
+
292
+ sleep(ms) {
293
+ return new Promise(resolve => setTimeout(resolve, ms));
294
+ }
295
+
296
+ /**
297
+ * Reset all state (call after recovery)
298
+ */
299
+ reset() {
300
+ this.lastStdinTime = Date.now();
301
+ this.lastStdoutTime = Date.now();
302
+ this.sigwinchCount = 0;
303
+ this.renderTimes = [];
304
+ this.isGlitched = false;
305
+ this.glitchStartTime = null;
306
+ this.log('state reset');
307
+ }
308
+
309
+ /**
310
+ * Get current metrics
311
+ */
312
+ getMetrics() {
313
+ return {
314
+ ...this.metrics,
315
+ isGlitched: this.isGlitched,
316
+ glitchDuration: this.getGlitchDuration(),
317
+ renderRate: this.renderTimes.length,
318
+ sigwinchRate: this.sigwinchCount,
319
+ lastStdinAgo: Date.now() - this.lastStdinTime,
320
+ lastStdoutAgo: Date.now() - this.lastStdoutTime
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Disable the detector
326
+ */
327
+ disable() {
328
+ if (this.checkInterval) {
329
+ clearInterval(this.checkInterval);
330
+ this.checkInterval = null;
331
+ }
332
+ this.installed = false;
333
+ this.log('disabled');
334
+ }
335
+ }
336
+
337
+ // Singleton instance
338
+ let instance = null;
339
+
340
+ function getDetector(config) {
341
+ if (!instance) {
342
+ instance = new GlitchDetector(config);
343
+ }
344
+ return instance;
345
+ }
346
+
347
+ module.exports = {
348
+ GlitchDetector,
349
+ getDetector,
350
+ DEFAULT_CONFIG
351
+ };
package/index.cjs CHANGED
@@ -4,20 +4,24 @@
4
4
  * claudescreenfix-hardwicksoftware - stops the scroll glitch from cooking your terminal
5
5
  *
6
6
  * the problem:
7
- * claude code uses ink (react for terminals) and it dont clear scrollback
8
- * so after like 30 min your terminal got thousands of lines in the buffer
7
+ * claude code uses ink (react for terminals) and it doesn't clear scrollback
8
+ * so after like 30 min your terminal's got thousands of lines in the buffer
9
9
  * every re-render touches ALL of em - O(n) where n keeps growing
10
10
  * resize events fire with no debounce so tmux/screen users get cooked
11
11
  *
12
12
  * what we do:
13
13
  * - hook stdout.write to inject scrollback clears periodically
14
- * - debounce SIGWINCH so resize aint thrashing
14
+ * - debounce SIGWINCH so resize ain't thrashing
15
15
  * - enhance /clear to actually clear scrollback not just the screen
16
16
  *
17
- * FIXED v1.0.1: typing issue where stdin echo was being intercepted
18
- * - now detects stdin echo writes and passes them through unmodified
19
- * - uses setImmediate for periodic clears to not interrupt typing
20
- * - tracks "active typing" window to defer clears during input
17
+ * v1.0.1: fixed the typing bug where keystrokes got eaten
18
+ * - stdin echoes now pass through untouched
19
+ * - clears happen async so typing isn't interrupted
20
+ *
21
+ * v2.0.0: added glitch detection + 120 line limit
22
+ * - actually detects when terminal's fucked instead of just clearing blindly
23
+ * - caps output at 120 lines so buffer won't explode
24
+ * - can force send enter key to break out of frozen state
21
25
  */
22
26
 
23
27
  const CLEAR_SCROLLBACK = '\x1b[3J';
@@ -26,12 +30,25 @@ const CURSOR_RESTORE = '\x1b[u';
26
30
  const CLEAR_SCREEN = '\x1b[2J';
27
31
  const HOME_CURSOR = '\x1b[H';
28
32
 
33
+ // Try to load glitch detector (optional dependency)
34
+ let GlitchDetector = null;
35
+ let glitchDetector = null;
36
+ try {
37
+ const detector = require('./glitch-detector.cjs');
38
+ GlitchDetector = detector.GlitchDetector;
39
+ glitchDetector = detector.getDetector();
40
+ } catch (e) {
41
+ // Glitch detector not available, continue without it
42
+ }
43
+
29
44
  // config - tweak these if needed
30
45
  const config = {
31
46
  resizeDebounceMs: 150, // how long to wait before firing resize
32
47
  periodicClearMs: 60000, // clear scrollback every 60s
33
48
  clearAfterRenders: 500, // or after 500 render cycles
34
49
  typingCooldownMs: 500, // wait this long after typing to clear
50
+ maxLineCount: 120, // NEW: max terminal lines before forced trim
51
+ glitchRecoveryEnabled: true, // NEW: enable automatic glitch recovery
35
52
  debug: process.env.CLAUDE_TERMINAL_FIX_DEBUG === '1',
36
53
  disabled: process.env.CLAUDE_TERMINAL_FIX_DISABLED === '1'
37
54
  };
@@ -45,6 +62,8 @@ let installed = false;
45
62
  let lastTypingTime = 0; // track when user last typed
46
63
  let pendingClear = false; // defer clear if typing active
47
64
  let clearIntervalId = null;
65
+ let lineCount = 0; // NEW: track output line count for 120-line limit
66
+ let glitchRecoveryInProgress = false; // NEW: prevent recovery loops
48
67
 
49
68
  function log(...args) {
50
69
  if (config.debug) {
@@ -53,7 +72,7 @@ function log(...args) {
53
72
  }
54
73
 
55
74
  /**
56
- * check if user is actively typing (within cooldown window)
75
+ * check if user's actively typing (within cooldown window)
57
76
  */
58
77
  function isTypingActive() {
59
78
  return (Date.now() - lastTypingTime) < config.typingCooldownMs;
@@ -61,7 +80,8 @@ function isTypingActive() {
61
80
 
62
81
  /**
63
82
  * detect if this looks like a stdin echo (single printable char or short sequence)
64
- * stdin echoes are typically: single chars, backspace sequences, arrow key echoes
83
+ * stdin echoes are typically: single chars, backspace seqs, arrow key echoes
84
+ * we don't wanna mess with these or typing gets wonky
65
85
  */
66
86
  function isStdinEcho(chunk) {
67
87
  // single printable character (including space)
@@ -84,7 +104,7 @@ function isStdinEcho(chunk) {
84
104
  }
85
105
 
86
106
  /**
87
- * safe clear - defers if typing active
107
+ * safe clear - defers if typing's active so we don't eat keystrokes
88
108
  */
89
109
  function safeClearScrollback() {
90
110
  if (isTypingActive()) {
@@ -112,7 +132,7 @@ function safeClearScrollback() {
112
132
 
113
133
  /**
114
134
  * installs the fix - hooks into stdout and sigwinch
115
- * call this once at startup, calling again is a no-op
135
+ * call this once at startup, calling again won't do anything
116
136
  */
117
137
  function install() {
118
138
  if (installed || config.disabled) {
@@ -142,9 +162,26 @@ function install() {
142
162
 
143
163
  renderCount++;
144
164
 
165
+ // track output for glitch detection
166
+ if (glitchDetector) {
167
+ glitchDetector.trackStdout();
168
+ }
169
+
170
+ // count lines so we can cap at 120
171
+ const newlineCount = (chunk.match(/\n/g) || []).length;
172
+ lineCount += newlineCount;
173
+
174
+ // hit the limit? force a trim
175
+ if (lineCount > config.maxLineCount) {
176
+ log('line limit exceeded (' + lineCount + '/' + config.maxLineCount + '), forcing trim');
177
+ lineCount = 0;
178
+ chunk = CURSOR_SAVE + CLEAR_SCROLLBACK + CURSOR_RESTORE + chunk;
179
+ }
180
+
145
181
  // ink clears screen before re-render, we piggyback on that
146
182
  // but only if not actively typing
147
183
  if (chunk.includes(CLEAR_SCREEN) || chunk.includes(HOME_CURSOR)) {
184
+ lineCount = 0; // Reset line count on screen clear
148
185
  if (config.clearAfterRenders > 0 && renderCount >= config.clearAfterRenders) {
149
186
  if (!isTypingActive()) {
150
187
  log('clearing scrollback after ' + renderCount + ' renders');
@@ -156,11 +193,35 @@ function install() {
156
193
  }
157
194
  }
158
195
 
159
- // /clear command should actually clear everything (immediate, user-requested)
196
+ // /clear should actually clear everything (it's user-requested so do it now)
160
197
  if (chunk.includes('Conversation cleared') || chunk.includes('Chat cleared')) {
161
198
  log('/clear detected, nuking scrollback');
199
+ lineCount = 0;
162
200
  chunk = CLEAR_SCROLLBACK + chunk;
163
201
  }
202
+
203
+ // glitched? try to recover
204
+ if (glitchDetector && config.glitchRecoveryEnabled && !glitchRecoveryInProgress) {
205
+ if (glitchDetector.isInGlitchState()) {
206
+ glitchRecoveryInProgress = true;
207
+ log('GLITCH DETECTED - initiating recovery');
208
+
209
+ // Force clear scrollback immediately
210
+ chunk = CURSOR_SAVE + CLEAR_SCROLLBACK + CURSOR_RESTORE + chunk;
211
+ lineCount = 0;
212
+ renderCount = 0;
213
+
214
+ // Attempt full recovery asynchronously
215
+ setImmediate(async () => {
216
+ try {
217
+ await glitchDetector.attemptRecovery();
218
+ } catch (e) {
219
+ log('recovery error:', e.message);
220
+ }
221
+ glitchRecoveryInProgress = false;
222
+ });
223
+ }
224
+ }
164
225
  }
165
226
 
166
227
  return originalWrite(chunk, encoding, callback);
@@ -178,8 +239,28 @@ function install() {
178
239
  }, config.periodicClearMs);
179
240
  }
180
241
 
242
+ // hook up the glitch detector
243
+ if (glitchDetector) {
244
+ glitchDetector.install();
245
+
246
+ // Listen for glitch events
247
+ glitchDetector.on('glitch-detected', (data) => {
248
+ log('GLITCH EVENT:', JSON.stringify(data.signals));
249
+ });
250
+
251
+ glitchDetector.on('recovery-success', (data) => {
252
+ log('recovery successful via', data.method);
253
+ });
254
+
255
+ glitchDetector.on('recovery-failed', () => {
256
+ log('recovery failed - may need manual intervention');
257
+ });
258
+
259
+ log('glitch detector installed');
260
+ }
261
+
181
262
  installed = true;
182
- log('installed successfully - v1.0.1 with typing fix');
263
+ log('installed successfully - v2.0.0 with glitch detection & 120-line limit');
183
264
  }
184
265
 
185
266
  function installResizeDebounce() {
@@ -219,7 +300,7 @@ function installResizeDebounce() {
219
300
  }
220
301
 
221
302
  /**
222
- * manually clear scrollback - call this whenever you want
303
+ * manually clear scrollback - call this whenever you want, it won't break anything
223
304
  */
224
305
  function clearScrollback() {
225
306
  if (originalWrite) {
@@ -234,12 +315,40 @@ function clearScrollback() {
234
315
  * get current stats for debugging
235
316
  */
236
317
  function getStats() {
237
- return {
318
+ const stats = {
238
319
  renderCount,
320
+ lineCount,
239
321
  lastResizeTime,
240
322
  installed,
241
323
  config
242
324
  };
325
+
326
+ // add glitch stats if available
327
+ if (glitchDetector) {
328
+ stats.glitch = glitchDetector.getMetrics();
329
+ }
330
+
331
+ return stats;
332
+ }
333
+
334
+ /**
335
+ * force recovery if shit hits the fan
336
+ */
337
+ async function forceRecovery() {
338
+ if (glitchDetector) {
339
+ log('forcing recovery manually');
340
+ return await glitchDetector.attemptRecovery();
341
+ }
342
+ // Fallback if no detector
343
+ clearScrollback();
344
+ return true;
345
+ }
346
+
347
+ /**
348
+ * check if terminal's currently cooked
349
+ */
350
+ function isGlitched() {
351
+ return glitchDetector ? glitchDetector.isInGlitchState() : false;
243
352
  }
244
353
 
245
354
  /**
@@ -253,7 +362,7 @@ function setConfig(key, value) {
253
362
  }
254
363
 
255
364
  /**
256
- * disable the fix (mostly for testing)
365
+ * disable the fix (it's mostly for testing)
257
366
  */
258
367
  function disable() {
259
368
  if (originalWrite) {
@@ -268,5 +377,9 @@ module.exports = {
268
377
  getStats,
269
378
  setConfig,
270
379
  disable,
271
- config
380
+ config,
381
+ // NEW v2.0 exports
382
+ forceRecovery,
383
+ isGlitched,
384
+ getDetector: () => glitchDetector
272
385
  };
package/loader.cjs CHANGED
@@ -6,7 +6,7 @@
6
6
  * use like: node --require claudescreenfix-hardwicksoftware/loader.cjs $(which claude)
7
7
  *
8
8
  * this auto-installs the fix before claude code even starts
9
- * no code changes needed in claude itself
9
+ * you don't need to change any code in claude itself - it just works
10
10
  */
11
11
 
12
12
  const fix = require('./index.cjs');
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "claudescreenfix-hardwicksoftware",
3
- "version": "1.0.1",
4
- "description": "fixes the scroll glitch in claude code cli - unbounded scrollback, resize thrashing, all that bs",
3
+ "version": "2.0.0",
4
+ "description": "fixes the scroll glitch in claude code cli - now with GLITCH DETECTION, 120-line limit enforcement, and auto-recovery",
5
5
  "main": "index.cjs",
6
6
  "bin": {
7
7
  "claude-fixed": "./bin/claude-fixed.js"
8
8
  },
9
- "scripts": {},
9
+ "scripts": {
10
+ "test": "node -e \"const fix = require('./index.cjs'); fix.install(); console.log(fix.getStats());\""
11
+ },
10
12
  "keywords": [
11
13
  "claude",
12
14
  "terminal",
@@ -15,7 +17,10 @@
15
17
  "ink",
16
18
  "cli",
17
19
  "glitch",
18
- "performance"
20
+ "performance",
21
+ "glitch-detection",
22
+ "recovery",
23
+ "120-line-limit"
19
24
  ],
20
25
  "author": "jonhardwick-spec",
21
26
  "license": "MIT",
@@ -32,6 +37,7 @@
32
37
  "files": [
33
38
  "index.cjs",
34
39
  "loader.cjs",
40
+ "glitch-detector.cjs",
35
41
  "bin/",
36
42
  "README.md",
37
43
  "LICENSE"