dankgrinder 8.45.0 → 8.48.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 +7 -0
- package/lib/ui.js +144 -60
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -2826,6 +2826,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2826
2826
|
const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(parsedGapMin, 150);
|
|
2827
2827
|
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));
|
|
2828
2828
|
|
|
2829
|
+
ui.setPhase('login');
|
|
2829
2830
|
ui.log(-1, `Logging in ${accounts.length} accounts...`);
|
|
2830
2831
|
const BATCH_SIZE = 10;
|
|
2831
2832
|
for (let i = 0; i < accounts.length; i += BATCH_SIZE) {
|
|
@@ -2874,6 +2875,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2874
2875
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
2875
2876
|
|
|
2876
2877
|
// ── Phase 2: Inventory check ────────────────────────────────────
|
|
2878
|
+
ui.setPhase('inventory');
|
|
2877
2879
|
ui.log(-1, `Checking inventory...`);
|
|
2878
2880
|
let invFailed = 0;
|
|
2879
2881
|
await Promise.all(activeWorkers.map(async (w) => {
|
|
@@ -2887,6 +2889,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2887
2889
|
}
|
|
2888
2890
|
|
|
2889
2891
|
// ── Phase 2.5: Balance check ───────────────────────────────────
|
|
2892
|
+
ui.setPhase('balance');
|
|
2890
2893
|
ui.log(-1, `Checking balances...`);
|
|
2891
2894
|
for (const w of activeWorkers) {
|
|
2892
2895
|
try { await w.checkBalance(true); } catch {}
|
|
@@ -2899,6 +2902,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2899
2902
|
ui.log(-1, `Balance: +⏣${totalCoins.toLocaleString()} across ${activeWorkers.length} accounts`);
|
|
2900
2903
|
|
|
2901
2904
|
// ── Phase 2.75: DM history check ────────────────────────────────
|
|
2905
|
+
ui.setPhase('dms');
|
|
2902
2906
|
ui.log(-1, `Checking DM history...`);
|
|
2903
2907
|
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [];
|
|
2904
2908
|
for (const w of activeWorkers) {
|
|
@@ -2924,9 +2928,11 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2924
2928
|
ui.log(-1, `DM: ${parts.length > 0 ? parts.join(', ') : 'clean'}`);
|
|
2925
2929
|
|
|
2926
2930
|
// ── Phase 3: Start grind loops ───────────────────────────────────
|
|
2931
|
+
ui.setPhase('grinding');
|
|
2927
2932
|
ui.log(-1, `Starting ${activeWorkers.length} grind loops...`);
|
|
2928
2933
|
ui.setLive(true);
|
|
2929
2934
|
ui.draw();
|
|
2935
|
+
ui.startRefresh(3000);
|
|
2930
2936
|
for (const w of activeWorkers) {
|
|
2931
2937
|
if (!shutdownCalled) w.grindLoop();
|
|
2932
2938
|
}
|
|
@@ -2997,6 +3003,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2997
3003
|
shutdownInProgress = true;
|
|
2998
3004
|
shutdownCalled = true;
|
|
2999
3005
|
setDashboardActive(false);
|
|
3006
|
+
ui.stopRefresh();
|
|
3000
3007
|
ui.stop();
|
|
3001
3008
|
process.stdout.write(c.show + '\n');
|
|
3002
3009
|
|
package/lib/ui.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI Live Dashboard —
|
|
3
|
-
* Box drawn once. Events appended line-by-line. No flicker.
|
|
2
|
+
* CLI Live Dashboard — ASCII banner, per-account status table, event stream below.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
let _startTime = Date.now();
|
|
@@ -8,11 +7,26 @@ let _workers = [];
|
|
|
8
7
|
let _isShuttingDown = () => false;
|
|
9
8
|
let _version = '0.0.0';
|
|
10
9
|
let _live = false;
|
|
10
|
+
let _phase = 'init'; // 'init' | 'login' | 'inventory' | 'balance' | 'dms' | 'grinding'
|
|
11
|
+
let _boxRow = 0; // which terminal row the box top starts on
|
|
12
|
+
let _boxHeight = 0; // total rows consumed by the box
|
|
13
|
+
let _eventCountAtDraw = 0; // events logged since last draw
|
|
14
|
+
let _eventCount = 0; // total events ever logged
|
|
11
15
|
|
|
12
16
|
// ── Spinner frames ────────────────────────────────────────────
|
|
13
17
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
14
18
|
function spinnerFrame() { return SPINNER[Math.floor(Date.now() / 150) % SPINNER.length]; }
|
|
15
19
|
|
|
20
|
+
// ── Compact ASCII art banner (fits in 80-col box) ─────────────
|
|
21
|
+
const BANNER_LINES = [
|
|
22
|
+
' ██████╗ ██╗ ██╗███╗ ██╗ ██████╗ ███████╗ ██████╗ ███╗ ██╗ DANKGRINDER',
|
|
23
|
+
' ██╔══██╗██║ ██║████╗ ██║██╔════╝ ██╔════╝██╔═══██╗████╗ ██║ v' + 'PLACEHOLDER',
|
|
24
|
+
' ██║ ██║██║ ██║██╔██╗ ██║██║ ███╗█████╗ ██║ ██║██╔██╗ ██║',
|
|
25
|
+
' ██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██║ ██║██║╚██╗██║',
|
|
26
|
+
' ██████╔╝╚██████╔╝██║ ╚████║╚██████╔╝███████╗╚██████╔╝██║ ╚████║',
|
|
27
|
+
' ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝',
|
|
28
|
+
];
|
|
29
|
+
|
|
16
30
|
// ── Palette ───────────────────────────────────────────────────
|
|
17
31
|
const PALETTE = [
|
|
18
32
|
'\x1b[38;2;77;212;238m', '\x1b[38;2;255;194;77m', '\x1b[38;2;130;210;100m',
|
|
@@ -30,12 +44,14 @@ const c = {
|
|
|
30
44
|
yellow: '\x1b[38;2;255;220;80m',
|
|
31
45
|
cyan: '\x1b[38;2;80;220;255m',
|
|
32
46
|
blue: '\x1b[38;2;100;160;255m',
|
|
47
|
+
white: '\x1b[37m',
|
|
33
48
|
};
|
|
34
49
|
const DIM = c.dim;
|
|
35
50
|
|
|
36
51
|
function trunc(s, n) { s = String(s || ''); return s.length <= n ? s : s.slice(0, n - 1) + '…'; }
|
|
37
52
|
function padR(s, n) { return trunc(s, n).padEnd(n); }
|
|
38
53
|
function padL(s, n, char) { return String(s).padStart(n, char || ' '); }
|
|
54
|
+
function stripAnsi(s) { return String(s).replace(/\x1b\[[0-9;]*m/g, ''); }
|
|
39
55
|
|
|
40
56
|
function fmtUptime() {
|
|
41
57
|
const s = Math.floor((Date.now() - _startTime) / 1000);
|
|
@@ -50,7 +66,7 @@ function fmtUptime() {
|
|
|
50
66
|
|
|
51
67
|
// ── Status helpers ─────────────────────────────────────────────
|
|
52
68
|
function statusColor(w) {
|
|
53
|
-
if (!w.
|
|
69
|
+
if (!w.channel) return c.red;
|
|
54
70
|
if (w.paused || w.dashboardPaused) return c.yellow;
|
|
55
71
|
if (w.busy || w._invRunning || w._sellRunning) return c.yellow;
|
|
56
72
|
if (Date.now() < w.globalCooldownUntil) return c.blue;
|
|
@@ -58,12 +74,12 @@ function statusColor(w) {
|
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
function statusText(w) {
|
|
61
|
-
if (!w.
|
|
77
|
+
if (!w.channel) return spinnerFrame() + ' CONN';
|
|
62
78
|
if (w.paused) return 'PAUSED';
|
|
63
79
|
if (w.dashboardPaused) return 'HELD';
|
|
64
80
|
if (w._alert?.type === 'death') return 'DEAD';
|
|
65
81
|
if (w._alert?.type === 'lowls') return 'LOW LS';
|
|
66
|
-
if (w.busy || w._invRunning || w._sellRunning) return spinnerFrame() + '
|
|
82
|
+
if (w.busy || w._invRunning || w._sellRunning) return spinnerFrame() + ' WORK';
|
|
67
83
|
if (Date.now() < w.globalCooldownUntil) {
|
|
68
84
|
const wait = Math.ceil((w.globalCooldownUntil - Date.now()) / 1000);
|
|
69
85
|
return '⏳' + wait + 's';
|
|
@@ -71,68 +87,112 @@ function statusText(w) {
|
|
|
71
87
|
return '● READY';
|
|
72
88
|
}
|
|
73
89
|
|
|
90
|
+
// ── Format helpers ─────────────────────────────────────────────
|
|
91
|
+
function fmtCoins(n) {
|
|
92
|
+
if (!n && n !== 0) return DIM + '—' + c.reset;
|
|
93
|
+
if (n >= 1_000_000) return c.green + '+' + (n / 1_000_000).toFixed(1) + 'M' + c.reset;
|
|
94
|
+
if (n >= 1_000) return c.green + '+' + (n / 1_000).toFixed(1) + 'k' + c.reset;
|
|
95
|
+
if (n > 0) return c.green + '+' + n + c.reset;
|
|
96
|
+
return DIM + '—' + c.reset;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function fmtLifesavers(w) {
|
|
100
|
+
const ls = w._lifesavers;
|
|
101
|
+
if (ls === undefined || ls === null) return DIM + '—' + c.reset;
|
|
102
|
+
if (ls === 0) return c.red + '0' + c.reset;
|
|
103
|
+
if (ls <= 2) return c.yellow + ls + c.reset;
|
|
104
|
+
return c.green + ls + c.reset;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function fmtLevel(w) {
|
|
108
|
+
const lv = w._level;
|
|
109
|
+
if (!lv) return DIM + '—' + c.reset;
|
|
110
|
+
return c.cyan + lv + c.reset;
|
|
111
|
+
}
|
|
112
|
+
|
|
74
113
|
// ── Layout ────────────────────────────────────────────────────
|
|
75
114
|
function layout() {
|
|
76
115
|
const W = Math.min(process.stdout.columns || 100, 120);
|
|
77
116
|
const rows = process.stdout.rows || 40;
|
|
78
|
-
|
|
79
|
-
const maxAccounts = Math.min(_workers.length, Math.max(3, rows - 16));
|
|
117
|
+
const maxAccounts = Math.min(_workers.length, Math.max(3, rows - 14));
|
|
80
118
|
return { W, maxAccounts };
|
|
81
119
|
}
|
|
82
120
|
|
|
83
|
-
// ── Draw the
|
|
121
|
+
// ── Draw the full dashboard ───────────────────────────────────
|
|
84
122
|
function draw() {
|
|
85
123
|
const { W, maxAccounts } = layout();
|
|
86
124
|
const inner = W - 2;
|
|
87
125
|
|
|
88
|
-
// ──
|
|
89
|
-
|
|
90
|
-
|
|
126
|
+
// ── Clear screen + home ──
|
|
127
|
+
const isFirst = _boxRow === 0;
|
|
128
|
+
if (isFirst) {
|
|
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
|
+
}
|
|
91
136
|
|
|
92
|
-
// ──
|
|
137
|
+
// ── Top border ──
|
|
93
138
|
process.stdout.write(`\x1b[38;2;77;212;238m┌${'─'.repeat(inner)}┐\x1b[0m\n`);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
139
|
+
|
|
140
|
+
// ── ASCII banner (dynamic — includes version) ──
|
|
141
|
+
const bannerWithVer = BANNER_LINES[1].replace('PLACEHOLDER', _version);
|
|
142
|
+
const bannerLines = [BANNER_LINES[0], bannerWithVer, ...BANNER_LINES.slice(2)];
|
|
143
|
+
|
|
144
|
+
for (let i = 0; i < bannerLines.length; i++) {
|
|
145
|
+
const line = bannerLines[i];
|
|
146
|
+
// Gradient: cyan → pink for row 0 and 1, dim for others
|
|
147
|
+
if (i < 2) {
|
|
148
|
+
const gradLine = gradientLine(line, 77, 212, 238, 255, 92, 147);
|
|
149
|
+
const pad = ' '.repeat(Math.max(0, inner - stripAnsi(gradLine).length));
|
|
150
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m${gradLine}${pad}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
151
|
+
} else {
|
|
152
|
+
const dimLine = DIM + line + c.reset;
|
|
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`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
99
157
|
|
|
100
158
|
// ── Status bar ──
|
|
101
|
-
const running = _workers.filter(w => w.
|
|
159
|
+
const running = _workers.filter(w => w.channel && !w.paused && !w.dashboardPaused).length;
|
|
102
160
|
const paused = _workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
103
|
-
const errors = _workers.filter(w => !w.
|
|
161
|
+
const errors = _workers.filter(w => !w.channel).length;
|
|
162
|
+
const phaseLabel = _phase === 'grinding' ? '' : ` ${c.dim}[${_phase}]${c.reset}`;
|
|
104
163
|
const statusParts = [
|
|
105
164
|
`${c.green}●${c.reset} ${running} online`,
|
|
106
165
|
paused > 0 ? `${c.yellow}~${c.reset} ${paused} paused` : null,
|
|
107
166
|
errors > 0 ? `${c.red}E${c.reset} ${errors} error` : null,
|
|
108
167
|
`${DIM}Ctrl+C${c.reset}`,
|
|
109
168
|
].filter(Boolean);
|
|
110
|
-
const statusStr = statusParts.join(' ');
|
|
169
|
+
const statusStr = statusParts.join(' ') + phaseLabel;
|
|
111
170
|
const statusPad = ' '.repeat(Math.max(0, inner - stripAnsi(statusStr)));
|
|
112
171
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${statusStr}${statusPad}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
113
172
|
process.stdout.write(`\x1b[38;2;77;212;238m├${'─'.repeat(inner)}┤\x1b[0m\n`);
|
|
114
173
|
|
|
115
174
|
// ── Table header ──
|
|
116
|
-
const col = { st: 9, name: 18,
|
|
117
|
-
const nameExtra = Math.max(0, inner - col.st - col.name - col.
|
|
175
|
+
const col = { st: 9, name: 18, cmd: 16, bal: 8, ls: 3, lv: 3, earned: 8 };
|
|
176
|
+
const nameExtra = Math.max(0, inner - col.st - col.name - col.cmd - col.bal - col.ls - col.lv - col.earned - 14);
|
|
118
177
|
col.name += nameExtra;
|
|
119
178
|
|
|
120
179
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
121
180
|
process.stdout.write(`${c.bold}#${c.reset} `);
|
|
122
181
|
process.stdout.write(`${c.bold}${padR('STATUS', col.st)}${c.reset} `);
|
|
123
182
|
process.stdout.write(`${c.bold}${padR('ACCOUNT', col.name)}${c.reset} `);
|
|
124
|
-
process.stdout.write(`${c.bold}${padR('
|
|
125
|
-
process.stdout.write(`${c.bold}${padL('
|
|
126
|
-
process.stdout.write(`${c.bold}${padL('
|
|
183
|
+
process.stdout.write(`${c.bold}${padR('DOING', col.cmd)}${c.reset} `);
|
|
184
|
+
process.stdout.write(`${c.bold}${padL('BAL', col.bal)}${c.reset} `);
|
|
185
|
+
process.stdout.write(`${c.bold}${padL('LS', col.ls)}${c.reset} `);
|
|
186
|
+
process.stdout.write(`${c.bold}${padL('LV', col.lv)}${c.reset} `);
|
|
127
187
|
process.stdout.write(`${c.bold}${padL('EARNED', col.earned)}${c.reset} `);
|
|
128
188
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
129
189
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(inner)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
130
190
|
|
|
131
191
|
// ── Account rows ──
|
|
132
192
|
const sorted = [..._workers].sort((a, b) => {
|
|
133
|
-
if (!a.
|
|
134
|
-
const aA = a.
|
|
135
|
-
const bA = b.
|
|
193
|
+
if (!a.channel !== !b.channel) return !a.channel ? 1 : -1;
|
|
194
|
+
const aA = a.channel && !a.paused && !a.dashboardPaused;
|
|
195
|
+
const bA = b.channel && !b.paused && !b.dashboardPaused;
|
|
136
196
|
if (aA !== bA) return bA ? 1 : -1;
|
|
137
197
|
return (b.stats.commands || 0) - (a.stats.commands || 0);
|
|
138
198
|
});
|
|
@@ -144,17 +204,19 @@ function draw() {
|
|
|
144
204
|
const col2 = wc(wi);
|
|
145
205
|
const stCol = statusColor(w);
|
|
146
206
|
const stTxt = statusText(w);
|
|
147
|
-
const earned = w.stats.coins
|
|
148
|
-
const
|
|
149
|
-
const
|
|
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);
|
|
150
211
|
|
|
151
212
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
152
213
|
process.stdout.write(`${DIM}${padL(wi + 1, 2)}${c.reset} `);
|
|
153
214
|
process.stdout.write(`${stCol}${padR(stTxt, col.st)}${c.reset} `);
|
|
154
|
-
process.stdout.write(`${col2}${padR(w.username || '?', col.name)}${c.reset} `);
|
|
155
|
-
process.stdout.write(`${DIM}${padR(w.lastStatus || 'idle', col.
|
|
156
|
-
process.stdout.write(`${padL(
|
|
157
|
-
process.stdout.write(`${padL(
|
|
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)} `);
|
|
158
220
|
process.stdout.write(`${padL(earned, col.earned)} `);
|
|
159
221
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
160
222
|
}
|
|
@@ -165,9 +227,10 @@ function draw() {
|
|
|
165
227
|
}
|
|
166
228
|
|
|
167
229
|
// ── Totals ──
|
|
168
|
-
let totalCoins = 0, totalCmds = 0, totalOk = 0;
|
|
230
|
+
let totalCoins = 0, totalBal = 0, totalCmds = 0, totalOk = 0;
|
|
169
231
|
for (const w of _workers) {
|
|
170
232
|
totalCoins += w.stats.coins || 0;
|
|
233
|
+
totalBal += w.stats.balance || 0;
|
|
171
234
|
totalCmds += w.stats.commands || 0;
|
|
172
235
|
totalOk += w.stats.successes || 0;
|
|
173
236
|
}
|
|
@@ -179,32 +242,35 @@ function draw() {
|
|
|
179
242
|
process.stdout.write(`${c.bold}Σ${c.reset} `);
|
|
180
243
|
process.stdout.write(`${DIM}${padL(_workers.length, 2)} acc${c.reset} `);
|
|
181
244
|
process.stdout.write(`${' '.repeat(col.name)} `);
|
|
182
|
-
process.stdout.write(`${' '.repeat(col.
|
|
183
|
-
process.stdout.write(`${padL(
|
|
184
|
-
process.stdout.write(`${
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
} else {
|
|
188
|
-
process.stdout.write(`${DIM}${padL('—', col.earned)}${c.reset} `);
|
|
189
|
-
}
|
|
245
|
+
process.stdout.write(`${' '.repeat(col.cmd)} `);
|
|
246
|
+
process.stdout.write(`${padL(totalBal > 0 ? '⏣' + totalBal.toLocaleString() : '—', col.bal)} `);
|
|
247
|
+
process.stdout.write(`${' '.repeat(col.ls)} `);
|
|
248
|
+
process.stdout.write(`${' '.repeat(col.lv)} `);
|
|
249
|
+
process.stdout.write(`${fmtCoins(totalCoins)} `);
|
|
190
250
|
process.stdout.write(`${DIM}${fmtUptime()} | ${memMB}MB${c.reset} `.padEnd(inner));
|
|
191
251
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
192
252
|
|
|
193
253
|
// ── Bottom ──
|
|
194
254
|
process.stdout.write(`\x1b[38;2;77;212;238m└${'─'.repeat(inner)}┘\x1b[0m\n`);
|
|
195
255
|
|
|
196
|
-
//
|
|
197
|
-
|
|
256
|
+
// Record box height + current event count for next redraw
|
|
257
|
+
_boxHeight = bannerLines.length + 1 + 1 + 2 + shown.length + (sorted.length > maxAccounts ? 1 : 0) + 3;
|
|
258
|
+
_eventCountAtDraw = _eventCount;
|
|
198
259
|
}
|
|
199
260
|
|
|
200
|
-
// ──
|
|
201
|
-
function
|
|
202
|
-
|
|
261
|
+
// ── Gradient line ─────────────────────────────────────────────
|
|
262
|
+
function gradientLine(text, r1, g1, b1, r2, g2, b2) {
|
|
263
|
+
let out = '';
|
|
264
|
+
for (let i = 0; i < text.length; i++) {
|
|
265
|
+
const t = text.length <= 1 ? 0 : i / (text.length - 1);
|
|
266
|
+
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]}`;
|
|
267
|
+
}
|
|
268
|
+
return out + '\x1b[0m';
|
|
203
269
|
}
|
|
204
270
|
|
|
205
271
|
// ── Event tracking ────────────────────────────────────────────
|
|
206
272
|
let _eventLines = []; // [accountIdx] = [{text, ts}]
|
|
207
|
-
const MAX_EVENTS =
|
|
273
|
+
const MAX_EVENTS = 4;
|
|
208
274
|
|
|
209
275
|
// ── Public API ────────────────────────────────────────────────
|
|
210
276
|
|
|
@@ -215,36 +281,54 @@ function init({ workers, isShuttingDown }) {
|
|
|
215
281
|
_version = '0.0.0';
|
|
216
282
|
_eventLines = [];
|
|
217
283
|
_live = false;
|
|
284
|
+
_phase = 'init';
|
|
285
|
+
_boxRow = 0;
|
|
286
|
+
_boxHeight = 0;
|
|
287
|
+
_eventCount = 0;
|
|
288
|
+
_eventCountAtDraw = 0;
|
|
218
289
|
}
|
|
219
290
|
|
|
220
|
-
function drawBanner(version) { _version = version; }
|
|
291
|
+
function drawBanner(version) { _version = version || '0.0.0'; }
|
|
292
|
+
|
|
221
293
|
function start() {}
|
|
222
|
-
function stop() { _live = false; process.stdout.write(c.reset + '\n'); }
|
|
294
|
+
function stop() { _live = false; _phase = 'init'; process.stdout.write(c.reset + '\n'); }
|
|
295
|
+
|
|
223
296
|
function setLive(val) { _live = val; }
|
|
224
297
|
|
|
298
|
+
function setPhase(phase) { _phase = phase; }
|
|
299
|
+
|
|
225
300
|
// log: append event below the box (no redraw of box)
|
|
226
301
|
function log(accountIdx, msg) {
|
|
227
302
|
const now = new Date();
|
|
228
303
|
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
229
304
|
|
|
230
|
-
// Track event per account
|
|
231
305
|
if (accountIdx >= 0) {
|
|
232
306
|
if (!_eventLines[accountIdx]) _eventLines[accountIdx] = [];
|
|
233
307
|
_eventLines[accountIdx].push({ text: msg, ts });
|
|
234
308
|
if (_eventLines[accountIdx].length > MAX_EVENTS) _eventLines[accountIdx].shift();
|
|
235
309
|
}
|
|
310
|
+
_eventCount++;
|
|
236
311
|
|
|
237
|
-
if (!_live) return;
|
|
312
|
+
if (!_live) return;
|
|
238
313
|
|
|
239
|
-
//
|
|
314
|
+
// Clean event line: colored name + timestamp + message
|
|
240
315
|
const col2 = accountIdx >= 0 ? wc(accountIdx) : c.cyan;
|
|
241
316
|
const name = accountIdx >= 0 ? trunc(_workers[accountIdx]?.username || '?', 14) : 'GLOBAL';
|
|
242
|
-
|
|
243
|
-
process.stdout.write(text + '\n');
|
|
317
|
+
process.stdout.write(`${col2}${name}${c.reset} ${DIM}[${ts}]${c.reset} ${msg}\n`);
|
|
244
318
|
}
|
|
245
319
|
|
|
246
|
-
function logGlobal(msg) {
|
|
247
|
-
|
|
320
|
+
function logGlobal(msg) { log(-1, msg); }
|
|
321
|
+
|
|
322
|
+
// Start periodic box refresh (every 3s for spinner/countdown updates)
|
|
323
|
+
let _refreshInterval = null;
|
|
324
|
+
function startRefresh(ms = 3000) {
|
|
325
|
+
if (_refreshInterval) clearInterval(_refreshInterval);
|
|
326
|
+
_refreshInterval = setInterval(() => {
|
|
327
|
+
if (_live) draw();
|
|
328
|
+
}, ms);
|
|
329
|
+
}
|
|
330
|
+
function stopRefresh() {
|
|
331
|
+
if (_refreshInterval) { clearInterval(_refreshInterval); _refreshInterval = null; }
|
|
248
332
|
}
|
|
249
333
|
|
|
250
|
-
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop, setLive };
|
|
334
|
+
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop, setLive, setPhase, startRefresh, stopRefresh };
|