dankgrinder 8.41.0 → 8.43.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 +6 -8
- package/lib/ui.js +76 -127
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -721,14 +721,11 @@ class AccountWorker {
|
|
|
721
721
|
log(type, msg) {
|
|
722
722
|
const stripped = msg.replace(/\x1b\[[0-9;]*m/g, '');
|
|
723
723
|
if (type !== 'debug') this.lastStatus = stripped.substring(0, 28);
|
|
724
|
-
// Route through UI
|
|
725
|
-
const icons = { info: '.', success: '[OK]', error: '[X]', warn: '[!]', cmd: '>', coin: '$', buy: '#', bal: '*', debug: '.' };
|
|
726
|
-
const icon = icons[type] || icons.info;
|
|
727
|
-
const text = `${icon} ${this.username} ${msg}`;
|
|
724
|
+
// Route through UI — msg already has context, no need to prepend username
|
|
728
725
|
if (typeof ui !== 'undefined') {
|
|
729
|
-
try { ui.log(this.idx,
|
|
726
|
+
try { ui.log(this.idx, msg); } catch { process.stdout.write(msg + '\n'); }
|
|
730
727
|
} else {
|
|
731
|
-
process.stdout.write(
|
|
728
|
+
process.stdout.write(msg + '\n');
|
|
732
729
|
}
|
|
733
730
|
}
|
|
734
731
|
|
|
@@ -2512,6 +2509,7 @@ class AccountWorker {
|
|
|
2512
2509
|
clearTimeout(timeoutId);
|
|
2513
2510
|
this.username = this.client.user.tag || this.username;
|
|
2514
2511
|
this.avatarUrl = this.client.user.displayAvatarURL?.({ format: 'png', dynamic: true, size: 128 }) || null;
|
|
2512
|
+
this.loggedIn = true; // so statusColor/statusText show correct state
|
|
2515
2513
|
|
|
2516
2514
|
// Attach raw gateway logger for CV2 component capture
|
|
2517
2515
|
rawLogger.attachRawLogger(this.client, { channelId: this.account.channel_id });
|
|
@@ -2872,7 +2870,6 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2872
2870
|
if (timedOutWorkers.length > 0) {
|
|
2873
2871
|
ui.log(-1, `${timedOutWorkers.length} timed out (retrying in background)`);
|
|
2874
2872
|
}
|
|
2875
|
-
ui.draw();
|
|
2876
2873
|
|
|
2877
2874
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
2878
2875
|
|
|
@@ -2928,10 +2925,11 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2928
2925
|
|
|
2929
2926
|
// ── Phase 3: Start grind loops ───────────────────────────────────
|
|
2930
2927
|
ui.log(-1, `Starting ${activeWorkers.length} grind loops...`);
|
|
2928
|
+
ui.setLive(true);
|
|
2929
|
+
ui.draw();
|
|
2931
2930
|
for (const w of activeWorkers) {
|
|
2932
2931
|
if (!shutdownCalled) w.grindLoop();
|
|
2933
2932
|
}
|
|
2934
|
-
ui.draw();
|
|
2935
2933
|
|
|
2936
2934
|
// Cluster heartbeat — lets other nodes see this node is alive
|
|
2937
2935
|
if (CLUSTER_ENABLED) {
|
package/lib/ui.js
CHANGED
|
@@ -1,42 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI Live Dashboard — box
|
|
3
|
-
*
|
|
2
|
+
* CLI Live Dashboard — clean box at top, events stream below.
|
|
3
|
+
* Box drawn once. Events appended line-by-line. No flicker.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
let _startTime = Date.now();
|
|
7
7
|
let _workers = [];
|
|
8
8
|
let _isShuttingDown = () => false;
|
|
9
9
|
let _version = '0.0.0';
|
|
10
|
+
let _live = false;
|
|
10
11
|
|
|
11
12
|
// ── Spinner frames ────────────────────────────────────────────
|
|
12
13
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
13
14
|
function spinnerFrame() { return SPINNER[Math.floor(Date.now() / 150) % SPINNER.length]; }
|
|
14
15
|
|
|
15
|
-
// ── Big ASCII art banner ──────────────────────────────────────
|
|
16
|
-
const BANNER_LINES = [
|
|
17
|
-
' ██████╗ ██╗ ██╗███╗ ██╗ ██████╗ ███████╗ ██████╗ ███╗ ██╗',
|
|
18
|
-
' ██╔══██╗██║ ██║████╗ ██║██╔════╝ ██╔════╝██╔═══██╗████╗ ██║',
|
|
19
|
-
' ██║ ██║██║ ██║██╔██╗ ██║██║ ███╗█████╗ ██║ ██║██╔██╗ ██║',
|
|
20
|
-
' ██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██║ ██║██║╚██╗██║',
|
|
21
|
-
' ██████╔╝╚██████╔╝██║ ╚████║╚██████╔╝███████╗╚██████╔╝██║ ╚████║',
|
|
22
|
-
' ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝',
|
|
23
|
-
' ██╗██╗ ██╗███╗ ██╗ ██████╗ ██╗ ███████╗██████╗ ',
|
|
24
|
-
' ██║██║ ██║████╗ ██║██╔═══██╗██║ ██╔════╝██╔══██╗',
|
|
25
|
-
' ███████╗███████╗ ██║██║ ██║██╔██╗ ██║██║ ██║██║ █████╗ ██████╔╝',
|
|
26
|
-
' ╚════██║╚════██║ ██║██║ ██║██║╚██╗██║██║ ██║██║ ██╔══╝ ██╔══██╗',
|
|
27
|
-
' ██║ ██║ ██║╚██████╔╝██║ ╚████║╚██████╔╝███████╗███████╗██║ ██║',
|
|
28
|
-
' ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚══════╝╚═╝ ╚═╝',
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
function gradientLine(text, r1, g1, b1, r2, g2, b2) {
|
|
32
|
-
let out = '';
|
|
33
|
-
for (let i = 0; i < text.length; i++) {
|
|
34
|
-
const t = text.length <= 1 ? 0 : i / (text.length - 1);
|
|
35
|
-
out += `\x1b[38;2;${Math.round(r1 + (r2 - r1) * t)};${Math.round(g1 + (g2 - g1) * t)};${Math.round(b1 + (b2 - b1) * t)}m${text[i]}`;
|
|
36
|
-
}
|
|
37
|
-
return out + '\x1b[0m';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
16
|
// ── Palette ───────────────────────────────────────────────────
|
|
41
17
|
const PALETTE = [
|
|
42
18
|
'\x1b[38;2;77;212;238m', '\x1b[38;2;255;194;77m', '\x1b[38;2;130;210;100m',
|
|
@@ -60,11 +36,6 @@ const DIM = c.dim;
|
|
|
60
36
|
function trunc(s, n) { s = String(s || ''); return s.length <= n ? s : s.slice(0, n - 1) + '…'; }
|
|
61
37
|
function padR(s, n) { return trunc(s, n).padEnd(n); }
|
|
62
38
|
function padL(s, n, char) { return String(s).padStart(n, char || ' '); }
|
|
63
|
-
function padC(s, n) {
|
|
64
|
-
s = String(s || '');
|
|
65
|
-
const pad = Math.max(0, n - s.length);
|
|
66
|
-
return ' '.repeat(Math.floor(pad / 2)) + s + ' '.repeat(Math.ceil(pad / 2));
|
|
67
|
-
}
|
|
68
39
|
|
|
69
40
|
function fmtUptime() {
|
|
70
41
|
const s = Math.floor((Date.now() - _startTime) / 1000);
|
|
@@ -79,7 +50,7 @@ function fmtUptime() {
|
|
|
79
50
|
|
|
80
51
|
// ── Status helpers ─────────────────────────────────────────────
|
|
81
52
|
function statusColor(w) {
|
|
82
|
-
if (!w.
|
|
53
|
+
if (!w.loggedIn || !w.channel) return c.red;
|
|
83
54
|
if (w.paused || w.dashboardPaused) return c.yellow;
|
|
84
55
|
if (w.busy || w._invRunning || w._sellRunning) return c.yellow;
|
|
85
56
|
if (Date.now() < w.globalCooldownUntil) return c.blue;
|
|
@@ -87,7 +58,7 @@ function statusColor(w) {
|
|
|
87
58
|
}
|
|
88
59
|
|
|
89
60
|
function statusText(w) {
|
|
90
|
-
if (!w.
|
|
61
|
+
if (!w.loggedIn || !w.channel) return 'CONNECTING';
|
|
91
62
|
if (w.paused) return 'PAUSED';
|
|
92
63
|
if (w.dashboardPaused) return 'HELD';
|
|
93
64
|
if (w._alert?.type === 'death') return 'DEAD';
|
|
@@ -102,62 +73,48 @@ function statusText(w) {
|
|
|
102
73
|
|
|
103
74
|
// ── Layout ────────────────────────────────────────────────────
|
|
104
75
|
function layout() {
|
|
105
|
-
const W = Math.min(process.stdout.columns ||
|
|
76
|
+
const W = Math.min(process.stdout.columns || 100, 120);
|
|
106
77
|
const rows = process.stdout.rows || 40;
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
const totalsH = 2; // totals + hr
|
|
111
|
-
const footerH = 1; // blank
|
|
112
|
-
const maxAccounts = Math.min(_workers.length, Math.max(3, rows - bannerH - statusH - headerH - totalsH - footerH - 8));
|
|
113
|
-
const eventH = Math.max(3, rows - bannerH - statusH - headerH - totalsH - footerH - maxAccounts);
|
|
114
|
-
return { W, bannerH, statusH, headerH, totalsH, footerH, maxAccounts, eventH };
|
|
78
|
+
// Box: header(2) + title(1) + gap(1) + statusBar(1) + divider(1) + headerRow(1) + hr(1) + maxAccounts + totals(2) + divider(1)
|
|
79
|
+
const maxAccounts = Math.min(_workers.length, Math.max(3, rows - 16));
|
|
80
|
+
return { W, maxAccounts };
|
|
115
81
|
}
|
|
116
82
|
|
|
117
|
-
// ── Draw the
|
|
83
|
+
// ── Draw the fixed box ────────────────────────────────────────
|
|
118
84
|
function draw() {
|
|
119
|
-
const { W,
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
// ──
|
|
123
|
-
//
|
|
124
|
-
process.stdout.write(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
133
|
-
|
|
134
|
-
// ── Status bar
|
|
135
|
-
const running = _workers.filter(w => w.
|
|
85
|
+
const { W, maxAccounts } = layout();
|
|
86
|
+
const inner = W - 2;
|
|
87
|
+
|
|
88
|
+
// ── Save cursor + clear screen ──
|
|
89
|
+
process.stdout.write('\x1b[s'); // save cursor
|
|
90
|
+
process.stdout.write('\x1b[2J\x1b[H'); // clear screen + home
|
|
91
|
+
|
|
92
|
+
// ── Title ──
|
|
93
|
+
process.stdout.write(`\x1b[38;2;77;212;238m┌${'─'.repeat(inner)}┐\x1b[0m\n`);
|
|
94
|
+
const title = ` ${c.bold}${c.cyan}DANK${c.reset}${c.bold}${c.white}GRINDER${c.reset} ${c.dim}v${_version}${c.reset} `;
|
|
95
|
+
const dots = ' '.repeat(Math.max(0, inner - stripAnsi(title) - 10));
|
|
96
|
+
const upStr = `${DIM}${fmtUptime()}${c.reset}`;
|
|
97
|
+
const upPad = ' '.repeat(Math.max(0, inner - stripAnsi(title) - stripAnsi(dots) - stripAnsi(upStr) - 2));
|
|
98
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m${title}${dots}${upStr}${upPad}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
99
|
+
|
|
100
|
+
// ── Status bar ──
|
|
101
|
+
const running = _workers.filter(w => w.loggedIn && w.channel && !w.paused && !w.dashboardPaused).length;
|
|
136
102
|
const paused = _workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
137
|
-
const errors = _workers.filter(w => !w.
|
|
138
|
-
const
|
|
139
|
-
`${c.
|
|
140
|
-
`${c.
|
|
141
|
-
`${
|
|
142
|
-
`${c.green}·${c.reset}${running}`,
|
|
143
|
-
`${c.yellow}~${c.reset}${paused}`,
|
|
144
|
-
errors > 0 ? `${c.red}E${c.reset}${errors}` : null,
|
|
103
|
+
const errors = _workers.filter(w => !w.loggedIn || !w.channel).length;
|
|
104
|
+
const statusParts = [
|
|
105
|
+
`${c.green}●${c.reset} ${running} online`,
|
|
106
|
+
paused > 0 ? `${c.yellow}~${c.reset} ${paused} paused` : null,
|
|
107
|
+
errors > 0 ? `${c.red}E${c.reset} ${errors} error` : null,
|
|
145
108
|
`${DIM}Ctrl+C${c.reset}`,
|
|
146
|
-
].filter(Boolean)
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
process.stdout.write(`\x1b[38;2;77;212;238m
|
|
109
|
+
].filter(Boolean);
|
|
110
|
+
const statusStr = statusParts.join(' ');
|
|
111
|
+
const statusPad = ' '.repeat(Math.max(0, inner - stripAnsi(statusStr)));
|
|
112
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${statusStr}${statusPad}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
113
|
+
process.stdout.write(`\x1b[38;2;77;212;238m├${'─'.repeat(inner)}┤\x1b[0m\n`);
|
|
150
114
|
|
|
151
115
|
// ── Table header ──
|
|
152
|
-
const col = {
|
|
153
|
-
|
|
154
|
-
name: 18, // Account name
|
|
155
|
-
last: 20, // Last command
|
|
156
|
-
cmds: 6, // Commands
|
|
157
|
-
ok: 4, // OK%
|
|
158
|
-
earned: 10, // Earned
|
|
159
|
-
};
|
|
160
|
-
const nameExtra = Math.max(0, W - 4 - col.st - col.name - col.last - col.cmds - col.ok - col.earned - 14);
|
|
116
|
+
const col = { st: 9, name: 18, last: 24, cmds: 6, ok: 5, earned: 10 };
|
|
117
|
+
const nameExtra = Math.max(0, inner - col.st - col.name - col.last - col.cmds - col.ok - col.earned - 8);
|
|
161
118
|
col.name += nameExtra;
|
|
162
119
|
|
|
163
120
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
@@ -169,13 +126,13 @@ function draw() {
|
|
|
169
126
|
process.stdout.write(`${c.bold}${padL('OK%', col.ok)}${c.reset} `);
|
|
170
127
|
process.stdout.write(`${c.bold}${padL('EARNED', col.earned)}${c.reset} `);
|
|
171
128
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
172
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(
|
|
129
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(inner)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
173
130
|
|
|
174
131
|
// ── Account rows ──
|
|
175
132
|
const sorted = [..._workers].sort((a, b) => {
|
|
176
|
-
if (!a.
|
|
177
|
-
const aA = a.
|
|
178
|
-
const bA = b.
|
|
133
|
+
if (!a.loggedIn !== !b.loggedIn) return !a.loggedIn ? 1 : -1;
|
|
134
|
+
const aA = a.loggedIn && a.channel && !a.paused && !a.dashboardPaused;
|
|
135
|
+
const bA = b.loggedIn && b.channel && !b.paused && !b.dashboardPaused;
|
|
179
136
|
if (aA !== bA) return bA ? 1 : -1;
|
|
180
137
|
return (b.stats.commands || 0) - (a.stats.commands || 0);
|
|
181
138
|
});
|
|
@@ -186,14 +143,14 @@ function draw() {
|
|
|
186
143
|
const wi = _workers.indexOf(w);
|
|
187
144
|
const col2 = wc(wi);
|
|
188
145
|
const stCol = statusColor(w);
|
|
189
|
-
const
|
|
146
|
+
const stTxt = statusText(w);
|
|
190
147
|
const earned = w.stats.coins > 0 ? `${c.green}+${w.stats.coins.toLocaleString()}${c.reset}` : DIM + '—' + c.reset;
|
|
191
148
|
const cmds = w.stats.commands || 0;
|
|
192
149
|
const rate = w.stats.commands > 0 ? Math.round((w.stats.successes / w.stats.commands) * 100) : 0;
|
|
193
150
|
|
|
194
151
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
195
152
|
process.stdout.write(`${DIM}${padL(wi + 1, 2)}${c.reset} `);
|
|
196
|
-
process.stdout.write(`${stCol}${padR(
|
|
153
|
+
process.stdout.write(`${stCol}${padR(stTxt, col.st)}${c.reset} `);
|
|
197
154
|
process.stdout.write(`${col2}${padR(w.username || '?', col.name)}${c.reset} `);
|
|
198
155
|
process.stdout.write(`${DIM}${padR(w.lastStatus || 'idle', col.last)}${c.reset} `);
|
|
199
156
|
process.stdout.write(`${padL(cmds, col.cmds)} `);
|
|
@@ -203,7 +160,8 @@ function draw() {
|
|
|
203
160
|
}
|
|
204
161
|
|
|
205
162
|
if (sorted.length > maxAccounts) {
|
|
206
|
-
|
|
163
|
+
const extra = sorted.length - maxAccounts;
|
|
164
|
+
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`);
|
|
207
165
|
}
|
|
208
166
|
|
|
209
167
|
// ── Totals ──
|
|
@@ -216,50 +174,32 @@ function draw() {
|
|
|
216
174
|
const rate = totalCmds > 0 ? Math.round((totalOk / totalCmds) * 100) : 0;
|
|
217
175
|
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
218
176
|
|
|
219
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(
|
|
177
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(inner)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
220
178
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
221
179
|
process.stdout.write(`${c.bold}Σ${c.reset} `);
|
|
222
180
|
process.stdout.write(`${DIM}${padL(_workers.length, 2)} acc${c.reset} `);
|
|
223
181
|
process.stdout.write(`${' '.repeat(col.name)} `);
|
|
224
|
-
process.stdout.write(`${
|
|
182
|
+
process.stdout.write(`${' '.repeat(col.last)} `);
|
|
225
183
|
process.stdout.write(`${padL(totalCmds, col.cmds)} `);
|
|
226
184
|
process.stdout.write(`${padL(rate, col.ok)}% `);
|
|
227
|
-
|
|
228
|
-
|
|
185
|
+
if (totalCoins > 0) {
|
|
186
|
+
process.stdout.write(`${c.green}${padL('+' + totalCoins.toLocaleString(), col.earned)}${c.reset} `);
|
|
187
|
+
} else {
|
|
188
|
+
process.stdout.write(`${DIM}${padL('—', col.earned)}${c.reset} `);
|
|
189
|
+
}
|
|
190
|
+
process.stdout.write(`${DIM}${fmtUptime()} | ${memMB}MB${c.reset} `.padEnd(inner));
|
|
229
191
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
230
192
|
|
|
231
|
-
// ──
|
|
232
|
-
process.stdout.write(`\x1b[38;2;77;212;238m
|
|
233
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${c.bold}EVENTS${c.reset}${' '.repeat(Math.max(0, W - 10))}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
234
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(W - 4)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
235
|
-
|
|
236
|
-
// Show recent events per account
|
|
237
|
-
const evLines = [];
|
|
238
|
-
for (let i = 0; i < _workers.length; i++) {
|
|
239
|
-
if (_eventLines[i] && _eventLines[i].length > 0) {
|
|
240
|
-
const latest = _eventLines[i][_eventLines[i].length - 1];
|
|
241
|
-
const col2 = wc(i);
|
|
242
|
-
const name = trunc(_workers[i]?.username || '?', 14);
|
|
243
|
-
evLines.push({ i, text: latest.text, ts: latest.ts, col: col2, name });
|
|
244
|
-
}
|
|
245
|
-
}
|
|
193
|
+
// ── Bottom ──
|
|
194
|
+
process.stdout.write(`\x1b[38;2;77;212;238m└${'─'.repeat(inner)}┘\x1b[0m\n`);
|
|
246
195
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
251
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
252
|
-
process.stdout.write(`${ev.col}${padR(ev.name, 14)}${c.reset} `);
|
|
253
|
-
process.stdout.write(`${DIM}[${ts}]${c.reset} ${ev.text}${' '.repeat(Math.max(0, W - 20 - ev.name.length - ev.text.length))}`);
|
|
254
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
for (let i = 0; i < Math.max(0, eventH - evLines.length); i++) {
|
|
258
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${' '.repeat(W - 4)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
259
|
-
}
|
|
196
|
+
// Restore cursor to after the box
|
|
197
|
+
process.stdout.write('\x1b[u');
|
|
198
|
+
}
|
|
260
199
|
|
|
261
|
-
|
|
262
|
-
|
|
200
|
+
// ── Strip ANSI for length calc ───────────────────────────────
|
|
201
|
+
function stripAnsi(s) {
|
|
202
|
+
return String(s).replace(/\x1b\[[0-9;]*m/g, '');
|
|
263
203
|
}
|
|
264
204
|
|
|
265
205
|
// ── Event tracking ────────────────────────────────────────────
|
|
@@ -274,28 +214,37 @@ function init({ workers, isShuttingDown }) {
|
|
|
274
214
|
_isShuttingDown = isShuttingDown || (() => false);
|
|
275
215
|
_version = '0.0.0';
|
|
276
216
|
_eventLines = [];
|
|
217
|
+
_live = false;
|
|
277
218
|
}
|
|
278
219
|
|
|
279
220
|
function drawBanner(version) { _version = version; }
|
|
280
221
|
function start() {}
|
|
281
|
-
function stop() { process.stdout.write(c.reset + '\n'); }
|
|
222
|
+
function stop() { _live = false; process.stdout.write(c.reset + '\n'); }
|
|
223
|
+
function setLive(val) { _live = val; }
|
|
282
224
|
|
|
225
|
+
// log: append event below the box (no redraw of box)
|
|
283
226
|
function log(accountIdx, msg) {
|
|
284
227
|
const now = new Date();
|
|
285
228
|
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
286
229
|
|
|
230
|
+
// Track event per account
|
|
287
231
|
if (accountIdx >= 0) {
|
|
288
232
|
if (!_eventLines[accountIdx]) _eventLines[accountIdx] = [];
|
|
289
233
|
_eventLines[accountIdx].push({ text: msg, ts });
|
|
290
234
|
if (_eventLines[accountIdx].length > MAX_EVENTS) _eventLines[accountIdx].shift();
|
|
291
235
|
}
|
|
292
236
|
|
|
293
|
-
|
|
237
|
+
if (!_live) return; // during login: just track, don't output yet
|
|
238
|
+
|
|
239
|
+
// ── Append event cleanly below the box ──
|
|
240
|
+
const col2 = accountIdx >= 0 ? wc(accountIdx) : c.cyan;
|
|
241
|
+
const name = accountIdx >= 0 ? trunc(_workers[accountIdx]?.username || '?', 14) : 'GLOBAL';
|
|
242
|
+
const text = DIM + name + c.reset + ' ' + DIM + ts + c.reset + ' ' + msg;
|
|
243
|
+
process.stdout.write(text + '\n');
|
|
294
244
|
}
|
|
295
245
|
|
|
296
246
|
function logGlobal(msg) {
|
|
297
|
-
// Global events go to account 0
|
|
298
247
|
log(-1, msg);
|
|
299
248
|
}
|
|
300
249
|
|
|
301
|
-
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop };
|
|
250
|
+
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop, setLive };
|