dankgrinder 7.62.0 → 7.63.0
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/lib/grinder.js +134 -297
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -375,11 +375,18 @@ function scheduleRender() {
|
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
+
// ══════════════════════════════════════════════════════════════
|
|
379
|
+
// ═ CLI Dashboard — clean, modern, stable rendering
|
|
380
|
+
// ═ Structure: [3-line header] [accounts table] [live feed] [footer]
|
|
381
|
+
// ║ Uses full-width box borders, ANSI cursor positioning,
|
|
382
|
+
// ═ per-row ANSI-aware padding for stable display.
|
|
383
|
+
// ══════════════════════════════════════════════════════════════
|
|
378
384
|
function renderDashboard() {
|
|
379
385
|
if (!dashboardStarted || workers.length === 0 || dashboardRendering || shutdownCalled) return;
|
|
380
386
|
dashboardRendering = true;
|
|
381
387
|
lastRenderTime = Date.now();
|
|
382
388
|
|
|
389
|
+
// ── Aggregate session stats ─────────────────────────────────────
|
|
383
390
|
totalBalance = 0; totalCoins = 0; totalCommands = 0;
|
|
384
391
|
let totalErrors = 0;
|
|
385
392
|
let totalLosses = 0;
|
|
@@ -397,183 +404,89 @@ function renderDashboard() {
|
|
|
397
404
|
setTimeout(() => { isNewHigh = false; }, 3000);
|
|
398
405
|
}
|
|
399
406
|
|
|
400
|
-
// ── Layout: use FULL terminal width ──
|
|
401
|
-
const lines = [];
|
|
402
407
|
const tw = Math.max(process.stdout.columns || 80, 60);
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
const
|
|
408
|
+
const RE = /\x1b\[[0-9;]*m/g;
|
|
409
|
+
const vis = (s) => String(s).replace(RE, '').length;
|
|
410
|
+
const pad = (content, width) => content + ' '.repeat(Math.max(0, width - vis(content)));
|
|
411
|
+
|
|
412
|
+
// Color shortcuts
|
|
413
|
+
const A = rgb(139, 92, 246); // purple accent
|
|
414
|
+
const G = rgb(52, 211, 153); // green
|
|
415
|
+
const B = rgb(96, 165, 250); // blue
|
|
416
|
+
const Au = rgb(255, 215, 0); // gold
|
|
417
|
+
const O = rgb(251, 146, 60); // orange
|
|
418
|
+
const Cy = rgb(34, 211, 238); // cyan
|
|
419
|
+
const R = rgb(239, 68, 68); // red
|
|
420
|
+
const Y = rgb(251, 191, 36); // yellow
|
|
415
421
|
const W = c.white;
|
|
416
422
|
const D = c.dim;
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
// ── Box drawing (scales to tw) ──
|
|
420
|
-
const bTop = A + '╔' + '═'.repeat(tw - 2) + '╗' + c.reset;
|
|
421
|
-
const bMid = A + '╟' + '─'.repeat(tw - 2) + '╢' + c.reset;
|
|
422
|
-
const bSep = A + '╠' + '═'.repeat(tw - 2) + '╣' + c.reset;
|
|
423
|
-
const bBot = A + '╚' + '═'.repeat(tw - 2) + '╝' + c.reset;
|
|
424
|
-
const bRow = (content) => {
|
|
425
|
-
const vis = content.replace(RE, '').length;
|
|
426
|
-
const pad = Math.max(0, iw - vis);
|
|
427
|
-
return A + '║' + c.reset + ' ' + content + ' '.repeat(pad) + ' ' + A + '║' + c.reset;
|
|
428
|
-
};
|
|
429
|
-
const bEmpty = bRow('');
|
|
423
|
+
const _ = c.reset;
|
|
430
424
|
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
// ═══════════════════════════════════════════════════════════════
|
|
434
|
-
lines.push(bTop);
|
|
435
|
-
lines.push(bEmpty);
|
|
425
|
+
// ── Build rows array ──────────────────────────────────────────
|
|
426
|
+
const rows = [];
|
|
436
427
|
|
|
428
|
+
// ── HEADER ────────────────────────────────────────────────────
|
|
437
429
|
const spin = getSpinner('braille');
|
|
438
|
-
|
|
439
|
-
// Title — big gradient banner
|
|
440
|
-
const titleLines = [
|
|
441
|
-
'██████╗ █████╗ ███╗ ██╗██╗ ██╗ ██████╗ ██████╗ ██╗███╗ ██╗██████╗ ███████╗██████╗',
|
|
442
|
-
'██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝ ██╔════╝ ██╔══██╗██║████╗ ██║██╔══██╗██╔════╝██╔══██╗',
|
|
443
|
-
'██║ ██║███████║██╔██╗ ██║█████╔╝ ██║ ███╗██████╔╝██║██╔██╗ ██║██║ ██║█████╗ ██████╔╝',
|
|
444
|
-
'██║ ██║██╔══██║██║╚██╗██║██╔═██╗ ██║ ██║██╔══██╗██║██║╚██╗██║██║ ██║██╔══╝ ██╔══██╗',
|
|
445
|
-
'██████╔╝██║ ██║██║ ╚████║██║ ██╗ ╚██████╔╝██║ ██║██║██║ ╚████║██████╔╝███████╗██║ ██║',
|
|
446
|
-
'╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝',
|
|
447
|
-
];
|
|
448
|
-
// Check terminal width — fall back to compact title if too narrow
|
|
449
|
-
const termW = (process.stdout.columns || 120) - 6; // account for box borders
|
|
450
|
-
const useBigTitle = termW >= 92;
|
|
451
|
-
if (useBigTitle) {
|
|
452
|
-
for (let i = 0; i < titleLines.length; i++) {
|
|
453
|
-
const t = i / (titleLines.length - 1);
|
|
454
|
-
const from = t < 0.5
|
|
455
|
-
? [lerp(192, 139, t * 2), lerp(132, 92, t * 2), lerp(252, 246, t * 2)]
|
|
456
|
-
: [lerp(139, 34, (t - 0.5) * 2), lerp(92, 211, (t - 0.5) * 2), lerp(246, 238, (t - 0.5) * 2)];
|
|
457
|
-
lines.push(bRow(` ${c.bold}${gradientLine(titleLines[i], from, [52, 211, 153])}${c.reset}`));
|
|
458
|
-
}
|
|
459
|
-
} else {
|
|
460
|
-
const titleGrad = gradientText(' D A N K G R I N D E R ', [192, 132, 252], [52, 211, 153]);
|
|
461
|
-
lines.push(bRow(` ${c.bold}${titleGrad}${c.reset} ${D}v${PKG_VERSION}${c.reset} ${G}${spin}${c.reset}`));
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
lines.push(bRow(` ${D}v${PKG_VERSION}${c.reset} ${G}${spin}${c.reset} ${Y}◷${c.reset} ${D}UP${c.reset} ${c.bold}${Y}${formatUptime()}${c.reset}`));
|
|
465
|
-
|
|
466
|
-
// Subtitle info
|
|
467
430
|
const activeCount = workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
468
431
|
const invalidCount = workers.filter(w => w._tokenInvalid).length;
|
|
469
432
|
const pausedCount = workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
470
433
|
const recovCount = workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
471
|
-
const mode = CLOUD_MODE ? `${Cy}CLOUD${c.reset}` : (CLUSTER_ENABLED ? `${Cy}CLUSTER${c.reset}` : `${D}Standalone${c.reset}`);
|
|
472
|
-
const cmdCount = AccountWorker.COMMAND_MAP.length;
|
|
473
|
-
|
|
474
434
|
const netQ = workers.length > 0
|
|
475
435
|
? workers.reduce((s, w) => s + (w._lastPing < 200 ? 1 : w._lastPing < 500 ? 0.5 : 0), 0) / workers.length : 1;
|
|
476
|
-
const netDot = netQ > 0.8 ? `${G}●${
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
const infoParts = [
|
|
480
|
-
mode,
|
|
481
|
-
`${netDot} ${netLbl}`,
|
|
482
|
-
`${B}${cmdCount}${c.reset} ${D}commands${c.reset}`,
|
|
483
|
-
`${G}${activeCount}${c.reset}${D}/${c.reset}${W}${workers.length}${c.reset} ${D}active${c.reset}`,
|
|
484
|
-
];
|
|
485
|
-
if (invalidCount > 0) infoParts.push(`${R}${invalidCount} invalid${c.reset}`);
|
|
486
|
-
if (pausedCount > 0) infoParts.push(`${Y}${pausedCount} paused${c.reset}`);
|
|
487
|
-
if (recovCount > 0) infoParts.push(`${O}${recovCount} recovering${c.reset}`);
|
|
488
|
-
lines.push(bRow(` ${infoParts.join(` ${D}·${c.reset} `)}`));
|
|
489
|
-
|
|
490
|
-
lines.push(bEmpty);
|
|
491
|
-
|
|
492
|
-
// ═══════════════════════════════════════════════════════════════
|
|
493
|
-
// STATS PANEL — left: all metrics | right: big trend + rate
|
|
494
|
-
// ═══════════════════════════════════════════════════════════════
|
|
495
|
-
lines.push(bSep);
|
|
496
|
-
lines.push(bEmpty);
|
|
497
|
-
|
|
498
|
-
const now = Date.now();
|
|
499
|
-
if (now - lastEarningsSample > 8000) { earningsHistory.push(totalCoins); lastEarningsSample = now; }
|
|
436
|
+
const netDot = netQ > 0.8 ? `${G}●${_}` : netQ > 0.5 ? `${Y}●${_}` : `${R}●${_}`;
|
|
437
|
+
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
500
438
|
const elapsedHrs = (Date.now() - startTime) / 3_600_000;
|
|
501
439
|
const perHr = elapsedHrs > 0.01 ? Math.round(totalCoins / elapsedHrs) : 0;
|
|
502
|
-
|
|
503
|
-
// ── Compute metric values ─────────────────────────────────────
|
|
504
440
|
const cpmVal = globalCmdRate.getRate().toFixed(1);
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
508
|
-
const memCol = memMB > 900 ? [239, 68, 68] : memMB > 600 ? [251, 191, 36] : [52, 211, 153];
|
|
509
|
-
const memBar = progressBar(memMB, 1024, 10, memCol, [40, 40, 55]);
|
|
510
|
-
const perHrColor = perHr >= 0 ? G : R;
|
|
511
|
-
const perHrSign = perHr >= 0 ? '+' : '';
|
|
512
|
-
const newHighFlag = isNewHigh ? ` ${R}${c.bold}★ NEW HIGH ★${c.reset}` : '';
|
|
513
|
-
|
|
514
|
-
// ── Big trend sparkline (takes ~40% of inner width) ─────────
|
|
515
|
-
const sparkW = Math.max(28, Math.floor(iw * 0.4));
|
|
516
|
-
const spark = drawSparkline(earningsHistory.toArray(), sparkW);
|
|
517
|
-
const sparkLabel = `${A}~${c.reset} ${D}TREND${c.reset}`;
|
|
518
|
-
|
|
519
|
-
// ── Left metric rows (each left-aligned, ANSI-aware padding) ─
|
|
520
|
-
// Helper: ANSI-strip-aware pad — strip ANSI then pad the visible content
|
|
521
|
-
const padRow = (content, totalVis) => {
|
|
522
|
-
const vis = content.replace(RE, '').length;
|
|
523
|
-
return content + ' '.repeat(Math.max(0, totalVis - vis));
|
|
524
|
-
};
|
|
441
|
+
const now = Date.now();
|
|
442
|
+
if (now - lastEarningsSample > 8000) { earningsHistory.push(totalCoins); lastEarningsSample = now; }
|
|
525
443
|
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const
|
|
530
|
-
const
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
//
|
|
546
|
-
//
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
const colEarn
|
|
555
|
-
const
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
lines.push(bRow(` ${hNum} ${hSts} ${hMedal} ${hName} ${hBal} ${hEarn} ${hBar} ${hAct}`));
|
|
571
|
-
lines.push(bRow(` ${D}${'─'.repeat(iw - 2)}${c.reset}`));
|
|
572
|
-
|
|
573
|
-
// Sort workers by original index for consistent display
|
|
574
|
-
const sortedWorkers = [...workers].sort((a, b) => a.idx - b.idx);
|
|
444
|
+
const topLine = `${A}┌${'─'.repeat(tw - 2)}┐${_}`;
|
|
445
|
+
rows.push(topLine);
|
|
446
|
+
|
|
447
|
+
const title = `${c.bold}${gradientText(' DANK GRINDER ', [139, 92, 246], [52, 211, 153])}${_}`;
|
|
448
|
+
const modeLabel = CLOUD_MODE ? `${Cy}CLOUD${_}` : (CLUSTER_ENABLED ? `${Cy}CLUSTER${_}` : `${D}local${_}`);
|
|
449
|
+
const row1Content = `${title} ${D}v${PKG_VERSION}${_} ${G}${spin}${_}` +
|
|
450
|
+
` ${D}up${_} ${Y}${formatUptime()}${_}` +
|
|
451
|
+
` ${netDot} ${D}net${_}` +
|
|
452
|
+
` ${Au}⏣${_}${W}${formatCoins(totalBalance)}${_} ${D}bal${_}` +
|
|
453
|
+
` ${G}${perHr >= 0 ? '+' : ''}⏣${formatCoins(perHr)}${_}/h${_}` +
|
|
454
|
+
` ${G}${activeCount}${_}/${W}${workers.length}${_} ${D}active${_}` +
|
|
455
|
+
(invalidCount > 0 ? ` ${R}${invalidCount} invalid${_}` : '') +
|
|
456
|
+
(pausedCount > 0 ? ` ${Y}${pausedCount} paused${_}` : '') +
|
|
457
|
+
(recovCount > 0 ? ` ${O}${recovCount} recov${_}` : '') +
|
|
458
|
+
` ${D}${totalCommands}${_} cmds` +
|
|
459
|
+
` ${D}mem ${memMB}MB${_}`;
|
|
460
|
+
rows.push(`${A}│${_} ${pad(row1Content, tw - 4)} ${A}│${_}`);
|
|
461
|
+
rows.push(`${A}└${'─'.repeat(tw - 2)}┘${_}`);
|
|
462
|
+
|
|
463
|
+
// ── ACCOUNTS TABLE ────────────────────────────────────────────
|
|
464
|
+
// Columns: #, S, Name, Balance, Lvl, LS, Earned, Activity
|
|
465
|
+
const colNum = 3; // "#1"
|
|
466
|
+
const colSts = 2; // "●"
|
|
467
|
+
const colName = Math.max(14, Math.min(20, Math.floor(tw * 0.22)));
|
|
468
|
+
const colBal = 9; // "⏣ 1.2M"
|
|
469
|
+
const colLvl = 5; // "L273"
|
|
470
|
+
const colLs = 4; // "♥37"
|
|
471
|
+
const colEarn = 8; // "+500K"
|
|
472
|
+
const colAct = Math.max(6, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn - 16);
|
|
473
|
+
const gap = 2;
|
|
474
|
+
const colGap = ' '.repeat(gap);
|
|
475
|
+
|
|
476
|
+
const hNum = `${D}${pad('#', colNum)}${_}`;
|
|
477
|
+
const hSts = `${D}${pad('S', colSts)}${_}`;
|
|
478
|
+
const hName = `${gradientText(pad('Account', colName), [139, 92, 246], [96, 165, 250])}${_}`;
|
|
479
|
+
const hBal = `${D}${pad('Balance', colBal)}${_}`;
|
|
480
|
+
const hLvl = `${D}${pad('Lvl', colLvl)}${_}`;
|
|
481
|
+
const hLs = `${D}${pad('LS', colLs)}${_}`;
|
|
482
|
+
const hEarn = `${D}${pad('Earned', colEarn)}${_}`;
|
|
483
|
+
const hAct = `${D}${pad('Activity', colAct)}${_}`;
|
|
484
|
+
|
|
485
|
+
rows.push(`${A}┌${'─'.repeat(tw - 2)}┐${_}`);
|
|
486
|
+
rows.push(`${A}│${_} ${hNum}${colGap}${hSts}${colGap}${hName}${colGap}${hBal}${colGap}${hLvl}${colGap}${hLs}${colGap}${hEarn}${colGap}${hAct} ${A}│${_}`);
|
|
487
|
+
rows.push(`${A}├${'─'.repeat(tw - 2)}┤${_}`);
|
|
575
488
|
|
|
576
|
-
|
|
489
|
+
const sortedWorkers = [...workers].sort((a, b) => a.idx - b.idx);
|
|
577
490
|
const topEarners = [...workers]
|
|
578
491
|
.filter(w => (w.stats.coins || 0) > 0)
|
|
579
492
|
.sort((a, b) => (b.stats.coins || 0) - (a.stats.coins || 0))
|
|
@@ -581,166 +494,90 @@ function renderDashboard() {
|
|
|
581
494
|
const topIds = new Map();
|
|
582
495
|
topEarners.forEach((w, i) => topIds.set(w.account.id, i));
|
|
583
496
|
|
|
584
|
-
|
|
585
|
-
const
|
|
497
|
+
// Adapt row count to available terminal height
|
|
498
|
+
const maxRows = Math.max(4, Math.min(sortedWorkers.length, Math.floor((process.stdout.rows || 24) - 14)));
|
|
499
|
+
const visibleWorkers = sortedWorkers.slice(0, maxRows);
|
|
586
500
|
|
|
587
501
|
for (const wk of visibleWorkers) {
|
|
588
|
-
const origNum = (wk.idx + 1).toString().padStart(colNum);
|
|
589
|
-
const rawStat = (wk.lastStatus || 'ready').replace(RE, '');
|
|
590
|
-
const activityText = rawStat.substring(0, colActivity);
|
|
591
|
-
|
|
592
|
-
// ── Status icon ──
|
|
593
|
-
let stsIcon, actLabel;
|
|
594
502
|
const isRecov = wk._recoveryAttempts > 0 && wk._errorCooldownUntil > Date.now();
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
stsIcon = `${O}${getSpinner('braille')}${c.reset}`;
|
|
604
|
-
actLabel = `${O}recovering (${sL}s)${c.reset}`;
|
|
605
|
-
} else if (wk.paused) {
|
|
606
|
-
stsIcon = `${R}⏸${c.reset}`;
|
|
607
|
-
actLabel = `${R}PAUSED${c.reset}`;
|
|
608
|
-
} else if (wk.dashboardPaused) {
|
|
609
|
-
stsIcon = `${Y}⏸${c.reset}`;
|
|
610
|
-
actLabel = `${Y}paused${c.reset}`;
|
|
611
|
-
} else if (wk.busy) {
|
|
612
|
-
stsIcon = `${G}${getSpinner('pulse')}${c.reset}`;
|
|
613
|
-
actLabel = `${D}${activityText}${c.reset}`;
|
|
614
|
-
} else {
|
|
615
|
-
stsIcon = `${G}●${c.reset}`;
|
|
616
|
-
actLabel = `${D}${activityText}${c.reset}`;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// ── Medal (fixed 3-char visible width + 1 space) ──
|
|
620
|
-
let medalStr;
|
|
621
|
-
if (topIds.has(wk.account.id)) {
|
|
622
|
-
const rank = topIds.get(wk.account.id);
|
|
623
|
-
if (rank === 0) medalStr = `${Au}1st${c.reset} `;
|
|
624
|
-
else if (rank === 1) medalStr = `${rgb(192, 192, 192)}2nd${c.reset} `;
|
|
625
|
-
else medalStr = `${rgb(205, 127, 50)}3rd${c.reset} `;
|
|
626
|
-
} else {
|
|
627
|
-
medalStr = ' '; // 4 chars: 3 empty + 1 space
|
|
628
|
-
}
|
|
503
|
+
let stsIcon;
|
|
504
|
+
if (wk._tokenInvalid) stsIcon = `${R}✗${_}`;
|
|
505
|
+
else if (!wk.running) stsIcon = `${D}○${_}`;
|
|
506
|
+
else if (isRecov) stsIcon = `${O}${getSpinner('braille').substring(0, 1)}${_}`;
|
|
507
|
+
else if (wk.paused) stsIcon = `${R}⏸${_}`;
|
|
508
|
+
else if (wk.dashboardPaused) stsIcon = `${Y}⏸${_}`;
|
|
509
|
+
else if (wk.busy) stsIcon = `${G}${getSpinner('pulse').substring(0, 1)}${_}`;
|
|
510
|
+
else stsIcon = `${G}●${_}`;
|
|
629
511
|
|
|
630
|
-
// ── Name (fixed visible width, padded) ──
|
|
631
512
|
const rawName = (wk.username || wk.account.label || '?').substring(0, colName);
|
|
632
|
-
const nameStr = `${wk.color}${
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if (wk.stats.balance > 0) {
|
|
637
|
-
balStr = `${Au}⏣${c.reset}${W}${formatCoins(wk.stats.balance).padStart(colBal - 2)}${c.reset}`;
|
|
638
|
-
} else {
|
|
639
|
-
balStr = `${D}⏣${' '.repeat(colBal - 3)}-${c.reset}`;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// ── Lifesaver indicator ──
|
|
513
|
+
const nameStr = `${wk.color}${rawName}${_}`;
|
|
514
|
+
const balStr = wk.stats.balance > 0 ? `${Au}⏣${_}${W}${formatCoins(wk.stats.balance)}${_}` : `${D}⏣-${_}`;
|
|
515
|
+
const lvl = wk._level || 0;
|
|
516
|
+
const lvlStr = lvl > 0 ? `${Cy}L${lvl}${_}` : `${D}L???${_}`;
|
|
643
517
|
const ls = wk._lifesavers;
|
|
644
518
|
let lsStr;
|
|
645
|
-
if (ls === 0)
|
|
646
|
-
else if (ls != null && ls <= 2) lsStr = `${Y}♥${ls}${
|
|
647
|
-
else if (ls != null)
|
|
519
|
+
if (ls === 0) lsStr = `${R}♥${ls}${_}`;
|
|
520
|
+
else if (ls != null && ls <= 2) lsStr = `${Y}♥${ls}${_}`;
|
|
521
|
+
else if (ls != null) lsStr = `${G}♥${ls}${_}`;
|
|
648
522
|
else {
|
|
649
|
-
// Unknown — pulse to show it's still being determined
|
|
650
523
|
const pulse = PULSE_CHARS[Math.floor(Date.now() / 400) % PULSE_CHARS.length];
|
|
651
|
-
lsStr = `${D}${pulse}♥?${
|
|
524
|
+
lsStr = `${D}${pulse}♥?${_}`;
|
|
652
525
|
}
|
|
526
|
+
const earnNum = wk.stats.coins || 0;
|
|
527
|
+
const earnStr = earnNum > 0 ? `${G}+${formatCoins(earnNum)}${_}` : `${D}────${_}`;
|
|
528
|
+
const rawAct = (wk.lastStatus || 'ready').replace(RE, '').substring(0, colAct);
|
|
529
|
+
const actStr = `${D}${pad(rawAct, colAct)}${_}`;
|
|
530
|
+
const numStr = `${D}${pad(String(wk.idx + 1), colNum)}${_}`;
|
|
653
531
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const lvlStr = lvl > 0 ? `${Cy}L${String(lvl).padStart(3)}${c.reset}` : `${D}L???${c.reset}`;
|
|
532
|
+
rows.push(`${A}│${_} ${numStr}${colGap}${pad(stsIcon, colSts)}${colGap}${pad(nameStr, colName)}${colGap}${pad(balStr, colBal)}${colGap}${pad(lvlStr, colLvl)}${colGap}${pad(lsStr, colLs)}${colGap}${pad(earnStr, colEarn)}${colGap}${actStr} ${A}│${_}`);
|
|
533
|
+
}
|
|
657
534
|
|
|
658
|
-
|
|
659
|
-
const
|
|
660
|
-
|
|
661
|
-
if (earnNum > 0) {
|
|
662
|
-
earnStr = `${G}+${formatCoins(earnNum).padEnd(colEarn - 1)}${c.reset}`;
|
|
663
|
-
} else if (earnNum < 0) {
|
|
664
|
-
earnStr = `${R}${formatCoins(earnNum).padEnd(colEarn)}${c.reset}`;
|
|
665
|
-
} else {
|
|
666
|
-
earnStr = `${D}${'-'.padEnd(colEarn)}${c.reset}`;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// ── Progress bar (fixed width) ──
|
|
670
|
-
const earnBarFill = earnNum > 0 ? Math.min(colBar, Math.max(1, Math.floor(Math.log10(earnNum + 1)))) : 0;
|
|
671
|
-
const earnBar = progressBar(earnBarFill, colBar, colBar, [52, 211, 153], [40, 40, 55]);
|
|
672
|
-
|
|
673
|
-
lines.push(bRow(` ${D}${origNum}${c.reset} ${stsIcon} ${medalStr}${nameStr} ${balStr} ${lvlStr} ${lsStr} ${earnStr} ${earnBar} ${actLabel}`));
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// Overflow summary
|
|
677
|
-
if (sortedWorkers.length > MAX_VIS) {
|
|
678
|
-
const rest = sortedWorkers.length - MAX_VIS;
|
|
679
|
-
let ha = 0, hp = 0, hr = 0, ho = 0, hi = 0;
|
|
680
|
-
for (let i = MAX_VIS; i < sortedWorkers.length; i++) {
|
|
681
|
-
const w = sortedWorkers[i];
|
|
682
|
-
if (w._tokenInvalid) hi++;
|
|
683
|
-
else if (!w.running) ho++;
|
|
684
|
-
else if (w.paused || w.dashboardPaused) hp++;
|
|
685
|
-
else if (w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()) hr++;
|
|
686
|
-
else ha++;
|
|
687
|
-
}
|
|
688
|
-
const parts = [`${D}+${rest} more${c.reset}`];
|
|
689
|
-
if (ha > 0) parts.push(`${G}${ha} active${c.reset}`);
|
|
690
|
-
if (hp > 0) parts.push(`${Y}${hp} paused${c.reset}`);
|
|
691
|
-
if (hr > 0) parts.push(`${O}${hr} recovering${c.reset}`);
|
|
692
|
-
if (hi > 0) parts.push(`${R}${hi} invalid${c.reset}`);
|
|
693
|
-
if (ho > 0) parts.push(`${D}${ho} offline${c.reset}`);
|
|
694
|
-
lines.push(bRow(` ${parts.join(` ${D}·${c.reset} `)}`));
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// ═══════════════════════════════════════════════════════════════
|
|
698
|
-
// HEALTH BAR
|
|
699
|
-
// ═══════════════════════════════════════════════════════════════
|
|
700
|
-
const totalRecoveries = workers.reduce((s, w) => s + (w._totalRecoveries || 0), 0);
|
|
701
|
-
const totalDisconnects = workers.reduce((s, w) => s + (w._disconnectCount || 0), 0);
|
|
702
|
-
if (recovCount > 0 || pausedCount > 0 || invalidCount > 0 || totalRecoveries > 0 || totalDisconnects > 0) {
|
|
703
|
-
lines.push(bMid);
|
|
704
|
-
const hParts = [];
|
|
705
|
-
if (invalidCount > 0) hParts.push(`${R}✗ ${invalidCount} invalid${c.reset}`);
|
|
706
|
-
if (recovCount > 0) hParts.push(`${O}${getSpinner('braille')} ${recovCount} recovering${c.reset}`);
|
|
707
|
-
if (pausedCount > 0) hParts.push(`${Y}⏸ ${pausedCount} paused${c.reset}`);
|
|
708
|
-
if (totalRecoveries > 0) hParts.push(`${D}${totalRecoveries} auto-healed${c.reset}`);
|
|
709
|
-
if (totalDisconnects > 0) hParts.push(`${D}${totalDisconnects} reconnects${c.reset}`);
|
|
710
|
-
lines.push(bRow(` ${D}HEALTH${c.reset} ${hParts.join(` ${D}·${c.reset} `)}`));
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Cluster
|
|
714
|
-
if (CLUSTER_ENABLED) {
|
|
715
|
-
const nodeShort = NODE_ID.substring(0, 12);
|
|
716
|
-
const claimedCount = workers.filter(w => w.running).length;
|
|
717
|
-
lines.push(bRow(` ${Cy}CLUSTER${c.reset} ${D}node:${c.reset} ${Cy}${nodeShort}${c.reset} ${D}claimed:${c.reset} ${W}${claimedCount}${c.reset}`));
|
|
535
|
+
if (sortedWorkers.length > maxRows) {
|
|
536
|
+
const rest = sortedWorkers.length - maxRows;
|
|
537
|
+
rows.push(`${A}│${_} ${D}+${rest} more accounts${_}${' '.repeat(Math.max(0, tw - 22 - rest.toString().length))}${A}│${_}`);
|
|
718
538
|
}
|
|
539
|
+
rows.push(`${A}└${'─'.repeat(tw - 2)}┘${_}`);
|
|
719
540
|
|
|
720
|
-
//
|
|
721
|
-
// LIVE LOG FEED
|
|
722
|
-
// ═══════════════════════════════════════════════════════════════
|
|
541
|
+
// ── LIVE FEED ─────────────────────────────────────────────────
|
|
723
542
|
const logEntries = recentLogs.toArray();
|
|
724
543
|
if (logEntries.length > 0) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
544
|
+
rows.push(`${A}┌${'─'.repeat(tw - 2)}┐${_}`);
|
|
545
|
+
rows.push(`${A}│${_} ${gradientText(' LIVE FEED ', [139, 92, 246], [52, 211, 153])}${_} ${G}${getSpinner('pulse')}${_}${' '.repeat(Math.max(0, tw - 22))}${A}│${_}`);
|
|
546
|
+
rows.push(`${A}├${'─'.repeat(tw - 2)}┤${_}`);
|
|
728
547
|
for (const entry of logEntries) {
|
|
729
|
-
|
|
548
|
+
let lineText;
|
|
549
|
+
if (typeof entry === 'string') {
|
|
550
|
+
lineText = entry;
|
|
551
|
+
} else if (entry && typeof entry === 'object') {
|
|
552
|
+
const ts = entry.ts ? new Date(entry.ts).toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '';
|
|
553
|
+
const user = entry.username ? String(entry.username) : '';
|
|
554
|
+
const cmd = entry.command ? `[${entry.command}]` : '';
|
|
555
|
+
const resp = entry.response || '';
|
|
556
|
+
lineText = `${D}${ts}${_} ${entry.color || D}${user}${_} ${D}${cmd}${_} ${resp}`;
|
|
557
|
+
} else {
|
|
558
|
+
lineText = String(entry);
|
|
559
|
+
}
|
|
560
|
+
rows.push(`${A}│${_} ${D}${pad(lineText, tw - 4)}${_} ${A}│${_}`);
|
|
730
561
|
}
|
|
562
|
+
rows.push(`${A}└${'─'.repeat(tw - 2)}┘${_}`);
|
|
731
563
|
}
|
|
732
564
|
|
|
733
|
-
//
|
|
734
|
-
|
|
735
|
-
|
|
565
|
+
// ── FOOTER ────────────────────────────────────────────────────
|
|
566
|
+
rows.push(`${A}┌${'─'.repeat(tw - 2)}┐${_}`);
|
|
567
|
+
rows.push(`${A}│${_} ${modeLabel} ${D}P=pause R=resume S=status Q=quit${_}${' '.repeat(Math.max(0, tw - 48))}${A}│${_}`);
|
|
568
|
+
rows.push(`${A}└${'─'.repeat(tw - 2)}┘${_}`);
|
|
736
569
|
|
|
737
|
-
// ── Flush
|
|
570
|
+
// ── Flush ─────────────────────────────────────────────────────
|
|
738
571
|
process.stdout.write('\x1b[H');
|
|
739
|
-
for (const
|
|
740
|
-
process.stdout.write(c.clearLine + '\r' +
|
|
572
|
+
for (const row of rows) {
|
|
573
|
+
process.stdout.write(c.clearLine + '\r' + row + '\n');
|
|
574
|
+
}
|
|
575
|
+
// Erase any leftover lines from previous render
|
|
576
|
+
const clearDown = Math.max(0, dashboardLines - rows.length);
|
|
577
|
+
if (clearDown > 0) {
|
|
578
|
+
process.stdout.write(`\x1b[${clearDown}B\x1b[2K`);
|
|
741
579
|
}
|
|
742
|
-
|
|
743
|
-
dashboardLines = lines.length;
|
|
580
|
+
dashboardLines = rows.length;
|
|
744
581
|
dashboardRendering = false;
|
|
745
582
|
}
|
|
746
583
|
|