dankgrinder 8.82.0 → 8.84.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 (3) hide show
  1. package/lib/grinder.js +31 -4
  2. package/lib/ui.js +70 -42
  3. package/package.json +1 -1
package/lib/grinder.js CHANGED
@@ -3053,19 +3053,46 @@ async function start(apiKey, apiUrl, opts = {}) {
3053
3053
  console.log('');
3054
3054
  console.log(`${c.yellow}[${signal}] Shutting down...${c.reset}`);
3055
3055
 
3056
- // Collect stats from all workers
3056
+ // Build the stats table visually gracefully
3057
3057
  let finalCoins = 0;
3058
3058
  let finalCmds = 0;
3059
+
3060
+ let table = ' ╭───────────────┬────────────────┬──────────┬────────╮\n';
3061
+ table += ' │ ACCOUNT │ GAINED │ COMMANDS │ OK % │\n';
3062
+ table += ' ├───────────────┼────────────────┼──────────┼────────┤\n';
3063
+
3059
3064
  for (const wk of workers) {
3060
3065
  const rate = wk.stats.commands > 0 ? ((wk.stats.successes / wk.stats.commands) * 100).toFixed(0) : 0;
3061
- console.log(` ${c.dim}${wk.username || '?'}${c.reset} ${c.green}+⏣${wk.stats.coins.toLocaleString()}${c.reset} ${wk.stats.commands}cmds ${rate}%ok`);
3062
3066
  finalCoins += wk.stats.coins || 0;
3063
3067
  finalCmds += wk.stats.commands || 0;
3064
- }
3068
+
3069
+ const unRaw = (wk.username || '?').substring(0, 13);
3070
+ const coinsRaw = '+⏣' + (wk.stats.coins || 0).toLocaleString();
3071
+ const cmdsRaw = String(wk.stats.commands || 0);
3072
+ const okRaw = rate + '%';
3073
+
3074
+ const un = unRaw.padEnd(13);
3075
+ const coins = coinsRaw.substring(0, 14).padEnd(14);
3076
+ const cmds = cmdsRaw.substring(0, 8).padEnd(8);
3077
+ const ok = okRaw.substring(0, 6).padEnd(6);
3078
+
3079
+ table += ` │ ${c.cyan}${un}${c.reset} │ ${c.green}${coins}${c.reset} │ ${cmds} │ ${ok} │\n`;
3080
+ }
3081
+
3082
+ if (workers.length === 0) {
3083
+ table += ` │ ${c.dim}${'No workers...'.padEnd(13)}${c.reset} │ ${''.padEnd(14)} │ ${''.padEnd(8)} │ ${''.padEnd(6)} │\n`;
3084
+ }
3085
+
3086
+ table += ' ╰───────────────┴────────────────┴──────────┴────────╯\n';
3087
+ process.stdout.write(table);
3065
3088
 
3066
3089
  const memFinal = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
3067
3090
  const cpm = globalCmdRate.getRate().toFixed(1);
3068
- console.log(`${c.bold}Total:${c.reset} +⏣${finalCoins.toLocaleString()} in ${ui.formatUptime()} | ${finalCmds}cmds | ~${cpm}cmd/m | ${memFinal}MB`);
3091
+
3092
+ console.log(` ${c.bold}======================================================${c.reset}`);
3093
+ console.log(` ${c.bold}TOTAL:${c.reset} ${c.green}+⏣${finalCoins.toLocaleString()}${c.reset} in ${ui.formatUptime()}`);
3094
+ console.log(` ${c.bold}STATS:${c.reset} ${finalCmds} commands | ~${cpm} cmd/m | ${memFinal}MB used`);
3095
+ console.log(` ${c.bold}======================================================${c.reset}\n`);
3069
3096
 
3070
3097
  // Stop workers immediately (don't wait) — instant shutdown
