dankgrinder 7.67.0 → 7.68.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 +122 -101
- package/package.json +1 -1
package/lib/dashboard.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DankGrinder CLI Dashboard
|
|
3
3
|
*
|
|
4
|
-
* Simple
|
|
5
|
-
*
|
|
4
|
+
* Fresh rewrite. Simple line-by-line overwrite — no full clear, no cursor tricks.
|
|
5
|
+
* Every render writes N rows from line 1 onward. Previous content is overwritten.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
'use strict';
|
|
15
17
|
|
|
16
|
-
//
|
|
17
|
-
let _c, _rgb,
|
|
18
|
+
// Local refs (set per render)
|
|
19
|
+
let _c, _rgb, _spinnerFn, _gradientFn;
|
|
18
20
|
|
|
19
|
-
function
|
|
21
|
+
function init(ctx) {
|
|
20
22
|
_c = ctx.c;
|
|
21
23
|
_rgb = ctx.rgb;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
_spinnerFn = ctx.getSpinner;
|
|
25
|
+
_gradientFn = ctx.gradientText;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// ── Helpers ─────────────────────────────────────────────────────
|
|
@@ -33,6 +35,10 @@ function pad(str, width) {
|
|
|
33
35
|
return str + ' '.repeat(Math.max(0, width - vis(str)));
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
function clearLine(content) {
|
|
39
|
+
return `${_c.clearLine}\r${content}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
function fmtCoins(n) {
|
|
37
43
|
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
38
44
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -55,12 +61,12 @@ function fmtUptime(startTime) {
|
|
|
55
61
|
function renderDashboard(ctx) {
|
|
56
62
|
if (!ctx.dashboardStarted || ctx.workers.length === 0 || ctx.dashboardRendering) return;
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
init(ctx);
|
|
59
65
|
|
|
60
|
-
const _ = _c.reset;
|
|
61
66
|
const tw = Math.max(process.stdout.columns || 80, 60);
|
|
67
|
+
const _ = _c.reset;
|
|
62
68
|
|
|
63
|
-
// Color
|
|
69
|
+
// Color shortcuts
|
|
64
70
|
const A = _rgb(139, 92, 246);
|
|
65
71
|
const G = _rgb(52, 211, 153);
|
|
66
72
|
const Au = _rgb(255, 215, 0);
|
|
@@ -70,49 +76,30 @@ function renderDashboard(ctx) {
|
|
|
70
76
|
const Y = _rgb(251, 191, 36);
|
|
71
77
|
const W = _c.white;
|
|
72
78
|
const D = _c.dim;
|
|
73
|
-
const CL = _c.clearLine;
|
|
74
79
|
|
|
75
|
-
// ── Aggregate
|
|
76
|
-
let aggBalance = 0, aggCoins = 0,
|
|
80
|
+
// ── Aggregate stats ───────────────────────────────────────────
|
|
81
|
+
let aggBalance = 0, aggCoins = 0, aggCmds = 0, aggErrors = 0;
|
|
77
82
|
for (const w of ctx.workers) {
|
|
78
|
-
aggBalance
|
|
79
|
-
aggCoins
|
|
80
|
-
|
|
81
|
-
aggErrors
|
|
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;
|
|
82
87
|
}
|
|
83
|
-
const successRate =
|
|
88
|
+
const successRate = aggCmds > 0 ? Math.round(((aggCmds - aggErrors) / aggCmds) * 100) : 100;
|
|
84
89
|
const elapsedHrs = (Date.now() - ctx.startTime) / 3_600_000;
|
|
85
|
-
const
|
|
90
|
+
const perHr = elapsedHrs > 0.01 ? Math.round(aggCoins / elapsedHrs) : 0;
|
|
86
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');
|
|
87
94
|
|
|
88
95
|
const activeCount = ctx.workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
89
96
|
const invalidCount = ctx.workers.filter(w => w._tokenInvalid).length;
|
|
90
97
|
const pausedCount = ctx.workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
91
98
|
const recovCount = ctx.workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
92
|
-
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
93
|
-
const spin = _getSpinner('braille');
|
|
94
99
|
|
|
95
|
-
// ── Line builder helper ───────────────────────────────────────
|
|
96
|
-
// prefix + content padded to tw + suffix
|
|
97
|
-
function line(prefix, content, suffix) {
|
|
98
|
-
const raw = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
99
|
-
const padLen = Math.max(0, tw - vis(prefix) - raw.length - vis(suffix));
|
|
100
|
-
return `${prefix}${content}${' '.repeat(padLen)}${suffix}`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const dash = D + '─'.repeat(tw) + _;
|
|
104
|
-
|
|
105
|
-
// ── HEADER ────────────────────────────────────────────────────
|
|
106
|
-
const title = _c.bold + _gradientText(' DANK GRINDER ', [139, 92, 246], [52, 211, 153]) + _;
|
|
107
100
|
const modeTag = ctx.CLOUD_MODE ? `${Cy}CLOUD${_}` : (ctx.CLUSTER_ENABLED ? `${Cy}CLUSTER${_}` : `${D}local${_}`);
|
|
108
|
-
const activeTag = `${G}${activeCount}/${W}${ctx.workers.length}${_} active`;
|
|
109
|
-
const invTag = invalidCount > 0 ? ` ${R}${invalidCount} inv${_}` : '';
|
|
110
|
-
const pauseTag = pausedCount > 0 ? ` ${Y}${pausedCount} pause${_}` : '';
|
|
111
|
-
const recovTag = recovCount > 0 ? ` ${O}${recovCount} recov${_}` : '';
|
|
112
|
-
|
|
113
|
-
const h1 = `${title} ${D}v${ctx.PKG_VERSION}${_} ${G}${spin}${_} ${D}up${_} ${W}${fmtUptime(ctx.startTime)}${_} ${D}bal${_} ${Au}⏣${_}${W}${fmtCoins(aggBalance)}${_} ${D}/h${_} ${G}+⏣${fmtCoins(coinsPerHr)}${_} ${activeTag}${invTag}${pauseTag}${recovTag} ${D}${aggCommands}${_}cmds ${D}${cpmVal}${_}/min ${D}${memMB}MB ${modeTag}`;
|
|
114
101
|
|
|
115
|
-
// ──
|
|
102
|
+
// ── Column layout ─────────────────────────────────────────────
|
|
116
103
|
const colNum = 4;
|
|
117
104
|
const colSts = 3;
|
|
118
105
|
const colName = Math.max(16, Math.min(22, Math.floor(tw * 0.22)));
|
|
@@ -120,56 +107,77 @@ function renderDashboard(ctx) {
|
|
|
120
107
|
const colLvl = 5;
|
|
121
108
|
const colLs = 4;
|
|
122
109
|
const colEarn = 9;
|
|
123
|
-
const colAct = Math.max(8, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn -
|
|
110
|
+
const colAct = Math.max(8, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn - 12);
|
|
124
111
|
const gap = ' ';
|
|
125
112
|
|
|
126
|
-
|
|
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('╟'));
|
|
127
165
|
|
|
128
166
|
const sorted = [...ctx.workers].sort((a, b) => a.idx - b.idx);
|
|
129
167
|
const maxRows = Math.max(5, Math.min(sorted.length, Math.floor((process.stdout.rows || 24) - 12)));
|
|
130
168
|
const visible = sorted.slice(0, maxRows);
|
|
131
169
|
const extraRows = sorted.length - maxRows;
|
|
132
170
|
|
|
133
|
-
// ── LIVE FEED ─────────────────────────────────────────────────
|
|
134
|
-
const logs = ctx.recentLogs.toArray();
|
|
135
|
-
|
|
136
|
-
// ── FOOTER ───────────────────────────────────────────────────
|
|
137
|
-
const footer = `${modeTag} ${D}P=pause R=resume S=status Q=quit${_}`;
|
|
138
|
-
|
|
139
|
-
// ── WRITE ALL ROWS (overwrite from top) ─────────────────────
|
|
140
|
-
// We write \r + clear line to overwrite each line in place.
|
|
141
|
-
// No full screen clear — just overwrite. Simple and reliable.
|
|
142
|
-
|
|
143
|
-
let rowNum = 0;
|
|
144
|
-
|
|
145
|
-
function writeRow(content) {
|
|
146
|
-
process.stdout.write(`${CL}\r${content}\n`);
|
|
147
|
-
rowNum++;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 1. Header
|
|
151
|
-
writeRow(line(A + '╔', D + '═'.repeat(tw - 2), A + '╗'));
|
|
152
|
-
writeRow(line(`${A}║ `, h1, `${A} ║`));
|
|
153
|
-
writeRow(line(A + '╠', D + '═'.repeat(tw - 2), A + '╣'));
|
|
154
|
-
writeRow(line(`${A}║ `, `${D}session${_} ${G}${fmtCoins(aggCoins)}${_} ${D}coins${_} ${G}${successRate}%${_} ${D}success${_} ${W}${fmtCoins(ctx.sessionPeakCoins)}${_} ${D}peak${_}${ctx.isNewHigh ? ` ${Au}★ NEW HIGH${_}` : ''}`, `${A} ║`));
|
|
155
|
-
writeRow(line(A + '╚', D + '═'.repeat(tw - 2), A + '╝'));
|
|
156
|
-
|
|
157
|
-
// 2. Table header
|
|
158
|
-
writeRow(line(A + '╔', D + '─'.repeat(tw - 2), A + '╗'));
|
|
159
|
-
writeRow(line(`${A}║ `, headers, `${A} ║`));
|
|
160
|
-
writeRow(line(A + '╟', D + '─'.repeat(tw - 2), A + '╢'));
|
|
161
|
-
|
|
162
|
-
// 3. Account rows
|
|
163
171
|
for (const wk of visible) {
|
|
164
172
|
const isRecov = wk._recoveryAttempts > 0 && wk._errorCooldownUntil > Date.now();
|
|
165
173
|
|
|
166
174
|
let stsIcon;
|
|
167
|
-
if (wk._tokenInvalid)
|
|
168
|
-
else if (!wk.running)
|
|
169
|
-
else if (isRecov)
|
|
175
|
+
if (wk._tokenInvalid) stsIcon = `${R}✗${_}`;
|
|
176
|
+
else if (!wk.running) stsIcon = `${D}○${_}`;
|
|
177
|
+
else if (isRecov) stsIcon = `${O}${_spinnerFn('braille').substring(0, 1)}${_}`;
|
|
170
178
|
else if (wk.paused) stsIcon = `${R}⏸${_}`;
|
|
171
179
|
else if (wk.dashboardPaused) stsIcon = `${Y}⏸${_}`;
|
|
172
|
-
else if (wk.busy) stsIcon = `${G}${
|
|
180
|
+
else if (wk.busy) stsIcon = `${G}${_spinnerFn('pulse').substring(0, 1)}${_}`;
|
|
173
181
|
else stsIcon = `${G}●${_}`;
|
|
174
182
|
|
|
175
183
|
const name = (wk.username || wk.account.label || '?').substring(0, colName);
|
|
@@ -179,9 +187,9 @@ function renderDashboard(ctx) {
|
|
|
179
187
|
const lvlStr = lvl > 0 ? `${Cy}L${lvl}${_}` : `${D}L???${_}`;
|
|
180
188
|
const ls = wk._lifesavers;
|
|
181
189
|
let lsStr;
|
|
182
|
-
if (ls === 0)
|
|
190
|
+
if (ls === 0) lsStr = `${R}♥${ls}${_}`;
|
|
183
191
|
else if (ls != null && ls <= 2) lsStr = `${Y}♥${ls}${_}`;
|
|
184
|
-
else if (ls != null)
|
|
192
|
+
else if (ls != null) lsStr = `${G}♥${ls}${_}`;
|
|
185
193
|
else {
|
|
186
194
|
const p = ctx.PULSE_CHARS[Math.floor(Date.now() / 400) % ctx.PULSE_CHARS.length];
|
|
187
195
|
lsStr = `${D}${p}♥?${_}`;
|
|
@@ -194,26 +202,32 @@ function renderDashboard(ctx) {
|
|
|
194
202
|
const numStr = `${D}${pad(String(wk.idx + 1), colNum)}${_}`;
|
|
195
203
|
|
|
196
204
|
const rowStr = [
|
|
197
|
-
numStr,
|
|
198
|
-
pad(
|
|
199
|
-
pad(
|
|
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,
|
|
200
213
|
].join(gap);
|
|
201
214
|
|
|
202
|
-
|
|
215
|
+
rows.push(mkRow(rowStr));
|
|
203
216
|
}
|
|
204
217
|
|
|
205
218
|
if (extraRows > 0) {
|
|
206
|
-
|
|
219
|
+
rows.push(mkRow(`${D}+${extraRows} more${_}`));
|
|
207
220
|
}
|
|
208
221
|
|
|
209
|
-
|
|
222
|
+
rows.push(border('╚'));
|
|
210
223
|
|
|
211
|
-
//
|
|
224
|
+
// LIVE FEED
|
|
225
|
+
const logs = ctx.recentLogs.toArray();
|
|
212
226
|
if (logs.length > 0) {
|
|
213
|
-
const feedTitle = `${
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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('╟'));
|
|
217
231
|
|
|
218
232
|
for (const entry of logs) {
|
|
219
233
|
let lineText;
|
|
@@ -228,17 +242,24 @@ function renderDashboard(ctx) {
|
|
|
228
242
|
} else {
|
|
229
243
|
lineText = String(entry);
|
|
230
244
|
}
|
|
231
|
-
|
|
245
|
+
rows.push(mkRow(`${D}${lineText}${_}`));
|
|
232
246
|
}
|
|
233
|
-
|
|
247
|
+
rows.push(border('╚'));
|
|
234
248
|
}
|
|
235
249
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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: write all rows, each clears its line first ─────────
|
|
256
|
+
// This is how top/htop work — overwrite each row in place.
|
|
257
|
+
// No full-screen clear, no cursor tricks. Simple and reliable.
|
|
258
|
+
for (const row of rows) {
|
|
259
|
+
process.stdout.write(`${_c.clearLine}\r${row}\n`);
|
|
260
|
+
}
|
|
240
261
|
|
|
241
|
-
return
|
|
262
|
+
return rows.length;
|
|
242
263
|
}
|
|
243
264
|
|
|
244
265
|
module.exports = { renderDashboard };
|