dankgrinder 8.48.0 → 8.51.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 +4 -2
- package/lib/ui.js +121 -104
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -2932,7 +2932,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2932
2932
|
ui.log(-1, `Starting ${activeWorkers.length} grind loops...`);
|
|
2933
2933
|
ui.setLive(true);
|
|
2934
2934
|
ui.draw();
|
|
2935
|
-
ui.startRefresh(
|
|
2935
|
+
ui.startRefresh();
|
|
2936
2936
|
for (const w of activeWorkers) {
|
|
2937
2937
|
if (!shutdownCalled) w.grindLoop();
|
|
2938
2938
|
}
|
|
@@ -3050,10 +3050,12 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3050
3050
|
const webhookMsg = `+⏣ ${finalCoins.toLocaleString()} | ${finalCmds} cmds | ${formatUptime()}` +
|
|
3051
3051
|
(totalRecoveries > 0 ? ` | ${totalRecoveries} auto-recoveries` : '') +
|
|
3052
3052
|
(CLUSTER_ENABLED ? ` | node: ${NODE_ID.substring(0, 12)}` : '');
|
|
3053
|
-
sendWebhook('Session Ended', webhookMsg, 0x8b5cf6);
|
|
3053
|
+
sendWebhook('Session Ended', webhookMsg, 0x8b5cf6).catch(() => {});
|
|
3054
3054
|
|
|
3055
3055
|
if (redis) { redis.disconnect().catch(() => {}); }
|
|
3056
3056
|
console.log(`${c.green}Goodbye!${c.reset}\n`);
|
|
3057
|
+
// Force exit after 5s so Ctrl+C always terminates even if cleanup hangs
|
|
3058
|
+
setTimeout(() => process.exit(0), 5000);
|
|
3057
3059
|
process.exit(0);
|
|
3058
3060
|
}
|
|
3059
3061
|
|
package/lib/ui.js
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI Live Dashboard —
|
|
2
|
+
* CLI Live Dashboard — cursor-positioned in-place row updates.
|
|
3
|
+
* Box drawn once at startup. Account rows updated in-place. Events below box.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
let _startTime = Date.now();
|
|
6
7
|
let _workers = [];
|
|
7
|
-
let _isShuttingDown = () => false;
|
|
8
8
|
let _version = '0.0.0';
|
|
9
9
|
let _live = false;
|
|
10
|
-
let _phase = 'init';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
let
|
|
14
|
-
let
|
|
10
|
+
let _phase = 'init';
|
|
11
|
+
|
|
12
|
+
// Terminal dimensions at last draw
|
|
13
|
+
let _W = 100;
|
|
14
|
+
let _inner = 98;
|
|
15
|
+
let _maxAccounts = 4;
|
|
16
|
+
|
|
17
|
+
// Row map: which terminal row each account row starts at (1-indexed)
|
|
18
|
+
let _accountRows = []; // _accountRows[accountIdx] = terminalRow
|
|
15
19
|
|
|
16
20
|
// ── Spinner frames ────────────────────────────────────────────
|
|
17
21
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
18
22
|
function spinnerFrame() { return SPINNER[Math.floor(Date.now() / 150) % SPINNER.length]; }
|
|
19
23
|
|
|
20
|
-
// ──
|
|
24
|
+
// ── ASCII banner ──────────────────────────────────────────────
|
|
21
25
|
const BANNER_LINES = [
|
|
22
26
|
' ██████╗ ██╗ ██╗███╗ ██╗ ██████╗ ███████╗ ██████╗ ███╗ ██╗ DANKGRINDER',
|
|
23
|
-
' ██╔══██╗██║ ██║████╗ ██║██╔════╝ ██╔════╝██╔═══██╗████╗ ██║
|
|
27
|
+
' ██╔══██╗██║ ██║████╗ ██║██╔════╝ ██╔════╝██╔═══██╗████╗ ██║ vPLACEHOLDER',
|
|
24
28
|
' ██║ ██║██║ ██║██╔██╗ ██║██║ ███╗█████╗ ██║ ██║██╔██╗ ██║',
|
|
25
29
|
' ██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██║ ██║██║╚██╗██║',
|
|
26
30
|
' ██████╔╝╚██████╔╝██║ ╚████║╚██████╔╝███████╗╚██████╔╝██║ ╚████║',
|
|
@@ -43,7 +47,6 @@ const c = {
|
|
|
43
47
|
red: '\x1b[38;2;255;80;100m',
|
|
44
48
|
yellow: '\x1b[38;2;255;220;80m',
|
|
45
49
|
cyan: '\x1b[38;2;80;220;255m',
|
|
46
|
-
blue: '\x1b[38;2;100;160;255m',
|
|
47
50
|
white: '\x1b[37m',
|
|
48
51
|
};
|
|
49
52
|
const DIM = c.dim;
|
|
@@ -52,6 +55,7 @@ function trunc(s, n) { s = String(s || ''); return s.length <= n ? s : s.slice(0
|
|
|
52
55
|
function padR(s, n) { return trunc(s, n).padEnd(n); }
|
|
53
56
|
function padL(s, n, char) { return String(s).padStart(n, char || ' '); }
|
|
54
57
|
function stripAnsi(s) { return String(s).replace(/\x1b\[[0-9;]*m/g, ''); }
|
|
58
|
+
function clearLine() { process.stdout.write('\x1b[2K'); } // clear current line
|
|
55
59
|
|
|
56
60
|
function fmtUptime() {
|
|
57
61
|
const s = Math.floor((Date.now() - _startTime) / 1000);
|
|
@@ -69,7 +73,7 @@ function statusColor(w) {
|
|
|
69
73
|
if (!w.channel) return c.red;
|
|
70
74
|
if (w.paused || w.dashboardPaused) return c.yellow;
|
|
71
75
|
if (w.busy || w._invRunning || w._sellRunning) return c.yellow;
|
|
72
|
-
if (Date.now() < w.globalCooldownUntil) return c.
|
|
76
|
+
if (Date.now() < w.globalCooldownUntil) return c.cyan;
|
|
73
77
|
return c.green;
|
|
74
78
|
}
|
|
75
79
|
|
|
@@ -82,7 +86,7 @@ function statusText(w) {
|
|
|
82
86
|
if (w.busy || w._invRunning || w._sellRunning) return spinnerFrame() + ' WORK';
|
|
83
87
|
if (Date.now() < w.globalCooldownUntil) {
|
|
84
88
|
const wait = Math.ceil((w.globalCooldownUntil - Date.now()) / 1000);
|
|
85
|
-
return '⏳' + wait + 's';
|
|
89
|
+
return '⏳' + padL(wait, 3) + 's';
|
|
86
90
|
}
|
|
87
91
|
return '● READY';
|
|
88
92
|
}
|
|
@@ -110,48 +114,56 @@ function fmtLevel(w) {
|
|
|
110
114
|
return c.cyan + lv + c.reset;
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
// ──
|
|
114
|
-
function
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
117
|
+
// ── Build a full account row string ──────────────────────────
|
|
118
|
+
function buildAccountRow(w, wi, col) {
|
|
119
|
+
const col2 = wc(wi);
|
|
120
|
+
const stCol = statusColor(w);
|
|
121
|
+
const stTxt = statusText(w);
|
|
122
|
+
const earned = fmtCoins(w.stats.coins);
|
|
123
|
+
const bal = w.stats.balance !== undefined ? fmtCoins(w.stats.balance) : DIM + '?' + c.reset;
|
|
124
|
+
const ls = fmtLifesavers(w);
|
|
125
|
+
const lv = fmtLevel(w);
|
|
126
|
+
const name = padR(trunc(w.username || '?', col.name), col.name);
|
|
127
|
+
const doing = padR(w.lastStatus || 'idle', col.cmd);
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
'\x1b[2K' + // clear the line first
|
|
131
|
+
`\x1b[38;2;77;212;238m│\x1b[0m ` +
|
|
132
|
+
`${DIM}${padL(wi + 1, 2)}${c.reset} ` +
|
|
133
|
+
`${stCol}${padR(stTxt, col.st)}${c.reset} ` +
|
|
134
|
+
`${col2}${name}${c.reset} ` +
|
|
135
|
+
`${DIM}${doing}${c.reset} ` +
|
|
136
|
+
`${padL(bal, col.bal)} ` +
|
|
137
|
+
`${padL(ls, col.ls)} ` +
|
|
138
|
+
`${padL(lv, col.lv)} ` +
|
|
139
|
+
`${padL(earned, col.earned)} ` +
|
|
140
|
+
`\x1b[38;2;77;212;238m│\x1b[0m\n`
|
|
141
|
+
);
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
// ── Draw the
|
|
144
|
+
// ── Draw the FULL box (called once at startup) ───────────────
|
|
122
145
|
function draw() {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
130
|
-
} else {
|
|
131
|
-
// Move cursor up to box start: box rows + events since last draw
|
|
132
|
-
const eventsSinceDraw = _eventCount - _eventCountAtDraw;
|
|
133
|
-
const up = _boxHeight + eventsSinceDraw;
|
|
134
|
-
process.stdout.write(`\x1b[${up}A`);
|
|
135
|
-
}
|
|
146
|
+
const rows = process.stdout.rows || 40;
|
|
147
|
+
_W = Math.min(process.stdout.columns || 100, 120);
|
|
148
|
+
_inner = _W - 2;
|
|
149
|
+
|
|
150
|
+
// Clear entire screen
|
|
151
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
136
152
|
|
|
137
153
|
// ── Top border ──
|
|
138
|
-
process.stdout.write(`\x1b[38;2;77;212;238m┌${'─'.repeat(
|
|
154
|
+
process.stdout.write(`\x1b[38;2;77;212;238m┌${'─'.repeat(_inner)}┐\x1b[0m\n`);
|
|
139
155
|
|
|
140
|
-
// ── ASCII banner
|
|
156
|
+
// ── ASCII banner ──
|
|
141
157
|
const bannerWithVer = BANNER_LINES[1].replace('PLACEHOLDER', _version);
|
|
142
158
|
const bannerLines = [BANNER_LINES[0], bannerWithVer, ...BANNER_LINES.slice(2)];
|
|
143
|
-
|
|
144
159
|
for (let i = 0; i < bannerLines.length; i++) {
|
|
145
160
|
const line = bannerLines[i];
|
|
146
|
-
|
|
161
|
+
const inner = _inner - stripAnsi(line).length;
|
|
147
162
|
if (i < 2) {
|
|
148
163
|
const gradLine = gradientLine(line, 77, 212, 238, 255, 92, 147);
|
|
149
|
-
|
|
150
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m${gradLine}${pad}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
164
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m${gradLine}${' '.repeat(_inner - stripAnsi(gradLine).length)}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
151
165
|
} else {
|
|
152
|
-
|
|
153
|
-
const pad = ' '.repeat(Math.max(0, inner - stripAnsi(dimLine).length));
|
|
154
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m${dimLine}${pad}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
166
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m${DIM}${line}${c.reset}${' '.repeat(_inner - stripAnsi(line).length)}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
155
167
|
}
|
|
156
168
|
}
|
|
157
169
|
|
|
@@ -159,7 +171,7 @@ function draw() {
|
|
|
159
171
|
const running = _workers.filter(w => w.channel && !w.paused && !w.dashboardPaused).length;
|
|
160
172
|
const paused = _workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
161
173
|
const errors = _workers.filter(w => !w.channel).length;
|
|
162
|
-
const phaseLabel = _phase === 'grinding' ? '' : ` ${
|
|
174
|
+
const phaseLabel = _phase === 'grinding' ? '' : ` ${DIM}[${_phase}]${c.reset}`;
|
|
163
175
|
const statusParts = [
|
|
164
176
|
`${c.green}●${c.reset} ${running} online`,
|
|
165
177
|
paused > 0 ? `${c.yellow}~${c.reset} ${paused} paused` : null,
|
|
@@ -167,16 +179,15 @@ function draw() {
|
|
|
167
179
|
`${DIM}Ctrl+C${c.reset}`,
|
|
168
180
|
].filter(Boolean);
|
|
169
181
|
const statusStr = statusParts.join(' ') + phaseLabel;
|
|
170
|
-
|
|
171
|
-
process.stdout.write(`\x1b[38;2;77;212;238m
|
|
172
|
-
process.stdout.write(`\x1b[38;2;77;212;238m├${'─'.repeat(inner)}┤\x1b[0m\n`);
|
|
182
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${statusStr}${' '.repeat(_inner - stripAnsi(statusStr))}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
183
|
+
process.stdout.write(`\x1b[38;2;77;212;238m├${'─'.repeat(_inner)}┤\x1b[0m\n`);
|
|
173
184
|
|
|
174
185
|
// ── Table header ──
|
|
175
186
|
const col = { st: 9, name: 18, cmd: 16, bal: 8, ls: 3, lv: 3, earned: 8 };
|
|
176
|
-
const nameExtra = Math.max(0,
|
|
187
|
+
const nameExtra = Math.max(0, _inner - col.st - col.name - col.cmd - col.bal - col.ls - col.lv - col.earned - 14);
|
|
177
188
|
col.name += nameExtra;
|
|
178
189
|
|
|
179
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
190
|
+
process.stdout.write(`\x1b[2K\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
180
191
|
process.stdout.write(`${c.bold}#${c.reset} `);
|
|
181
192
|
process.stdout.write(`${c.bold}${padR('STATUS', col.st)}${c.reset} `);
|
|
182
193
|
process.stdout.write(`${c.bold}${padR('ACCOUNT', col.name)}${c.reset} `);
|
|
@@ -186,7 +197,16 @@ function draw() {
|
|
|
186
197
|
process.stdout.write(`${c.bold}${padL('LV', col.lv)}${c.reset} `);
|
|
187
198
|
process.stdout.write(`${c.bold}${padL('EARNED', col.earned)}${c.reset} `);
|
|
188
199
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
189
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(
|
|
200
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(_inner)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
201
|
+
|
|
202
|
+
// Track row positions
|
|
203
|
+
const BOX_TOP = 1;
|
|
204
|
+
const bannerH = bannerLines.length; // 6
|
|
205
|
+
const statusBarRow = BOX_TOP + bannerH + 1; // row 8
|
|
206
|
+
const headerRow = statusBarRow + 1; // row 9
|
|
207
|
+
const hrRow = headerRow + 1; // row 10
|
|
208
|
+
_maxAccounts = Math.min(_workers.length, Math.max(3, rows - 14));
|
|
209
|
+
_accountRows = [];
|
|
190
210
|
|
|
191
211
|
// ── Account rows ──
|
|
192
212
|
const sorted = [..._workers].sort((a, b) => {
|
|
@@ -196,49 +216,27 @@ function draw() {
|
|
|
196
216
|
if (aA !== bA) return bA ? 1 : -1;
|
|
197
217
|
return (b.stats.commands || 0) - (a.stats.commands || 0);
|
|
198
218
|
});
|
|
199
|
-
const shown = sorted.slice(0,
|
|
219
|
+
const shown = sorted.slice(0, _maxAccounts);
|
|
200
220
|
|
|
201
221
|
for (let si = 0; si < shown.length; si++) {
|
|
202
222
|
const w = shown[si];
|
|
203
223
|
const wi = _workers.indexOf(w);
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const earned = fmtCoins(w.stats.coins);
|
|
208
|
-
const bal = w.stats.balance !== undefined ? fmtCoins(w.stats.balance) : DIM + '?' + c.reset;
|
|
209
|
-
const ls = fmtLifesavers(w);
|
|
210
|
-
const lv = fmtLevel(w);
|
|
211
|
-
|
|
212
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
213
|
-
process.stdout.write(`${DIM}${padL(wi + 1, 2)}${c.reset} `);
|
|
214
|
-
process.stdout.write(`${stCol}${padR(stTxt, col.st)}${c.reset} `);
|
|
215
|
-
process.stdout.write(`${col2}${padR(trunc(w.username || '?', col.name), col.name)}${c.reset} `);
|
|
216
|
-
process.stdout.write(`${DIM}${padR(w.lastStatus || 'idle', col.cmd)}${c.reset} `);
|
|
217
|
-
process.stdout.write(`${padL(bal, col.bal)} `);
|
|
218
|
-
process.stdout.write(`${padL(ls, col.ls)} `);
|
|
219
|
-
process.stdout.write(`${padL(lv, col.lv)} `);
|
|
220
|
-
process.stdout.write(`${padL(earned, col.earned)} `);
|
|
221
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (sorted.length > maxAccounts) {
|
|
225
|
-
const extra = sorted.length - maxAccounts;
|
|
226
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${DIM}+ ${extra} more accounts${' '.repeat(Math.max(0, inner - 20 - String(extra).length))}${c.reset} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
224
|
+
const rowNum = hrRow + 1 + si;
|
|
225
|
+
_accountRows[wi] = rowNum;
|
|
226
|
+
process.stdout.write(buildAccountRow(w, wi, col));
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// ── Totals ──
|
|
230
|
-
let totalCoins = 0, totalBal = 0
|
|
230
|
+
let totalCoins = 0, totalBal = 0;
|
|
231
231
|
for (const w of _workers) {
|
|
232
232
|
totalCoins += w.stats.coins || 0;
|
|
233
233
|
totalBal += w.stats.balance || 0;
|
|
234
|
-
totalCmds += w.stats.commands || 0;
|
|
235
|
-
totalOk += w.stats.successes || 0;
|
|
236
234
|
}
|
|
237
|
-
const rate = totalCmds > 0 ? Math.round((totalOk / totalCmds) * 100) : 0;
|
|
238
235
|
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
239
236
|
|
|
240
|
-
|
|
241
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
237
|
+
const totalsRow = hrRow + 1 + shown.length;
|
|
238
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(_inner)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
239
|
+
process.stdout.write(`\x1b[2K\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
242
240
|
process.stdout.write(`${c.bold}Σ${c.reset} `);
|
|
243
241
|
process.stdout.write(`${DIM}${padL(_workers.length, 2)} acc${c.reset} `);
|
|
244
242
|
process.stdout.write(`${' '.repeat(col.name)} `);
|
|
@@ -247,15 +245,28 @@ function draw() {
|
|
|
247
245
|
process.stdout.write(`${' '.repeat(col.ls)} `);
|
|
248
246
|
process.stdout.write(`${' '.repeat(col.lv)} `);
|
|
249
247
|
process.stdout.write(`${fmtCoins(totalCoins)} `);
|
|
250
|
-
process.stdout.write(`${DIM}${fmtUptime()} | ${memMB}MB${c.reset} `.padEnd(
|
|
248
|
+
process.stdout.write(`${DIM}${fmtUptime()} | ${memMB}MB${c.reset} `.padEnd(_inner));
|
|
251
249
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
252
250
|
|
|
253
251
|
// ── Bottom ──
|
|
254
|
-
process.stdout.write(`\x1b[38;2;77;212;238m└${'─'.repeat(
|
|
252
|
+
process.stdout.write(`\x1b[38;2;77;212;238m└${'─'.repeat(_inner)}┘\x1b[0m\n`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── Update ONE account row in place ─────────────────────────
|
|
256
|
+
function updateAccountRow(accountIdx) {
|
|
257
|
+
const row = _accountRows[accountIdx];
|
|
258
|
+
if (!row) return; // not visible
|
|
255
259
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
260
|
+
const w = _workers[accountIdx];
|
|
261
|
+
if (!w) return;
|
|
262
|
+
|
|
263
|
+
const col = { st: 9, name: 18, cmd: 16, bal: 8, ls: 3, lv: 3, earned: 8 };
|
|
264
|
+
const nameExtra = Math.max(0, _inner - col.st - col.name - col.cmd - col.bal - col.ls - col.lv - col.earned - 14);
|
|
265
|
+
col.name += nameExtra;
|
|
266
|
+
|
|
267
|
+
// Move cursor to this row, column 1
|
|
268
|
+
process.stdout.write(`\x1b[${row};1H`);
|
|
269
|
+
process.stdout.write(buildAccountRow(w, accountIdx, col));
|
|
259
270
|
}
|
|
260
271
|
|
|
261
272
|
// ── Gradient line ─────────────────────────────────────────────
|
|
@@ -270,34 +281,33 @@ function gradientLine(text, r1, g1, b1, r2, g2, b2) {
|
|
|
270
281
|
|
|
271
282
|
// ── Event tracking ────────────────────────────────────────────
|
|
272
283
|
let _eventLines = []; // [accountIdx] = [{text, ts}]
|
|
273
|
-
const MAX_EVENTS =
|
|
284
|
+
const MAX_EVENTS = 15;
|
|
274
285
|
|
|
275
286
|
// ── Public API ────────────────────────────────────────────────
|
|
276
287
|
|
|
277
288
|
function init({ workers, isShuttingDown }) {
|
|
278
289
|
_startTime = Date.now();
|
|
279
290
|
_workers = workers;
|
|
280
|
-
_isShuttingDown = isShuttingDown || (() => false);
|
|
281
291
|
_version = '0.0.0';
|
|
282
292
|
_eventLines = [];
|
|
283
293
|
_live = false;
|
|
284
294
|
_phase = 'init';
|
|
285
|
-
|
|
286
|
-
_boxHeight = 0;
|
|
287
|
-
_eventCount = 0;
|
|
288
|
-
_eventCountAtDraw = 0;
|
|
295
|
+
_accountRows = [];
|
|
289
296
|
}
|
|
290
297
|
|
|
291
298
|
function drawBanner(version) { _version = version || '0.0.0'; }
|
|
292
|
-
|
|
293
299
|
function start() {}
|
|
294
|
-
function stop() { _live = false; _phase = 'init'; process.stdout.write(c.reset + '\n'); }
|
|
295
300
|
|
|
296
|
-
function
|
|
301
|
+
function stop() {
|
|
302
|
+
_live = false;
|
|
303
|
+
_phase = 'init';
|
|
304
|
+
process.stdout.write('\x1b[2J\x1b[H' + c.reset + '\n');
|
|
305
|
+
}
|
|
297
306
|
|
|
307
|
+
function setLive(val) { _live = val; }
|
|
298
308
|
function setPhase(phase) { _phase = phase; }
|
|
299
309
|
|
|
300
|
-
// log:
|
|
310
|
+
// log: update account row in-place + append event below box
|
|
301
311
|
function log(accountIdx, msg) {
|
|
302
312
|
const now = new Date();
|
|
303
313
|
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
@@ -306,12 +316,13 @@ function log(accountIdx, msg) {
|
|
|
306
316
|
if (!_eventLines[accountIdx]) _eventLines[accountIdx] = [];
|
|
307
317
|
_eventLines[accountIdx].push({ text: msg, ts });
|
|
308
318
|
if (_eventLines[accountIdx].length > MAX_EVENTS) _eventLines[accountIdx].shift();
|
|
319
|
+
// Update the row in place
|
|
320
|
+
if (_live) updateAccountRow(accountIdx);
|
|
309
321
|
}
|
|
310
|
-
_eventCount++;
|
|
311
322
|
|
|
312
323
|
if (!_live) return;
|
|
313
324
|
|
|
314
|
-
//
|
|
325
|
+
// Append event below the box (after the bottom border)
|
|
315
326
|
const col2 = accountIdx >= 0 ? wc(accountIdx) : c.cyan;
|
|
316
327
|
const name = accountIdx >= 0 ? trunc(_workers[accountIdx]?.username || '?', 14) : 'GLOBAL';
|
|
317
328
|
process.stdout.write(`${col2}${name}${c.reset} ${DIM}[${ts}]${c.reset} ${msg}\n`);
|
|
@@ -319,16 +330,22 @@ function log(accountIdx, msg) {
|
|
|
319
330
|
|
|
320
331
|
function logGlobal(msg) { log(-1, msg); }
|
|
321
332
|
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
333
|
+
// Force full redraw (call this when window resizes or box needs rebuild)
|
|
334
|
+
function fullRedraw() { if (_live) draw(); }
|
|
335
|
+
|
|
336
|
+
// ── Periodic refresh: animate spinners & countdowns every 3s ──
|
|
337
|
+
let _refreshTimer = null;
|
|
338
|
+
function startRefresh() {
|
|
339
|
+
if (_refreshTimer) return;
|
|
340
|
+
_refreshTimer = setInterval(() => {
|
|
341
|
+
if (!_live) return;
|
|
342
|
+
for (let i = 0; i < _workers.length; i++) {
|
|
343
|
+
updateAccountRow(i);
|
|
344
|
+
}
|
|
345
|
+
}, 1500);
|
|
329
346
|
}
|
|
330
347
|
function stopRefresh() {
|
|
331
|
-
if (
|
|
348
|
+
if (_refreshTimer) { clearInterval(_refreshTimer); _refreshTimer = null; }
|
|
332
349
|
}
|
|
333
350
|
|
|
334
|
-
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop, setLive, setPhase, startRefresh, stopRefresh };
|
|
351
|
+
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop, setLive, setPhase, updateAccountRow, fullRedraw, startRefresh, stopRefresh };
|