dankgrinder 8.31.0 → 8.33.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 CHANGED
@@ -8,6 +8,7 @@ const {
8
8
  AhoCorasick, LRUCache, StringPool, AsyncBatchQueue, JitterBackoff,
9
9
  } = require('./structures');
10
10
  const PKG_VERSION = require('../package.json').version;
11
+ const ui = require('./ui');
11
12
 
12
13
  // Global shutdown flag
13
14
  let shutdownCalled = false;
@@ -255,7 +256,8 @@ function gradientText(text, from, to) {
255
256
  }
256
257
 
257
258
  function colorBanner() {
258
- return `DANKGRINDER v${PKG_VERSION}`;
259
+ const title = `DANKGRINDER v${PKG_VERSION}`;
260
+ return gradientLine(title, [77, 142, 255], [255, 92, 147]);
259
261
  }
260
262
 
261
263
  // ── Simple Logging ─────────────────────────────────────────────
@@ -2658,7 +2660,8 @@ captchaDetector.build();
2658
2660
  // ══════════════════════════════════════════════════════════════
2659
2661
 
2660
2662
  async function start(apiKey, apiUrl, opts = {}) {
2661
- console.log('DANKGRINDER starting...');
2663
+ ui.init({ workers, isShuttingDown: () => shutdownCalled });
2664
+ ui.drawBanner(PKG_VERSION);
2662
2665
  CLOUD_ADMIN_KEY = process.env.CLOUD_ADMIN_KEY || '';
2663
2666
  API_KEY = apiKey;
2664
2667
  API_URL = apiUrl || process.env.DANKGRINDER_URL || 'http://localhost:3000';
@@ -2881,7 +2884,8 @@ async function start(apiKey, apiUrl, opts = {}) {
2881
2884
  for (const w of activeWorkers) {
2882
2885
  if (!shutdownCalled) w.grindLoop();
2883
2886
  }
2884
- console.log(`All grind loops started. v${PKG_VERSION} | Ctrl+C to stop`);
2887
+ ui.start();
2888
+ console.log(`${c.dim}All grind loops started. | Ctrl+C to stop${c.reset}`);
2885
2889
 
2886
2890
  // Cluster heartbeat — lets other nodes see this node is alive
2887
2891
  if (CLUSTER_ENABLED) {
@@ -2949,6 +2953,7 @@ async function start(apiKey, apiUrl, opts = {}) {
2949
2953
  shutdownInProgress = true;
2950
2954
  shutdownCalled = true;
2951
2955
  setDashboardActive(false);
2956
+ ui.stop();
2952
2957
  process.stdout.write(c.show + '\n');
2953
2958
 
2954
2959
  console.log('');
package/lib/ui.js ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * CLI Live Dashboard
3
+ * Renders a terminal table that updates in place every 10s.
4
+ * Shows per-account status, earned coins, commands, and session uptime.
5
+ */
6
+
7
+ let _startTime = Date.now();
8
+ let _workers = [];
9
+ let _isShuttingDown = () => false;
10
+
11
+ function formatUptime() {
12
+ const s = Math.floor((Date.now() - _startTime) / 1000);
13
+ if (s < 60) return `${s}s`;
14
+ const m = Math.floor(s / 60);
15
+ const h = Math.floor(m / 60);
16
+ const d = Math.floor(h / 24);
17
+ if (d > 0) return `${d}d ${h % 24}h`;
18
+ if (h > 0) return `${h}h ${m % 60}m`;
19
+ return `${m}m`;
20
+ }
21
+
22
+ // ANSI helpers
23
+ const c = {
24
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
25
+ green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m',
26
+ cyan: '\x1b[36m', blue: '\x1b[34m',
27
+ };
28
+ const DIM = c.dim;
29
+
30
+ function rgb(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`; }
31
+ function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
32
+ function gradientLine(text, from, to) {
33
+ const fr = Array.isArray(from) ? from[0] : 128;
34
+ const fg = Array.isArray(from) ? from[1] : 128;
35
+ const fb = Array.isArray(from) ? from[2] : 128;
36
+ const tr = Array.isArray(to) ? to[0] : 255;
37
+ const tg = Array.isArray(to) ? to[1] : 255;
38
+ const tb = Array.isArray(to) ? to[2] : 255;
39
+ let out = '';
40
+ for (let i = 0; i < text.length; i++) {
41
+ const t = text.length <= 1 ? 0 : i / (text.length - 1);
42
+ out += `\x1b[38;2;${lerp(fr, tr, t)};${lerp(fg, tg, t)};${lerp(fb, tb, t)}m${text[i]}`;
43
+ }
44
+ return out + c.reset;
45
+ }
46
+
47
+ let _lineCount = 0;
48
+ let _interval = null;
49
+
50
+ function _statusIcon(w) {
51
+ if (!w.running || !w.channel) return `${c.red}✗${c.reset}`;
52
+ if (w.paused || w.dashboardPaused) return `${c.yellow}⏸${c.reset}`;
53
+ if (w.busy || w._invRunning || w._sellRunning) return `${c.cyan}⚙${c.reset}`;
54
+ if (Date.now() < w.globalCooldownUntil) return `${c.blue}⏳${c.reset}`;
55
+ return `${c.green}●${c.reset}`;
56
+ }
57
+
58
+ function _render() {
59
+ const workers = _workers;
60
+ const W = Math.min(process.stdout.columns || 120, 120);
61
+ const lines = [];
62
+
63
+ lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
64
+ lines.push(
65
+ ` ${c.bold}#${c.reset} ${c.bold}Account${c.reset}${' '.repeat(Math.max(1, 20 - 7))}` +
66
+ `${c.bold}Status${c.reset}${' '.repeat(Math.max(1, 14 - 6))}` +
67
+ `${c.bold}Earned${c.reset}${' '.repeat(Math.max(1, 12 - 5))}` +
68
+ `${c.bold}Cmds${c.reset}${' '.repeat(Math.max(1, 7 - 4))}` +
69
+ `${c.bold}OK%${c.reset}`
70
+ );
71
+ lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
72
+
73
+ for (let i = 0; i < workers.length; i++) {
74
+ const w = workers[i];
75
+ const name = (w.username || '?').substring(0, 20);
76
+ const status = (w.lastStatus || 'idle').substring(0, 14).padEnd(14);
77
+ const earned = w.stats.coins > 0 ? `+⏣${w.stats.coins.toLocaleString()}` : DIM + '—' + c.reset;
78
+ const cmds = String(w.stats.commands || 0);
79
+ const rate = w.stats.commands > 0 ? Math.round((w.stats.successes / w.stats.commands) * 100) : 0;
80
+
81
+ lines.push(
82
+ ` ${DIM}${String(i + 1).padStart(2)}${c.reset} ${name.padEnd(20)} ${status}` +
83
+ `${String(earned).padStart(12)} ${cmds.padStart(6)} ${String(rate).padStart(3)}%`
84
+ );
85
+ }
86
+
87
+ lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
88
+
89
+ // Totals
90
+ let totalCoins = 0, totalCmds = 0;
91
+ for (const w of workers) {
92
+ totalCoins += w.stats.coins || 0;
93
+ totalCmds += w.stats.commands || 0;
94
+ }
95
+ const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
96
+ lines.push(
97
+ ` ${c.bold}Total${c.reset} ` +
98
+ `${totalCoins > 0 ? c.green + '+⏣' + totalCoins.toLocaleString() + c.reset : DIM + '—' + c.reset}` +
99
+ ` ${totalCmds} cmds ${formatUptime()} ${memMB}MB`
100
+ );
101
+ lines.push('');
102
+
103
+ // Redraw in place
104
+ process.stdout.write(`\x1b[${_lineCount}A`);
105
+ process.stdout.write(lines.join('\n') + '\n');
106
+ _lineCount = lines.length - 1;
107
+ }
108
+
109
+ function _clear() {
110
+ if (_lineCount > 0) {
111
+ process.stdout.write(`\x1b[${_lineCount}A`);
112
+ for (let i = 0; i < _lineCount; i++) {
113
+ process.stdout.write(`\x1b[2K\r${i < _lineCount - 1 ? '\x1b[1A' : ''}`);
114
+ }
115
+ _lineCount = 0;
116
+ }
117
+ }
118
+
119
+ // ── Public API ────────────────────────────────────────────────
120
+
121
+ function init({ workers, isShuttingDown }) {
122
+ _startTime = Date.now();
123
+ _workers = workers;
124
+ _isShuttingDown = isShuttingDown || (() => false);
125
+ }
126
+
127
+ function drawBanner(version) {
128
+ const title = `DANKGRINDER v${version}`;
129
+ const gradient = gradientLine(title, [77, 142, 255], [255, 92, 147]);
130
+ console.log('');
131
+ console.log(` ${gradient}${c.reset}`);
132
+ console.log(` ${DIM}Ctrl+C to stop${c.reset}`);
133
+ console.log('');
134
+ }
135
+
136
+ function start() {
137
+ if (_workers.length > 30) return; // too many accounts — stay quiet
138
+ _lineCount = 0;
139
+ _render();
140
+ _interval = setInterval(() => {
141
+ if (_isShuttingDown()) {
142
+ clearInterval(_interval);
143
+ _interval = null;
144
+ return;
145
+ }
146
+ _render();
147
+ }, 10_000);
148
+ }
149
+
150
+ function stop() {
151
+ if (_interval) { clearInterval(_interval); _interval = null; }
152
+ _clear();
153
+ }
154
+
155
+ module.exports = { init, drawBanner, start, stop };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "8.31.0",
3
+ "version": "8.33.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"