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