dankgrinder 7.71.0 → 7.73.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/dashboard.js +4 -269
- package/lib/grinder.js +36 -249
- package/package.json +1 -1
package/lib/dashboard.js
CHANGED
|
@@ -1,271 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Every render writes N rows from line 1 onward. Previous content is overwritten.
|
|
6
|
-
*
|
|
7
|
-
* Context:
|
|
8
|
-
* workers, dashboardStarted, dashboardRendering, dashboardLines,
|
|
9
|
-
* totalBalance, totalCoins, totalCommands, startTime,
|
|
10
|
-
* sessionPeakCoins, isNewHigh, recentLogs, globalCmdRate,
|
|
11
|
-
* CLOUD_MODE, CLUSTER_ENABLED, PKG_VERSION,
|
|
12
|
-
* AccountWorker, PULSE_CHARS, getSpinner, gradientText,
|
|
13
|
-
* rgb, c
|
|
2
|
+
* CLI Dashboard — REMOVED
|
|
3
|
+
* Dashboard functionality has been removed from the CLI.
|
|
4
|
+
* All startup output goes through simple console.log.
|
|
14
5
|
*/
|
|
15
|
-
|
|
16
|
-
'use strict';
|
|
17
|
-
|
|
18
|
-
// Local refs (set per render)
|
|
19
|
-
let _c, _rgb, _spinnerFn, _gradientFn;
|
|
20
|
-
|
|
21
|
-
function init(ctx) {
|
|
22
|
-
_c = ctx.c;
|
|
23
|
-
_rgb = ctx.rgb;
|
|
24
|
-
_spinnerFn = ctx.getSpinner;
|
|
25
|
-
_gradientFn = ctx.gradientText;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ── Helpers ─────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
function vis(s) {
|
|
31
|
-
return String(s).replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function pad(str, width) {
|
|
35
|
-
return str + ' '.repeat(Math.max(0, width - vis(str)));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function clearLine(content) {
|
|
39
|
-
return `${_c.clearLine}\r${content}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function fmtCoins(n) {
|
|
43
|
-
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
44
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
45
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
46
|
-
return n.toLocaleString();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function fmtUptime(startTime) {
|
|
50
|
-
const s = Math.floor((Date.now() - startTime) / 1000);
|
|
51
|
-
const h = Math.floor(s / 3600);
|
|
52
|
-
const m = Math.floor((s % 3600) / 60);
|
|
53
|
-
const sec = s % 60;
|
|
54
|
-
if (h > 0) return `${h}h ${m}m ${sec}s`;
|
|
55
|
-
if (m > 0) return `${m}m ${sec}s`;
|
|
56
|
-
return `${sec}s`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ── Render ──────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
function renderDashboard(ctx) {
|
|
62
|
-
if (!ctx.dashboardStarted || ctx.workers.length === 0 || ctx.dashboardRendering) return;
|
|
63
|
-
|
|
64
|
-
init(ctx);
|
|
65
|
-
|
|
66
|
-
const tw = Math.max(process.stdout.columns || 80, 60);
|
|
67
|
-
const _ = _c.reset;
|
|
68
|
-
|
|
69
|
-
// Color shortcuts
|
|
70
|
-
const A = _rgb(139, 92, 246);
|
|
71
|
-
const G = _rgb(52, 211, 153);
|
|
72
|
-
const Au = _rgb(255, 215, 0);
|
|
73
|
-
const O = _rgb(251, 146, 60);
|
|
74
|
-
const Cy = _rgb(34, 211, 238);
|
|
75
|
-
const R = _rgb(239, 68, 68);
|
|
76
|
-
const Y = _rgb(251, 191, 36);
|
|
77
|
-
const W = _c.white;
|
|
78
|
-
const D = _c.dim;
|
|
79
|
-
|
|
80
|
-
// ── Aggregate stats ───────────────────────────────────────────
|
|
81
|
-
let aggBalance = 0, aggCoins = 0, aggCmds = 0, aggErrors = 0;
|
|
82
|
-
for (const w of ctx.workers) {
|
|
83
|
-
aggBalance += (w.stats.balance || 0) + (w.stats.bankBalance || 0);
|
|
84
|
-
aggCoins += w.stats.coins || 0;
|
|
85
|
-
aggCmds += w.stats.commands || 0;
|
|
86
|
-
aggErrors += w.stats.errors || 0;
|
|
87
|
-
}
|
|
88
|
-
const successRate = aggCmds > 0 ? Math.round(((aggCmds - aggErrors) / aggCmds) * 100) : 100;
|
|
89
|
-
const elapsedHrs = (Date.now() - ctx.startTime) / 3_600_000;
|
|
90
|
-
const perHr = elapsedHrs > 0.01 ? Math.round(aggCoins / elapsedHrs) : 0;
|
|
91
|
-
const cpmVal = ctx.globalCmdRate.getRate().toFixed(1);
|
|
92
|
-
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
93
|
-
const spin = _spinnerFn('braille');
|
|
94
|
-
|
|
95
|
-
const activeCount = ctx.workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
96
|
-
const invalidCount = ctx.workers.filter(w => w._tokenInvalid).length;
|
|
97
|
-
const pausedCount = ctx.workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
98
|
-
const recovCount = ctx.workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
99
|
-
|
|
100
|
-
const modeTag = ctx.CLOUD_MODE ? `${Cy}CLOUD${_}` : (ctx.CLUSTER_ENABLED ? `${Cy}CLUSTER${_}` : `${D}local${_}`);
|
|
101
|
-
|
|
102
|
-
// ── Column layout ─────────────────────────────────────────────
|
|
103
|
-
const colNum = 4;
|
|
104
|
-
const colSts = 3;
|
|
105
|
-
const colName = Math.max(16, Math.min(22, Math.floor(tw * 0.22)));
|
|
106
|
-
const colBal = 10;
|
|
107
|
-
const colLvl = 5;
|
|
108
|
-
const colLs = 4;
|
|
109
|
-
const colEarn = 9;
|
|
110
|
-
const colAct = Math.max(8, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn - 12);
|
|
111
|
-
const gap = ' ';
|
|
112
|
-
|
|
113
|
-
// ── Helpers to build rows ─────────────────────────────────────
|
|
114
|
-
const border = (char) => `${A}${char}${D}${'─'.repeat(tw - 2)}${A}${char}${_}`;
|
|
115
|
-
|
|
116
|
-
function mkRow(content, leftPad = ' ') {
|
|
117
|
-
const raw = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
118
|
-
const padLen = Math.max(0, tw - 4 - raw.length);
|
|
119
|
-
return `${A}│${_}${leftPad}${content}${' '.repeat(padLen)}${leftPad}${A}│${_}`;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ── Build all rows ────────────────────────────────────────────
|
|
123
|
-
const rows = [];
|
|
124
|
-
|
|
125
|
-
// HEADER
|
|
126
|
-
const title = _c.bold + _gradientFn(' DANK GRINDER ', [139, 92, 246], [52, 211, 153]) + _;
|
|
127
|
-
const h1 = [
|
|
128
|
-
title,
|
|
129
|
-
`${D}v${ctx.PKG_VERSION}${_}`,
|
|
130
|
-
`${G}${spin}${_}`,
|
|
131
|
-
`${D}up${_} ${W}${fmtUptime(ctx.startTime)}${_}`,
|
|
132
|
-
`${D}bal${_} ${Au}⏣${_}${W}${fmtCoins(aggBalance)}${_}`,
|
|
133
|
-
`${G}+⏣${fmtCoins(perHr)}${_}/h`,
|
|
134
|
-
`${G}${activeCount}${_}/${W}${ctx.workers.length}${_} active`,
|
|
135
|
-
invalidCount > 0 ? ` ${R}${invalidCount} inv${_}` : '',
|
|
136
|
-
pausedCount > 0 ? ` ${Y}${pausedCount} pause${_}` : '',
|
|
137
|
-
recovCount > 0 ? ` ${O}${recovCount} recov${_}` : '',
|
|
138
|
-
` ${D}${aggCmds}${_}cmds`,
|
|
139
|
-
` ${D}${cpmVal}${_}/min`,
|
|
140
|
-
` ${D}${memMB}MB`,
|
|
141
|
-
modeTag,
|
|
142
|
-
].filter(Boolean).join(' ');
|
|
143
|
-
|
|
144
|
-
rows.push(border('╔'));
|
|
145
|
-
rows.push(mkRow(h1));
|
|
146
|
-
rows.push(border('╠'));
|
|
147
|
-
rows.push(mkRow(`${D}session${_} ${G}${fmtCoins(aggCoins)}${_} coins ${G}${successRate}%${_} success ${W}${fmtCoins(ctx.sessionPeakCoins)}${_} peak${ctx.isNewHigh ? ` ${Au}★ NEW HIGH${_}` : ''}`));
|
|
148
|
-
rows.push(border('╚'));
|
|
149
|
-
|
|
150
|
-
// ACCOUNTS TABLE
|
|
151
|
-
const headers = [
|
|
152
|
-
`${D}${pad('#', colNum)}${_}`,
|
|
153
|
-
`${D}${pad('S', colSts)}${_}`,
|
|
154
|
-
`${_gradientFn(pad('Account', colName), [139, 92, 246], [96, 165, 250])}${_}`,
|
|
155
|
-
`${D}${pad('Balance', colBal)}${_}`,
|
|
156
|
-
`${D}${pad('Lvl', colLvl)}${_}`,
|
|
157
|
-
`${D}${pad('LS', colLs)}${_}`,
|
|
158
|
-
`${D}${pad('Earned', colEarn)}${_}`,
|
|
159
|
-
`${D}${pad('Activity', colAct)}${_}`,
|
|
160
|
-
].join(gap);
|
|
161
|
-
|
|
162
|
-
rows.push(border('╔'));
|
|
163
|
-
rows.push(mkRow(headers));
|
|
164
|
-
rows.push(border('╟'));
|
|
165
|
-
|
|
166
|
-
const sorted = [...ctx.workers].sort((a, b) => a.idx - b.idx);
|
|
167
|
-
const maxRows = Math.max(5, Math.min(sorted.length, Math.floor((process.stdout.rows || 24) - 12)));
|
|
168
|
-
const visible = sorted.slice(0, maxRows);
|
|
169
|
-
const extraRows = sorted.length - maxRows;
|
|
170
|
-
|
|
171
|
-
for (const wk of visible) {
|
|
172
|
-
const isRecov = wk._recoveryAttempts > 0 && wk._errorCooldownUntil > Date.now();
|
|
173
|
-
|
|
174
|
-
let stsIcon;
|
|
175
|
-
if (wk._tokenInvalid) stsIcon = `${R}✗${_}`;
|
|
176
|
-
else if (!wk.running) stsIcon = `${D}○${_}`;
|
|
177
|
-
else if (isRecov) stsIcon = `${O}${_spinnerFn('braille').substring(0, 1)}${_}`;
|
|
178
|
-
else if (wk.paused) stsIcon = `${R}⏸${_}`;
|
|
179
|
-
else if (wk.dashboardPaused) stsIcon = `${Y}⏸${_}`;
|
|
180
|
-
else if (wk.busy) stsIcon = `${G}${_spinnerFn('pulse').substring(0, 1)}${_}`;
|
|
181
|
-
else stsIcon = `${G}●${_}`;
|
|
182
|
-
|
|
183
|
-
const name = (wk.username || wk.account.label || '?').substring(0, colName);
|
|
184
|
-
const nameStr = `${wk.color}${name}${_}`;
|
|
185
|
-
const balStr = wk.stats.balance > 0 ? `${Au}⏣${_}${W}${fmtCoins(wk.stats.balance)}${_}` : `${D}⏣-${_}`;
|
|
186
|
-
const lvl = wk._level || 0;
|
|
187
|
-
const lvlStr = lvl > 0 ? `${Cy}L${lvl}${_}` : `${D}L???${_}`;
|
|
188
|
-
const ls = wk._lifesavers;
|
|
189
|
-
let lsStr;
|
|
190
|
-
if (ls === 0) lsStr = `${R}♥${ls}${_}`;
|
|
191
|
-
else if (ls != null && ls <= 2) lsStr = `${Y}♥${ls}${_}`;
|
|
192
|
-
else if (ls != null) lsStr = `${G}♥${ls}${_}`;
|
|
193
|
-
else {
|
|
194
|
-
const p = ctx.PULSE_CHARS[Math.floor(Date.now() / 400) % ctx.PULSE_CHARS.length];
|
|
195
|
-
lsStr = `${D}${p}♥?${_}`;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const earn = wk.stats.coins || 0;
|
|
199
|
-
const earnStr = earn > 0 ? `${G}+${fmtCoins(earn)}${_}` : `${D}────${_}`;
|
|
200
|
-
const actRaw = (wk.lastStatus || 'ready').replace(/\x1b\[[0-9;]*m/g, '').substring(0, colAct);
|
|
201
|
-
const actStr = `${D}${pad(actRaw, colAct)}${_}`;
|
|
202
|
-
const numStr = `${D}${pad(String(wk.idx + 1), colNum)}${_}`;
|
|
203
|
-
|
|
204
|
-
const rowStr = [
|
|
205
|
-
numStr,
|
|
206
|
-
pad(stsIcon, colSts),
|
|
207
|
-
pad(nameStr, colName),
|
|
208
|
-
pad(balStr, colBal),
|
|
209
|
-
pad(lvlStr, colLvl),
|
|
210
|
-
pad(lsStr, colLs),
|
|
211
|
-
pad(earnStr, colEarn),
|
|
212
|
-
actStr,
|
|
213
|
-
].join(gap);
|
|
214
|
-
|
|
215
|
-
rows.push(mkRow(rowStr));
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (extraRows > 0) {
|
|
219
|
-
rows.push(mkRow(`${D}+${extraRows} more${_}`));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
rows.push(border('╚'));
|
|
223
|
-
|
|
224
|
-
// LIVE FEED
|
|
225
|
-
const logs = ctx.recentLogs.toArray();
|
|
226
|
-
if (logs.length > 0) {
|
|
227
|
-
const feedTitle = `${_gradientFn(' LIVE FEED ', [139, 92, 246], [52, 211, 153])}${_} ${G}${_spinnerFn('pulse')}${_}`;
|
|
228
|
-
rows.push(border('╔'));
|
|
229
|
-
rows.push(mkRow(feedTitle));
|
|
230
|
-
rows.push(border('╟'));
|
|
231
|
-
|
|
232
|
-
for (const entry of logs) {
|
|
233
|
-
let lineText;
|
|
234
|
-
if (typeof entry === 'string') {
|
|
235
|
-
lineText = entry;
|
|
236
|
-
} else if (entry && typeof entry === 'object') {
|
|
237
|
-
const ts = entry.ts ? new Date(entry.ts).toLocaleTimeString('en-US', { hour12: false }) : '';
|
|
238
|
-
const user = entry.username ? String(entry.username) : '';
|
|
239
|
-
const cmd = entry.command ? `[${entry.command}]` : '';
|
|
240
|
-
const resp = entry.response || '';
|
|
241
|
-
lineText = `${D}${ts}${_} ${entry.color || D}${user}${_} ${D}${cmd}${_} ${resp}`;
|
|
242
|
-
} else {
|
|
243
|
-
lineText = String(entry);
|
|
244
|
-
}
|
|
245
|
-
rows.push(mkRow(`${D}${lineText}${_}`));
|
|
246
|
-
}
|
|
247
|
-
rows.push(border('╚'));
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// FOOTER
|
|
251
|
-
rows.push(border('╔'));
|
|
252
|
-
rows.push(mkRow(`${modeTag} ${D}P=pause R=resume S=status Q=quit${_}`));
|
|
253
|
-
rows.push(border('╚'));
|
|
254
|
-
|
|
255
|
-
// ── Flush: overwrite rows from line 1, then blank remaining screen ──
|
|
256
|
-
const rowsOnScreen = process.stdout.rows || 24;
|
|
257
|
-
for (const row of rows) {
|
|
258
|
-
process.stdout.write(`${_c.clearLine}\r${row}\n`);
|
|
259
|
-
}
|
|
260
|
-
// Fill remaining screen lines with blanks so old startup logs don't bleed through
|
|
261
|
-
const extra = rowsOnScreen - rows.length;
|
|
262
|
-
for (let i = 0; i < extra; i++) {
|
|
263
|
-
process.stdout.write(`${_c.clearLine}\r${' '.repeat(tw)}\n`);
|
|
264
|
-
}
|
|
265
|
-
// Move cursor to end so terminal doesn't scroll on next \r write
|
|
266
|
-
process.stdout.write(`\x1b[${rows.length};1H`);
|
|
267
|
-
|
|
268
|
-
return rows.length;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
module.exports = { renderDashboard };
|
|
6
|
+
module.exports = {};
|
package/lib/grinder.js
CHANGED
|
@@ -3,7 +3,6 @@ const Redis = require('ioredis');
|
|
|
3
3
|
const commands = require('./commands');
|
|
4
4
|
const { setDashboardActive, isCV2, ensureCV2, stripAnsi } = require('./commands/utils');
|
|
5
5
|
const rawLogger = require('./rawLogger');
|
|
6
|
-
const { renderDashboard: renderDashboardImpl } = require('./dashboard');
|
|
7
6
|
const {
|
|
8
7
|
BloomFilter, RingBuffer, TokenBucket, EMA, SlidingWindowCounter,
|
|
9
8
|
AhoCorasick, LRUCache, StringPool, AsyncBatchQueue, JitterBackoff,
|
|
@@ -336,123 +335,17 @@ function colorBanner() {
|
|
|
336
335
|
return out;
|
|
337
336
|
}
|
|
338
337
|
|
|
339
|
-
// ──
|
|
340
|
-
let dashboardLines = 0;
|
|
341
|
-
let dashboardStarted = false;
|
|
342
|
-
let dashboardRendering = false;
|
|
343
|
-
let lastRenderTime = 0;
|
|
344
|
-
let renderPending = false;
|
|
345
|
-
let totalBalance = 0;
|
|
346
|
-
let totalCoins = 0;
|
|
347
|
-
let totalCommands = 0;
|
|
348
|
-
let startTime = Date.now();
|
|
349
|
-
let shutdownCalled = false;
|
|
350
|
-
let sessionPeakCoins = 0;
|
|
351
|
-
let isNewHigh = false;
|
|
352
|
-
// RingBuffer: O(1) push, bounded memory, no array shifting or GC pressure
|
|
353
|
-
const recentLogs = new RingBuffer(8);
|
|
354
|
-
const MAX_LOGS = 8;
|
|
355
|
-
const RENDER_THROTTLE_MS = 200;
|
|
356
|
-
// Earnings history for sparkline (sample every 10 seconds)
|
|
357
|
-
const earningsHistory = new RingBuffer(30);
|
|
358
|
-
let lastEarningsSample = 0;
|
|
359
|
-
// Per-command stats tracking
|
|
360
|
-
const cmdStats = new Map();
|
|
361
|
-
// Coins per minute history for rate graph
|
|
362
|
-
const cpmHistory = new RingBuffer(20);
|
|
363
|
-
let lastCpmSample = 0;
|
|
364
|
-
|
|
365
|
-
function formatUptime() {
|
|
366
|
-
const s = Math.floor((Date.now() - startTime) / 1000);
|
|
367
|
-
const h = Math.floor(s / 3600);
|
|
368
|
-
const m = Math.floor((s % 3600) / 60);
|
|
369
|
-
const sec = s % 60;
|
|
370
|
-
if (h > 0) return `${h}h ${m}m ${sec}s`;
|
|
371
|
-
if (m > 0) return `${m}m ${sec}s`;
|
|
372
|
-
return `${sec}s`;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function formatCoins(n) {
|
|
376
|
-
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
377
|
-
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
378
|
-
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
379
|
-
return n.toLocaleString();
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function scheduleRender() {
|
|
383
|
-
if (renderPending || !dashboardStarted) return;
|
|
384
|
-
const now = Date.now();
|
|
385
|
-
const elapsed = now - lastRenderTime;
|
|
386
|
-
if (elapsed >= RENDER_THROTTLE_MS) {
|
|
387
|
-
renderDashboard();
|
|
388
|
-
} else {
|
|
389
|
-
renderPending = true;
|
|
390
|
-
setTimeout(() => { renderPending = false; renderDashboard(); }, RENDER_THROTTLE_MS - elapsed);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// ── Dashboard ──────────────────────────────────────────────────────────────────
|
|
395
|
-
// Thin wrapper: aggregates stats then delegates to ./dashboard.js
|
|
396
|
-
function renderDashboard() {
|
|
397
|
-
if (!dashboardStarted || workers.length === 0 || dashboardRendering || shutdownCalled) return;
|
|
398
|
-
dashboardRendering = true;
|
|
399
|
-
lastRenderTime = Date.now();
|
|
400
|
-
|
|
401
|
-
// Aggregate session totals
|
|
402
|
-
totalBalance = 0; totalCoins = 0; totalCommands = 0;
|
|
403
|
-
let totalErrors = 0;
|
|
404
|
-
for (const w of workers) {
|
|
405
|
-
totalBalance += (w.stats.balance || 0) + (w.stats.bankBalance || 0);
|
|
406
|
-
totalCoins += w.stats.coins || 0;
|
|
407
|
-
totalCommands += w.stats.commands || 0;
|
|
408
|
-
totalErrors += w.stats.errors || 0;
|
|
409
|
-
}
|
|
410
|
-
if (totalCoins > sessionPeakCoins) {
|
|
411
|
-
sessionPeakCoins = totalCoins;
|
|
412
|
-
isNewHigh = true;
|
|
413
|
-
setTimeout(() => { isNewHigh = false; }, 3000);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Pass all state into the dashboard module
|
|
417
|
-
const newLines = renderDashboardImpl({
|
|
418
|
-
workers, dashboardStarted, dashboardRendering, dashboardLines,
|
|
419
|
-
totalBalance, totalCoins, totalCommands, startTime,
|
|
420
|
-
sessionPeakCoins, isNewHigh, recentLogs, globalCmdRate,
|
|
421
|
-
earningsHistory, lastEarningsSample,
|
|
422
|
-
CLOUD_MODE, CLUSTER_ENABLED, PKG_VERSION,
|
|
423
|
-
AccountWorker, PULSE_CHARS, getSpinner, gradientText,
|
|
424
|
-
rgb, c, BOX,
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
if (newLines != null) dashboardLines = Math.max(dashboardLines, newLines);
|
|
428
|
-
dashboardRendering = false;
|
|
429
|
-
}
|
|
430
|
-
|
|
338
|
+
// ── Simple Logging ─────────────────────────────────────────────
|
|
431
339
|
function log(type, msg, label) {
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
cmd:
|
|
340
|
+
const colorIcons = {
|
|
341
|
+
info: `${c.dim}·${c.reset}`, success: `${rgb(52, 211, 153)}✓${c.reset}`,
|
|
342
|
+
error: `${rgb(239, 68, 68)}✗${c.reset}`, warn: `${rgb(251, 191, 36)}!${c.reset}`,
|
|
343
|
+
cmd: `${rgb(168, 85, 247)}▸${c.reset}`, coin: `${rgb(251, 191, 36)}$${c.reset}`,
|
|
344
|
+
buy: `${rgb(59, 130, 246)}♦${c.reset}`, bal: `${rgb(52, 211, 153)}◈${c.reset}`,
|
|
345
|
+
debug: `${c.dim}·${c.reset}`,
|
|
436
346
|
};
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
const tw = Math.max(process.stdout.columns || 80, 60);
|
|
440
|
-
if (dashboardStarted) {
|
|
441
|
-
const maxLen = tw - 8;
|
|
442
|
-
const entry = `${time} ${icons[type] || '·'} ${tagRaw ? tagRaw + ' ' : ''}${stripped}`;
|
|
443
|
-
recentLogs.push(entry.substring(0, maxLen));
|
|
444
|
-
scheduleRender();
|
|
445
|
-
} else {
|
|
446
|
-
const colorIcons = {
|
|
447
|
-
info: `${c.dim}·${c.reset}`, success: `${rgb(52, 211, 153)}✓${c.reset}`,
|
|
448
|
-
error: `${rgb(239, 68, 68)}✗${c.reset}`, warn: `${rgb(251, 191, 36)}!${c.reset}`,
|
|
449
|
-
cmd: `${rgb(168, 85, 247)}▸${c.reset}`, coin: `${rgb(251, 191, 36)}$${c.reset}`,
|
|
450
|
-
buy: `${rgb(59, 130, 246)}♦${c.reset}`, bal: `${rgb(52, 211, 153)}◈${c.reset}`,
|
|
451
|
-
debug: `${c.dim}·${c.reset}`,
|
|
452
|
-
};
|
|
453
|
-
const tagCol = label ? `${label} ` : '';
|
|
454
|
-
console.log(` ${colorIcons[type] || colorIcons.info} ${tagCol}${msg}`);
|
|
455
|
-
}
|
|
347
|
+
const tagCol = label ? `${label} ` : '';
|
|
348
|
+
console.log(` ${colorIcons[type] || colorIcons.info} ${tagCol}${msg}`);
|
|
456
349
|
}
|
|
457
350
|
|
|
458
351
|
async function fetchConfig(retries = 3, delayMs = 1500, opts = {}) {
|
|
@@ -884,7 +777,6 @@ class AccountWorker {
|
|
|
884
777
|
|
|
885
778
|
setStatus(text) {
|
|
886
779
|
this.lastStatus = stripAnsi(String(text || '')).replace(/\s+/g, ' ').trim();
|
|
887
|
-
if (dashboardStarted) scheduleRender();
|
|
888
780
|
}
|
|
889
781
|
|
|
890
782
|
waitForDankMemer(timeout = 15000) {
|
|
@@ -1497,34 +1389,32 @@ class AccountWorker {
|
|
|
1497
1389
|
|
|
1498
1390
|
// Update Redis with findings
|
|
1499
1391
|
if (redis) {
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
if (dashboardStarted) this.log('info', `DM level: ${c.bold}${currentLevel}${c.reset}`);
|
|
1505
|
-
}
|
|
1506
|
-
if (lastLifesaverCount >= 0) {
|
|
1507
|
-
await redis.set(`dkg:lifesavers:${this.account.id}`, String(lastLifesaverCount), 'EX', 86400);
|
|
1508
|
-
this._lifesavers = lastLifesaverCount;
|
|
1509
|
-
if (lastLifesaverCount === 0) {
|
|
1510
|
-
await redis.set(`raw:alert:no-lifesaver:${dm.id}`, '1', 'EX', 86400);
|
|
1511
|
-
await redis.set(`raw:alert:no-lifesaver:${this.channel?.id}`, '1', 'EX', 86400);
|
|
1512
|
-
if (dashboardStarted) this.log('error', `${c.red}0 LIFESAVERS! Crime/Search will be disabled.${c.reset}`);
|
|
1392
|
+
try {
|
|
1393
|
+
if (currentLevel > 0) {
|
|
1394
|
+
await redis.set(`dkg:level:${this.account.id}`, String(currentLevel), 'EX', 2592000);
|
|
1395
|
+
this._level = currentLevel;
|
|
1513
1396
|
}
|
|
1514
|
-
|
|
1397
|
+
if (lastLifesaverCount >= 0) {
|
|
1398
|
+
await redis.set(`dkg:lifesavers:${this.account.id}`, String(lastLifesaverCount), 'EX', 86400);
|
|
1399
|
+
this._lifesavers = lastLifesaverCount;
|
|
1400
|
+
if (lastLifesaverCount === 0) {
|
|
1401
|
+
await redis.set(`raw:alert:no-lifesaver:${dm.id}`, '1', 'EX', 86400);
|
|
1402
|
+
await redis.set(`raw:alert:no-lifesaver:${this.channel?.id}`, '1', 'EX', 86400);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
} catch { /* Redis errors non-fatal */ }
|
|
1515
1406
|
}
|
|
1516
1407
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
}
|
|
1408
|
+
return { deaths, levelUps, currentLevel, lifesavers: lastLifesaverCount, dmChannelId: dm.id };
|
|
1409
|
+
} catch (e) {
|
|
1410
|
+
lastError = e;
|
|
1411
|
+
if (attempt < maxRetries - 1) {
|
|
1412
|
+
await new Promise(r => setTimeout(r, delays[attempt]));
|
|
1523
1413
|
}
|
|
1524
1414
|
}
|
|
1525
|
-
if (dashboardStarted) this.log('debug', `DM check failed after ${maxRetries} attempts: ${lastError.message}`);
|
|
1526
|
-
return { deaths: 0, levelUps: 0, currentLevel: 0, lifesavers: -1 };
|
|
1527
1415
|
}
|
|
1416
|
+
return { deaths: 0, levelUps: 0, currentLevel: 0, lifesavers: -1 };
|
|
1417
|
+
}
|
|
1528
1418
|
|
|
1529
1419
|
// ── Run Single Command ──────────────────────────────────────
|
|
1530
1420
|
// Each modular command handler sends the command, waits for response,
|
|
@@ -2970,13 +2860,11 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2970
2860
|
}
|
|
2971
2861
|
}
|
|
2972
2862
|
}
|
|
2973
|
-
scheduleRender();
|
|
2974
2863
|
}
|
|
2975
2864
|
|
|
2976
2865
|
if (event.type === 'levelup') {
|
|
2977
2866
|
if (event.to > 0) {
|
|
2978
2867
|
w._level = event.to;
|
|
2979
|
-
scheduleRender();
|
|
2980
2868
|
}
|
|
2981
2869
|
}
|
|
2982
2870
|
}
|
|
@@ -3330,13 +3218,10 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3330
3218
|
const pulse = PULSE_CHARS[Math.floor(Date.now() / 400) % PULSE_CHARS.length];
|
|
3331
3219
|
parts.push(`${D}${pulse}♥?${c.reset}`);
|
|
3332
3220
|
}
|
|
3333
|
-
if (parts.length > 0) {
|
|
3334
|
-
recentLogs.push({ ts: Date.now(), username: w.username, color: w.color, command: 'dm check', response: parts.join(' '), status: 'ok' });
|
|
3335
|
-
}
|
|
3336
3221
|
} catch {}
|
|
3337
3222
|
}
|
|
3338
3223
|
if (dmNoLs.length > 0) {
|
|
3339
|
-
|
|
3224
|
+
log('warn', `⚠ No lifesavers: ${dmNoLs.join(', ')}`);
|
|
3340
3225
|
// Set Redis keys to block crime/search
|
|
3341
3226
|
for (const w of activeWorkers) {
|
|
3342
3227
|
if (dmNoLs.includes(w.username) && redis) {
|
|
@@ -3348,7 +3233,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3348
3233
|
}
|
|
3349
3234
|
}
|
|
3350
3235
|
if (dmUnknown.length > 0) {
|
|
3351
|
-
|
|
3236
|
+
log('warn', `⚠ Lifesavers unknown — live monitor: ${dmUnknown.join(', ')}`);
|
|
3352
3237
|
// Crime/search on these accounts will be skipped via safety hold until the live
|
|
3353
3238
|
// DM gateway listener detects a death (→ sets count) or confirms clean.
|
|
3354
3239
|
}
|
|
@@ -3356,7 +3241,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3356
3241
|
if (dmDeaths > 0) dmSummaryParts.push(`${dmDeaths} deaths`);
|
|
3357
3242
|
if (dmLevelUps > 0) dmSummaryParts.push(`${dmLevelUps} level-ups`);
|
|
3358
3243
|
if (dmUnknown.length > 0) dmSummaryParts.push(`${dmUnknown.length} pending`);
|
|
3359
|
-
|
|
3244
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} DM check: ${dmSummaryParts.length > 0 ? dmSummaryParts.join(', ') : 'clean — no deaths or level-ups'}`);
|
|
3360
3245
|
console.log('');
|
|
3361
3246
|
|
|
3362
3247
|
console.log(` ${rgb(139, 92, 246)}${c.bold}>>>${c.reset} ${gradientText('Starting grind loops...', [139, 92, 246], [52, 211, 153])}`);
|
|
@@ -3366,25 +3251,8 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3366
3251
|
if (!shutdownCalled) w.grindLoop();
|
|
3367
3252
|
}
|
|
3368
3253
|
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
setDashboardActive(true);
|
|
3372
|
-
|
|
3373
|
-
// Clear screen and position cursor at top-left before dashboard takes over
|
|
3374
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
3375
|
-
|
|
3376
|
-
// Setup keyboard shortcuts
|
|
3377
|
-
setupKeyboardShortcuts();
|
|
3378
|
-
|
|
3379
|
-
// Re-render on terminal resize so layout adapts to window size
|
|
3380
|
-
process.stdout.on('resize', () => {
|
|
3381
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
3382
|
-
dashboardLines = 0;
|
|
3383
|
-
scheduleRender();
|
|
3384
|
-
});
|
|
3385
|
-
|
|
3386
|
-
setInterval(() => scheduleRender(), 1000);
|
|
3387
|
-
scheduleRender();
|
|
3254
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} All grind loops started — ${activeWorkers.length} accounts active`);
|
|
3255
|
+
console.log(` v${PKG_VERSION} | press Ctrl+C to stop`);
|
|
3388
3256
|
|
|
3389
3257
|
// Cluster heartbeat — lets other nodes see this node is alive
|
|
3390
3258
|
if (CLUSTER_ENABLED) {
|
|
@@ -3441,7 +3309,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3441
3309
|
const before = workers.length;
|
|
3442
3310
|
// Keep ALL workers visible — never remove from array (user wants to see gaps)
|
|
3443
3311
|
// Only clean up workerMap entries for accounts fully removed from API
|
|
3444
|
-
if (workers.length !== before)
|
|
3312
|
+
if (workers.length !== before) { /* workers changed */ }
|
|
3445
3313
|
} catch {}
|
|
3446
3314
|
}, 10_000);
|
|
3447
3315
|
|
|
@@ -3450,18 +3318,9 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3450
3318
|
if (sigintHandled) return;
|
|
3451
3319
|
sigintHandled = true;
|
|
3452
3320
|
shutdownCalled = true;
|
|
3453
|
-
dashboardStarted = false;
|
|
3454
3321
|
setDashboardActive(false);
|
|
3455
3322
|
process.stdout.write(c.show);
|
|
3456
3323
|
|
|
3457
|
-
if (dashboardLines > 0) {
|
|
3458
|
-
process.stdout.write(c.cursorUp(dashboardLines));
|
|
3459
|
-
for (let i = 0; i < dashboardLines; i++) {
|
|
3460
|
-
process.stdout.write(c.clearLine + '\r\n');
|
|
3461
|
-
}
|
|
3462
|
-
process.stdout.write(c.cursorUp(dashboardLines));
|
|
3463
|
-
}
|
|
3464
|
-
|
|
3465
3324
|
const sepBar = rgb(139, 92, 246) + c.bold + '═'.repeat(tw) + c.reset;
|
|
3466
3325
|
console.log('');
|
|
3467
3326
|
console.log(` ${rgb(251, 191, 36)}${c.bold}Session Summary${c.reset}`);
|
|
@@ -3523,79 +3382,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3523
3382
|
}
|
|
3524
3383
|
|
|
3525
3384
|
// ══════════════════════════════════════════════════════════════
|
|
3526
|
-
//
|
|
3527
|
-
// ══════════════════════════════════════════════════════════════
|
|
3528
|
-
// Single-key shortcuts for common actions
|
|
3529
|
-
function setupKeyboardShortcuts() {
|
|
3530
|
-
if (process.stdin.isTTY) {
|
|
3531
|
-
process.stdin.setRawMode(true);
|
|
3532
|
-
process.stdin.resume();
|
|
3533
|
-
process.stdin.setEncoding('utf8');
|
|
3534
|
-
|
|
3535
|
-
// Premium styled keyboard shortcuts with gradient box
|
|
3536
|
-
const accent = rgb(139, 92, 246);
|
|
3537
|
-
const dim = c.dim;
|
|
3538
|
-
const kw = 60;
|
|
3539
|
-
console.log('');
|
|
3540
|
-
console.log(` ${accent}╭${'─'.repeat(kw)}╮${c.reset}`);
|
|
3541
|
-
console.log(` ${accent}│${c.reset} ${gradientText('KEYBOARD SHORTCUTS', [192, 132, 252], [52, 211, 153])}${' '.repeat(kw - 20)}${accent}│${c.reset}`);
|
|
3542
|
-
console.log(` ${accent}├${'─'.repeat(kw)}┤${c.reset}`);
|
|
3543
|
-
console.log(` ${accent}│${c.reset} ${rgb(96, 165, 250)}P${c.reset} ${dim}Pause all${c.reset} ${rgb(52, 211, 153)}R${c.reset} ${dim}Resume all${c.reset} ${rgb(251, 191, 36)}S${c.reset} ${dim}Status${c.reset} ${rgb(239, 68, 68)}Q${c.reset} ${dim}Quit${c.reset}${' '.repeat(Math.max(0, kw - 54))}${accent}│${c.reset}`);
|
|
3544
|
-
console.log(` ${accent}╰${'─'.repeat(kw)}╯${c.reset}`);
|
|
3545
|
-
console.log('');
|
|
3546
|
-
|
|
3547
|
-
process.stdin.on('data', (key) => {
|
|
3548
|
-
const k = key.toString().toLowerCase();
|
|
3549
|
-
|
|
3550
|
-
// Ctrl+C or q = quit
|
|
3551
|
-
if (k === '\u0003' || k === 'q') {
|
|
3552
|
-
process.stdout.write(c.show);
|
|
3553
|
-
console.log(`\n\n ${c.yellow}Shutting down gracefully...${c.reset}`);
|
|
3554
|
-
process.emit('SIGINT');
|
|
3555
|
-
return;
|
|
3556
|
-
}
|
|
3557
|
-
|
|
3558
|
-
// p = pause all accounts
|
|
3559
|
-
if (k === 'p') {
|
|
3560
|
-
let count = 0;
|
|
3561
|
-
workers.forEach(w => { if (w.running && !w.paused) { w.paused = true; count++; } });
|
|
3562
|
-
recentLogs.push(`>> PAUSED ${count} accounts (press R to resume)`);
|
|
3563
|
-
scheduleRender();
|
|
3564
|
-
return;
|
|
3565
|
-
}
|
|
3566
|
-
|
|
3567
|
-
// r = resume all accounts
|
|
3568
|
-
if (k === 'r') {
|
|
3569
|
-
let count = 0;
|
|
3570
|
-
workers.forEach(w => { if (w.paused) { w.paused = false; count++; } });
|
|
3571
|
-
recentLogs.push(`>> RESUMED ${count} accounts`);
|
|
3572
|
-
scheduleRender();
|
|
3573
|
-
return;
|
|
3574
|
-
}
|
|
3575
|
-
|
|
3576
|
-
// s = show status summary (pushed to log feed)
|
|
3577
|
-
if (k === 's') {
|
|
3578
|
-
const active = workers.filter(w => w.running && !w.paused).length;
|
|
3579
|
-
const paused = workers.filter(w => w.paused).length;
|
|
3580
|
-
const invalid = workers.filter(w => w._tokenInvalid).length;
|
|
3581
|
-
const offline = workers.filter(w => !w.running && !w._tokenInvalid).length;
|
|
3582
|
-
const recovering = workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
3583
|
-
const totalEarn = workers.reduce((s, w) => s + (w.stats.coins || 0), 0);
|
|
3584
|
-
recentLogs.push(`>> STATUS: ${active} active, ${paused} paused, ${invalid} invalid, ${offline} offline, ${recovering} recovering`);
|
|
3585
|
-
recentLogs.push(`>> EARNINGS: +${formatCoins(totalEarn)} this session | BALANCE: ${formatCoins(totalBalance)}`);
|
|
3586
|
-
scheduleRender();
|
|
3587
|
-
return;
|
|
3588
|
-
}
|
|
3589
|
-
|
|
3590
|
-
// ? or h = show help
|
|
3591
|
-
if (k === '?' || k === 'h') {
|
|
3592
|
-
recentLogs.push('>> SHORTCUTS: P=pause R=resume S=status Q=quit ?=help');
|
|
3593
|
-
scheduleRender();
|
|
3594
|
-
return;
|
|
3595
|
-
}
|
|
3596
|
-
});
|
|
3597
|
-
}
|
|
3598
|
-
}
|
|
3385
|
+
// Keyboard shortcuts removed — no display to update
|
|
3599
3386
|
|
|
3600
3387
|
// Export the start function for CLI
|
|
3601
3388
|
module.exports = { start };
|