claudefix 2.6.1 → 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 +155 -116
- 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,19 +863,23 @@ 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;
|
|
805
871
|
const cols = process.stdout.columns || 80;
|
|
806
872
|
|
|
807
873
|
if (sshMode || !showFooter) {
|
|
874
|
+
// SSH mode or no footer: NO scroll region manipulation
|
|
808
875
|
ptyProcess.resize(cols, rows);
|
|
809
876
|
} else {
|
|
810
|
-
//
|
|
877
|
+
// Local mode with footer: reserve bottom row(s) for footer
|
|
878
|
+
// FIX: Handle edge case where terminal is too small
|
|
811
879
|
const contentRows = Math.max(1, rows - footerRows);
|
|
812
880
|
|
|
881
|
+
// FIX: Reset scroll region first, then set new one to avoid clipping
|
|
882
|
+
// This prevents content from being cut off during dynamic resize
|
|
813
883
|
process.stdout.write(
|
|
814
884
|
'\x1b[r' + // Reset scroll region to full terminal
|
|
815
885
|
'\x1b7' + // Save cursor position
|
|
@@ -817,6 +887,7 @@ if (!usePTY) {
|
|
|
817
887
|
'\x1b8' // Restore cursor position
|
|
818
888
|
);
|
|
819
889
|
|
|
890
|
+
// Resize PTY to content area (excludes footer row(s))
|
|
820
891
|
ptyProcess.resize(cols, contentRows);
|
|
821
892
|
}
|
|
822
893
|
}
|
|
@@ -861,13 +932,6 @@ if (!usePTY) {
|
|
|
861
932
|
);
|
|
862
933
|
}
|
|
863
934
|
|
|
864
|
-
// FIX (Linux only): Clear screen once at startup to prevent ghost frames.
|
|
865
|
-
// Ink renders inline (no alternate screen) and re-renders during startup cause
|
|
866
|
-
// content to stack/triplicate. A single clear before first output fixes this.
|
|
867
|
-
// NOTE: Do NOT use alternate screen (\x1b[?1049h) — it makes Ink switch to
|
|
868
|
-
// full-screen mode where it sends \x1b[H\x1b[J which erases the footer.
|
|
869
|
-
let startupCleared = false;
|
|
870
|
-
|
|
871
935
|
// Initial setup
|
|
872
936
|
setupScrollRegion();
|
|
873
937
|
if (!sshMode && showFooter) drawFooter();
|
|
@@ -905,74 +969,53 @@ if (!usePTY) {
|
|
|
905
969
|
// macOS Terminal.app doesn't have VTE bugs, so skip aggressive escape mangling
|
|
906
970
|
const isAppleTerminal = process.env.TERM_PROGRAM === 'Apple_Terminal';
|
|
907
971
|
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
// if the buffer contains \x1b[H (home cursor = new render), inject \x1b[J after it.
|
|
913
|
-
// Since the entire render is flushed atomically, clear + content arrive together.
|
|
914
|
-
let outputBuffer = '';
|
|
915
|
-
let flushTimer = null;
|
|
916
|
-
const FLUSH_DELAY_MS = 16; // Normal flush delay (~1 frame)
|
|
917
|
-
const COALESCE_DELAY_MS = 80; // Extended delay during rapid full repaints
|
|
918
|
-
let lastFullRenderTime = 0; // When we last flushed a full repaint
|
|
919
|
-
|
|
920
|
-
function processAndFlush() {
|
|
921
|
-
flushTimer = null;
|
|
922
|
-
if (!outputBuffer || exiting) return;
|
|
923
|
-
|
|
924
|
-
let output = outputBuffer;
|
|
925
|
-
outputBuffer = '';
|
|
926
|
-
|
|
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;
|
|
927
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)
|
|
928
978
|
const shouldStrip = config.colorStripping &&
|
|
929
979
|
process.env.CLAUDE_STRIP_BG_COLORS !== '0' &&
|
|
930
980
|
!isAppleTerminal;
|
|
931
|
-
|
|
981
|
+
let output = shouldStrip ? stripColors(data) : data;
|
|
932
982
|
|
|
933
|
-
// Intercept scroll region resets from Ink
|
|
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.
|
|
934
987
|
if (showFooter && !sshMode) {
|
|
935
988
|
const cr = contentRows();
|
|
989
|
+
// Replace bare scroll region reset with our constrained one
|
|
936
990
|
output = output.replace(/\x1b\[r/g, `\x1b[1;${cr}r`);
|
|
991
|
+
// Also catch explicit full-terminal scroll regions like \x1b[1;24r
|
|
937
992
|
const fullRows = process.stdout.rows || 24;
|
|
938
993
|
output = output.replace(new RegExp(`\\x1b\\[1;${fullRows}r`, 'g'), `\x1b[1;${cr}r`);
|
|
939
994
|
}
|
|
940
995
|
|
|
941
|
-
// 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).
|
|
942
1000
|
if (!isMac) {
|
|
943
|
-
if (output.includes('\x1b[2J')) {
|
|
1001
|
+
if (output.includes('\x1b[2J') || output.includes('\x1b[3J')) {
|
|
944
1002
|
output = output.replace(/\x1b\[2J/g, '\x1b[2J\x1b[3J');
|
|
945
1003
|
}
|
|
946
1004
|
}
|
|
947
1005
|
|
|
948
|
-
// FIX (Linux only):
|
|
949
|
-
//
|
|
950
|
-
//
|
|
951
|
-
//
|
|
952
|
-
//
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
if (!isMac && !startupCleared) {
|
|
958
|
-
startupCleared = true;
|
|
959
|
-
output = '\x1b[2J\x1b[3J\x1b[H' + output;
|
|
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
|
+
}
|
|
960
1015
|
}
|
|
961
1016
|
|
|
962
1017
|
process.stdout.write(output);
|
|
963
1018
|
if (showFooter) scheduleFooterDraw();
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
ptyProcess.onData((data) => {
|
|
967
|
-
if (exiting) return;
|
|
968
|
-
// Accumulate into buffer
|
|
969
|
-
outputBuffer += data;
|
|
970
|
-
// Reset flush timer — wait for output burst to finish
|
|
971
|
-
if (flushTimer) clearTimeout(flushTimer);
|
|
972
|
-
// Use longer delay if we recently flushed a full render (coalesce rapid repaints)
|
|
973
|
-
const recentFullRender = (Date.now() - lastFullRenderTime) < 200;
|
|
974
|
-
const delay = recentFullRender ? COALESCE_DELAY_MS : FLUSH_DELAY_MS;
|
|
975
|
-
flushTimer = setTimeout(processAndFlush, delay);
|
|
976
1019
|
});
|
|
977
1020
|
|
|
978
1021
|
// Forward stdin with Ctrl+Shift+H hotkey
|
|
@@ -1037,9 +1080,7 @@ if (!usePTY) {
|
|
|
1037
1080
|
clearInterval(memCheckInterval);
|
|
1038
1081
|
if (gcInterval) clearInterval(gcInterval);
|
|
1039
1082
|
if (pendingDraw) clearTimeout(pendingDraw);
|
|
1040
|
-
|
|
1041
|
-
if (outputBuffer) { process.stdout.write(outputBuffer); outputBuffer = ''; }
|
|
1042
|
-
// Clean exit: leave alternate screen, reset scroll region
|
|
1083
|
+
// Clean exit: reset scroll region, clear screen, show exit banner
|
|
1043
1084
|
if (!sshMode && showFooter) {
|
|
1044
1085
|
process.stdout.write(
|
|
1045
1086
|
'\x1b[r' + // Reset scroll region to full terminal
|
|
@@ -1097,8 +1138,6 @@ if (!usePTY) {
|
|
|
1097
1138
|
if (cpuLimiter) try { cpuLimiter.kill(); } catch {}
|
|
1098
1139
|
if (footerInterval) clearInterval(footerInterval);
|
|
1099
1140
|
if (pendingDraw) clearTimeout(pendingDraw);
|
|
1100
|
-
if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; }
|
|
1101
|
-
if (outputBuffer) { process.stdout.write(outputBuffer); outputBuffer = ''; }
|
|
1102
1141
|
if (!sshMode && showFooter) {
|
|
1103
1142
|
process.stdout.write(
|
|
1104
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": {
|