claudefix 2.6.0 → 2.6.2
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.
- package/bin/claude-fixed.js +149 -135
- package/package.json +1 -1
package/bin/claude-fixed.js
CHANGED
|
@@ -295,56 +295,131 @@ if (debug) {
|
|
|
295
295
|
*/
|
|
296
296
|
function stripColors(data) {
|
|
297
297
|
let str = data;
|
|
298
|
+
const isGtk4 = forceNuclear || terminalType === 'ptyxis' || terminalType === 'gtk4-vte';
|
|
298
299
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
//
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
300
|
+
if (isGtk4) {
|
|
301
|
+
// THERMONUCLEAR MODE for Ptyxis/GTK4 terminals
|
|
302
|
+
// Strip ALL styling except basic foreground colors
|
|
303
|
+
// This is aggressive but ensures clean rendering
|
|
304
|
+
|
|
305
|
+
// First, strip ALL escape sequences
|
|
306
|
+
str = str.replace(/\x1b\[[0-9;]*m/g, (match) => {
|
|
307
|
+
// Only keep sequences that are JUST foreground colors (30-37, 90-97, 38;5;X, 38;2;R;G;B)
|
|
308
|
+
// and reset (0)
|
|
309
|
+
|
|
310
|
+
// Extract the codes
|
|
311
|
+
const codes = match.slice(2, -1).split(';').filter(c => c !== '');
|
|
312
|
+
|
|
313
|
+
if (codes.length === 0) return '\x1b[0m';
|
|
314
|
+
|
|
315
|
+
// Filter to only allowed codes
|
|
316
|
+
const allowed = [];
|
|
317
|
+
let i = 0;
|
|
318
|
+
while (i < codes.length) {
|
|
319
|
+
const code = parseInt(codes[i], 10);
|
|
320
|
+
|
|
321
|
+
// Reset
|
|
322
|
+
if (code === 0) {
|
|
323
|
+
allowed.push('0');
|
|
324
|
+
i++;
|
|
325
|
+
}
|
|
326
|
+
// Bold (1), but NOT dim (2)
|
|
327
|
+
else if (code === 1) {
|
|
328
|
+
allowed.push('1');
|
|
329
|
+
i++;
|
|
330
|
+
}
|
|
331
|
+
// Standard foreground (30-37)
|
|
332
|
+
else if (code >= 30 && code <= 37) {
|
|
333
|
+
allowed.push(codes[i]);
|
|
334
|
+
i++;
|
|
335
|
+
}
|
|
336
|
+
// Default foreground (39)
|
|
337
|
+
else if (code === 39) {
|
|
338
|
+
allowed.push('39');
|
|
339
|
+
i++;
|
|
340
|
+
}
|
|
341
|
+
// Bright foreground (90-97)
|
|
342
|
+
else if (code >= 90 && code <= 97) {
|
|
343
|
+
allowed.push(codes[i]);
|
|
344
|
+
i++;
|
|
345
|
+
}
|
|
346
|
+
// 256-color foreground (38;5;X)
|
|
347
|
+
else if (code === 38 && codes[i + 1] === '5' && codes[i + 2]) {
|
|
348
|
+
allowed.push('38', '5', codes[i + 2]);
|
|
349
|
+
i += 3;
|
|
350
|
+
}
|
|
351
|
+
// True color foreground (38;2;R;G;B)
|
|
352
|
+
else if (code === 38 && codes[i + 1] === '2' && codes[i + 4]) {
|
|
353
|
+
allowed.push('38', '2', codes[i + 2], codes[i + 3], codes[i + 4]);
|
|
354
|
+
i += 5;
|
|
355
|
+
}
|
|
356
|
+
// Skip backgrounds (40-47, 49, 100-107, 48;5;X, 48;2;R;G;B)
|
|
357
|
+
else if (code >= 40 && code <= 49) {
|
|
358
|
+
i++;
|
|
359
|
+
}
|
|
360
|
+
else if (code >= 100 && code <= 107) {
|
|
361
|
+
i++;
|
|
362
|
+
}
|
|
363
|
+
else if (code === 48 && codes[i + 1] === '5') {
|
|
364
|
+
i += 3; // Skip 48;5;X
|
|
365
|
+
}
|
|
366
|
+
else if (code === 48 && codes[i + 1] === '2') {
|
|
367
|
+
i += 5; // Skip 48;2;R;G;B
|
|
368
|
+
}
|
|
369
|
+
// Skip other problematic codes (2=dim, 7=inverse, 8=hidden)
|
|
370
|
+
else if (code === 2 || code === 7 || code === 8 || code === 22 || code === 27 || code === 28) {
|
|
371
|
+
i++;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
// Unknown - skip it
|
|
375
|
+
i++;
|
|
376
|
+
}
|
|
331
377
|
}
|
|
332
|
-
// Skip ALL backgrounds: 40-47, 49, 100-107
|
|
333
|
-
else if ((code >= 40 && code <= 49) || (code >= 100 && code <= 107)) { i++; }
|
|
334
|
-
// Skip 256-color bg (48;5;X)
|
|
335
|
-
else if (code === 48 && codes[i + 1] === '5') { i += 3; }
|
|
336
|
-
// Skip true color bg (48;2;R;G;B)
|
|
337
|
-
else if (code === 48 && codes[i + 1] === '2') { i += 5; }
|
|
338
|
-
// Skip dim (2), inverse (7), hidden (8) and their offs
|
|
339
|
-
else if (code === 2 || code === 7 || code === 8 || code === 27 || code === 28) { i++; }
|
|
340
|
-
// Unknown - skip
|
|
341
|
-
else { i++; }
|
|
342
|
-
}
|
|
343
378
|
|
|
344
|
-
|
|
345
|
-
|
|
379
|
+
if (allowed.length === 0) return '';
|
|
380
|
+
return `\x1b[${allowed.join(';')}m`;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return str;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Regular mode for other terminals
|
|
387
|
+
// Remove standalone background color sequences entirely
|
|
388
|
+
str = str.replace(/\x1b\[48;5;\d+m/g, ''); // 256-color bg
|
|
389
|
+
str = str.replace(/\x1b\[48;2;\d+;\d+;\d+m/g, ''); // true color bg
|
|
390
|
+
str = str.replace(/\x1b\[4[0-7]m/g, ''); // standard bg 40-47
|
|
391
|
+
str = str.replace(/\x1b\[49m/g, ''); // default bg
|
|
392
|
+
str = str.replace(/\x1b\[10[0-7]m/g, ''); // bright bg 100-107
|
|
393
|
+
str = str.replace(/\x1b\[7m/g, ''); // inverse
|
|
394
|
+
str = str.replace(/\x1b\[27m/g, ''); // inverse off
|
|
395
|
+
|
|
396
|
+
// NUCLEAR MODE: Extra aggressive stripping for VTE issues
|
|
397
|
+
str = str.replace(/\x1b\[2m/g, ''); // dim text (causes grey)
|
|
398
|
+
str = str.replace(/\x1b\[22m/g, ''); // dim off
|
|
399
|
+
str = str.replace(/\x1b\[8m/g, ''); // hidden text
|
|
400
|
+
str = str.replace(/\x1b\[28m/g, ''); // hidden off
|
|
401
|
+
|
|
402
|
+
// Strip any remaining compound sequences with background codes
|
|
403
|
+
// Matches patterns like \x1b[0;48;5;236m or \x1b[38;5;196;48;5;236m
|
|
404
|
+
str = str.replace(/\x1b\[([0-9;]*?)(;?48;[52];[0-9;]+)(;?[0-9;]*)m/g, (match, before, bg, after) => {
|
|
405
|
+
const parts = [before, after].filter(p => p && p.length > 0 && p !== ';');
|
|
406
|
+
if (parts.length === 0) return '\x1b[0m';
|
|
407
|
+
return `\x1b[${parts.join(';').replace(/^;|;$/g, '').replace(/;;+/g, ';')}m`;
|
|
346
408
|
});
|
|
347
409
|
|
|
410
|
+
// For combined sequences like \x1b[1;41m (bold + red bg), remove just the bg part
|
|
411
|
+
// This regex finds sequences with bg codes and removes just those codes
|
|
412
|
+
str = str.replace(/\x1b\[([0-9;]*)(?:;?)(4[0-7]|49|10[0-7]|48;5;\d+|48;2;\d+;\d+;\d+)(?:;?)([0-9;]*)m/g,
|
|
413
|
+
(match, before, bg, after) => {
|
|
414
|
+
const parts = [before, after].filter(p => p && p.length > 0);
|
|
415
|
+
if (parts.length === 0) return '';
|
|
416
|
+
return `\x1b[${parts.join(';')}m`;
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Clean up any malformed sequences that might be left
|
|
420
|
+
str = str.replace(/\x1b\[;+m/g, '\x1b[0m'); // \x1b[;;m -> \x1b[0m
|
|
421
|
+
str = str.replace(/\x1b\[m/g, '\x1b[0m'); // \x1b[m -> \x1b[0m
|
|
422
|
+
|
|
348
423
|
return str;
|
|
349
424
|
}
|
|
350
425
|
|
|
@@ -405,15 +480,6 @@ function getTerminalType() {
|
|
|
405
480
|
return 'ptyxis';
|
|
406
481
|
}
|
|
407
482
|
|
|
408
|
-
// XFCE4 Terminal - uses VTE but handles ANSI fine, does NOT need thermonuclear mode
|
|
409
|
-
// Must check BEFORE the VTE version check or it gets misclassified as gtk4-vte
|
|
410
|
-
if (termProgram === 'xfce4-terminal' || termProgram === 'Xfce Terminal' ||
|
|
411
|
-
process.env.XFCE_TERMINAL_VERSION ||
|
|
412
|
-
process.env.WINDOWPATH || // XFCE sets this
|
|
413
|
-
(process.env.XDG_CURRENT_DESKTOP || '').toLowerCase().includes('xfce')) {
|
|
414
|
-
return 'xfce-terminal';
|
|
415
|
-
}
|
|
416
|
-
|
|
417
483
|
// GTK4 terminals (like Ptyxis) often have GDK_BACKEND set
|
|
418
484
|
if (gdkBackend === 'wayland' && vteVersion) {
|
|
419
485
|
return 'gtk4-vte';
|
|
@@ -797,8 +863,8 @@ if (!usePTY) {
|
|
|
797
863
|
}
|
|
798
864
|
}
|
|
799
865
|
|
|
800
|
-
// How many footer rows to reserve (1 for claudefix, +
|
|
801
|
-
const footerRows = specmemActive ?
|
|
866
|
+
// How many footer rows to reserve (1 for claudefix, +2 if specmem active for team comms + status)
|
|
867
|
+
const footerRows = specmemActive ? 3 : 1;
|
|
802
868
|
|
|
803
869
|
function setupScrollRegion() {
|
|
804
870
|
const rows = process.stdout.rows || 24;
|
|
@@ -866,22 +932,9 @@ if (!usePTY) {
|
|
|
866
932
|
);
|
|
867
933
|
}
|
|
868
934
|
|
|
869
|
-
// FIX (Linux only): Enter alternate screen buffer to prevent ghost frames.
|
|
870
|
-
// Ink renders inline (no alternate screen) — content stacks/triplicates on re-render.
|
|
871
|
-
// By entering alternate screen ourselves, Ink's output goes to a clean buffer.
|
|
872
|
-
// On exit, we leave alternate screen to restore the original terminal content.
|
|
873
|
-
if (!isMac && !sshMode) {
|
|
874
|
-
process.stdout.write('\x1b[?1049h\x1b[H\x1b[J'); // Enter alternate screen + clear
|
|
875
|
-
}
|
|
876
|
-
|
|
877
935
|
// Initial setup
|
|
878
936
|
setupScrollRegion();
|
|
879
|
-
if (!sshMode && showFooter)
|
|
880
|
-
drawFooter();
|
|
881
|
-
// Re-draw footer after Ink's initial setup (Ink may overwrite during startup)
|
|
882
|
-
setTimeout(drawFooter, 500);
|
|
883
|
-
setTimeout(drawFooter, 1500);
|
|
884
|
-
}
|
|
937
|
+
if (!sshMode && showFooter) drawFooter();
|
|
885
938
|
|
|
886
939
|
// Footer refresh - debounced, only redraws AFTER PTY output settles
|
|
887
940
|
// No independent timer - footer only redraws in response to PTY activity
|
|
@@ -916,82 +969,53 @@ if (!usePTY) {
|
|
|
916
969
|
// macOS Terminal.app doesn't have VTE bugs, so skip aggressive escape mangling
|
|
917
970
|
const isAppleTerminal = process.env.TERM_PROGRAM === 'Apple_Terminal';
|
|
918
971
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
// if the buffer contains \x1b[H (home cursor = new render), inject \x1b[J after it.
|
|
924
|
-
// Since the entire render is flushed atomically, clear + content arrive together.
|
|
925
|
-
let outputBuffer = '';
|
|
926
|
-
let flushTimer = null;
|
|
927
|
-
const FLUSH_DELAY_MS = 16; // Normal flush delay (~1 frame)
|
|
928
|
-
const COALESCE_DELAY_MS = 80; // Extended delay during rapid full repaints
|
|
929
|
-
let lastFullRenderTime = 0; // When we last flushed a full repaint
|
|
930
|
-
|
|
931
|
-
function processAndFlush() {
|
|
932
|
-
flushTimer = null;
|
|
933
|
-
if (!outputBuffer || exiting) return;
|
|
934
|
-
|
|
935
|
-
let output = outputBuffer;
|
|
936
|
-
outputBuffer = '';
|
|
937
|
-
|
|
972
|
+
ptyProcess.onData((data) => {
|
|
973
|
+
// Stop forwarding output once we're cleaning up - prevents Claude's
|
|
974
|
+
// dying output from overwriting our exit banner
|
|
975
|
+
if (exiting) return;
|
|
938
976
|
// Only strip colors if config enabled and env var not disabled
|
|
977
|
+
// On macOS: skip color stripping entirely for Apple Terminal (no VTE bugs)
|
|
939
978
|
const shouldStrip = config.colorStripping &&
|
|
940
979
|
process.env.CLAUDE_STRIP_BG_COLORS !== '0' &&
|
|
941
980
|
!isAppleTerminal;
|
|
942
|
-
|
|
981
|
+
let output = shouldStrip ? stripColors(data) : data;
|
|
943
982
|
|
|
944
983
|
// FIX: Intercept scroll region resets from Ink/Claude output.
|
|
984
|
+
// Ink sends \x1b[r which resets scroll region to full terminal,
|
|
985
|
+
// causing our footer area to become content area and old content bleeds through.
|
|
986
|
+
// Replace with our constrained scroll region.
|
|
945
987
|
if (showFooter && !sshMode) {
|
|
946
988
|
const cr = contentRows();
|
|
989
|
+
// Replace bare scroll region reset with our constrained one
|
|
947
990
|
output = output.replace(/\x1b\[r/g, `\x1b[1;${cr}r`);
|
|
991
|
+
// Also catch explicit full-terminal scroll regions like \x1b[1;24r
|
|
948
992
|
const fullRows = process.stdout.rows || 24;
|
|
949
993
|
output = output.replace(new RegExp(`\\x1b\\[1;${fullRows}r`, 'g'), `\x1b[1;${cr}r`);
|
|
950
994
|
}
|
|
951
995
|
|
|
952
|
-
// FIX (Linux only):
|
|
996
|
+
// FIX (Linux only): When Ink sends a screen clear (\x1b[2J), inject scrollback
|
|
997
|
+
// clear to prevent old content from sticking around in VTE terminals.
|
|
998
|
+
// SKIP on macOS: Terminal.app handles \x1b[3J differently and it causes
|
|
999
|
+
// visual glitches (screen flashing, content disappearing).
|
|
953
1000
|
if (!isMac) {
|
|
954
|
-
if (output.includes('\x1b[2J')) {
|
|
1001
|
+
if (output.includes('\x1b[2J') || output.includes('\x1b[3J')) {
|
|
955
1002
|
output = output.replace(/\x1b\[2J/g, '\x1b[2J\x1b[3J');
|
|
956
1003
|
}
|
|
957
1004
|
}
|
|
958
1005
|
|
|
959
|
-
// FIX (Linux only):
|
|
960
|
-
//
|
|
961
|
-
//
|
|
962
|
-
//
|
|
963
|
-
//
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
// region and footer. Remove these so our alternate screen stays in control.
|
|
969
|
-
if (!isMac && !sshMode) {
|
|
970
|
-
output = output.replace(/\x1b\[\?1049[hl]/g, '');
|
|
971
|
-
// Also strip smcup/rmcup variants
|
|
972
|
-
output = output.replace(/\x1b\[\?47[hl]/g, '');
|
|
1006
|
+
// FIX (Linux only): Detect Ink's home cursor (\x1b[H or \x1b[1;1H) followed
|
|
1007
|
+
// by content - this is a differential re-render. Clear to end of screen after
|
|
1008
|
+
// home to prevent stale content below the new render from showing through.
|
|
1009
|
+
// SKIP on macOS: Apple Terminal repaints on \x1b[J which causes rapid
|
|
1010
|
+
// flickering when combined with Ink's frequent re-renders.
|
|
1011
|
+
if (!isMac) {
|
|
1012
|
+
if (output.includes('\x1b[H') || output.includes('\x1b[1;1H')) {
|
|
1013
|
+
output = output.replace(/(\x1b\[(?:1;1)?H)/, '$1\x1b[J');
|
|
1014
|
+
}
|
|
973
1015
|
}
|
|
974
1016
|
|
|
975
1017
|
process.stdout.write(output);
|
|
976
|
-
// Re-assert scroll region after each flush (Ink may have sent sequences that
|
|
977
|
-
// override it, even after our replacements)
|
|
978
|
-
if (showFooter && !sshMode) {
|
|
979
|
-
const cr = contentRows();
|
|
980
|
-
process.stdout.write(`\x1b7\x1b[1;${cr}r\x1b8`);
|
|
981
|
-
}
|
|
982
1018
|
if (showFooter) scheduleFooterDraw();
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
ptyProcess.onData((data) => {
|
|
986
|
-
if (exiting) return;
|
|
987
|
-
// Accumulate into buffer
|
|
988
|
-
outputBuffer += data;
|
|
989
|
-
// Reset flush timer — wait for output burst to finish
|
|
990
|
-
if (flushTimer) clearTimeout(flushTimer);
|
|
991
|
-
// Use longer delay if we recently flushed a full render (coalesce rapid repaints)
|
|
992
|
-
const recentFullRender = (Date.now() - lastFullRenderTime) < 200;
|
|
993
|
-
const delay = recentFullRender ? COALESCE_DELAY_MS : FLUSH_DELAY_MS;
|
|
994
|
-
flushTimer = setTimeout(processAndFlush, delay);
|
|
995
1019
|
});
|
|
996
1020
|
|
|
997
1021
|
// Forward stdin with Ctrl+Shift+H hotkey
|
|
@@ -1056,12 +1080,7 @@ if (!usePTY) {
|
|
|
1056
1080
|
clearInterval(memCheckInterval);
|
|
1057
1081
|
if (gcInterval) clearInterval(gcInterval);
|
|
1058
1082
|
if (pendingDraw) clearTimeout(pendingDraw);
|
|
1059
|
-
|
|
1060
|
-
if (outputBuffer) { process.stdout.write(outputBuffer); outputBuffer = ''; }
|
|
1061
|
-
// Clean exit: leave alternate screen, reset scroll region
|
|
1062
|
-
if (!isMac && !sshMode) {
|
|
1063
|
-
process.stdout.write('\x1b[?1049l'); // Exit alternate screen buffer
|
|
1064
|
-
}
|
|
1083
|
+
// Clean exit: reset scroll region, clear screen, show exit banner
|
|
1065
1084
|
if (!sshMode && showFooter) {
|
|
1066
1085
|
process.stdout.write(
|
|
1067
1086
|
'\x1b[r' + // Reset scroll region to full terminal
|
|
@@ -1119,11 +1138,6 @@ if (!usePTY) {
|
|
|
1119
1138
|
if (cpuLimiter) try { cpuLimiter.kill(); } catch {}
|
|
1120
1139
|
if (footerInterval) clearInterval(footerInterval);
|
|
1121
1140
|
if (pendingDraw) clearTimeout(pendingDraw);
|
|
1122
|
-
if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; }
|
|
1123
|
-
if (outputBuffer) { process.stdout.write(outputBuffer); outputBuffer = ''; }
|
|
1124
|
-
if (!isMac && !sshMode) {
|
|
1125
|
-
process.stdout.write('\x1b[?1049l'); // Exit alternate screen buffer
|
|
1126
|
-
}
|
|
1127
1141
|
if (!sshMode && showFooter) {
|
|
1128
1142
|
process.stdout.write(
|
|
1129
1143
|
'\x1b[r' + // Reset scroll region
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudefix",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "Fixes screen glitching, blocky colors, AND MEMORY LEAKS in Claude Code CLI on Linux and macOS. All features optional via env vars. Shows config options on install. Developed by Hardwick Software Services @ https://justcalljon.pro",
|
|
5
5
|
"main": "index.cjs",
|
|
6
6
|
"bin": {
|