@compilr-dev/sdk 0.9.4 → 0.9.6
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/dist/compressors/bash.js +192 -216
- package/package.json +1 -1
package/dist/compressors/bash.js
CHANGED
|
@@ -19,26 +19,24 @@ export function compressBashOutput(command, stdout) {
|
|
|
19
19
|
return compressGitLog(stdout);
|
|
20
20
|
if (cmd.match(/^git\s+diff/))
|
|
21
21
|
return compressGitDiff(stdout);
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// git write commands (add, commit, push, pull, merge, rebase, checkout, switch, branch)
|
|
23
|
+
// — leave uncompressed. These are side-effect commands that cannot be re-run,
|
|
24
|
+
// and the agent needs to see the full result to confirm what happened.
|
|
24
25
|
// npm/yarn/pnpm
|
|
25
|
-
|
|
26
|
-
return compressNpmInstall(stdout);
|
|
26
|
+
// npm install — side-effect command, leave uncompressed
|
|
27
27
|
if (cmd.match(/^(npm|yarn|pnpm)\s+(test|run\s+test)/))
|
|
28
28
|
return compressTestOutput(stdout);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (cmd.match(/^(npm|yarn|pnpm)\s+run\s+(build|tsc)/))
|
|
32
|
-
return compressBuildOutput(stdout);
|
|
29
|
+
// lint output — leave uncompressed. Agent needs every error location to fix code.
|
|
30
|
+
// npm build — side-effect command, leave uncompressed
|
|
33
31
|
// Direct test runners
|
|
34
32
|
if (cmd.match(/^(jest|vitest|mocha|pytest|cargo\s+test|go\s+test)/))
|
|
35
33
|
return compressTestOutput(stdout);
|
|
36
|
-
// Direct linters
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
// Direct linters — leave uncompressed. Agent needs every error to fix code.
|
|
35
|
+
// ls (strip metadata, keep filenames)
|
|
36
|
+
if (cmd.match(/^ls\b/))
|
|
37
|
+
return compressLsOutput(stdout);
|
|
38
|
+
// find / fd — already minimal (just paths), pass through unchanged
|
|
39
|
+
// tree — already structured, pass through unchanged
|
|
42
40
|
// curl / wget (HTTP responses)
|
|
43
41
|
if (cmd.match(/^(curl|wget)\b/))
|
|
44
42
|
return compressCurlOutput(stdout);
|
|
@@ -46,130 +44,181 @@ export function compressBashOutput(command, stdout) {
|
|
|
46
44
|
}
|
|
47
45
|
// ─── Git Status ─────────────────────────────────────────────────────────────
|
|
48
46
|
function compressGitStatus(output) {
|
|
49
|
-
const lines = output.split('\n').filter((l) => l.trim());
|
|
50
|
-
if (lines.length <= 20)
|
|
51
|
-
return output;
|
|
52
|
-
const sections = [];
|
|
53
|
-
let current = null;
|
|
54
|
-
for (const line of lines) {
|
|
55
|
-
if (line.startsWith('On branch') ||
|
|
56
|
-
line.startsWith('Your branch') ||
|
|
57
|
-
line.startsWith('HEAD detached')) {
|
|
58
|
-
sections.push({ header: line, files: [] });
|
|
59
|
-
}
|
|
60
|
-
else if (line.match(/^(Changes|Untracked|Unmerged)/)) {
|
|
61
|
-
current = { header: line, files: [] };
|
|
62
|
-
sections.push(current);
|
|
63
|
-
}
|
|
64
|
-
else if (current &&
|
|
65
|
-
(line.startsWith('\t') || line.match(/^\s+(modified|new file|deleted|renamed)/))) {
|
|
66
|
-
current.files.push(line.trim());
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
// Rebuild with capped file lists
|
|
70
|
-
const maxFilesPerSection = 15;
|
|
71
47
|
const result = [];
|
|
72
|
-
for (const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
48
|
+
for (const line of output.split('\n')) {
|
|
49
|
+
const trimmed = line.trim();
|
|
50
|
+
// Skip empty lines
|
|
51
|
+
if (!trimmed)
|
|
52
|
+
continue;
|
|
53
|
+
// Skip git hints (noise — agent doesn't need instructions on how to use git)
|
|
54
|
+
if (trimmed.startsWith('(use "git') ||
|
|
55
|
+
trimmed.startsWith('(create/copy files') ||
|
|
56
|
+
trimmed.includes('(use "git add') ||
|
|
57
|
+
trimmed.includes('(use "git restore') ||
|
|
58
|
+
trimmed.includes('(use "git checkout') ||
|
|
59
|
+
trimmed.includes('(use "git reset') ||
|
|
60
|
+
trimmed.includes('(use "git pull') ||
|
|
61
|
+
trimmed.includes('(use "git push')) {
|
|
62
|
+
continue;
|
|
79
63
|
}
|
|
64
|
+
// "nothing to commit, working tree clean" — keep and stop
|
|
65
|
+
if (trimmed.includes('nothing to commit') && trimmed.includes('working tree clean')) {
|
|
66
|
+
result.push(trimmed);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
result.push(line);
|
|
80
70
|
}
|
|
81
|
-
return result.join('\n');
|
|
71
|
+
return result.length > 0 ? result.join('\n') : output;
|
|
82
72
|
}
|
|
83
73
|
// ─── Git Log ────────────────────────────────────────────────────────────────
|
|
84
74
|
function compressGitLog(output) {
|
|
85
75
|
const lines = output.split('\n');
|
|
86
|
-
if (lines.length <=
|
|
76
|
+
if (lines.length <= 10)
|
|
87
77
|
return output;
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
78
|
+
// Condense each commit to one line: hash message (date) <author>
|
|
79
|
+
// Strips verbose Author/Date/body lines, keeps all commits visible.
|
|
80
|
+
// Inspired by RTK's --pretty=format:%h %s (%ar) <%an> approach.
|
|
81
|
+
const result = [];
|
|
82
|
+
let currentHash = '';
|
|
83
|
+
let currentAuthor = '';
|
|
84
|
+
let currentDate = '';
|
|
85
|
+
let currentMessage = '';
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
const trimmed = line.trim();
|
|
88
|
+
if (line.startsWith('commit ')) {
|
|
89
|
+
// Flush previous commit
|
|
90
|
+
if (currentHash) {
|
|
91
|
+
result.push(formatCommitLine(currentHash, currentMessage, currentDate, currentAuthor));
|
|
92
|
+
}
|
|
93
|
+
currentHash = trimmed.replace('commit ', '').slice(0, 8);
|
|
94
|
+
currentAuthor = '';
|
|
95
|
+
currentDate = '';
|
|
96
|
+
currentMessage = '';
|
|
97
|
+
}
|
|
98
|
+
else if (trimmed.startsWith('Author:')) {
|
|
99
|
+
// Extract just the name (drop email)
|
|
100
|
+
const match = trimmed.match(/Author:\s*(.+?)(?:\s*<.*>)?$/);
|
|
101
|
+
currentAuthor = match ? match[1].trim() : trimmed.replace('Author:', '').trim();
|
|
102
|
+
}
|
|
103
|
+
else if (trimmed.startsWith('Date:')) {
|
|
104
|
+
currentDate = trimmed.replace('Date:', '').trim();
|
|
105
|
+
}
|
|
106
|
+
else if (trimmed &&
|
|
107
|
+
!trimmed.startsWith('Merge:') &&
|
|
108
|
+
!trimmed.startsWith('Signed-off-by:') &&
|
|
109
|
+
!trimmed.startsWith('Co-authored-by:') &&
|
|
110
|
+
!trimmed.startsWith('Co-Authored-By:')) {
|
|
111
|
+
// First non-empty body line = commit message
|
|
112
|
+
if (!currentMessage) {
|
|
113
|
+
currentMessage = trimmed;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Flush last commit
|
|
118
|
+
if (currentHash) {
|
|
119
|
+
result.push(formatCommitLine(currentHash, currentMessage, currentDate, currentAuthor));
|
|
93
120
|
}
|
|
94
|
-
return
|
|
121
|
+
return result.join('\n');
|
|
122
|
+
}
|
|
123
|
+
function formatCommitLine(hash, message, date, author) {
|
|
124
|
+
// Truncate message to 72 chars
|
|
125
|
+
const msg = message.length > 72 ? message.slice(0, 69) + '...' : message;
|
|
126
|
+
// Shorten date: "Mon Apr 20 12:00:00 2026 +0200" → "Apr 20"
|
|
127
|
+
const shortDate = shortenDate(date);
|
|
128
|
+
return `${hash} ${msg} (${shortDate}) <${author}>`;
|
|
129
|
+
}
|
|
130
|
+
function shortenDate(date) {
|
|
131
|
+
// Try to extract "Mon Day" from various git date formats
|
|
132
|
+
const match = date.match(/(\w{3})\s+(\d{1,2})/);
|
|
133
|
+
if (match)
|
|
134
|
+
return `${match[1]} ${match[2]}`;
|
|
135
|
+
// Fallback: return first 10 chars
|
|
136
|
+
return date.slice(0, 10).trim();
|
|
95
137
|
}
|
|
96
138
|
// ─── Git Diff ───────────────────────────────────────────────────────────────
|
|
97
139
|
function compressGitDiff(output) {
|
|
98
140
|
const lines = output.split('\n');
|
|
99
|
-
if (lines.length <=
|
|
141
|
+
if (lines.length <= 20)
|
|
100
142
|
return output;
|
|
101
|
-
//
|
|
143
|
+
// Compact diff inspired by RTK: show file names, hunk headers, +/- lines
|
|
144
|
+
// with per-hunk truncation and per-file +N -N summaries.
|
|
102
145
|
const result = [];
|
|
103
146
|
let currentFile = '';
|
|
104
|
-
let
|
|
105
|
-
|
|
147
|
+
let added = 0;
|
|
148
|
+
let removed = 0;
|
|
149
|
+
let inHunk = false;
|
|
150
|
+
let hunkShown = 0;
|
|
151
|
+
let hunkSkipped = 0;
|
|
152
|
+
const maxHunkLines = 50;
|
|
153
|
+
const maxTotalLines = 300;
|
|
106
154
|
for (const line of lines) {
|
|
107
155
|
if (line.startsWith('diff --git')) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
156
|
+
// Flush previous hunk/file
|
|
157
|
+
if (hunkSkipped > 0) {
|
|
158
|
+
result.push(` ... (${String(hunkSkipped)} lines truncated)`);
|
|
159
|
+
hunkSkipped = 0;
|
|
160
|
+
}
|
|
161
|
+
if (currentFile && (added > 0 || removed > 0)) {
|
|
162
|
+
result.push(` +${String(added)} -${String(removed)}`);
|
|
163
|
+
}
|
|
164
|
+
// Start new file — extract filename
|
|
165
|
+
currentFile = line.split(' b/')[1] ?? 'unknown';
|
|
166
|
+
result.push(`\n${currentFile}`);
|
|
167
|
+
added = 0;
|
|
168
|
+
removed = 0;
|
|
169
|
+
inHunk = false;
|
|
170
|
+
hunkShown = 0;
|
|
111
171
|
}
|
|
112
|
-
else if (line.startsWith('
|
|
113
|
-
|
|
114
|
-
|
|
172
|
+
else if (line.startsWith('@@')) {
|
|
173
|
+
if (hunkSkipped > 0) {
|
|
174
|
+
result.push(` ... (${String(hunkSkipped)} lines truncated)`);
|
|
175
|
+
hunkSkipped = 0;
|
|
176
|
+
}
|
|
177
|
+
inHunk = true;
|
|
178
|
+
hunkShown = 0;
|
|
179
|
+
result.push(` ${line}`);
|
|
115
180
|
}
|
|
116
|
-
else if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
181
|
+
else if (inHunk) {
|
|
182
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
183
|
+
added++;
|
|
184
|
+
if (hunkShown < maxHunkLines) {
|
|
185
|
+
result.push(` ${line}`);
|
|
186
|
+
hunkShown++;
|
|
187
|
+
}
|
|
188
|
+
else
|
|
189
|
+
hunkSkipped++;
|
|
190
|
+
}
|
|
191
|
+
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
192
|
+
removed++;
|
|
193
|
+
if (hunkShown < maxHunkLines) {
|
|
194
|
+
result.push(` ${line}`);
|
|
195
|
+
hunkShown++;
|
|
196
|
+
}
|
|
197
|
+
else
|
|
198
|
+
hunkSkipped++;
|
|
120
199
|
}
|
|
121
|
-
else if (
|
|
122
|
-
|
|
200
|
+
else if (hunkShown < maxHunkLines && !line.startsWith('\\')) {
|
|
201
|
+
if (hunkShown > 0) {
|
|
202
|
+
result.push(` ${line}`);
|
|
203
|
+
hunkShown++;
|
|
204
|
+
}
|
|
123
205
|
}
|
|
124
206
|
}
|
|
125
|
-
|
|
126
|
-
result.push(
|
|
207
|
+
if (result.length >= maxTotalLines) {
|
|
208
|
+
result.push('\n... (more changes truncated)');
|
|
209
|
+
break;
|
|
127
210
|
}
|
|
128
211
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
function compressGitAction(output) {
|
|
133
|
-
const lines = output.split('\n').filter((l) => l.trim());
|
|
134
|
-
if (lines.length <= 15)
|
|
135
|
-
return output;
|
|
136
|
-
// Keep summary lines, strip verbose file lists
|
|
137
|
-
const important = lines.filter((l) => l.match(/^(\[|To |From |Already|Everything|Branch|Updating|Fast-forward|CONFLICT|error:|fatal:|warning:|\s*\d+ file|create mode|delete mode)/) ||
|
|
138
|
-
l.includes('->') ||
|
|
139
|
-
l.includes('insertions') ||
|
|
140
|
-
l.includes('deletions'));
|
|
141
|
-
if (important.length > 0 && important.length < lines.length) {
|
|
142
|
-
const omitted = lines.length - important.length;
|
|
143
|
-
important.push(`(${String(omitted)} lines of detail omitted)`);
|
|
144
|
-
return important.join('\n');
|
|
212
|
+
// Flush last file
|
|
213
|
+
if (hunkSkipped > 0) {
|
|
214
|
+
result.push(` ... (${String(hunkSkipped)} lines truncated)`);
|
|
145
215
|
}
|
|
146
|
-
|
|
216
|
+
if (currentFile && (added > 0 || removed > 0)) {
|
|
217
|
+
result.push(` +${String(added)} -${String(removed)}`);
|
|
218
|
+
}
|
|
219
|
+
return result.join('\n');
|
|
147
220
|
}
|
|
148
221
|
// ─── npm install ────────────────────────────────────────────────────────────
|
|
149
|
-
function compressNpmInstall(output) {
|
|
150
|
-
const lines = output.split('\n');
|
|
151
|
-
if (lines.length <= 10)
|
|
152
|
-
return output;
|
|
153
|
-
// Keep: added/removed/changed summary, warnings, errors
|
|
154
|
-
// Strip: progress bars, individual package resolutions, timing
|
|
155
|
-
const kept = lines.filter((l) => {
|
|
156
|
-
const t = l.trim();
|
|
157
|
-
if (!t)
|
|
158
|
-
return false;
|
|
159
|
-
if (t.startsWith('npm warn') || t.startsWith('npm error') || t.startsWith('npm ERR'))
|
|
160
|
-
return true;
|
|
161
|
-
if (t.match(/^added \d|^removed \d|^changed \d|^up to date/))
|
|
162
|
-
return true;
|
|
163
|
-
if (t.includes('vulnerabilit'))
|
|
164
|
-
return true;
|
|
165
|
-
if (t.startsWith('found '))
|
|
166
|
-
return true;
|
|
167
|
-
return false;
|
|
168
|
-
});
|
|
169
|
-
if (kept.length === 0)
|
|
170
|
-
return output; // Couldn't parse — return original
|
|
171
|
-
return kept.join('\n');
|
|
172
|
-
}
|
|
173
222
|
// ─── Test Output ────────────────────────────────────────────────────────────
|
|
174
223
|
function compressTestOutput(output) {
|
|
175
224
|
const lines = output.split('\n');
|
|
@@ -215,127 +264,54 @@ function compressTestOutput(output) {
|
|
|
215
264
|
return result.join('\n');
|
|
216
265
|
}
|
|
217
266
|
// ─── Lint Output ────────────────────────────────────────────────────────────
|
|
218
|
-
|
|
267
|
+
// ─── Build Output ───────────────────────────────────────────────────────────
|
|
268
|
+
// ─── ls Output (strip metadata, keep filenames) ────────────────────────────
|
|
269
|
+
function compressLsOutput(output) {
|
|
219
270
|
const lines = output.split('\n');
|
|
220
|
-
if (lines.length <=
|
|
271
|
+
if (lines.length <= 10)
|
|
221
272
|
return output;
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
273
|
+
// Detect ls -l style output (permissions + metadata + filename)
|
|
274
|
+
// e.g., "drwxr-xr-x@ 15 scozzola staff 480 Apr 11 16:16 .compilr"
|
|
275
|
+
const lsLongPattern = /^[dlrwx\-@+.]{10,}\s+\d+\s+\S+\s+\S+\s+\d+\s+\w+\s+\d+\s+[\d:]+\s+(.+)$/;
|
|
276
|
+
const hasMetadata = lines.some((l) => lsLongPattern.test(l.trim()));
|
|
277
|
+
if (!hasMetadata)
|
|
278
|
+
return output; // Plain ls or unknown format — pass through
|
|
279
|
+
// Strip metadata, keep only filenames with dir/file indicator
|
|
280
|
+
const result = [];
|
|
228
281
|
for (const line of lines) {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
if (t.match(/^✖|^\d+ problem|^\d+ error|^\d+ warning|^error:|^warning:/i)) {
|
|
232
|
-
summaryLines.push(line);
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
// ESLint-style: "file:line:col error message rule"
|
|
236
|
-
const ruleMatch = t.match(/\s+(error|warning)\s+.+\s+(\S+)$/);
|
|
237
|
-
if (ruleMatch) {
|
|
238
|
-
const rule = ruleMatch[2];
|
|
239
|
-
const count = (ruleCount.get(rule) ?? 0) + 1;
|
|
240
|
-
ruleCount.set(rule, count);
|
|
241
|
-
if (count <= maxPerRule) {
|
|
242
|
-
if (ruleMatch[1] === 'error')
|
|
243
|
-
errors.push(line);
|
|
244
|
-
else
|
|
245
|
-
warnings.push(line);
|
|
246
|
-
}
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
// TypeScript-style: "file(line,col): error TS..."
|
|
250
|
-
if (t.match(/error TS\d+/)) {
|
|
251
|
-
errors.push(line);
|
|
282
|
+
const trimmed = line.trim();
|
|
283
|
+
if (!trimmed)
|
|
252
284
|
continue;
|
|
285
|
+
if (trimmed.startsWith('total '))
|
|
286
|
+
continue; // Skip "total N" line
|
|
287
|
+
const match = trimmed.match(lsLongPattern);
|
|
288
|
+
if (match) {
|
|
289
|
+
const name = match[1];
|
|
290
|
+
const isDir = trimmed.startsWith('d');
|
|
291
|
+
result.push(isDir ? `${name}/` : name);
|
|
253
292
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Show rules that were capped
|
|
257
|
-
for (const [rule, count] of ruleCount) {
|
|
258
|
-
if (count > maxPerRule) {
|
|
259
|
-
result.push(` ... +${String(count - maxPerRule)} more ${rule}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
result.push(...summaryLines);
|
|
263
|
-
if (result.length < lines.length) {
|
|
264
|
-
return result.join('\n');
|
|
265
|
-
}
|
|
266
|
-
return output;
|
|
267
|
-
}
|
|
268
|
-
// ─── Build Output ───────────────────────────────────────────────────────────
|
|
269
|
-
function compressBuildOutput(output) {
|
|
270
|
-
const lines = output.split('\n');
|
|
271
|
-
if (lines.length <= 20)
|
|
272
|
-
return output;
|
|
273
|
-
// Keep errors and summary, strip progress/compilation messages
|
|
274
|
-
const kept = lines.filter((l) => {
|
|
275
|
-
const t = l.trim();
|
|
276
|
-
if (!t)
|
|
277
|
-
return false;
|
|
278
|
-
if (t.match(/^(error|Error|ERROR|warning|Warning|WARN)/))
|
|
279
|
-
return true;
|
|
280
|
-
if (t.match(/^(✓|✗|✘|Done|Built|Compiled|Successfully|Failed|FAIL)/))
|
|
281
|
-
return true;
|
|
282
|
-
if (t.match(/error TS\d+/))
|
|
283
|
-
return true;
|
|
284
|
-
if (t.includes('bundle') && t.includes('kB'))
|
|
285
|
-
return true; // Bundle size info
|
|
286
|
-
return false;
|
|
287
|
-
});
|
|
288
|
-
if (kept.length > 0 && kept.length < lines.length * 0.7) {
|
|
289
|
-
const omitted = lines.length - kept.length;
|
|
290
|
-
kept.push(`(${String(omitted)} lines of build output omitted)`);
|
|
291
|
-
return kept.join('\n');
|
|
292
|
-
}
|
|
293
|
-
return output;
|
|
294
|
-
}
|
|
295
|
-
// ─── File List (ls, find, tree) ─────────────────────────────────────────────
|
|
296
|
-
function compressFileList(output) {
|
|
297
|
-
const lines = output.split('\n').filter((l) => l.trim());
|
|
298
|
-
if (lines.length <= 20)
|
|
299
|
-
return output;
|
|
300
|
-
// Group by top-level directory, cap entries per directory
|
|
301
|
-
const groups = new Map();
|
|
302
|
-
for (const line of lines) {
|
|
303
|
-
const parts = line.replace(/^\.\//, '').split('/');
|
|
304
|
-
const dir = parts.length > 1 ? parts[0] : '.';
|
|
305
|
-
const arr = groups.get(dir) ?? [];
|
|
306
|
-
arr.push(line);
|
|
307
|
-
groups.set(dir, arr);
|
|
308
|
-
}
|
|
309
|
-
const maxPerDir = 5;
|
|
310
|
-
const result = [];
|
|
311
|
-
for (const [dir, files] of groups) {
|
|
312
|
-
if (dir !== '.')
|
|
313
|
-
result.push(`${dir}/ (${String(files.length)} files)`);
|
|
314
|
-
const shown = files.slice(0, maxPerDir);
|
|
315
|
-
for (const f of shown)
|
|
316
|
-
result.push(f);
|
|
317
|
-
if (files.length > maxPerDir) {
|
|
318
|
-
result.push(` ... +${String(files.length - maxPerDir)} more`);
|
|
293
|
+
else {
|
|
294
|
+
result.push(trimmed); // Keep unrecognized lines as-is
|
|
319
295
|
}
|
|
320
296
|
}
|
|
321
|
-
if (result.length < lines.length) {
|
|
322
|
-
result.push(`\n(${String(lines.length)} total entries, showing ${String(result.length)} lines)`);
|
|
323
|
-
}
|
|
324
297
|
return result.join('\n');
|
|
325
298
|
}
|
|
326
299
|
// ─── curl / wget ────────────────────────────────────────────────────────────
|
|
327
300
|
function compressCurlOutput(output) {
|
|
328
301
|
const lines = output.split('\n');
|
|
329
|
-
|
|
330
|
-
return output;
|
|
331
|
-
// Strip progress bars (curl -# output), keep headers and body
|
|
302
|
+
// Strip progress bars and transfer stats, keep headers and body
|
|
332
303
|
const kept = lines.filter((l) => {
|
|
333
304
|
const t = l.trim();
|
|
334
|
-
|
|
335
|
-
|
|
305
|
+
if (!t)
|
|
306
|
+
return true; // Keep blank lines (may separate headers from body)
|
|
307
|
+
// Strip curl progress table header: " % Total % Received ..."
|
|
308
|
+
if (t.startsWith('% Total') || t.startsWith('Dload'))
|
|
309
|
+
return false;
|
|
310
|
+
// Strip progress rows: " 0 0 0 0 0 0..." or " 50 1200 50 600..."
|
|
311
|
+
if (t.match(/^\d+\s+\d+\s+\d+\s+\d+/))
|
|
336
312
|
return false;
|
|
337
|
-
// Strip
|
|
338
|
-
if (t.match(
|
|
313
|
+
// Strip progress percentage lines
|
|
314
|
+
if (t.match(/^[#\s]*\d+(\.\d+)?%/))
|
|
339
315
|
return false;
|
|
340
316
|
return true;
|
|
341
317
|
});
|