3071
3098
  for (const wk of workers) {
package/lib/ui.js CHANGED
@@ -31,7 +31,7 @@ function applyGradient(str) {
31
31
  const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
32
32
  function getSpinner() { return SPINNER_FRAMES[Math.floor(Date.now() / 80) % SPINNER_FRAMES.length]; }
33
33
 
34
- const LOAD_FRAMES = ['[= ]', '[== ]', '[=== ]', '[ ===]', '[ ==]', '[ =]', '[ ==]', '[ ===]', '[=== ]', '[== ]'];
34
+ const LOAD_FRAMES = ['','','','','','','',''];
35
35
  function getLoader() { return LOAD_FRAMES[Math.floor(Date.now() / 150) % LOAD_FRAMES.length]; }
36
36
 
37
37
  const ACC_COLORS = ['#ff0054', '#ffbd00', '#390099', '#9e0059', '#ff5400', '#00f5d4', '#00bbf9', '#fee440', '#f15bb5', '#9b5de5'];
@@ -107,24 +107,58 @@ function makeRow(c1, c2, c3, c4, c5, c6, wCols, logW) {
107
107
  return `│ ${pad(c1, wCols.num)} │ ${pad(c2, wCols.name)} │ ${pad(c3, wCols.bal)} │ ${pad(c4, wCols.ls)} │ ${pad(c5, wCols.lv)} │ ${pad(c6, logW)} │`;
108
108
  }
109
109
 
110
+ function formatBal(n) {
111
+ if (n === undefined || isNaN(n)) return '—';
112
+ if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B';
113
+ if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
114
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + 'k';
115
+ return String(n);
116
+ }
117
+
110
118
  function render() {
111
119
  if (!_live) return;
112
120
  const C = process.stdout.columns || 110;
113
-
114
- let out = '';
121
+
122
+ // Clear screen to prevent duplication on resize
123
+ let out = '\x1b[2J\x1b[H';
124
+
115
125
  // Banner
116
126
  const titleStr = figlet.textSync('DANK GRINDER', { font: 'ANSI Shadow' });
117
127
  out += chalk.bold(applyGradient(titleStr));
118
- out += chalk.bold.magenta(`v${_version} — ${getLoader()} Running... `) + '\n\n';
128
+ out += chalk.bold.magenta(` v${_version} — ${getLoader()} Running... \n`);
119
129
 
120
130
  const wCols = { num: 4, name: 18, bal: 14, ls: 5, lv: 5 };
121
-
122
131
  const overhead = 19 + wCols.num + wCols.name + wCols.bal + wCols.ls + wCols.lv;
123
132
  const logW = Math.max(10, C - overhead);
124
133
  const actualC = logW + overhead;
125
134
 
126
- const summaryW = actualC - 4;
135
+ const sorted = [..._workers].sort((a, b) => {
136
+ if (!a.channel !== !b.channel) return a.channel ? -1 : 1;
137
+ const aA = a.channel && !a.paused && !a.dashboardPaused;
138
+ const bB = b.channel && !b.paused && !b.dashboardPaused;
139
+ if (aA !== bB) return aA ? -1 : 1;
140
+ return (b.stats.commands || 0) - (a.stats.commands || 0);
141
+ });
142
+
143
+ let totalCoins = 0, totalBal = 0, onlineCount = 0;
144
+ for (let i = 0; i < sorted.length; i++) {
145
+ const w = sorted[i];
146
+ totalCoins += w.stats.coins || 0;
147
+ totalBal += w.stats.balance || 0;
148
+ if (w.channel && !w.paused && !w.dashboardPaused) onlineCount++;
149
+ }
150
+
151
+ // High-Visibility Summary directly below the Title
152
+ const totC = totalCoins > 0 ? '+' + formatBal(totalCoins) : '+0';
153
+ const summStr = chalk.bold(' 🌟 ACT: ') + chalk.bold.magenta(_workers.length) + chalk.dim(' │ ') +
154
+ chalk.bold('⚡ ON: ') + chalk.bold.cyan(onlineCount) + chalk.dim(' │ ') +
155
+ chalk.bold('💰 BAL: ') + chalk.bold.green('⏣' + formatBal(totalBal)) + chalk.dim(' │ ') +
156
+ chalk.bold('📈 GAINED: ') + chalk.bold.yellow(totC) + chalk.dim(' │ ') +
157
+ chalk.bold('⏱ UP: ') + chalk.bold.dim(fmtUptime());
158
+
159
+ out += '\n' + summStr + '\n\n';
127
160
 
161
+ // Accounts Table
128
162
  out += '╭' + '─'.repeat(wCols.num + 2) + '┬'
129
163
  + '─'.repeat(wCols.name + 2) + '┬'
130
164
  + '─'.repeat(wCols.bal + 2) + '┬'
@@ -145,26 +179,14 @@ function render() {
145
179
  + '─'.repeat(wCols.lv + 2) + '┼'
146
180
  + '─'.repeat(logW + 2) + '┤\n';
147
181
 
148
- const sorted = [..._workers].sort((a, b) => {
149
- if (!a.channel !== !b.channel) return a.channel ? -1 : 1;
150
- const aA = a.channel && !a.paused && !a.dashboardPaused;
151
- const bB = b.channel && !b.paused && !b.dashboardPaused;
152
- if (aA !== bB) return aA ? -1 : 1;
153
- return (b.stats.commands || 0) - (a.stats.commands || 0);
154
- });
155
-
156
- let totalCoins = 0, totalBal = 0, onlineCount = 0;
157
182
  for (let i = 0; i < sorted.length; i++) {
158
183
  const w = sorted[i];
159
- totalCoins += w.stats.coins || 0;
160
- totalBal += w.stats.balance || 0;
161
- if (w.channel && !w.paused && !w.dashboardPaused) onlineCount++;
162
184
 
163
185
  let logText = w.lastStatus || 'idle';
164
186
  let activity = '';
165
187
  if (w.globalCooldownUntil && Date.now() < w.globalCooldownUntil) {
166
188
  const s = Math.ceil((w.globalCooldownUntil - Date.now()) / 1000);
167
- logText = s > 60 ? `cooldown ${Math.ceil(s/60)}m` : `cd ${s}s`;
189
+ logText = s > 60 ? `cd ${Math.ceil(s/60)}m` : `cd ${s}s`;
168
190
  activity = chalk.yellow(getSpinner());
169
191
  } else if (w.paused || w.dashboardPaused) {
170
192
  logText = 'paused';
@@ -177,16 +199,29 @@ function render() {
177
199
  const numStr = trunc(String(i + 1), wCols.num);
178
200
  const dot = getDot(w);
179
201
 
180
- const nameRaw = trunc(w.username || '?', wCols.name - 2);
202
+ // Support previous UI events format dynamically extracting name
203
+ const nameRaw = trunc(w.username || 'Worker ' + (i+1), wCols.name - 2);
181
204
  const nameStr = dot.color(dot.dot) + ' ' + getAccountColor(nameRaw)(chalk.bold(nameRaw));
182
205
 
183
- let bVal = w.stats.balance !== undefined ? (w.stats.balance >= 1000 ? w.stats.balance.toLocaleString() : String(w.stats.balance)) : '—';
206
+ // Balance Formatter
207
+ const bVal = w.stats.balance !== undefined ? formatBal(w.stats.balance) : '—';
184
208
  const balRaw = trunc(bVal === '—' ? '—' : '⏣' + bVal, wCols.bal);
185
209
  const balStr = balRaw === '—' ? chalk.dim('—') : chalk.bold.green(balRaw);
186
210
 
187
- const lsRaw = trunc(w._lifesavers !== undefined ? String(w._lifesavers) : '—', wCols.ls);
188
- const lColor = lsRaw === '—' ? chalk.dim : (w._lifesavers === 0 ? chalk.bold.red : (w._lifesavers <= 2 ? chalk.bold.yellow : chalk.bold.cyan));
189
- const lsStr = lColor(lsRaw);
211
+ // Heart Lifesavers
212
+ let lsRaw = '—';
213
+ let lsStr = chalk.dim('—');
214
+ if (w._lifesavers !== undefined) {
215
+ let lsCount = w._lifesavers;
216
+ let lsEmoji = '❤️'; // Red for 0-2
217
+ let lsColor = chalk.bold.red;
218
+
219
+ if (lsCount >= 5) { lsEmoji = '💚'; lsColor = chalk.bold.green; }
220
+ else if (lsCount >= 3) { lsEmoji = '💛'; lsColor = chalk.bold.yellow; }
221
+
222
+ lsRaw = trunc(`${lsEmoji} ${lsCount}`, wCols.ls);
223
+ lsStr = lsColor(lsRaw);
224
+ }
190
225
 
191
226
  const lvRaw = trunc(w._level !== undefined ? String(w._level) : '—', wCols.lv);
192
227
  const lvStr = chalk.bold.magenta(lvRaw);
@@ -197,30 +232,23 @@ function render() {
197
232
  out += makeRow(numStr, nameStr, balStr, lsStr, lvStr, logStr, wCols, logW) + '\n';
198
233
  }
199
234
 
200
- out += '├' + '─'.repeat(wCols.num + 2) + '┴'
235
+ if (sorted.length === 0) {
236
+ out += makeRow('—', 'No Accounts', '—', '—', '—', 'Waiting...', wCols, logW) + '\n';
237
+ }
238
+
239
+ out += '╰' + '─'.repeat(wCols.num + 2) + '┴'
201
240
  + '─'.repeat(wCols.name + 2) + '┴'
202
241
  + '─'.repeat(wCols.bal + 2) + '┴'
203
242
  + '─'.repeat(wCols.ls + 2) + '┴'
204
243
  + '─'.repeat(wCols.lv + 2) + '┴'
205
- + '─'.repeat(logW + 2) + '┤\n';
244
+ + '─'.repeat(logW + 2) + '╯\n\n';
206
245
 
207
- const totC = totalCoins >= 1000000 ? (totalCoins/1000000).toFixed(1)+'M' : (totalCoins >= 1000 ? (totalCoins/1000).toFixed(1)+'k' : totalCoins);
208
- const summLine = chalk.bold('Σ ACT: ') + chalk.bold.magenta(_workers.length) + chalk.dim('') +
209
- chalk.bold('⚡ ON: ') + chalk.bold.cyan(onlineCount) + chalk.dim(' ') +
210
- chalk.bold('💰 TR/BAL: ') + chalk.bold.green('⏣' + totalBal.toLocaleString()) + chalk.dim(' │ ') +
211
- chalk.bold('📈 GAINED: ') + chalk.bold.yellow('+' + totC) + chalk.dim(' │ ') +
212
- chalk.bold('⏱ UP: ') + chalk.bold.dim(fmtUptime());
213
-
214
- out += `│ ${pad(summLine, summaryW)} │\n`;
215
- out += '├' + '─'.repeat(actualC - 2) + '┤\n';
216
- out += `│ ${pad(chalk.bold.cyan('📻 LATEST EVENTS'), summaryW)} │\n`;
217
-
218
- if (_events.length === 0) out += `│ ${pad(chalk.dim(' Quiet here...'), summaryW)} │\n`;
219
- _events.forEach(ev => { out += `│ ${pad(chalk.dim(`[${ev.ts}] `) + ev.icon + ' ' + ev.msg, summaryW)} │\n`; });
220
-
221
- out += '╰' + '─'.repeat(actualC - 2) + '╯\n';
246
+ // Latest events beneath the table cleanly
247
+ out += chalk.bold.cyan(' 📻 LATEST EVENTS') + '\n';
248
+ if (_events.length === 0) out += chalk.dim(' Quiet here...\n');
249
+ _events.forEach(ev => { out += ` ${chalk.dim(`[${ev.ts}]`)} ${ev.icon} ${ev.msg}\n`; });
222
250
 
223
- logUpdate(out);
251
+ process.stdout.write(out);
224
252
  }
225
253
 
226
254
  function start() {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "8.82.0",
3
+ "version": "8.84.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"