dankgrinder 8.37.0 → 8.38.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 +21 -25
- package/lib/ui.js +95 -141
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -2755,8 +2755,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2755
2755
|
w.setCooldown?.('crime', 86400);
|
|
2756
2756
|
w.setCooldown?.('search', 86400);
|
|
2757
2757
|
sendWebhook?.('DEATH ALERT (DM)', `**${w.username}** died in DMs! **0 lifesavers!**\nCrime/search auto-disabled.`, 0xef4444);
|
|
2758
|
-
ui.
|
|
2759
|
-
ui.draw();
|
|
2758
|
+
ui.log(`${c.red}E${c.reset} DEATH — ${w.username} has 0 lifesavers! Crime/search disabled`);
|
|
2760
2759
|
} else {
|
|
2761
2760
|
w.log?.('warn', `DEATH in DMs — ${event.lifesaversLeft} lifesavers remaining`);
|
|
2762
2761
|
if (prev !== event.lifesaversLeft) {
|
|
@@ -2766,8 +2765,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2766
2765
|
if (event.lifesaversLeft <= 2) {
|
|
2767
2766
|
w._alert = { type: 'lowls' };
|
|
2768
2767
|
sendWebhook?.('LOW LIFESAVERS', `**${w.username}** has only **${event.lifesaversLeft}** lifesaver(s) left!`, 0xfbbf24);
|
|
2769
|
-
ui.
|
|
2770
|
-
ui.draw();
|
|
2768
|
+
ui.log(`${c.yellow}!${c.reset} ${w.username} — only ${event.lifesaversLeft} lifesavers!`);
|
|
2771
2769
|
}
|
|
2772
2770
|
}
|
|
2773
2771
|
}
|
|
@@ -2776,7 +2774,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2776
2774
|
if (event.type === 'levelup') {
|
|
2777
2775
|
if (event.to > 0) {
|
|
2778
2776
|
w._level = event.to;
|
|
2779
|
-
ui.
|
|
2777
|
+
ui.log(`${c.blue}↑${c.reset} ${w.username} leveled up to ${event.to}`);
|
|
2780
2778
|
}
|
|
2781
2779
|
}
|
|
2782
2780
|
}
|
|
@@ -2790,8 +2788,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2790
2788
|
const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(parsedGapMin, 150);
|
|
2791
2789
|
const randomLoginGap = () => LOGIN_GAP_MAX_MS <= LOGIN_GAP_MIN_MS ? LOGIN_GAP_MIN_MS : LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
|
|
2792
2790
|
|
|
2793
|
-
ui.
|
|
2794
|
-
ui.logEvent(`${c.dim}Logging in ${accounts.length} accounts...${c.reset}`);
|
|
2791
|
+
ui.log(`${c.dim}Logging in ${accounts.length} accounts...${c.reset}`);
|
|
2795
2792
|
const BATCH_SIZE = 10;
|
|
2796
2793
|
for (let i = 0; i < accounts.length; i += BATCH_SIZE) {
|
|
2797
2794
|
if (shutdownCalled) break;
|
|
@@ -2806,21 +2803,20 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2806
2803
|
await worker.start();
|
|
2807
2804
|
if (worker._tokenInvalid) {
|
|
2808
2805
|
worker.lastStatus = 'invalid token';
|
|
2809
|
-
ui.
|
|
2806
|
+
ui.log(`${c.red}E${c.reset} [${i + idx + 1}] ${acc.label || acc.id} — invalid token`);
|
|
2810
2807
|
} else if (worker.channel) {
|
|
2811
2808
|
worker.lastStatus = 'ready';
|
|
2812
|
-
ui.
|
|
2813
|
-
ui.draw();
|
|
2809
|
+
ui.log(`${c.green}·${c.reset} [${i + idx + 1}] ${worker.username} connected`);
|
|
2814
2810
|
} else {
|
|
2815
2811
|
worker.lastStatus = 'timeout';
|
|
2816
|
-
ui.
|
|
2812
|
+
ui.log(`${c.yellow}·${c.reset} [${i + idx + 1}] ${acc.label || acc.id} — timeout`);
|
|
2817
2813
|
}
|
|
2818
2814
|
} catch (e) {
|
|
2819
|
-
ui.
|
|
2815
|
+
ui.log(`${c.red}E${c.reset} [${i + idx + 1}] ERROR: ${e.message}`);
|
|
2820
2816
|
}
|
|
2821
2817
|
}));
|
|
2822
2818
|
} catch (e) {
|
|
2823
|
-
ui.
|
|
2819
|
+
ui.log(`${c.red}!${c.reset} BATCH ERROR: ${e.message}`);
|
|
2824
2820
|
}
|
|
2825
2821
|
if (i + BATCH_SIZE < accounts.length) await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
2826
2822
|
hintGC();
|
|
@@ -2829,32 +2825,32 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2829
2825
|
const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
|
|
2830
2826
|
const invalidWorkers = workers.filter(w => w._tokenInvalid);
|
|
2831
2827
|
const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
|
|
2832
|
-
ui.
|
|
2828
|
+
ui.log(`${c.green}·${c.reset} Login: ${loginDone}/${accounts.length} connected`);
|
|
2833
2829
|
if (invalidWorkers.length > 0) {
|
|
2834
|
-
for (const w of invalidWorkers) ui.
|
|
2830
|
+
for (const w of invalidWorkers) ui.log(`${c.red}E${c.reset} invalid token: ${w.account.label || w.account.id}`);
|
|
2835
2831
|
}
|
|
2836
2832
|
if (timedOutWorkers.length > 0) {
|
|
2837
|
-
ui.
|
|
2833
|
+
ui.log(`${c.yellow}~${c.reset} ${timedOutWorkers.length} timed out (will retry in background)`);
|
|
2838
2834
|
}
|
|
2839
2835
|
ui.draw();
|
|
2840
2836
|
|
|
2841
2837
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
2842
2838
|
|
|
2843
2839
|
// ── Phase 2: Inventory check ────────────────────────────────────
|
|
2844
|
-
ui.
|
|
2840
|
+
ui.log(`${c.dim}Checking inventory...${c.reset}`);
|
|
2845
2841
|
let invFailed = 0;
|
|
2846
2842
|
await Promise.all(activeWorkers.map(async (w) => {
|
|
2847
2843
|
try { await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
2848
2844
|
catch { invFailed++; }
|
|
2849
2845
|
}));
|
|
2850
2846
|
if (invFailed > 0) {
|
|
2851
|
-
ui.
|
|
2847
|
+
ui.log(`${c.red}!${c.reset} Inventory failed for ${invFailed} accounts`);
|
|
2852
2848
|
} else {
|
|
2853
|
-
ui.
|
|
2849
|
+
ui.log(`${c.green}·${c.reset} Inventory OK`);
|
|
2854
2850
|
}
|
|
2855
2851
|
|
|
2856
2852
|
// ── Phase 2.5: Balance check ───────────────────────────────────
|
|
2857
|
-
ui.
|
|
2853
|
+
ui.log(`${c.dim}Checking balances...${c.reset}`);
|
|
2858
2854
|
for (const w of activeWorkers) {
|
|
2859
2855
|
try { await w.checkBalance(true); } catch {}
|
|
2860
2856
|
}
|
|
@@ -2863,10 +2859,10 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2863
2859
|
totalCoins += w.stats?.balance || 0;
|
|
2864
2860
|
totalCoins += w.stats?.bankBalance || 0;
|
|
2865
2861
|
}
|
|
2866
|
-
ui.
|
|
2862
|
+
ui.log(`${c.blue}$${c.reset} Balances: ${c.green}+⏣${totalCoins.toLocaleString()}${c.reset} across ${activeWorkers.length} accounts`);
|
|
2867
2863
|
|
|
2868
2864
|
// ── Phase 2.75: DM history check ────────────────────────────────
|
|
2869
|
-
ui.
|
|
2865
|
+
ui.log(`${c.dim}Checking DM history...${c.reset}`);
|
|
2870
2866
|
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [];
|
|
2871
2867
|
for (const w of activeWorkers) {
|
|
2872
2868
|
try {
|
|
@@ -2884,14 +2880,14 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2884
2880
|
}
|
|
2885
2881
|
} catch {}
|
|
2886
2882
|
}
|
|
2887
|
-
if (dmNoLs.length > 0) ui.
|
|
2883
|
+
if (dmNoLs.length > 0) ui.log(`${c.red}E${c.reset} No lifesavers: ${dmNoLs.join(', ')}`);
|
|
2888
2884
|
const parts = [];
|
|
2889
2885
|
if (dmDeaths > 0) parts.push(`${dmDeaths} deaths`);
|
|
2890
2886
|
if (dmLevelUps > 0) parts.push(`${dmLevelUps} level-ups`);
|
|
2891
|
-
ui.
|
|
2887
|
+
ui.log(`${c.green}·${c.reset} DM: ${parts.length > 0 ? parts.join(', ') : c.green + 'clean' + c.reset}`);
|
|
2892
2888
|
|
|
2893
2889
|
// ── Phase 3: Start grind loops ───────────────────────────────────
|
|
2894
|
-
ui.
|
|
2890
|
+
ui.log(`${c.green}·${c.reset} Starting ${activeWorkers.length} grind loops...`);
|
|
2895
2891
|
for (const w of activeWorkers) {
|
|
2896
2892
|
if (!shutdownCalled) w.grindLoop();
|
|
2897
2893
|
}
|
package/lib/ui.js
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI Live Dashboard —
|
|
3
|
-
*
|
|
4
|
-
* Each account has its own color. Events logged below.
|
|
2
|
+
* CLI Live Dashboard — one table, append-only events below.
|
|
3
|
+
* Draws once after login. Events stream below without touching the table.
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
let _startTime = Date.now();
|
|
8
7
|
let _workers = [];
|
|
9
8
|
let _isShuttingDown = () => false;
|
|
9
|
+
let _version = '0.0.0';
|
|
10
|
+
|
|
11
|
+
// ── Big ASCII art banner ──────────────────────────────────────
|
|
12
|
+
const BANNER = [
|
|
13
|
+
' ██████╗ ██╗ ██╗███╗ ██╗ ██████╗ ███████╗ ██████╗ ███╗ ██╗',
|
|
14
|
+
' ██╔══██╗██║ ██║████╗ ██║██╔════╝ ██╔════╝██╔═══██╗████╗ ██║',
|
|
15
|
+
' ██║ ██║██║ ██║██╔██╗ ██║██║ ███╗█████╗ ██║ ██║██╔██╗ ██║',
|
|
16
|
+
' ██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██║ ██║██║╚██╗██║',
|
|
17
|
+
' ██████╔╝╚██████╔╝██║ ╚████║╚██████╔╝███████╗╚██████╔╝██║ ╚████║',
|
|
18
|
+
' ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝',
|
|
19
|
+
' ██╗██╗ ██╗███╗ ██╗ ██████╗ ██╗ ███████╗██████╗ ',
|
|
20
|
+
' ██║██║ ██║████╗ ██║██╔═══██╗██║ ██╔════╝██╔══██╗',
|
|
21
|
+
' ███████╗███████╗ ██║██║ ██║██╔██╗ ██║██║ ██║██║ █████╗ ██████╔╝',
|
|
22
|
+
' ╚════██║╚════██║ ██║██║ ██║██║╚██╗██║██║ ██║██║ ██╔══╝ ██╔══██╗',
|
|
23
|
+
' ██║ ██║ ██║╚██████╔╝██║ ╚████║╚██████╔╝███████╗███████╗██║ ██║',
|
|
24
|
+
' ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝',
|
|
25
|
+
];
|
|
10
26
|
|
|
11
|
-
//
|
|
27
|
+
// Gradient each banner line cyan→pink
|
|
28
|
+
function gradientLine(text, r1, g1, b1, r2, g2, b2) {
|
|
29
|
+
let out = '';
|
|
30
|
+
for (let i = 0; i < text.length; i++) {
|
|
31
|
+
const t = text.length <= 1 ? 0 : i / (text.length - 1);
|
|
32
|
+
const lr = Math.round(r1 + (r2 - r1) * t);
|
|
33
|
+
const lg = Math.round(g1 + (g2 - g1) * t);
|
|
34
|
+
const lb = Math.round(b1 + (b2 - b1) * t);
|
|
35
|
+
out += `\x1b[38;2;${lr};${lg};${lb}m${text[i]}`;
|
|
36
|
+
}
|
|
37
|
+
return out + '\x1b[0m';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Dark-theme palette ────────────────────────────────────────
|
|
12
41
|
const PALETTE = [
|
|
13
42
|
'\x1b[38;2;77;212;238m', // cyan
|
|
14
43
|
'\x1b[38;2;255;194;77m', // amber
|
|
@@ -18,13 +47,12 @@ const PALETTE = [
|
|
|
18
47
|
'\x1b[38;2;255;180;80m', // orange
|
|
19
48
|
'\x1b[38;2;180;130;255m', // lavender
|
|
20
49
|
'\x1b[38;2;100;255;180m', // teal
|
|
21
|
-
'\x1b[38;2;255;150;100m',
|
|
50
|
+
'\x1b[38;2;255;150;100m', // peach
|
|
22
51
|
'\x1b[38;2;150;255;200m', // mint
|
|
23
52
|
'\x1b[38;2;255;255;120m', // yellow
|
|
24
53
|
'\x1b[38;2;200;150;255m', // violet
|
|
25
54
|
];
|
|
26
|
-
|
|
27
|
-
function workerColor(idx) { return PALETTE[idx % PALETTE.length]; }
|
|
55
|
+
function wc(idx) { return PALETTE[idx % PALETTE.length]; }
|
|
28
56
|
|
|
29
57
|
// ── ANSI helpers ──────────────────────────────────────────────
|
|
30
58
|
const c = {
|
|
@@ -34,12 +62,13 @@ const c = {
|
|
|
34
62
|
yellow: '\x1b[38;2;255;220;80m',
|
|
35
63
|
cyan: '\x1b[38;2;80;220;255m',
|
|
36
64
|
blue: '\x1b[38;2;100;160;255m',
|
|
37
|
-
gray: '\x1b[38;2;120;130;150m',
|
|
38
|
-
white: '\x1b[38;2;220;220;240m',
|
|
39
65
|
};
|
|
40
66
|
const DIM = c.dim;
|
|
41
67
|
|
|
42
|
-
|
|
68
|
+
function trunc(s, n) { s = String(s || ''); return s.length <= n ? s : s.slice(0, n - 1) + '…'; }
|
|
69
|
+
function padR(s, n) { return trunc(s, n).padEnd(n); }
|
|
70
|
+
function padL(s, n) { return String(s).padStart(n); }
|
|
71
|
+
|
|
43
72
|
function fmtUptime() {
|
|
44
73
|
const s = Math.floor((Date.now() - _startTime) / 1000);
|
|
45
74
|
if (s < 60) return `${s}s`;
|
|
@@ -51,26 +80,6 @@ function fmtUptime() {
|
|
|
51
80
|
return `${m}m`;
|
|
52
81
|
}
|
|
53
82
|
|
|
54
|
-
// ── Column widths based on terminal width ────────────────────
|
|
55
|
-
function colWidths(totalW) {
|
|
56
|
-
const minW = 80;
|
|
57
|
-
const w = Math.max(totalW, minW);
|
|
58
|
-
// Columns: # | Status | Account | LastCmd | Cmds | OK% | Earned
|
|
59
|
-
// Reserve: 1+2+1 + 1+3+1 + 1+16+1 + 1+20+1 + 1+5+1 + 1+3+1 + 1+12+1 = ~72 fixed
|
|
60
|
-
const fixed = 74; // 1+2+1 + 1+3+1 + 1+16+1 + 1+20+1 + 1+5+1 + 1+3+1 + 1+12+1
|
|
61
|
-
const extra = Math.max(0, w - fixed);
|
|
62
|
-
return {
|
|
63
|
-
status: 3,
|
|
64
|
-
num: 2,
|
|
65
|
-
name: 16,
|
|
66
|
-
lastCmd: 20 + Math.floor(extra * 0.5),
|
|
67
|
-
cmds: 5,
|
|
68
|
-
ok: 3,
|
|
69
|
-
earned: 12,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ── Status icon ─────────────────────────────────────────────
|
|
74
83
|
function icon(w) {
|
|
75
84
|
if (!w.running || !w.channel) return `${c.red}E${c.reset}`;
|
|
76
85
|
if (w.paused || w.dashboardPaused) return `${c.yellow}P${c.reset}`;
|
|
@@ -82,103 +91,73 @@ function icon(w) {
|
|
|
82
91
|
return `${c.green}·${c.reset}`;
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
function sortedWorkers() {
|
|
95
|
+
return [..._workers].sort((a, b) => {
|
|
96
|
+
if (!a.channel !== !b.channel) return !a.channel ? 1 : -1;
|
|
97
|
+
const aA = a.running && a.channel && !a.paused && !a.dashboardPaused;
|
|
98
|
+
const bA = b.running && b.channel && !b.paused && !b.dashboardPaused;
|
|
99
|
+
if (aA !== bA) return bA ? 1 : -1;
|
|
100
|
+
return (b.stats.commands || 0) - (a.stats.commands || 0);
|
|
101
|
+
});
|
|
90
102
|
}
|
|
91
103
|
|
|
92
|
-
// ── Draw
|
|
93
|
-
function
|
|
94
|
-
const rows = 8; // banner(3) + 1gap + header(1) + hr(1) + N rows + total(1) + gap(1)
|
|
95
|
-
const availRows = process.stdout.rows || 40;
|
|
96
|
-
const visible = Math.min(_workers.length, Math.max(5, availRows - rows));
|
|
104
|
+
// ── Draw full table (call once after login) ──────────────────
|
|
105
|
+
function draw() {
|
|
97
106
|
const W = Math.min(process.stdout.columns || 120, 120);
|
|
98
|
-
|
|
99
|
-
const cols = colWidths(W);
|
|
100
107
|
const hr = '─'.repeat(W);
|
|
101
|
-
|
|
102
|
-
const running = _workers.filter(w => w.running && w.channel).length;
|
|
108
|
+
const running = _workers.filter(w => w.running && w.channel && !w.paused && !w.dashboardPaused).length;
|
|
103
109
|
const paused = _workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
104
|
-
const active = running + paused;
|
|
105
110
|
|
|
106
|
-
|
|
111
|
+
console.log('\x1b[2J\x1b[H');
|
|
107
112
|
console.log('');
|
|
108
|
-
console.log(` ${c.bold}DANK${c.reset}${c.blue}GRINDER${c.reset} ${DIM}v${version}${c.reset} ${c.green}●${c.reset}online ${DIM}|${c.reset} ${fmtUptime()} ${c.green}·${c.reset}${running} ${c.yellow}~${c.reset}${paused} ${c.dim}Ctrl+C${c.reset}`);
|
|
109
113
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
console.log(
|
|
114
|
+
// Big banner with gradient
|
|
115
|
+
for (const line of BANNER) {
|
|
116
|
+
console.log(gradientLine(line, 77, 212, 238, 255, 92, 147));
|
|
113
117
|
}
|
|
114
|
-
console.log('');
|
|
115
118
|
|
|
116
|
-
|
|
119
|
+
console.log('');
|
|
117
120
|
console.log(
|
|
118
|
-
`
|
|
119
|
-
`${c.
|
|
120
|
-
`${
|
|
121
|
-
`${c.
|
|
122
|
-
`${c.
|
|
123
|
-
`${
|
|
124
|
-
|
|
121
|
+
` ${c.bold}v${_version}${c.reset} ` +
|
|
122
|
+
`${c.green}●${c.reset}online ` +
|
|
123
|
+
`${fmtUptime()} ` +
|
|
124
|
+
`${c.green}·${c.reset}${running} ` +
|
|
125
|
+
`${c.yellow}~${c.reset}${paused} ` +
|
|
126
|
+
`${DIM}Ctrl+C${c.reset}`
|
|
127
|
+
);
|
|
128
|
+
console.log(` ${hr}`);
|
|
129
|
+
console.log(
|
|
130
|
+
` ${c.bold}#${c.reset} ${c.bold}${padR('St', 2)}${c.reset} ` +
|
|
131
|
+
`${c.bold}${padR('Account', 18)}${c.reset} ` +
|
|
132
|
+
`${c.bold}${padR('Last Command', 20)}${c.reset} ` +
|
|
133
|
+
`${c.bold}${padL('Cmds', 5)}${c.reset} ` +
|
|
134
|
+
`${c.bold}${padL('OK%', 3)}${c.reset} ` +
|
|
135
|
+
`${c.bold}${padL('Earned', 8)}${c.reset}`
|
|
125
136
|
);
|
|
126
137
|
console.log(` ${hr}`);
|
|
127
138
|
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ── Sort: running first, then by activity ──────────────────
|
|
132
|
-
function sortedWorkers(visible) {
|
|
133
|
-
return [..._workers]
|
|
134
|
-
.sort((a, b) => {
|
|
135
|
-
// Invalid/timeout at bottom
|
|
136
|
-
if (!a.channel && !!b.channel) return 1;
|
|
137
|
-
if (!b.channel && !!a.channel) return -1;
|
|
138
|
-
// Running/active first
|
|
139
|
-
const aActive = a.running && a.channel && !a.paused && !a.dashboardPaused;
|
|
140
|
-
const bActive = b.running && b.channel && !b.paused && !b.dashboardPaused;
|
|
141
|
-
if (aActive !== bActive) return bActive ? 1 : -1;
|
|
142
|
-
// Sort by total activity
|
|
143
|
-
const aScore = (a.stats.commands || 0) + (a.stats.coins || 0);
|
|
144
|
-
const bScore = (b.stats.commands || 0) + (b.stats.coins || 0);
|
|
145
|
-
return bScore - aScore;
|
|
146
|
-
})
|
|
147
|
-
.slice(0, visible);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── Draw all rows ───────────────────────────────────────────
|
|
151
|
-
function drawRows({ visible, W, cols }) {
|
|
152
|
-
const hr = '─'.repeat(W);
|
|
153
|
-
const sorted = sortedWorkers(visible);
|
|
154
|
-
|
|
139
|
+
const sorted = sortedWorkers();
|
|
155
140
|
for (let si = 0; si < sorted.length; si++) {
|
|
156
141
|
const w = sorted[si];
|
|
157
|
-
const wi = _workers.indexOf(w);
|
|
158
|
-
const col =
|
|
159
|
-
|
|
160
|
-
const earned = w.stats.coins > 0
|
|
161
|
-
? `${c.green}+${w.stats.coins.toLocaleString()}${c.reset}`
|
|
162
|
-
: DIM + '—' + c.reset;
|
|
142
|
+
const wi = _workers.indexOf(w);
|
|
143
|
+
const col = wc(wi);
|
|
144
|
+
const earned = w.stats.coins > 0 ? `${c.green}+${w.stats.coins.toLocaleString()}${c.reset}` : DIM + '—' + c.reset;
|
|
163
145
|
const cmds = w.stats.commands || 0;
|
|
164
|
-
const rate = w.stats.commands > 0
|
|
165
|
-
? Math.round((w.stats.successes / w.stats.commands) * 100)
|
|
166
|
-
: 0;
|
|
146
|
+
const rate = w.stats.commands > 0 ? Math.round((w.stats.successes / w.stats.commands) * 100) : 0;
|
|
167
147
|
|
|
168
148
|
process.stdout.write(
|
|
169
149
|
` ${icon(w)} ` +
|
|
170
|
-
`${
|
|
171
|
-
`${col}${
|
|
172
|
-
`${DIM}${
|
|
173
|
-
`${
|
|
174
|
-
`${
|
|
175
|
-
`${earned.padStart(
|
|
150
|
+
`${padL(wi + 1, 2)} ` +
|
|
151
|
+
`${col}${padR(w.username || '?', 18)}${c.reset} ` +
|
|
152
|
+
`${DIM}${padR(w.lastStatus || 'idle', 20)}${c.reset} ` +
|
|
153
|
+
`${padL(cmds, 5)} ` +
|
|
154
|
+
`${padL(rate, 3)}% ` +
|
|
155
|
+
`${earned.padStart(8)}\n`
|
|
176
156
|
);
|
|
177
157
|
}
|
|
178
158
|
|
|
179
159
|
console.log(` ${hr}`);
|
|
180
160
|
|
|
181
|
-
// Totals
|
|
182
161
|
let totalCoins = 0, totalCmds = 0, totalOk = 0;
|
|
183
162
|
for (const w of _workers) {
|
|
184
163
|
totalCoins += w.stats.coins || 0;
|
|
@@ -187,61 +166,36 @@ function drawRows({ visible, W, cols }) {
|
|
|
187
166
|
}
|
|
188
167
|
const rate = totalCmds > 0 ? Math.round((totalOk / totalCmds) * 100) : 0;
|
|
189
168
|
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
190
|
-
const extra = Math.max(0, W - 74);
|
|
191
|
-
const namePad = 16 + Math.floor(extra * 0.5);
|
|
192
169
|
|
|
193
170
|
console.log(
|
|
194
171
|
` ${c.bold}Σ${c.reset} ` +
|
|
195
|
-
`${DIM}${
|
|
196
|
-
`${
|
|
197
|
-
`${
|
|
198
|
-
`${String(totalCmds).padStart(cols.cmds)} ` +
|
|
199
|
-
`${String(rate).padStart(cols.ok)}% ` +
|
|
172
|
+
`${DIM}${padL(_workers.length, 2)} acc${c.reset} ` +
|
|
173
|
+
`${' '.repeat(18)}` +
|
|
174
|
+
`${DIM}${padL(totalCmds, 5)} cmds ${rate}% ` +
|
|
200
175
|
`${totalCoins > 0 ? c.green + '+' + totalCoins.toLocaleString() + c.reset : DIM + '—' + c.reset}` +
|
|
201
|
-
`${' '.repeat(Math.max(0,
|
|
202
|
-
` ${DIM}${fmtUptime()} | ${memMB}MB${c.reset}`
|
|
176
|
+
`${' '.repeat(Math.max(0, 8 - String(totalCoins).length - 1))}${DIM}${fmtUptime()} | ${memMB}MB${c.reset}`
|
|
203
177
|
);
|
|
178
|
+
console.log(` ${hr}`);
|
|
204
179
|
console.log('');
|
|
205
180
|
}
|
|
206
181
|
|
|
207
|
-
// ──
|
|
208
|
-
|
|
209
|
-
const MAX_EVENTS = 6;
|
|
210
|
-
|
|
211
|
-
function logEvent(msg) {
|
|
182
|
+
// ── Append event log line ────────────────────────────────────
|
|
183
|
+
function log(msg) {
|
|
212
184
|
const now = new Date();
|
|
213
|
-
const ts = `${
|
|
214
|
-
process.stdout.write(`
|
|
215
|
-
_eventCount++;
|
|
216
|
-
if (_eventCount > MAX_EVENTS) _eventCount = MAX_EVENTS;
|
|
185
|
+
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
186
|
+
process.stdout.write(` ${DIM}[${ts}]${c.reset} ${msg}\n`);
|
|
217
187
|
}
|
|
218
188
|
|
|
219
189
|
// ── Public API ────────────────────────────────────────────────
|
|
220
|
-
|
|
221
190
|
function init({ workers, isShuttingDown }) {
|
|
222
191
|
_startTime = Date.now();
|
|
223
192
|
_workers = workers;
|
|
224
193
|
_isShuttingDown = isShuttingDown || (() => false);
|
|
225
|
-
|
|
194
|
+
_version = '0.0.0';
|
|
226
195
|
}
|
|
227
196
|
|
|
228
|
-
function drawBanner(version) {}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
function start() {
|
|
233
|
-
// No periodic refresh — dashboard re-draws on each event
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function draw() {
|
|
237
|
-
if (_isShuttingDown()) return;
|
|
238
|
-
const info = drawHeader(require('../package.json').version);
|
|
239
|
-
drawRows(info);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function stop() {
|
|
243
|
-
if (_interval) { clearInterval(_interval); _interval = null; }
|
|
244
|
-
process.stdout.write(c.reset + '\n');
|
|
245
|
-
}
|
|
197
|
+
function drawBanner(version) { _version = version; }
|
|
198
|
+
function start() {}
|
|
199
|
+
function stop() { process.stdout.write(c.reset + '\n'); }
|
|
246
200
|
|
|
247
|
-
module.exports = { init, drawBanner, start, draw,
|
|
201
|
+
module.exports = { init, drawBanner, start, draw, log, workerColor: wc, stop };
|