dankgrinder 8.25.0 → 8.31.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/grinder.js +53 -39
  2. package/package.json +1 -1
package/lib/grinder.js CHANGED
@@ -88,14 +88,13 @@ async function sendWebhook(title, description, color = 0x5865f2) {
88
88
  }
89
89
 
90
90
  // ── Terminal Colors & ANSI ───────────────────────────────────
91
- // All colors stripped — plain text output only
92
91
  const c = {
93
- reset: '', bold: '', dim: '', italic: '',
94
- green: '', red: '', yellow: '', cyan: '',
95
- magenta: '', white: '', blue: '',
96
- bgGreen: '', bgRed: '', bgYellow: '', bgCyan: '',
97
- bgMagenta: '', bgBlue: '', bgWhite: '',
98
- // Cursor control (kept for functional use)
92
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
93
+ green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', cyan: '\x1b[36m',
94
+ magenta: '\x1b[35m', white: '\x1b[37m', blue: '\x1b[34m',
95
+ bgGreen: '\x1b[42m', bgRed: '\x1b[41m', bgYellow: '\x1b[43m', bgCyan: '\x1b[46m',
96
+ bgMagenta: '\x1b[45m', bgBlue: '\x1b[44m', bgWhite: '\x1b[47m',
97
+ // Cursor control
99
98
  clearLine: '\x1b[2K',
100
99
  cursorUp: (n) => `\x1b[${n}A`,
101
100
  cursorTo: (col) => `\x1b[${col}G`,
@@ -230,17 +229,29 @@ async function filterClaimableAccounts(accounts) {
230
229
  return claimable;
231
230
  }
232
231
 
233
- // ── Truecolor gradient helpers (disabled — plain text only) ──
234
- function rgb(r, g, b) { return ''; }
235
- function bgRgb(r, g, b) { return ''; }
232
+ // ── Truecolor gradient helpers ─────────────────────────────────
233
+ function rgb(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`; }
234
+ function bgRgb(r, g, b) { return `\x1b[48;2;${r};${g};${b}m`; }
236
235
  function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
237
236
 
238
237
  function gradientLine(text, from, to) {
239
- return text;
238
+ // from/to are [r,g,b] arrays
239
+ const fr = Array.isArray(from) ? from[0] : 128;
240
+ const fg = Array.isArray(from) ? from[1] : 128;
241
+ const fb = Array.isArray(from) ? from[2] : 128;
242
+ const tr = Array.isArray(to) ? to[0] : 255;
243
+ const tg = Array.isArray(to) ? to[1] : 255;
244
+ const tb = Array.isArray(to) ? to[2] : 255;
245
+ let out = '';
246
+ for (let i = 0; i < text.length; i++) {
247
+ const t = text.length <= 1 ? 0 : i / (text.length - 1);
248
+ out += `\x1b[38;2;${lerp(fr, tr, t)};${lerp(fg, tg, t)};${lerp(fb, tb, t)}m${text[i]}`;
249
+ }
250
+ return out + c.reset;
240
251
  }
241
252
 
242
253
  function gradientText(text, from, to) {
243
- return text;
254
+ return gradientLine(text, from, to);
244
255
  }
245
256
 
246
257
  function colorBanner() {
@@ -2931,64 +2942,67 @@ async function start(apiKey, apiUrl, opts = {}) {
2931
2942
  } catch {}
2932
2943
  }, 10_000);
2933
2944
 
2934
- let sigintHandled = false;
2935
- process.on('SIGINT', async () => {
2936
- if (sigintHandled) return;
2937
- sigintHandled = true;
2945
+ let shutdownInProgress = false;
2946
+
2947
+ async function gracefulShutdown(signal) {
2948
+ if (shutdownInProgress) return;
2949
+ shutdownInProgress = true;
2938
2950
  shutdownCalled = true;
2939
2951
  setDashboardActive(false);
2940
- process.stdout.write(c.show);
2952
+ process.stdout.write(c.show + '\n');
2941
2953
 
2942
2954
  console.log('');
2943
- console.log('Session Summary');
2955
+ console.log(`${c.yellow}[${signal}] Shutting down...${c.reset}`);
2944
2956
 
2945
- // Collect stats from all workers (including rotated-out ones)
2957
+ // Collect stats from all workers
2946
2958
  let finalCoins = 0;
2947
2959
  let finalCmds = 0;
2948
2960
  for (const wk of workers) {
2949
2961
  const rate = wk.stats.commands > 0 ? ((wk.stats.successes / wk.stats.commands) * 100).toFixed(0) : 0;
2950
- console.log(` ${(wk.username || '?').padEnd(18)} +${wk.stats.coins.toLocaleString().padStart(8)} coins ${wk.stats.commands} cmds ${rate}% ok`);
2962
+ console.log(` ${c.dim}${wk.username || '?'}${c.reset} ${c.green}+⏣${wk.stats.coins.toLocaleString()}${c.reset} ${wk.stats.commands}cmds ${rate}%ok`);
2951
2963
  finalCoins += wk.stats.coins || 0;
2952
2964
  finalCmds += wk.stats.commands || 0;
2953
2965
  }
2954
2966
 
2955
2967
  const memFinal = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
2956
- const avgEarn = globalEarningsEMA.get();
2957
2968
  const cpm = globalCmdRate.getRate().toFixed(1);
2958
- console.log(`Total: +${finalCoins.toLocaleString()} coins in ${formatUptime()} | ${finalCmds} cmds | ~${cpm} cmd/m | ${memFinal}MB`);
2959
- console.log('');
2969
+ console.log(`${c.bold}Total:${c.reset} +⏣${finalCoins.toLocaleString()} in ${formatUptime()} | ${finalCmds}cmds | ~${cpm}cmd/m | ${memFinal}MB`);
2960
2970
 
2961
- // Release all cluster claims before stopping workers
2962
- const releasePromises = workers.map(wk => releaseClaim(wk.account.id).catch(() => {}));
2963
- await Promise.all(releasePromises).catch(() => {});
2964
-
2965
- for (const wk of workers) wk.stop();
2971
+ // Stop workers max 1s per worker so one hung client doesn't block shutdown
2972
+ await Promise.all(workers.map(wk => Promise.race([
2973
+ new Promise(resolve => { wk.stop(); resolve(true); }),
2974
+ new Promise(resolve => setTimeout(() => resolve(false), 1000)),
2975
+ ])));
2966
2976
  workerMap.clear();
2967
2977
 
2968
- // Remove this node's heartbeat from Redis
2978
+ // Release cluster claims
2979
+ await Promise.allSettled(workers.map(wk => releaseClaim(wk.account.id)));
2980
+
2981
+ // Remove heartbeat
2969
2982
  if (redis && CLUSTER_ENABLED) {
2970
- try { await redis.del(`${CLUSTER_PREFIX}node:${NODE_ID}`); } catch {}
2983
+ await Promise.allSettled([redis.del(`${CLUSTER_PREFIX}node:${NODE_ID}`)]);
2971
2984
  }
2972
2985
 
2973
2986
  const totalRecoveries = workers.reduce((sum, wk) => sum + (wk._totalRecoveries || 0), 0);
2974
2987
  const totalDisconnects = workers.reduce((sum, wk) => sum + (wk._disconnectCount || 0), 0);
2975
2988
  const totalRateLimits = workers.reduce((sum, wk) => sum + (wk._rateLimitHits || 0), 0);
2976
2989
 
2990
+ if (totalRecoveries > 0 || totalDisconnects > 0) {
2991
+ console.log(` ${c.dim}${totalRecoveries} recoveries, ${totalDisconnects} disconnects, ${totalRateLimits} rate-limits${c.reset}`);
2992
+ }
2993
+
2977
2994
  const webhookMsg = `+⏣ ${finalCoins.toLocaleString()} | ${finalCmds} cmds | ${formatUptime()}` +
2978
2995
  (totalRecoveries > 0 ? ` | ${totalRecoveries} auto-recoveries` : '') +
2979
2996
  (CLUSTER_ENABLED ? ` | node: ${NODE_ID.substring(0, 12)}` : '');
2980
2997
  sendWebhook('Session Ended', webhookMsg, 0x8b5cf6);
2981
2998
 
2982
- if (totalRecoveries > 0 || totalDisconnects > 0) {
2983
- console.log(` ${c.dim}Recovery stats: ${totalRecoveries} auto-recoveries, ${totalDisconnects} disconnects, ${totalRateLimits} rate limits${c.reset}`);
2984
- }
2985
- if (CLUSTER_ENABLED) {
2986
- console.log(` ${c.dim}Cluster node: ${NODE_ID} — claims released${c.reset}`);
2987
- }
2999
+ if (redis) { redis.disconnect().catch(() => {}); }
3000
+ console.log(`${c.green}Goodbye!${c.reset}\n`);
3001
+ process.exit(0);
3002
+ }
2988
3003
 
2989
- if (redis) { try { redis.disconnect(); } catch {} }
2990
- setTimeout(() => process.exit(0), 1500);
2991
- });
3004
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
3005
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
2992
3006
  }
2993
3007
 
2994
3008
  // ══════════════════════════════════════════════════════════════
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "8.25.0",
3
+ "version": "8.31.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"