dankgrinder 8.53.0 → 8.54.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 -7
- package/lib/ui.js +108 -95
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -3024,11 +3024,10 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3024
3024
|
const cpm = globalCmdRate.getRate().toFixed(1);
|
|
3025
3025
|
console.log(`${c.bold}Total:${c.reset} +⏣${finalCoins.toLocaleString()} in ${formatUptime()} | ${finalCmds}cmds | ~${cpm}cmd/m | ${memFinal}MB`);
|
|
3026
3026
|
|
|
3027
|
-
// Stop workers
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
])));
|
|
3027
|
+
// Stop workers immediately (don't wait) — instant shutdown
|
|
3028
|
+
for (const wk of workers) {
|
|
3029
|
+
try { wk.stop(); } catch {}
|
|
3030
|
+
}
|
|
3032
3031
|
workerMap.clear();
|
|
3033
3032
|
|
|
3034
3033
|
// Release cluster claims
|
|
@@ -3054,13 +3053,14 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3054
3053
|
|
|
3055
3054
|
if (redis) { redis.disconnect().catch(() => {}); }
|
|
3056
3055
|
console.log(`${c.green}Goodbye!${c.reset}\n`);
|
|
3057
|
-
// Force exit
|
|
3058
|
-
setTimeout(() => process.exit(0),
|
|
3056
|
+
// Force exit so Ctrl+C always terminates immediately
|
|
3057
|
+
setTimeout(() => process.exit(0), 2000);
|
|
3059
3058
|
process.exit(0);
|
|
3060
3059
|
}
|
|
3061
3060
|
|
|
3062
3061
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
3063
3062
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
3063
|
+
process.on('SIGHUP', () => gracefulShutdown('SIGHUP'));
|
|
3064
3064
|
}
|
|
3065
3065
|
|
|
3066
3066
|
// ══════════════════════════════════════════════════════════════
|
package/lib/ui.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI Live Dashboard — cursor-positioned in-place row updates.
|
|
3
|
-
* Box drawn once
|
|
3
|
+
* Box drawn once. Rows updated in-place. Events below box.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
let _startTime = Date.now();
|
|
@@ -9,13 +9,15 @@ let _version = '0.0.0';
|
|
|
9
9
|
let _live = false;
|
|
10
10
|
let _phase = 'init';
|
|
11
11
|
|
|
12
|
-
// Terminal dimensions
|
|
12
|
+
// Terminal dimensions
|
|
13
13
|
let _W = 100;
|
|
14
14
|
let _inner = 98;
|
|
15
15
|
let _maxAccounts = 4;
|
|
16
16
|
|
|
17
|
-
// Row map: which terminal row each account
|
|
18
|
-
let _accountRows = []; // _accountRows[accountIdx] =
|
|
17
|
+
// Row map: which terminal row each account starts at (1-indexed)
|
|
18
|
+
let _accountRows = []; // _accountRows[accountIdx] = row
|
|
19
|
+
let _totalsRow = 0;
|
|
20
|
+
let _bottomRow = 0;
|
|
19
21
|
|
|
20
22
|
// ── Spinner frames ────────────────────────────────────────────
|
|
21
23
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
@@ -50,20 +52,21 @@ const c = {
|
|
|
50
52
|
white: '\x1b[37m',
|
|
51
53
|
};
|
|
52
54
|
const DIM = c.dim;
|
|
55
|
+
const B = '\x1b[38;2;77;212;238m'; // box border color
|
|
53
56
|
|
|
57
|
+
function row(n) { process.stdout.write(`\x1b[${n};1H`); }
|
|
58
|
+
function clrLine() { process.stdout.write('\x1b[2K'); }
|
|
54
59
|
function trunc(s, n) { s = String(s || ''); return s.length <= n ? s : s.slice(0, n - 1) + '…'; }
|
|
55
60
|
function padR(s, n) { return trunc(s, n).padEnd(n); }
|
|
56
61
|
function padL(s, n, char) { return String(s).padStart(n, char || ' '); }
|
|
57
62
|
function stripAnsi(s) { return String(s).replace(/\x1b\[[0-9;]*m/g, ''); }
|
|
58
|
-
function
|
|
63
|
+
function ln() { process.stdout.write('\n'); }
|
|
59
64
|
|
|
60
65
|
function fmtUptime() {
|
|
61
66
|
const s = Math.floor((Date.now() - _startTime) / 1000);
|
|
62
67
|
if (s < 60) return `${s}s`;
|
|
63
68
|
const m = Math.floor(s / 60);
|
|
64
69
|
const h = Math.floor(m / 60);
|
|
65
|
-
const d = Math.floor(h / 24);
|
|
66
|
-
if (d > 0) return `${d}d${h % 24}h`;
|
|
67
70
|
if (h > 0) return `${h}h${m % 60}m`;
|
|
68
71
|
return `${m}m`;
|
|
69
72
|
}
|
|
@@ -114,8 +117,24 @@ function fmtLevel(w) {
|
|
|
114
117
|
return c.cyan + lv + c.reset;
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
// ──
|
|
118
|
-
function
|
|
120
|
+
// ── Layout ────────────────────────────────────────────────────
|
|
121
|
+
function layout() {
|
|
122
|
+
_W = Math.min(process.stdout.columns || 100, 120);
|
|
123
|
+
_inner = _W - 2;
|
|
124
|
+
const rows = process.stdout.rows || 40;
|
|
125
|
+
_maxAccounts = Math.min(_workers.length, Math.max(3, rows - 16));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Column layout ─────────────────────────────────────────────
|
|
129
|
+
function getCol() {
|
|
130
|
+
const col = { st: 9, name: 18, cmd: 16, bal: 8, ls: 3, lv: 3, earned: 8 };
|
|
131
|
+
const nameExtra = Math.max(0, _inner - col.st - col.name - col.cmd - col.bal - col.ls - col.lv - col.earned - 14);
|
|
132
|
+
col.name += nameExtra;
|
|
133
|
+
return col;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Build account row string ─────────────────────────────────
|
|
137
|
+
function accountRow(w, wi, col) {
|
|
119
138
|
const col2 = wc(wi);
|
|
120
139
|
const stCol = statusColor(w);
|
|
121
140
|
const stTxt = statusText(w);
|
|
@@ -125,46 +144,54 @@ function buildAccountRow(w, wi, col) {
|
|
|
125
144
|
const lv = fmtLevel(w);
|
|
126
145
|
const name = padR(trunc(w.username || '?', col.name), col.name);
|
|
127
146
|
const doing = padR(w.lastStatus || 'idle', col.cmd);
|
|
147
|
+
const earnedStr = stripAnsi(earned);
|
|
148
|
+
const balStr = stripAnsi(bal);
|
|
149
|
+
|
|
150
|
+
// Pad earned and bal to column widths
|
|
151
|
+
const earnedPadded = earned.padEnd(col.earned + (earned.length - earnedStr.length));
|
|
152
|
+
const balPadded = bal.padEnd(col.bal + (bal.length - balStr.length));
|
|
128
153
|
|
|
129
154
|
return (
|
|
130
|
-
|
|
131
|
-
`\x1b[38;2;77;212;238m│\x1b[0m ` +
|
|
155
|
+
`${B}│\x1b[0m ` +
|
|
132
156
|
`${DIM}${padL(wi + 1, 2)}${c.reset} ` +
|
|
133
157
|
`${stCol}${padR(stTxt, col.st)}${c.reset} ` +
|
|
134
158
|
`${col2}${name}${c.reset} ` +
|
|
135
159
|
`${DIM}${doing}${c.reset} ` +
|
|
136
|
-
`${
|
|
160
|
+
`${balPadded} ` +
|
|
137
161
|
`${padL(ls, col.ls)} ` +
|
|
138
162
|
`${padL(lv, col.lv)} ` +
|
|
139
|
-
`${
|
|
140
|
-
|
|
163
|
+
`${earnedPadded} ` +
|
|
164
|
+
`${B}│\x1b[0m`
|
|
141
165
|
);
|
|
142
166
|
}
|
|
143
167
|
|
|
144
|
-
// ── Draw the FULL box (called once
|
|
168
|
+
// ── Draw the FULL box (called once) ─────────────────────────
|
|
145
169
|
function draw() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
170
|
+
layout();
|
|
171
|
+
const col = getCol();
|
|
172
|
+
const bannerH = BANNER_LINES.length;
|
|
149
173
|
|
|
150
|
-
// Clear entire screen
|
|
174
|
+
// Clear entire screen + home
|
|
151
175
|
process.stdout.write('\x1b[2J\x1b[H');
|
|
152
176
|
|
|
177
|
+
let r = 1;
|
|
178
|
+
|
|
153
179
|
// ── Top border ──
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
for (let i = 0; i <
|
|
160
|
-
|
|
161
|
-
const
|
|
180
|
+
row(r++); clrLine();
|
|
181
|
+
process.stdout.write(`${B}┌${'─'.repeat(_inner)}┐`);
|
|
182
|
+
ln();
|
|
183
|
+
|
|
184
|
+
// ── Banner ──
|
|
185
|
+
for (let i = 0; i < bannerH; i++) {
|
|
186
|
+
row(r++); clrLine();
|
|
187
|
+
const line = i === 1 ? BANNER_LINES[1].replace('PLACEHOLDER', _version) : BANNER_LINES[i];
|
|
162
188
|
if (i < 2) {
|
|
163
189
|
const gradLine = gradientLine(line, 77, 212, 238, 255, 92, 147);
|
|
164
|
-
process.stdout.write(
|
|
190
|
+
process.stdout.write(`${B}│\x1b[0m ${gradLine}${' '.repeat(_inner - 2 - stripAnsi(gradLine) - 2)}${B}│`);
|
|
165
191
|
} else {
|
|
166
|
-
process.stdout.write(
|
|
192
|
+
process.stdout.write(`${B}│\x1b[0m ${DIM}${line}${c.reset}${' '.repeat(_inner - 2 - stripAnsi(line) - 2)}${B}│`);
|
|
167
193
|
}
|
|
194
|
+
ln();
|
|
168
195
|
}
|
|
169
196
|
|
|
170
197
|
// ── Status bar ──
|
|
@@ -179,34 +206,25 @@ function draw() {
|
|
|
179
206
|
`${DIM}Ctrl+C${c.reset}`,
|
|
180
207
|
].filter(Boolean);
|
|
181
208
|
const statusStr = statusParts.join(' ') + phaseLabel;
|
|
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`);
|
|
184
209
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
col.name += nameExtra;
|
|
210
|
+
row(r++); clrLine();
|
|
211
|
+
process.stdout.write(`${B}│\x1b[0m ${statusStr}${' '.repeat(_inner - 1 - stripAnsi(statusStr))}${B}│`);
|
|
212
|
+
ln();
|
|
189
213
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
process.stdout.write(`${
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
process.stdout.write(`${c.bold}${padL('LV', col.lv)}${c.reset}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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 = [];
|
|
214
|
+
// ── Divider ──
|
|
215
|
+
row(r++); clrLine();
|
|
216
|
+
process.stdout.write(`${B}├${'─'.repeat(_inner)}┤`);
|
|
217
|
+
ln();
|
|
218
|
+
|
|
219
|
+
// ── Header ──
|
|
220
|
+
row(r++); clrLine();
|
|
221
|
+
process.stdout.write(`${B}│\x1b[0m ${c.bold}#${c.reset} ${c.bold}${padR('STATUS', col.st)}${c.reset} ${c.bold}${padR('ACCOUNT', col.name)}${c.reset} ${c.bold}${padR('DOING', col.cmd)}${c.reset} ${c.bold}${padL('BAL', col.bal)}${c.reset} ${c.bold}${padL('LS', col.ls)}${c.reset} ${c.bold}${padL('LV', col.lv)}${c.reset} ${c.bold}${padL('EARNED', col.earned)}${c.reset} ${B}│`);
|
|
222
|
+
ln();
|
|
223
|
+
|
|
224
|
+
// ── HR ──
|
|
225
|
+
row(r++); clrLine();
|
|
226
|
+
process.stdout.write(`${B}│\x1b[0m ${'─'.repeat(_inner)} ${B}│`);
|
|
227
|
+
ln();
|
|
210
228
|
|
|
211
229
|
// ── Account rows ──
|
|
212
230
|
const sorted = [..._workers].sort((a, b) => {
|
|
@@ -218,14 +236,22 @@ function draw() {
|
|
|
218
236
|
});
|
|
219
237
|
const shown = sorted.slice(0, _maxAccounts);
|
|
220
238
|
|
|
239
|
+
_accountRows = [];
|
|
221
240
|
for (let si = 0; si < shown.length; si++) {
|
|
222
241
|
const w = shown[si];
|
|
223
242
|
const wi = _workers.indexOf(w);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
process.stdout.write(
|
|
243
|
+
_accountRows[wi] = r;
|
|
244
|
+
row(r++); clrLine();
|
|
245
|
+
process.stdout.write(accountRow(w, wi, col));
|
|
246
|
+
ln();
|
|
227
247
|
}
|
|
228
248
|
|
|
249
|
+
// ── Totals divider ──
|
|
250
|
+
_totalsRow = r;
|
|
251
|
+
row(r++); clrLine();
|
|
252
|
+
process.stdout.write(`${B}│\x1b[0m ${'─'.repeat(_inner)} ${B}│`);
|
|
253
|
+
ln();
|
|
254
|
+
|
|
229
255
|
// ── Totals ──
|
|
230
256
|
let totalCoins = 0, totalBal = 0;
|
|
231
257
|
for (const w of _workers) {
|
|
@@ -233,40 +259,35 @@ function draw() {
|
|
|
233
259
|
totalBal += w.stats.balance || 0;
|
|
234
260
|
}
|
|
235
261
|
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
262
|
+
const balStr = totalBal > 0 ? '⏣' + totalBal.toLocaleString() : '—';
|
|
263
|
+
const balPadded = balStr.padEnd(col.bal);
|
|
264
|
+
const earnedStr = stripAnsi(fmtCoins(totalCoins));
|
|
236
265
|
|
|
237
|
-
|
|
238
|
-
process.stdout.write(
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
process.stdout.write(`${' '.repeat(col.name)} `);
|
|
243
|
-
process.stdout.write(`${' '.repeat(col.cmd)} `);
|
|
244
|
-
process.stdout.write(`${padL(totalBal > 0 ? '⏣' + totalBal.toLocaleString() : '—', col.bal)} `);
|
|
245
|
-
process.stdout.write(`${' '.repeat(col.ls)} `);
|
|
246
|
-
process.stdout.write(`${' '.repeat(col.lv)} `);
|
|
247
|
-
process.stdout.write(`${fmtCoins(totalCoins)} `);
|
|
248
|
-
process.stdout.write(`${DIM}${fmtUptime()} | ${memMB}MB${c.reset} `.padEnd(_inner));
|
|
249
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
266
|
+
row(r++); clrLine();
|
|
267
|
+
process.stdout.write(
|
|
268
|
+
`${B}│\x1b[0m ${c.bold}Σ${c.reset} ${DIM}${padL(_workers.length, 2)} acc${c.reset} ${' '.repeat(col.name + col.cmd)} ${balPadded} ${' '.repeat(col.ls + col.lv)} ${fmtCoins(totalCoins)}${' '.repeat(_inner - 2 - stripAnsi(` ${c.bold}Σ${c.reset} ${DIM}${padL(_workers.length, 2)} acc${c.reset} ${' '.repeat(col.name + col.cmd)} ${balPadded} ${' '.repeat(col.ls + col.lv)} ${fmtCoins(totalCoins)}`))}${B}│`
|
|
269
|
+
);
|
|
270
|
+
ln();
|
|
250
271
|
|
|
251
272
|
// ── Bottom ──
|
|
252
|
-
|
|
273
|
+
_bottomRow = r;
|
|
274
|
+
row(r++); clrLine();
|
|
275
|
+
process.stdout.write(`${B}└${'─'.repeat(_inner)}┘`);
|
|
276
|
+
ln();
|
|
253
277
|
}
|
|
254
278
|
|
|
255
|
-
// ── Update ONE account row in
|
|
279
|
+
// ── Update ONE account row in-place ─────────────────────────
|
|
256
280
|
function updateAccountRow(accountIdx) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
281
|
+
if (!_live) return;
|
|
282
|
+
const rowNum = _accountRows[accountIdx];
|
|
283
|
+
if (!rowNum) return; // not visible
|
|
260
284
|
const w = _workers[accountIdx];
|
|
261
285
|
if (!w) return;
|
|
286
|
+
const col = getCol();
|
|
262
287
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// Move cursor to this row, column 1
|
|
268
|
-
process.stdout.write(`\x1b[${row};1H`);
|
|
269
|
-
process.stdout.write(buildAccountRow(w, accountIdx, col));
|
|
288
|
+
row(rowNum); clrLine();
|
|
289
|
+
process.stdout.write(accountRow(w, accountIdx, col));
|
|
290
|
+
ln();
|
|
270
291
|
}
|
|
271
292
|
|
|
272
293
|
// ── Gradient line ─────────────────────────────────────────────
|
|
@@ -280,7 +301,7 @@ function gradientLine(text, r1, g1, b1, r2, g2, b2) {
|
|
|
280
301
|
}
|
|
281
302
|
|
|
282
303
|
// ── Event tracking ────────────────────────────────────────────
|
|
283
|
-
let _eventLines = [];
|
|
304
|
+
let _eventLines = [];
|
|
284
305
|
const MAX_EVENTS = 15;
|
|
285
306
|
|
|
286
307
|
// ── Public API ────────────────────────────────────────────────
|
|
@@ -307,7 +328,6 @@ function stop() {
|
|
|
307
328
|
function setLive(val) { _live = val; }
|
|
308
329
|
function setPhase(phase) { _phase = phase; }
|
|
309
330
|
|
|
310
|
-
// log: update account row in-place + append event below box
|
|
311
331
|
function log(accountIdx, msg) {
|
|
312
332
|
const now = new Date();
|
|
313
333
|
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
@@ -316,13 +336,11 @@ function log(accountIdx, msg) {
|
|
|
316
336
|
if (!_eventLines[accountIdx]) _eventLines[accountIdx] = [];
|
|
317
337
|
_eventLines[accountIdx].push({ text: msg, ts });
|
|
318
338
|
if (_eventLines[accountIdx].length > MAX_EVENTS) _eventLines[accountIdx].shift();
|
|
319
|
-
// Update the row in place
|
|
320
339
|
if (_live) updateAccountRow(accountIdx);
|
|
321
340
|
}
|
|
322
341
|
|
|
323
342
|
if (!_live) return;
|
|
324
343
|
|
|
325
|
-
// Append event below the box (after the bottom border)
|
|
326
344
|
const col2 = accountIdx >= 0 ? wc(accountIdx) : c.cyan;
|
|
327
345
|
const name = accountIdx >= 0 ? trunc(_workers[accountIdx]?.username || '?', 14) : 'GLOBAL';
|
|
328
346
|
process.stdout.write(`${col2}${name}${c.reset} ${DIM}[${ts}]${c.reset} ${msg}\n`);
|
|
@@ -330,22 +348,17 @@ function log(accountIdx, msg) {
|
|
|
330
348
|
|
|
331
349
|
function logGlobal(msg) { log(-1, msg); }
|
|
332
350
|
|
|
333
|
-
//
|
|
334
|
-
function fullRedraw() { if (_live) draw(); }
|
|
335
|
-
|
|
336
|
-
// ── Periodic refresh: animate spinners & countdowns every 3s ──
|
|
351
|
+
// ── Refresh ──────────────────────────────────────────────────
|
|
337
352
|
let _refreshTimer = null;
|
|
338
353
|
function startRefresh() {
|
|
339
354
|
if (_refreshTimer) return;
|
|
340
355
|
_refreshTimer = setInterval(() => {
|
|
341
356
|
if (!_live) return;
|
|
342
|
-
for (let i = 0; i < _workers.length; i++)
|
|
343
|
-
updateAccountRow(i);
|
|
344
|
-
}
|
|
357
|
+
for (let i = 0; i < _workers.length; i++) updateAccountRow(i);
|
|
345
358
|
}, 1500);
|
|
346
359
|
}
|
|
347
360
|
function stopRefresh() {
|
|
348
361
|
if (_refreshTimer) { clearInterval(_refreshTimer); _refreshTimer = null; }
|
|
349
362
|
}
|
|
350
363
|
|
|
351
|
-
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop, setLive, setPhase, updateAccountRow,
|
|
364
|
+
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop, setLive, setPhase, updateAccountRow, startRefresh, stopRefresh };
|