-
-
Notifications
You must be signed in to change notification settings - Fork 82
Expand file tree
/
Copy pathshell.ts
More file actions
104 lines (94 loc) · 4.03 KB
/
Copy pathshell.ts
File metadata and controls
104 lines (94 loc) · 4.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* Cleans and formats terminal output while preserving structure and paths
* Handles ANSI, OSC, and various terminal control sequences
*/
export function cleanTerminalOutput(input: string): string {
// Step 1: Remove OSC sequences (including those with parameters)
const removeOsc = input
.replace(/\x1b\](\d+;[^\x07\x1b]*|\d+[^\x07\x1b]*)\x07/g, '')
.replace(/\](\d+;[^\n]*|\d+[^\n]*)/g, '');
// Step 2: Remove ANSI escape sequences and color codes more thoroughly
const removeAnsi = removeOsc
// Remove all escape sequences with parameters
.replace(/\u001b\[[\?]?[0-9;]*[a-zA-Z]/g, '')
.replace(/\x1b\[[\?]?[0-9;]*[a-zA-Z]/g, '')
// Remove color codes
.replace(/\u001b\[[0-9;]*m/g, '')
.replace(/\x1b\[[0-9;]*m/g, '')
// Clean up any remaining escape characters
.replace(/\u001b/g, '')
.replace(/\x1b/g, '');
// Step 3: Clean up carriage returns and newlines
const cleanNewlines = removeAnsi
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
.replace(/\n{3,}/g, '\n\n');
// Step 4: Add newlines at key breakpoints while preserving paths
const formatOutput = cleanNewlines
// Preserve prompt line
.replace(/^([~\/][^\n❯]+)❯/m, '$1\n❯')
// Add newline before command output indicators
.replace(/(?<!^|\n)>/g, '\n>')
// Add newline before error keywords without breaking paths
.replace(/(?<!^|\n|\w)(error|failed|warning|Error|Failed|Warning):/g, '\n$1:')
// Add newline before 'at' in stack traces without breaking paths
.replace(/(?<!^|\n|\/)(at\s+(?!async|sync))/g, '\nat ')
// Ensure 'at async' stays on same line
.replace(/\bat\s+async/g, 'at async')
// Add newline before npm error indicators
.replace(/(?<!^|\n)(npm ERR!)/g, '\n$1');
// Step 5: Clean up whitespace while preserving intentional spacing
const cleanSpaces = formatOutput
.split('\n')
.map((line) => line.trim())
.filter((line) => line.length > 0)
.join('\n');
// Step 6: Final cleanup
return cleanSpaces
.replace(/\n{3,}/g, '\n\n') // Replace multiple newlines with double newlines
.replace(/:\s+/g, ': ') // Normalize spacing after colons
.replace(/\s{2,}/g, ' ') // Remove multiple spaces
.replace(/^\s+|\s+$/g, '') // Trim start and end
.replace(/\u0000/g, ''); // Remove null characters
}
const BANNED_LINES = ['transforming (', 'computing gzip size', 'idealTree buildDeps', 'timing reify:unpack'];
// Taken from https://github.com/sindresorhus/cli-spinners
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
// Cleaning terminal output helps the agent focus on the important parts and
// not waste input tokens.
export function cleanCodinitOutput(output: string) {
output = cleanTerminalOutput(output);
const normalizedNewlines = output.replace('\r\n', '\n').replace('\r', '\n');
const rawLines = normalizedNewlines.split('\n');
let lastSpinnerLine: string | null = null;
let lines = [];
for (const line of rawLines) {
if (BANNED_LINES.some((bannedLine) => line.includes(bannedLine))) {
continue;
}
if (SPINNER_FRAMES.some((spinnerFrame) => line.startsWith(spinnerFrame))) {
const lineWithoutSpinner = line.slice(1).trim();
if (lineWithoutSpinner === lastSpinnerLine) {
continue;
}
lastSpinnerLine = lineWithoutSpinner;
lines.push(lineWithoutSpinner);
continue;
}
lines.push(line);
}
// Remove all esbuild "could not resolve" errors except the last one
const firstEsbuildNodeErrror = lines.findIndex((line) => line.includes('[ERROR] Could not resolve'));
if (firstEsbuildNodeErrror !== -1) {
const lastEsbuildNodeError = lines.findLastIndex((line) => line.includes('[ERROR] Could not resolve'));
if (lastEsbuildNodeError !== -1) {
// just keep the last one
lines = [...lines.slice(0, firstEsbuildNodeErrror), ...lines.slice(lastEsbuildNodeError)];
}
}
const result = lines.join('\n');
if (output !== result) {
console.log(`Sanitized output: ${output.length} -> ${result.length}`);
}
return result;
}