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.
Files changed (2) hide show
  1. package/lib/dashboard.js +122 -101
  2. 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, stable terminal dashboard using \r overwrite + clear-line.
5
- * No full screen clear just overwrites rows from top to bottom.
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
- * Usage:
8
- * const { renderDashboard } = require('./dashboard');
9
- * renderDashboard(context); // called every ~1s from grinder.js
10
- *
11
- * Context: same as before, see docstring in file header.
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
- // ── Local refs (set once per render via initColors) ────────────
17
- let _c, _rgb, _getSpinner, _gradientText;
18
+ // Local refs (set per render)
19
+ let _c, _rgb, _spinnerFn, _gradientFn;
18
20
 
19
- function initColors(ctx) {
21
+ function init(ctx) {
20
22
  _c = ctx.c;
21
23
  _rgb = ctx.rgb;
22
- _getSpinner = ctx.getSpinner;
23
- _gradientText = ctx.gradientText;
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
- initColors(ctx);
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 palette
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, aggCommands = 0, aggErrors = 0;
80
+ // ── Aggregate stats ───────────────────────────────────────────
81
+ let aggBalance = 0, aggCoins = 0, aggCmds = 0, aggErrors = 0;
77
82
  for (const w of ctx.workers) {
78
- aggBalance += (w.stats.balance || 0) + (w.stats.bankBalance || 0);
79
- aggCoins += w.stats.coins || 0;
80
- aggCommands += w.stats.commands || 0;
81
- aggErrors += w.stats.errors || 0;
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 = aggCommands > 0 ? Math.round(((aggCommands - aggErrors) / aggCommands) * 100) : 100;
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 coinsPerHr = elapsedHrs > 0.01 ? Math.round(aggCoins / elapsedHrs) : 0;
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
- // ── ACCOUNTS TABLE ───────────────────────────────────────────
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 - 10);
110
+ const colAct = Math.max(8, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn - 12);
124
111
  const gap = ' ';
125
112
 
126
- const headers = `${D}${pad('#', colNum)}${_}${gap}${D}${pad('S', colSts)}${_}${gap}${_gradientText(pad('Account', colName), [139, 92, 246], [96, 165, 250])}${_}${gap}${D}${pad('Balance', colBal)}${_}${gap}${D}${pad('Lvl', colLvl)}${_}${gap}${D}${pad('LS', colLs)}${_}${gap}${D}${pad('Earned', colEarn)}${_}${gap}${D}${pad('Activity', colAct)}${_}`;
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) stsIcon = `${R}✗${_}`;
168
- else if (!wk.running) stsIcon = `${D}○${_}`;
169
- else if (isRecov) stsIcon = `${O}${_getSpinner('braille').substring(0, 1)}${_}`;
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}${_getSpinner('pulse').substring(0, 1)}${_}`;
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) lsStr = `${R}♥${ls}${_}`;
190
+ if (ls === 0) lsStr = `${R}♥${ls}${_}`;
183
191
  else if (ls != null && ls <= 2) lsStr = `${Y}♥${ls}${_}`;
184
- else if (ls != null) lsStr = `${G}♥${ls}${_}`;
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, pad(stsIcon, colSts), pad(nameStr, colName),
198
- pad(balStr, colBal), pad(lvlStr, colLvl), pad(lsStr, colLs),
199
- pad(earnStr, colEarn), actStr,
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
- writeRow(line(`${A}║ `, rowStr, `${A} ║`));
215
+ rows.push(mkRow(rowStr));
203
216
  }
204
217
 
205
218
  if (extraRows > 0) {
206
- writeRow(line(`${A}║ `, `${D}+${extraRows} more${_}${' '.repeat(Math.max(0, tw - 16))}`, `${A} ║`));
219
+ rows.push(mkRow(`${D}+${extraRows} more${_}`));
207
220
  }
208
221
 
209
- writeRow(line(A + '╚', D + '─'.repeat(tw - 2), A + '╝'));
222
+ rows.push(border('╚'));
210
223
 
211
- // 4. Live feed
224
+ // LIVE FEED
225
+ const logs = ctx.recentLogs.toArray();
212
226
  if (logs.length > 0) {
213
- const feedTitle = `${_gradientText(' LIVE FEED ', [139, 92, 246], [52, 211, 153])}${_} ${G}${_getSpinner('pulse')}${_}`;
214
- writeRow(line(A + '╔', D + '─'.repeat(tw - 2), A + '╗'));
215
- writeRow(line(`${A}║ `, feedTitle, `${A} ║`));
216
- writeRow(line(A + '╟', D + '─'.repeat(tw - 2), A + '╢'));
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
- writeRow(line(`${A}║ `, `${D}${lineText}${_}`, `${A} ║`));
245
+ rows.push(mkRow(`${D}${lineText}${_}`));
232
246
  }
233
- writeRow(line(A + '╚', D + '─'.repeat(tw - 2), A + '╝'));
247
+ rows.push(border('╚'));
234
248
  }
235
249
 
236
- // 5. Footer
237
- writeRow(line(A + '╔', D + '─'.repeat(tw - 2), A + '╗'));
238
- writeRow(line(`${A}║ `, footer, `${A} ║`));
239
- writeRow(line(A + '╚', D + '─'.repeat(tw - 2), A + '╝'));
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 rowNum;
262
+ return rows.length;
242
263
  }
243
264
 
244
265
  module.exports = { renderDashboard };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "7.67.0",
3
+ "version": "7.68.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"