dankgrinder 8.33.0 → 8.36.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 +26 -23
  2. package/lib/ui.js +40 -9
  3. package/package.json +1 -1
package/lib/grinder.js CHANGED
@@ -2783,7 +2783,8 @@ async function start(apiKey, apiUrl, opts = {}) {
2783
2783
  const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(parsedGapMin, 150);
2784
2784
  const randomLoginGap = () => LOGIN_GAP_MAX_MS <= LOGIN_GAP_MIN_MS ? LOGIN_GAP_MIN_MS : LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
2785
2785
 
2786
- console.log(`Logging in ${accounts.length} accounts...`);
2786
+ ui.start(); // show dashboard before login
2787
+ ui.printLogin(`${c.dim}Logging in ${accounts.length} accounts...${c.reset}`);
2787
2788
  const BATCH_SIZE = 10;
2788
2789
  for (let i = 0; i < accounts.length; i += BATCH_SIZE) {
2789
2790
  if (shutdownCalled) break;
@@ -2792,25 +2793,27 @@ async function start(apiKey, apiUrl, opts = {}) {
2792
2793
  await Promise.all(batch.map(async (acc, idx) => {
2793
2794
  try {
2794
2795
  if (idx > 0) await new Promise(r => setTimeout(r, 100 + Math.floor(Math.random() * 500)));
2795
- console.log(` [${i + idx + 1}] starting: ${acc.label || acc.id}`);
2796
2796
  const worker = new AccountWorker(acc, i + idx);
2797
2797
  workers.push(worker);
2798
2798
  workerMap.set(acc.id, worker);
2799
2799
  await worker.start();
2800
- console.log(` [${i + idx + 1}] done: ${acc.label || acc.id}`);
2801
2800
  if (worker._tokenInvalid) {
2802
- console.log(` [${i + idx + 1}] FAIL - invalid token: ${acc.label || acc.id}`);
2801
+ worker.lastStatus = 'invalid token';
2802
+ ui.printLogin(` ${c.red}✗${c.reset} [${i + idx + 1}] ${acc.label || acc.id}: invalid token`);
2803
2803
  } else if (worker.channel) {
2804
- console.log(` [${i + idx + 1}] OK - ${worker.username}`);
2804
+ worker.lastStatus = 'ready';
2805
+ ui.printLogin(` ${c.green}✓${c.reset} [${i + idx + 1}] ${worker.username}`);
2806
+ ui.render();
2805
2807
  } else {
2806
- console.log(` [${i + idx + 1}] TIMEOUT`);
2808
+ worker.lastStatus = 'timeout';
2809
+ ui.printLogin(` ${c.yellow}✗${c.reset} [${i + idx + 1}] ${acc.label || acc.id}: timeout`);
2807
2810
  }
2808
2811
  } catch (e) {
2809
- console.log(` [${i + idx + 1}] ERROR: ${e.message}`);
2812
+ ui.printLogin(` ${c.red}✗${c.reset} [${i + idx + 1}] ERROR: ${e.message}`);
2810
2813
  }
2811
2814
  }));
2812
2815
  } catch (e) {
2813
- console.log(`BATCH ERROR: ${e.message}`);
2816
+ ui.printLogin(`${c.red}BATCH ERROR: ${e.message}${c.reset}`);
2814
2817
  }
2815
2818
  if (i + BATCH_SIZE < accounts.length) await new Promise(r => setTimeout(r, randomLoginGap()));
2816
2819
  hintGC();
@@ -2819,31 +2822,32 @@ async function start(apiKey, apiUrl, opts = {}) {
2819
2822
  const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
2820
2823
  const invalidWorkers = workers.filter(w => w._tokenInvalid);
2821
2824
  const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
2822
- console.log(`Login complete: ${loginDone}/${accounts.length} connected`);
2825
+ ui.printLogin(`${c.green}Login complete: ${loginDone}/${accounts.length} connected${c.reset}`);
2823
2826
  if (invalidWorkers.length > 0) {
2824
- for (const w of invalidWorkers) console.log(` FAIL - invalid token: ${w.account.label || w.account.id}`);
2827
+ for (const w of invalidWorkers) ui.printLogin(` ${c.red}FAIL${c.reset} invalid token: ${w.account.label || w.account.id}`);
2825
2828
  }
2826
2829
  if (timedOutWorkers.length > 0) {
2827
- console.log(` WARN - ${timedOutWorkers.length} timed out (will retry in background)`);
2830
+ ui.printLogin(` ${c.yellow}WARN${c.reset} ${timedOutWorkers.length} timed out (will retry in background)`);
2828
2831
  }
2832
+ ui.clearLoginLines();
2829
2833
 
2830
2834
  const activeWorkers = workers.filter(w => !w._tokenInvalid);
2831
2835
 
2832
2836
  // ── Phase 2: Inventory check ────────────────────────────────────
2833
- console.log('Checking inventory...');
2837
+ ui.printLogin(`${c.dim}Checking inventory...${c.reset}`);
2834
2838
  let invFailed = 0;
2835
2839
  await Promise.all(activeWorkers.map(async (w) => {
2836
2840
  try { await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
2837
2841
  catch { invFailed++; }
2838
2842
  }));
2839
2843
  if (invFailed > 0) {
2840
- console.log(`Inventory failed for ${invFailed} accounts. Not starting grind loops.`);
2841
- return;
2844
+ ui.printLogin(`${c.red}Inventory failed for ${invFailed} accounts${c.reset}`);
2845
+ } else {
2846
+ ui.printLogin(`${c.green}Inventory OK${c.reset}`);
2842
2847
  }
2843
- console.log('Inventory check complete');
2844
2848
 
2845
2849
  // ── Phase 2.5: Balance check ───────────────────────────────────
2846
- console.log('Checking balances...');
2850
+ ui.printLogin(`${c.dim}Checking balances...${c.reset}`);
2847
2851
  for (const w of activeWorkers) {
2848
2852
  try { await w.checkBalance(true); } catch {}
2849
2853
  }
@@ -2852,10 +2856,10 @@ async function start(apiKey, apiUrl, opts = {}) {
2852
2856
  totalCoins += w.stats?.balance || 0;
2853
2857
  totalCoins += w.stats?.bankBalance || 0;
2854
2858
  }
2855
- console.log(`Balances: total ${totalCoins.toLocaleString()} coins across ${activeWorkers.length} accounts`);
2859
+ ui.printLogin(`Balances: total ${c.green}+⏣${totalCoins.toLocaleString()}${c.reset} across ${activeWorkers.length} accounts`);
2856
2860
 
2857
2861
  // ── Phase 2.75: DM history check ────────────────────────────────
2858
- console.log('Checking DM history...');
2862
+ ui.printLogin(`${c.dim}Checking DM history...${c.reset}`);
2859
2863
  let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [];
2860
2864
  for (const w of activeWorkers) {
2861
2865
  try {
@@ -2873,19 +2877,18 @@ async function start(apiKey, apiUrl, opts = {}) {
2873
2877
  }
2874
2878
  } catch {}
2875
2879
  }
2876
- if (dmNoLs.length > 0) console.log(` WARN - No lifesavers: ${dmNoLs.join(', ')}`);
2880
+ if (dmNoLs.length > 0) ui.printLogin(` ${c.yellow}WARN${c.reset} No lifesavers: ${dmNoLs.join(', ')}`);
2877
2881
  const parts = [];
2878
2882
  if (dmDeaths > 0) parts.push(`${dmDeaths} deaths`);
2879
2883
  if (dmLevelUps > 0) parts.push(`${dmLevelUps} level-ups`);
2880
- console.log(`DM check: ${parts.length > 0 ? parts.join(', ') : 'clean'}`);
2884
+ ui.printLogin(`DM check: ${parts.length > 0 ? parts.join(', ') : c.green + 'clean' + c.reset}`);
2885
+ ui.clearLoginLines();
2881
2886
 
2882
2887
  // ── Phase 3: Start grind loops ───────────────────────────────────
2883
- console.log(`Starting ${activeWorkers.length} grind loops...`);
2884
2888
  for (const w of activeWorkers) {
2885
2889
  if (!shutdownCalled) w.grindLoop();
2886
2890
  }
2887
- ui.start();
2888
- console.log(`${c.dim}All grind loops started. | Ctrl+C to stop${c.reset}`);
2891
+ ui.render();
2889
2892
 
2890
2893
  // Cluster heartbeat — lets other nodes see this node is alive
2891
2894
  if (CLUSTER_ENABLED) {
package/lib/ui.js CHANGED
@@ -46,6 +46,7 @@ function gradientLine(text, from, to) {
46
46
 
47
47
  let _lineCount = 0;
48
48
  let _interval = null;
49
+ let _loginLines = 0; // number of login progress lines to clear
49
50
 
50
51
  function _statusIcon(w) {
51
52
  if (!w.running || !w.channel) return `${c.red}✗${c.reset}`;
@@ -55,7 +56,7 @@ function _statusIcon(w) {
55
56
  return `${c.green}●${c.reset}`;
56
57
  }
57
58
 
58
- function _render() {
59
+ function _render(extraLines = []) {
59
60
  const workers = _workers;
60
61
  const W = Math.min(process.stdout.columns || 120, 120);
61
62
  const lines = [];
@@ -84,6 +85,8 @@ function _render() {
84
85
  );
85
86
  }
86
87
 
88
+ for (const el of extraLines) lines.push(el);
89
+
87
90
  lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
88
91
 
89
92
  // Totals
@@ -100,19 +103,28 @@ function _render() {
100
103
  );
101
104
  lines.push('');
102
105
 
103
- // Redraw in place
104
- process.stdout.write(`\x1b[${_lineCount}A`);
106
+ // Redraw in place — also clear any login progress lines above
107
+ const clearLines = _lineCount + _loginLines;
108
+ if (clearLines > 0) {
109
+ process.stdout.write(`\x1b[${clearLines}A`);
110
+ for (let i = 0; i < clearLines; i++) {
111
+ process.stdout.write(`\x1b[2K\r${i < clearLines - 1 ? '\x1b[1A' : ''}`);
112
+ }
113
+ }
105
114
  process.stdout.write(lines.join('\n') + '\n');
106
115
  _lineCount = lines.length - 1;
116
+ _loginLines = 0;
107
117
  }
108
118
 
109
119
  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' : ''}`);
120
+ const clearLines = _lineCount + _loginLines;
121
+ if (clearLines > 0) {
122
+ process.stdout.write(`\x1b[${clearLines}A`);
123
+ for (let i = 0; i < clearLines; i++) {
124
+ process.stdout.write(`\x1b[2K\r${i < clearLines - 1 ? '\x1b[1A' : ''}`);
114
125
  }
115
126
  _lineCount = 0;
127
+ _loginLines = 0;
116
128
  }
117
129
  }
118
130
 
@@ -133,9 +145,11 @@ function drawBanner(version) {
133
145
  console.log('');
134
146
  }
135
147
 
148
+ // Show dashboard and start the 10s refresh interval
136
149
  function start() {
137
- if (_workers.length > 30) return; // too many accounts — stay quiet
150
+ if (_workers.length > 30) return;
138
151
  _lineCount = 0;
152
+ _loginLines = 0;
139
153
  _render();
140
154
  _interval = setInterval(() => {
141
155
  if (_isShuttingDown()) {
@@ -147,9 +161,26 @@ function start() {
147
161
  }, 10_000);
148
162
  }
149
163
 
164
+ // Immediately re-render the dashboard (use after worker status changes)
165
+ function render() {
166
+ if (_isShuttingDown()) return;
167
+ _render();
168
+ }
169
+
170
+ // Print a login progress line and count it so it gets cleared on next render
171
+ function printLogin(msg) {
172
+ console.log(msg);
173
+ _loginLines++;
174
+ }
175
+
176
+ // Clear login lines and re-render final state
177
+ function clearLoginLines() {
178
+ _render();
179
+ }
180
+
150
181
  function stop() {
151
182
  if (_interval) { clearInterval(_interval); _interval = null; }
152
183
  _clear();
153
184
  }
154
185
 
155
- module.exports = { init, drawBanner, start, stop };
186
+ module.exports = { init, drawBanner, start, render, printLogin, clearLoginLines, stop };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "8.33.0",
3
+ "version": "8.36.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"