clawmonitor 1.1.2 → 1.1.3
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/clawmonitor.js +81 -59
- package/package.json +1 -1
package/bin/clawmonitor.js
CHANGED
|
@@ -206,7 +206,11 @@ const durColor = (d) => {
|
|
|
206
206
|
// Pad a string to exactly N terminal columns (right-pad with spaces)
|
|
207
207
|
const padTo = (s, n) => {
|
|
208
208
|
const w = strWidth(s);
|
|
209
|
-
if (w
|
|
209
|
+
if (w > n) {
|
|
210
|
+
// Hard truncate to fit — strip ANSI, truncate, re-add border color
|
|
211
|
+
const plain = stripAnsi(s);
|
|
212
|
+
return truncTo(plain, n);
|
|
213
|
+
}
|
|
210
214
|
return s + ' '.repeat(n - w);
|
|
211
215
|
};
|
|
212
216
|
|
|
@@ -259,14 +263,14 @@ const strWidth = (s) => {
|
|
|
259
263
|
|
|
260
264
|
// truncTo returns the truncated string. Also sets truncTo.consumed for caller.
|
|
261
265
|
let _truncConsumed = 0;
|
|
262
|
-
const truncTo = (s, maxCols) => {
|
|
266
|
+
const truncTo = (s, maxCols, noEllipsis) => {
|
|
263
267
|
_truncConsumed = 0;
|
|
264
268
|
if (!s) return s;
|
|
265
269
|
// Fast path: pure ASCII
|
|
266
270
|
if (/^[\x20-\x7E]*$/.test(s)) {
|
|
267
271
|
if (s.length <= maxCols) { _truncConsumed = s.length; return s; }
|
|
268
272
|
_truncConsumed = maxCols;
|
|
269
|
-
return s.slice(0, maxCols) + '\u2026';
|
|
273
|
+
return noEllipsis ? s.slice(0, maxCols) : s.slice(0, maxCols) + '\u2026';
|
|
270
274
|
}
|
|
271
275
|
let w = 0, chars = [];
|
|
272
276
|
for (const {segment} of segmenter.segment(s)) {
|
|
@@ -279,7 +283,7 @@ const truncTo = (s, maxCols) => {
|
|
|
279
283
|
}
|
|
280
284
|
const result = chars.join('');
|
|
281
285
|
if (_truncConsumed < s.length) {
|
|
282
|
-
return result + '\u2026';
|
|
286
|
+
return noEllipsis ? result : result + '\u2026';
|
|
283
287
|
}
|
|
284
288
|
return result;
|
|
285
289
|
};
|
|
@@ -312,17 +316,17 @@ function wrapArg(key, val, maxCols, keyW) {
|
|
|
312
316
|
const out = [];
|
|
313
317
|
let text = s;
|
|
314
318
|
let lineIdx = 0;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const isLast = lineIdx === maxL - 1;
|
|
319
|
+
const maxL = opts.full ? 999 : 3;
|
|
320
|
+
while (text.length > 0 && lineIdx < maxL) {
|
|
321
|
+
const isLast = !opts.full && lineIdx === maxL - 1;
|
|
318
322
|
const budget = isLast ? maxValW - 1 : maxValW;
|
|
319
|
-
const chunk = truncTo(text, budget);
|
|
323
|
+
const chunk = truncTo(text, budget, !isLast); // only add … on last line
|
|
320
324
|
out.push({ key, indent: lineIdx === 0 ? null : indent, val: chunk });
|
|
321
325
|
text = text.slice(_truncConsumed);
|
|
322
326
|
lineIdx++;
|
|
323
327
|
if (!text) break;
|
|
324
328
|
if (isLast && text) {
|
|
325
|
-
out[out.length - 1].val =
|
|
329
|
+
out[out.length - 1].val = chunk.endsWith('\u2026') ? chunk : chunk + '\u2026';
|
|
326
330
|
break;
|
|
327
331
|
}
|
|
328
332
|
}
|
|
@@ -386,11 +390,24 @@ const colorToken = (t) => {
|
|
|
386
390
|
|
|
387
391
|
// Wrap tokens into lines, respecting token boundaries
|
|
388
392
|
function wrapTokens(tokens, maxCols, maxLines) {
|
|
393
|
+
const fullMode = maxLines >= 999;
|
|
389
394
|
const lines = [];
|
|
390
395
|
let lineTokens = [];
|
|
391
396
|
let lineW = 0;
|
|
392
397
|
let linesUsed = 0;
|
|
393
398
|
|
|
399
|
+
// Split a string to fit maxCols without adding ellipsis
|
|
400
|
+
const splitFit = (s, cols) => {
|
|
401
|
+
let w = 0, i = 0;
|
|
402
|
+
for (const {segment} of segmenter.segment(s)) {
|
|
403
|
+
const cw = charWidth(segment);
|
|
404
|
+
if (w + cw > cols) break;
|
|
405
|
+
w += cw;
|
|
406
|
+
i += segment.length;
|
|
407
|
+
}
|
|
408
|
+
return { text: s.slice(0, i), consumed: i, rest: s.slice(i) };
|
|
409
|
+
};
|
|
410
|
+
|
|
394
411
|
for (let ti = 0; ti < tokens.length; ti++) {
|
|
395
412
|
const t = tokens[ti];
|
|
396
413
|
if (lineTokens.length === 0 && t.type === 'ws') continue;
|
|
@@ -405,31 +422,32 @@ function wrapTokens(tokens, maxCols, maxLines) {
|
|
|
405
422
|
continue;
|
|
406
423
|
}
|
|
407
424
|
|
|
408
|
-
// Token doesn't fit — split it
|
|
425
|
+
// Token doesn't fit — split it
|
|
409
426
|
if (lineTokens.length > 0 && remaining > 5) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
lineTokens.push({ ...t, text: head });
|
|
427
|
+
const sp = splitFit(t.text, remaining);
|
|
428
|
+
lineTokens.push({ ...t, text: sp.text });
|
|
413
429
|
lines.push(lineTokens);
|
|
414
430
|
linesUsed++;
|
|
415
431
|
lineTokens = [];
|
|
416
432
|
lineW = 0;
|
|
417
433
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
434
|
+
let rest = sp.rest;
|
|
435
|
+
while (rest && linesUsed < maxLines) {
|
|
436
|
+
const isLast = !fullMode && linesUsed === maxLines - 1;
|
|
437
|
+
if (isLast) {
|
|
438
|
+
// Last line: truncate with ellipsis
|
|
439
|
+
const chunk = truncTo(rest, maxCols - 1);
|
|
440
|
+
lines.push([{ ...t, text: chunk + '\u2026' }]);
|
|
441
|
+
linesUsed++;
|
|
442
|
+
rest = '';
|
|
443
|
+
} else {
|
|
444
|
+
const sp2 = splitFit(rest, maxCols);
|
|
445
|
+
lines.push([{ ...t, text: sp2.text }]);
|
|
426
446
|
linesUsed++;
|
|
427
|
-
rest = rest
|
|
428
|
-
if (chunk.endsWith('\u2026')) break;
|
|
447
|
+
rest = sp2.rest;
|
|
429
448
|
}
|
|
430
449
|
}
|
|
431
450
|
} else {
|
|
432
|
-
// Flush current line, start fresh
|
|
433
451
|
if (lineTokens.length > 0) {
|
|
434
452
|
lines.push(lineTokens);
|
|
435
453
|
linesUsed++;
|
|
@@ -438,28 +456,34 @@ function wrapTokens(tokens, maxCols, maxLines) {
|
|
|
438
456
|
}
|
|
439
457
|
if (linesUsed >= maxLines) break;
|
|
440
458
|
|
|
441
|
-
// Split token onto new line(s)
|
|
442
459
|
let rest = t.text;
|
|
443
460
|
while (rest && linesUsed < maxLines) {
|
|
444
|
-
const isLast = linesUsed === maxLines - 1;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
461
|
+
const isLast = !fullMode && linesUsed === maxLines - 1;
|
|
462
|
+
if (isLast) {
|
|
463
|
+
const chunk = truncTo(rest, maxCols - 1);
|
|
464
|
+
lines.push([{ ...t, text: chunk + '\u2026' }]);
|
|
465
|
+
linesUsed++;
|
|
466
|
+
rest = '';
|
|
467
|
+
} else {
|
|
468
|
+
const sp = splitFit(rest, maxCols);
|
|
469
|
+
lines.push([{ ...t, text: sp.text }]);
|
|
470
|
+
linesUsed++;
|
|
471
|
+
rest = sp.rest;
|
|
472
|
+
}
|
|
451
473
|
}
|
|
452
474
|
}
|
|
453
475
|
}
|
|
454
476
|
if (lineTokens.length > 0 && linesUsed < maxLines) lines.push(lineTokens);
|
|
455
477
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
478
|
+
if (!fullMode) {
|
|
479
|
+
while (lines.length > maxLines) lines.pop();
|
|
480
|
+
if (lines.length >= maxLines) {
|
|
481
|
+
const last = lines[lines.length - 1];
|
|
482
|
+
const lastStr = last.map(t => t.text).join('');
|
|
483
|
+
if (!lastStr.endsWith('\u2026')) {
|
|
484
|
+
const truncated = truncTo(lastStr, maxCols - 1);
|
|
485
|
+
lines[lines.length - 1] = [{ type: 'plain', text: truncated + '\u2026' }];
|
|
486
|
+
}
|
|
463
487
|
}
|
|
464
488
|
}
|
|
465
489
|
|
|
@@ -471,22 +495,20 @@ const renderTokenLines = (tokenLines) => tokenLines.map(line => line.map(colorTo
|
|
|
471
495
|
|
|
472
496
|
// Wrap result text into up to N lines, each fitting within maxCols
|
|
473
497
|
function wrapResult(text, maxCols, maxLines) {
|
|
498
|
+
const fullMode = maxLines >= 999;
|
|
474
499
|
const flat = text.replace(/[\r\n]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
475
500
|
const lines = [];
|
|
476
|
-
let
|
|
477
|
-
while (
|
|
478
|
-
const isLast = lines.length === maxLines - 1;
|
|
501
|
+
let pos = 0;
|
|
502
|
+
while (pos < flat.length && lines.length < maxLines) {
|
|
503
|
+
const isLast = !fullMode && lines.length === maxLines - 1;
|
|
504
|
+
// Non-last lines: no ellipsis, strict budget
|
|
505
|
+
// Last line: ellipsis, budget - 1 for …
|
|
479
506
|
const budget = isLast ? maxCols - 1 : maxCols;
|
|
480
|
-
const chunk = truncTo(
|
|
481
|
-
lines.push(chunk);
|
|
482
|
-
|
|
483
|
-
if (
|
|
484
|
-
if (isLast)
|
|
485
|
-
// Force … on last line if more text remains
|
|
486
|
-
if (!lines[lines.length - 1].endsWith('\u2026'))
|
|
487
|
-
lines[lines.length - 1] = truncTo(chunk, budget - 1) + '\u2026';
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
507
|
+
const chunk = truncTo(flat.slice(pos), budget, !isLast); // noEllipsis for non-last
|
|
508
|
+
lines.push(isLast && !chunk.endsWith('\u2026') && pos + _truncConsumed < flat.length ? chunk + '\u2026' : chunk);
|
|
509
|
+
pos += _truncConsumed;
|
|
510
|
+
if (pos >= flat.length) break;
|
|
511
|
+
if (isLast) break;
|
|
490
512
|
}
|
|
491
513
|
return lines;
|
|
492
514
|
}
|
|
@@ -507,7 +529,7 @@ function fmt(e, label) {
|
|
|
507
529
|
}
|
|
508
530
|
|
|
509
531
|
// Card layout — full width
|
|
510
|
-
const W = process.stdout.columns || 80;
|
|
532
|
+
const W = parseInt(process.env.COLUMNS) || process.stdout.columns || 80;
|
|
511
533
|
const lines = [];
|
|
512
534
|
|
|
513
535
|
// Top border
|
|
@@ -541,12 +563,12 @@ function fmt(e, label) {
|
|
|
541
563
|
const isJson = flat.startsWith('{') || flat.startsWith('[');
|
|
542
564
|
if (isJson) {
|
|
543
565
|
const tokens = highlightJson(flat);
|
|
544
|
-
const tokenLines = wrapTokens(tokens, W -
|
|
566
|
+
const tokenLines = wrapTokens(tokens, W - 5, opts.full ? 999 : 2);
|
|
545
567
|
for (const tl of renderTokenLines(tokenLines)) {
|
|
546
568
|
lines.push(padTo(`${T.x('│')} ${tl}`, W - 1) + T.x('│'));
|
|
547
569
|
}
|
|
548
570
|
} else {
|
|
549
|
-
const rLines = wrapResult(flat, W -
|
|
571
|
+
const rLines = wrapResult(flat, W - 5, opts.full ? 999 : 2);
|
|
550
572
|
for (const rl of rLines) {
|
|
551
573
|
lines.push(padTo(`${T.x('│')} ${T.d(rl)}`, W - 1) + T.x('│'));
|
|
552
574
|
}
|
|
@@ -562,7 +584,7 @@ function fmt(e, label) {
|
|
|
562
584
|
function fmtLiveCall(tc, label) {
|
|
563
585
|
const t = fmtT(tc.timestamp);
|
|
564
586
|
const id = tc.id?.slice(0, 10) || '?';
|
|
565
|
-
const W = process.stdout.columns || 80;
|
|
587
|
+
const W = parseInt(process.env.COLUMNS) || process.stdout.columns || 80;
|
|
566
588
|
|
|
567
589
|
if (opts.compact) {
|
|
568
590
|
const args = Object.entries(tc.arguments || {}).slice(0, 2).map(([k, v]) => T.x(`${k}=`) + trunc(v, 50)).join(' ');
|
|
@@ -601,18 +623,18 @@ function fmtLiveResult(msg, ts) {
|
|
|
601
623
|
if (opts.compact) {
|
|
602
624
|
console.log(` ${T.x('↳')} ${meta} ${T.d(trunc(rStr, 70))}`);
|
|
603
625
|
} else {
|
|
604
|
-
const W = process.stdout.columns || 80;
|
|
626
|
+
const W = parseInt(process.env.COLUMNS) || process.stdout.columns || 80;
|
|
605
627
|
console.log(padTo(`${T.x('│')} ${icon} ${T.b(name)} ${meta}`, W - 1) + T.x('│'));
|
|
606
628
|
const flat = rStr.replace(/[\r\n]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
607
629
|
const isJson = flat.startsWith('{') || flat.startsWith('[');
|
|
608
630
|
if (isJson) {
|
|
609
631
|
const tokens = highlightJson(flat);
|
|
610
|
-
const tokenLines = wrapTokens(tokens, W -
|
|
632
|
+
const tokenLines = wrapTokens(tokens, W - 5, opts.full ? 999 : 2);
|
|
611
633
|
for (const tl of renderTokenLines(tokenLines)) {
|
|
612
634
|
console.log(padTo(`${T.x('│')} ${tl}`, W - 1) + T.x('│'));
|
|
613
635
|
}
|
|
614
636
|
} else {
|
|
615
|
-
const rLines = wrapResult(flat, W -
|
|
637
|
+
const rLines = wrapResult(flat, W - 5, opts.full ? 999 : 2);
|
|
616
638
|
for (const rl of rLines) {
|
|
617
639
|
console.log(padTo(`${T.x('│')} ${T.d(rl)}`, W - 1) + T.x('│'));
|
|
618
640
|
}
|