dankgrinder 7.79.0 โ 7.82.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 +10 -27
- package/lib/terminal.js +486 -508
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -2761,11 +2761,6 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2761
2761
|
const CLOUD_MODE = opts.cloud === true;
|
|
2762
2762
|
startTime = Date.now();
|
|
2763
2763
|
|
|
2764
|
-
if (CLOUD_MODE) {
|
|
2765
|
-
// In cloud mode, API_KEY is the CLOUD_ADMIN_KEY โ not used for user auth.
|
|
2766
|
-
// Per-account keys are fetched per-account from /api/cloud/grinders.
|
|
2767
|
-
console.log('๐ฅ๏ธ Starting in CLOUD MODE โ grinding all cloud-enabled accounts');
|
|
2768
|
-
}
|
|
2769
2764
|
REDIS_URL = process.env.REDIS_URL || '';
|
|
2770
2765
|
WEBHOOK_URL = process.env.WEBHOOK_URL || '';
|
|
2771
2766
|
|
|
@@ -2773,15 +2768,14 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2773
2768
|
let hasZlib = false;
|
|
2774
2769
|
try { require('zlib-sync'); hasZlib = true; } catch {}
|
|
2775
2770
|
|
|
2776
|
-
console.log
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
);
|
|
2784
|
-
log('info', `${c.dim}Fetching accounts...${c.reset}`);
|
|
2771
|
+
// Init terminal FIRST โ captures all subsequent console.log output
|
|
2772
|
+
terminal.setVersion(PKG_VERSION);
|
|
2773
|
+
terminal.init({ workers: [], startTime });
|
|
2774
|
+
|
|
2775
|
+
if (CLOUD_MODE) {
|
|
2776
|
+
console.log(`${rgb(139, 92, 246)}๐ฅ๏ธ Starting in CLOUD MODE โ grinding all cloud-enabled accounts${c.reset}`);
|
|
2777
|
+
}
|
|
2778
|
+
terminal.startPhase('Fetching accounts...');
|
|
2785
2779
|
|
|
2786
2780
|
const fetchOpts = CLOUD_MODE ? { cloud: true } : {};
|
|
2787
2781
|
let data = await fetchConfig(4, 2000, fetchOpts);
|
|
@@ -2792,9 +2786,11 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2792
2786
|
data = await fetchConfig(4, 2000, fetchOpts);
|
|
2793
2787
|
}
|
|
2794
2788
|
if (data && data.error) {
|
|
2789
|
+
terminal.endPhase(`API error: ${data.error}`, false);
|
|
2795
2790
|
log('error', `${data.error}`);
|
|
2796
2791
|
return;
|
|
2797
2792
|
}
|
|
2793
|
+
terminal.endPhase(`API connected โ ${data.accounts?.length || 0} accounts`);
|
|
2798
2794
|
|
|
2799
2795
|
// Cloud mode: post heartbeat every 30s
|
|
2800
2796
|
if (CLOUD_MODE) {
|
|
@@ -2839,10 +2835,6 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2839
2835
|
}
|
|
2840
2836
|
}
|
|
2841
2837
|
|
|
2842
|
-
const checks = [];
|
|
2843
|
-
checks.push(`${rgb(52, 211, 153)}โ${c.reset} ${c.white}API${c.reset}`);
|
|
2844
|
-
if (REDIS_URL) checks.push(redis ? `${rgb(52, 211, 153)}โ${c.reset} ${c.white}Redis${c.reset}` : `${rgb(251, 191, 36)}โ${c.reset} ${c.dim}Redis (connecting...)${c.reset}`);
|
|
2845
|
-
|
|
2846
2838
|
// Init rawLogger Redis (uses same URL โ logs all raw gateway data)
|
|
2847
2839
|
if (REDIS_URL) {
|
|
2848
2840
|
rawLogger.init(redis);
|
|
@@ -2887,16 +2879,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2887
2879
|
}
|
|
2888
2880
|
}
|
|
2889
2881
|
});
|
|
2890
|
-
checks.push(`${rgb(52, 211, 153)}โ${c.reset} ${c.white}RawLog${c.reset}`);
|
|
2891
|
-
}
|
|
2892
|
-
if (hasZlib) checks.push(`${rgb(52, 211, 153)}โ${c.reset} ${c.white}zlib${c.reset}`);
|
|
2893
|
-
if (WEBHOOK_URL) checks.push(`${rgb(52, 211, 153)}โ${c.reset} ${c.white}Webhook${c.reset}`);
|
|
2894
|
-
if (CLUSTER_ENABLED) {
|
|
2895
|
-
checks.push(`${rgb(52, 211, 153)}โ${c.reset} ${rgb(34, 211, 238)}Cluster${c.reset} ${c.dim}(${NODE_ID.substring(0, 12)})${c.reset}`);
|
|
2896
2882
|
}
|
|
2897
|
-
checks.push(`${rgb(52, 211, 153)}โ${c.reset} ${c.white}${accounts.length} Account${accounts.length > 1 ? 's' : ''}${c.reset}`);
|
|
2898
|
-
console.log(` ${checks.join(' ')}`);
|
|
2899
|
-
console.log('');
|
|
2900
2883
|
|
|
2901
2884
|
// โโ Terminal renderer init โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2902
2885
|
terminal.setVersion(PKG_VERSION);
|
package/lib/terminal.js
CHANGED
|
@@ -1,154 +1,161 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* terminal.js โ
|
|
2
|
+
* terminal.js โ Polished animated terminal renderer for DankGrinder
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
4
|
+
* Features:
|
|
5
|
+
* - Animated startup phases with multi-element spinners
|
|
6
|
+
* - Live leaderboard with coin-based rank medals (๐ฅ๐ฅ๐ฅ)
|
|
7
|
+
* - Accounts stay in fixed positions, medals update live
|
|
8
|
+
* - Pulsing status indicators for active accounts
|
|
9
|
+
* - Dark theme: deep purple borders, vibrant accent colors
|
|
10
|
+
* - 4 FPS render loop, dirty-row only, no flicker
|
|
11
|
+
* - Graceful fallback: plain console.log if not a TTY
|
|
11
12
|
*/
|
|
12
13
|
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
13
16
|
const READY = (() => {
|
|
14
|
-
try {
|
|
15
|
-
return process.stdout.isTTY && !process.env.NO_TERM;
|
|
16
|
-
} catch (_) { return false; }
|
|
17
|
+
try { return !!process.stdout.isTTY && !process.env.NO_TERM; } catch (_) { return false; }
|
|
17
18
|
})();
|
|
18
19
|
|
|
19
|
-
// โโ ANSI
|
|
20
|
+
// โโ ANSI helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
20
21
|
|
|
21
22
|
const A = {
|
|
22
23
|
reset: '\x1b[0m',
|
|
23
|
-
bold:
|
|
24
|
-
dim:
|
|
25
|
-
italic: '\x1b[3m',
|
|
26
|
-
|
|
27
|
-
black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m',
|
|
28
|
-
yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m',
|
|
29
|
-
cyan: '\x1b[36m', white: '\x1b[37m',
|
|
30
|
-
|
|
31
|
-
bgBlack: '\x1b[40m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m',
|
|
32
|
-
bgYellow: '\x1b[43m', bgBlue: '\x1b[44m', bgMagenta: '\x1b[45m',
|
|
33
|
-
bgCyan: '\x1b[46m', bgWhite: '\x1b[47m',
|
|
34
|
-
|
|
35
|
-
// True-color RGB
|
|
24
|
+
bold: '\x1b[1m',
|
|
25
|
+
dim: '\x1b[2m',
|
|
36
26
|
rgb: (r, g, b) => `\x1b[38;2;${r};${g};${b}m`,
|
|
37
|
-
|
|
38
|
-
// Cursor
|
|
39
|
-
save: '\x1b7', restore: '\x1b8',
|
|
40
|
-
hide: '\x1b[?25l', show: '\x1b[?25h',
|
|
41
|
-
up: (n = 1) => `\x1b[${n}A`,
|
|
42
|
-
down: (n = 1) => `\x1b[${n}B`,
|
|
43
|
-
clear: '\x1b[2J',
|
|
44
|
-
clearLine: '\x1b[2K',
|
|
45
|
-
home: '\x1b[H',
|
|
46
|
-
|
|
47
|
-
// Erase in display (clears scrollback too)
|
|
48
27
|
eraseAll: '\x1b[3J\x1b[2J\x1b[H',
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
teal: '\x1b[38;5;44m',
|
|
55
|
-
lime: '\x1b[38;5;82m',
|
|
56
|
-
crimson: '\x1b[38;5;196m',
|
|
57
|
-
slate: '\x1b[38;5;245m',
|
|
58
|
-
gold: '\x1b[38;5;220m',
|
|
59
|
-
emerald: '\x1b[38;5;48m',
|
|
28
|
+
clearLine: '\x1b[2K',
|
|
29
|
+
save: '\x1b7',
|
|
30
|
+
restore: '\x1b8',
|
|
31
|
+
hide: '\x1b[?25l',
|
|
32
|
+
show: '\x1b[?25h',
|
|
60
33
|
};
|
|
61
34
|
|
|
62
|
-
// โโ
|
|
35
|
+
// โโ Palette โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
63
36
|
|
|
64
37
|
const C = {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
38
|
+
border: A.rgb(55, 42, 95),
|
|
39
|
+
borderDim: A.rgb(38, 28, 65),
|
|
40
|
+
borderMid: A.rgb(80, 65, 130),
|
|
41
|
+
|
|
42
|
+
text: A.rgb(205, 200, 230),
|
|
43
|
+
textDim: A.rgb(90, 85, 115),
|
|
44
|
+
textFaint: A.rgb(55, 50, 75),
|
|
45
|
+
|
|
46
|
+
purple: A.rgb(167, 139, 250), // vivid lavender
|
|
47
|
+
cyan: A.rgb(34, 211, 238), // vivid cyan
|
|
48
|
+
gold: A.rgb(251, 191, 36), // vivid gold
|
|
49
|
+
green: A.rgb(52, 211, 153), // vivid green
|
|
50
|
+
pink: A.rgb(244, 114, 182), // vivid pink
|
|
51
|
+
orange: A.rgb(251, 146, 60), // vivid orange
|
|
52
|
+
red: A.rgb(248, 113, 113), // vivid red
|
|
53
|
+
blue: A.rgb(96, 165, 250), // vivid blue
|
|
54
|
+
|
|
55
|
+
// Rank medal colors
|
|
56
|
+
rank1: A.rgb(255, 215, 0),
|
|
57
|
+
rank2: A.rgb(192, 192, 192),
|
|
58
|
+
rank3: A.rgb(205, 127, 50),
|
|
59
|
+
|
|
60
|
+
// Per-account accent colors (cycles)
|
|
61
|
+
ACCT: [
|
|
62
|
+
A.rgb(167, 139, 250), // lavender
|
|
63
|
+
A.rgb(103, 232, 249), // sky cyan
|
|
64
|
+
A.rgb(253, 186, 116), // peach
|
|
65
|
+
A.rgb(167, 243, 208), // mint
|
|
66
|
+
A.rgb(252, 165, 201), // rose
|
|
67
|
+
A.rgb(165, 243, 252), // light cyan
|
|
68
|
+
A.rgb(196, 181, 253), // light purple
|
|
69
|
+
A.rgb(147, 226, 226), // light teal
|
|
70
|
+
],
|
|
97
71
|
};
|
|
98
72
|
|
|
73
|
+
// โโ Box-drawing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
74
|
+
|
|
75
|
+
const TL='โญ', TR='โฎ', BL='โฐ', BR='โฏ', H='โ', V='โ';
|
|
76
|
+
|
|
99
77
|
// โโ Spinner frames โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
100
78
|
|
|
101
|
-
|
|
79
|
+
// Dot spinner for phase labels
|
|
80
|
+
const SPIN_DOTS = ['โ ','โ ','โ น','โ ธ','โ ผ','โ ด','โ ฆ','โ ง','โ ฟ'];
|
|
102
81
|
|
|
103
|
-
//
|
|
82
|
+
// Block spinner for progress
|
|
83
|
+
const SPIN_BLOCK = ['โ','โ','๏ฟฝ','โ','โ','โ','โ','โ'];
|
|
84
|
+
|
|
85
|
+
// Pulse frames for active indicators
|
|
86
|
+
const PULSE = ['โ', 'โ', 'โ', 'โ'];
|
|
87
|
+
|
|
88
|
+
// โโ stdout capture โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
104
89
|
|
|
105
90
|
let _origWrite = null;
|
|
106
91
|
let _captureActive = false;
|
|
107
92
|
let _captureBuf = [];
|
|
108
93
|
|
|
109
|
-
function
|
|
110
|
-
if (_captureActive) {
|
|
111
|
-
_captureBuf.push(String(chunk));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
94
|
+
function _capWrite(chunk) {
|
|
95
|
+
if (_captureActive) { _captureBuf.push(String(chunk)); return; }
|
|
114
96
|
return _origWrite.call(process.stdout, chunk);
|
|
115
97
|
}
|
|
116
98
|
|
|
117
|
-
// โโ
|
|
99
|
+
// โโ ANSI utils โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
100
|
+
|
|
101
|
+
function ansiLen(s) {
|
|
102
|
+
let len = 0, i = 0;
|
|
103
|
+
const str = String(s);
|
|
104
|
+
while (i < str.length) {
|
|
105
|
+
if (str.charCodeAt(i) === 0x1b && str[i+1] === '[') {
|
|
106
|
+
let j = i+2;
|
|
107
|
+
while (j < str.length && str[j] !== 'm') j++;
|
|
108
|
+
i = j + 1;
|
|
109
|
+
} else { len++; i++; }
|
|
110
|
+
}
|
|
111
|
+
return len;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function rpad(s, w) { return s + ' '.repeat(Math.max(0, w - ansiLen(s))); }
|
|
115
|
+
|
|
116
|
+
// โโ Rank helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
117
|
+
|
|
118
|
+
const MEDALS = ['๐ฅ','๐ฅ','๐ฅ'];
|
|
119
|
+
|
|
120
|
+
function coinRank(wk, workers) {
|
|
121
|
+
const c = wk.stats?.coins || 0;
|
|
122
|
+
let r = 1;
|
|
123
|
+
for (const w of workers) {
|
|
124
|
+
if ((w.stats?.coins || 0) > c) r++;
|
|
125
|
+
}
|
|
126
|
+
return r;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// โโ Terminal โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
118
130
|
|
|
119
131
|
class Terminal {
|
|
120
132
|
constructor() {
|
|
121
|
-
this.workers
|
|
122
|
-
this.events
|
|
123
|
-
this.MAX_EVENTS =
|
|
133
|
+
this.workers = [];
|
|
134
|
+
this.events = [];
|
|
135
|
+
this.MAX_EVENTS = 3;
|
|
124
136
|
|
|
125
|
-
this.
|
|
137
|
+
this.phase = '';
|
|
126
138
|
this.phaseFrame = 0;
|
|
127
|
-
this.phaseTimer
|
|
139
|
+
this.phaseTimer = null;
|
|
140
|
+
this.phaseDone = 0;
|
|
141
|
+
this.phaseTotal = 0;
|
|
128
142
|
|
|
129
143
|
this.dirtyWorkers = new Set();
|
|
130
|
-
this.
|
|
131
|
-
this.dirtyStats = true;
|
|
144
|
+
this.dirtyStats = true;
|
|
132
145
|
this._renderTimer = null;
|
|
133
|
-
this._startTime
|
|
146
|
+
this._startTime = 0;
|
|
147
|
+
this._active = false;
|
|
148
|
+
this._shutdown = false;
|
|
149
|
+
this._origLog = null;
|
|
134
150
|
|
|
151
|
+
this._w = 110;
|
|
152
|
+
this._h = 35;
|
|
135
153
|
this.windowStart = 0;
|
|
136
|
-
this.windowSize
|
|
137
|
-
this._followIdx = -1;
|
|
154
|
+
this.windowSize = 8;
|
|
138
155
|
|
|
139
|
-
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
this._w = 80;
|
|
144
|
-
this._h = 24;
|
|
145
|
-
this._resizeTimer = null;
|
|
146
|
-
|
|
147
|
-
// โโ Startup capture โโ
|
|
148
|
-
this._capturing = false;
|
|
149
|
-
this._origLog = null;
|
|
150
|
-
this._phaseProgressDone = 0;
|
|
151
|
-
this._phaseProgressTotal = 0;
|
|
156
|
+
// Pulse animation state
|
|
157
|
+
this._pulseFrame = 0;
|
|
158
|
+
this._pulseTimer = null;
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
// โโ Public API โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -159,26 +166,21 @@ class Terminal {
|
|
|
159
166
|
this._updateSize();
|
|
160
167
|
|
|
161
168
|
if (READY) {
|
|
162
|
-
// Override stdout.write to capture everything during startup
|
|
163
169
|
_origWrite = process.stdout.write.bind(process.stdout);
|
|
164
|
-
process.stdout.write =
|
|
165
|
-
this._capturing = true;
|
|
170
|
+
process.stdout.write = _capWrite;
|
|
166
171
|
_captureActive = true;
|
|
167
172
|
_captureBuf = [];
|
|
168
|
-
|
|
169
|
-
// Also override console.log temporarily
|
|
170
173
|
this._origLog = console.log;
|
|
171
174
|
console.log = (...args) => {
|
|
175
|
+
const s = args.join(' ');
|
|
172
176
|
if (this._active) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (msg.trim()) this.flashEvent('info', msg);
|
|
177
|
+
const clean = s.replace(/\x1b\[[0-9;]*m/g, '').substring(0, 100);
|
|
178
|
+
if (clean.trim()) this.flashEvent('info', clean);
|
|
176
179
|
} else {
|
|
177
|
-
|
|
180
|
+
_capWrite(s + '\n');
|
|
178
181
|
}
|
|
179
182
|
};
|
|
180
|
-
|
|
181
|
-
process.stdout.on('resize', this._onResize.bind(this));
|
|
183
|
+
process.stdout.on('resize', () => this._onResize());
|
|
182
184
|
this._drawStartupScreen();
|
|
183
185
|
}
|
|
184
186
|
}
|
|
@@ -186,63 +188,52 @@ class Terminal {
|
|
|
186
188
|
setVersion(v) { this._version = v; }
|
|
187
189
|
|
|
188
190
|
startPhase(name) {
|
|
189
|
-
this.
|
|
191
|
+
this.phase = name;
|
|
192
|
+
this.phaseDone = 0;
|
|
193
|
+
this.phaseTotal = 0;
|
|
190
194
|
this.phaseFrame = 0;
|
|
191
|
-
|
|
192
|
-
this._phaseProgressTotal = 0;
|
|
193
|
-
|
|
194
|
-
if (!READY) {
|
|
195
|
-
console.log(` โณ ${name}...`);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
195
|
+
if (!READY) return;
|
|
198
196
|
if (this.phaseTimer) clearInterval(this.phaseTimer);
|
|
199
197
|
this.phaseTimer = setInterval(() => {
|
|
200
|
-
this.phaseFrame = (this.phaseFrame + 1) %
|
|
201
|
-
this.
|
|
202
|
-
},
|
|
203
|
-
this.
|
|
198
|
+
this.phaseFrame = (this.phaseFrame + 1) % SPIN_DOTS.length;
|
|
199
|
+
this._renderPhase();
|
|
200
|
+
}, 80);
|
|
201
|
+
this._renderPhase();
|
|
204
202
|
}
|
|
205
203
|
|
|
206
204
|
updateProgress(done, total) {
|
|
207
|
-
this.
|
|
208
|
-
this.
|
|
205
|
+
this.phaseDone = done;
|
|
206
|
+
this.phaseTotal = total;
|
|
209
207
|
if (!READY) return;
|
|
210
|
-
this.
|
|
208
|
+
this._renderProgress();
|
|
211
209
|
}
|
|
212
210
|
|
|
213
211
|
endPhase(name, ok = true) {
|
|
214
212
|
if (this.phaseTimer) { clearInterval(this.phaseTimer); this.phaseTimer = null; }
|
|
215
|
-
this.phaseName = '';
|
|
216
|
-
this._phaseProgressDone = 0;
|
|
217
|
-
this._phaseProgressTotal = 0;
|
|
218
|
-
|
|
219
213
|
if (!READY) {
|
|
220
|
-
const icon = ok ?
|
|
221
|
-
console.log(` ${icon}`);
|
|
214
|
+
const icon = ok ? `${C.green}โ${A.reset}` : `${C.red}โ${A.reset}`;
|
|
215
|
+
console.log(` ${icon} ${name}`);
|
|
222
216
|
return;
|
|
223
217
|
}
|
|
224
|
-
const icon = ok ? `${C.
|
|
225
|
-
const
|
|
218
|
+
const icon = ok ? `${C.green}โ${A.reset}` : `${C.red}โ${A.reset}`;
|
|
219
|
+
const label = `${icon} ${name}`;
|
|
220
|
+
// Write to rows 5 (phase) and 7 (progress) with result
|
|
226
221
|
const w = this._w;
|
|
227
|
-
|
|
228
|
-
|
|
222
|
+
const V = C.border;
|
|
223
|
+
const line = rpad(` ${label}`, w - 3);
|
|
229
224
|
this._write(
|
|
230
|
-
A.save +
|
|
231
|
-
this.
|
|
232
|
-
|
|
233
|
-
this.
|
|
234
|
-
|
|
235
|
-
this._cursor(10, 1) + A.clearLine +
|
|
236
|
-
this._cursor(11, 1) + A.clearLine +
|
|
237
|
-
this._cursor(12, 1) + A.clearLine +
|
|
238
|
-
`${this._rpad(line, w)}` +
|
|
225
|
+
`${A.save}` +
|
|
226
|
+
this._at(5, 1) + A.clearLine +
|
|
227
|
+
`${V} ${line} ${V}${A.reset}` +
|
|
228
|
+
this._at(7, 1) + A.clearLine +
|
|
229
|
+
`${V} ${rpad('', w - 3)} ${V}${A.reset}` +
|
|
239
230
|
A.restore
|
|
240
231
|
);
|
|
241
232
|
}
|
|
242
233
|
|
|
243
234
|
flashEvent(type, msg) {
|
|
244
235
|
const now = new Date();
|
|
245
|
-
const ts = `${
|
|
236
|
+
const ts = `${C.textDim}${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}${A.reset}`;
|
|
246
237
|
this.events.unshift({ ts, type, msg, id: Date.now() });
|
|
247
238
|
if (this.events.length > this.MAX_EVENTS) this.events.pop();
|
|
248
239
|
this.dirtyEvents = true;
|
|
@@ -255,7 +246,8 @@ class Terminal {
|
|
|
255
246
|
}
|
|
256
247
|
|
|
257
248
|
markWorkerDirty(idx) {
|
|
258
|
-
this.dirtyWorkers.
|
|
249
|
+
this.dirtyWorkers = new Set(this.workers.map((_, i) => i));
|
|
250
|
+
this.dirtyStats = true;
|
|
259
251
|
}
|
|
260
252
|
|
|
261
253
|
setActive() {
|
|
@@ -263,464 +255,416 @@ class Terminal {
|
|
|
263
255
|
this._active = true;
|
|
264
256
|
|
|
265
257
|
if (READY) {
|
|
266
|
-
// โโ Stop capturing, fully clear screen, draw live view โโ
|
|
267
258
|
_captureActive = false;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// Restore stdout.write first
|
|
271
|
-
if (_origWrite) {
|
|
272
|
-
process.stdout.write = _origWrite;
|
|
273
|
-
_origWrite = null;
|
|
274
|
-
}
|
|
275
|
-
if (this._origLog) {
|
|
276
|
-
console.log = this._origLog;
|
|
277
|
-
this._origLog = null;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Discard all buffered startup output โ we only want the live view
|
|
259
|
+
if (_origWrite) { process.stdout.write = _origWrite; _origWrite = null; }
|
|
260
|
+
if (this._origLog) { console.log = this._origLog; this._origLog = null; }
|
|
281
261
|
_captureBuf = [];
|
|
282
262
|
|
|
283
|
-
|
|
284
|
-
this._write(A.eraseAll);
|
|
285
|
-
|
|
286
|
-
// Draw live view
|
|
263
|
+
this._write(A.eraseAll + A.hide);
|
|
287
264
|
this._drawLiveView();
|
|
288
|
-
|
|
289
|
-
// Reset dirty flags so we don't redraw header every frame
|
|
290
265
|
this.dirtyWorkers.clear();
|
|
291
266
|
this.dirtyEvents = false;
|
|
292
267
|
this.dirtyStats = false;
|
|
293
268
|
|
|
269
|
+
// Start pulse animation for active status indicators
|
|
270
|
+
if (this._pulseTimer) clearInterval(this._pulseTimer);
|
|
271
|
+
this._pulseTimer = setInterval(() => {
|
|
272
|
+
this._pulseFrame = (this._pulseFrame + 1) % PULSE.length;
|
|
273
|
+
this.dirtyStats = true; // pulse affects account rows
|
|
274
|
+
}, 400);
|
|
275
|
+
|
|
294
276
|
this._startRenderLoop();
|
|
295
277
|
} else {
|
|
296
|
-
|
|
297
|
-
if (this._origLog) {
|
|
298
|
-
console.log = this._origLog;
|
|
299
|
-
this._origLog = null;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
scrollBy(delta) {
|
|
305
|
-
if (!READY || this._shutdown) return;
|
|
306
|
-
const max = Math.max(0, this.workers.length - this.windowSize);
|
|
307
|
-
this.windowStart = Math.max(0, Math.min(max, this.windowStart + delta));
|
|
308
|
-
this._followIdx = -1;
|
|
309
|
-
this.dirtyWorkers = new Set();
|
|
310
|
-
for (let i = this.windowStart; i < this.windowStart + this.windowSize; i++) {
|
|
311
|
-
if (i < this.workers.length) this.dirtyWorkers.add(i);
|
|
278
|
+
if (this._origLog) { console.log = this._origLog; this._origLog = null; }
|
|
312
279
|
}
|
|
313
280
|
}
|
|
314
281
|
|
|
315
282
|
shutdown(summary = {}) {
|
|
316
283
|
this._shutdown = true;
|
|
317
284
|
if (this._renderTimer) { clearInterval(this._renderTimer); this._renderTimer = null; }
|
|
318
|
-
if (this.
|
|
319
|
-
|
|
320
|
-
// Restore stdout state
|
|
321
|
-
if (READY && _origWrite) {
|
|
322
|
-
process.stdout.write = _origWrite;
|
|
323
|
-
_origWrite = null;
|
|
324
|
-
}
|
|
325
|
-
if (this._origLog) {
|
|
326
|
-
console.log = this._origLog;
|
|
327
|
-
this._origLog = null;
|
|
328
|
-
}
|
|
285
|
+
if (this._pulseTimer) { clearInterval(this._pulseTimer); this._pulseTimer = null; }
|
|
286
|
+
if (this.phaseTimer) { clearInterval(this.phaseTimer); this.phaseTimer = null; }
|
|
329
287
|
|
|
288
|
+
if (READY && _origWrite) { process.stdout.write = _origWrite; _origWrite = null; }
|
|
289
|
+
if (this._origLog) { console.log = this._origLog; this._origLog = null; }
|
|
330
290
|
this._write(A.show);
|
|
331
291
|
|
|
332
|
-
const w = this._w;
|
|
333
292
|
const { totalCoins = 0, totalCmds = 0, totalSuccess = 0,
|
|
334
293
|
workers = [], uptime = 0, memMB = 0 } = summary;
|
|
335
294
|
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
const g = C.statValue;
|
|
339
|
-
const dim = A.dim;
|
|
340
|
-
const r = A.reset;
|
|
341
|
-
|
|
342
|
-
let out = '';
|
|
343
|
-
out += A.eraseAll + A.home + A.save;
|
|
295
|
+
const w = this._w;
|
|
296
|
+
let out = A.eraseAll;
|
|
344
297
|
|
|
345
|
-
//
|
|
346
|
-
out +=
|
|
347
|
-
out +=
|
|
348
|
-
out +=
|
|
298
|
+
// Top bar
|
|
299
|
+
out += this._boxTop();
|
|
300
|
+
out += this._statsBar();
|
|
301
|
+
out += this._sep();
|
|
349
302
|
|
|
350
303
|
// Column headers
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
// Per-account rows
|
|
360
|
-
let row = 6;
|
|
361
|
-
for (let i = 0; i < workers.length && row < this._h - 3; i++) {
|
|
362
|
-
const wk = workers[i];
|
|
363
|
-
const rate = wk.stats?.commands > 0
|
|
364
|
-
? `${((wk.stats.successes / wk.stats.commands) * 100).toFixed(0)}%`
|
|
365
|
-
: '0%';
|
|
366
|
-
const ls = wk._lifesavers ?? '?';
|
|
367
|
-
const lsColor = ls === 0 ? C.lifesaversLow : ls <= 2 ? C.lifesaversMid : C.lifesavers;
|
|
368
|
-
const statusIcon = (!wk.running || wk._tokenInvalid) ? 'โซ offline'
|
|
369
|
-
: wk.paused || wk.dashboardPaused ? '๐ด paused'
|
|
370
|
-
: '๐ข active';
|
|
371
|
-
|
|
372
|
-
const line = [
|
|
373
|
-
`${g}${String(i + 1).padEnd(2)}${r}`,
|
|
374
|
-
`${C.name}${A.bold}${(wk.username || '?').substring(0, 18).padEnd(19)}${r}`,
|
|
375
|
-
`${C.coins}โฃ${(wk.stats?.coins || 0).toLocaleString().padStart(8)}${r}`,
|
|
376
|
-
`${C.level}Lv.${wk._level ?? '?'}${r}`,
|
|
377
|
-
`${lsColor}โฅ${String(ls).padStart(2)}${r}`,
|
|
378
|
-
`${g}${String(wk.stats?.commands || 0).padStart(4)}cmds${r}`,
|
|
379
|
-
`${g}${rate.padStart(4)}${r}`,
|
|
380
|
-
`${g}${statusIcon}${r}`,
|
|
381
|
-
].join(' ');
|
|
382
|
-
out += `${this._at(row++, 1)}${b} ${this._rpad(line, w - 4)} ${C.v}${r}`;
|
|
304
|
+
out += `${this._row(4, this._colHdr())}`;
|
|
305
|
+
out += this._sep();
|
|
306
|
+
|
|
307
|
+
// Account rows
|
|
308
|
+
let row = 5;
|
|
309
|
+
for (let i = 0; i < workers.length && row < this._h - 4; i++) {
|
|
310
|
+
out += `${this._row(row++, this._accountLine(workers[i], i, workers))}`;
|
|
383
311
|
}
|
|
384
312
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const totalRate = totalCmds > 0 ? `${((totalSuccess / totalCmds) * 100).toFixed(0)}%` : '0%';
|
|
390
|
-
const uptimeStr = this._fmtUptime(uptime);
|
|
391
|
-
const totalLine = [
|
|
392
|
-
`${h}๐ฐ TOTAL:${r}`,
|
|
393
|
-
`${C.coins}${A.bold}โฃ ${totalCoins.toLocaleString()}${r}`,
|
|
394
|
-
`${dim}${totalCmds} cmds${r}`,
|
|
395
|
-
`${dim}${totalRate} OK${r}`,
|
|
396
|
-
`${dim}${uptimeStr}${r}`,
|
|
397
|
-
`${dim}${memMB}MB RAM${r}`,
|
|
398
|
-
].join(' ');
|
|
399
|
-
out += `${this._at(row++, 1)}${b} ${this._rpad(totalLine, w - 4)} ${C.v}${r}`;
|
|
400
|
-
out += `${this._at(row, 1)}${b}${C.botLeft}${'โ'.repeat(w - 2)}${C.botRight}${r}`;
|
|
401
|
-
out += A.restore + A.show;
|
|
313
|
+
out += this._sep();
|
|
314
|
+
row++;
|
|
315
|
+
out += `${this._row(row++, this._totalLine(totalCoins, totalCmds, totalSuccess, uptime, memMB))}`;
|
|
316
|
+
out += this._boxBot();
|
|
402
317
|
|
|
403
318
|
this._write(out);
|
|
404
319
|
}
|
|
405
320
|
|
|
406
|
-
// โโ
|
|
321
|
+
// โโ Startup Screen โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
407
322
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this._h = process.stdout.rows || 24;
|
|
412
|
-
this.windowSize = Math.max(3, this._h - 11);
|
|
413
|
-
} catch (_) {
|
|
414
|
-
this._w = 80; this._h = 24; this.windowSize = 8;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
323
|
+
_drawStartupScreen() {
|
|
324
|
+
const w = this._w;
|
|
325
|
+
let out = A.eraseAll;
|
|
417
326
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
this._resizeTimer = setTimeout(() => {
|
|
421
|
-
this._updateSize();
|
|
422
|
-
if (this._active) {
|
|
423
|
-
this._write(A.eraseAll);
|
|
424
|
-
this._drawLiveView();
|
|
425
|
-
}
|
|
426
|
-
}, 100);
|
|
427
|
-
}
|
|
327
|
+
// Top border
|
|
328
|
+
out += `${this._at(1,1)}${C.border}${TL}${'โ'.repeat(w-2)}${TR}${A.reset}\n`;
|
|
428
329
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
while (i < str.length) {
|
|
433
|
-
if (str.charCodeAt(i) === 0x1b && str[i + 1] === '[') {
|
|
434
|
-
let j = i + 2;
|
|
435
|
-
while (j < str.length && str[j] !== 'm') j++;
|
|
436
|
-
i = j + 1;
|
|
437
|
-
} else { len++; i++; }
|
|
438
|
-
}
|
|
439
|
-
return len;
|
|
440
|
-
}
|
|
330
|
+
// Version title
|
|
331
|
+
const title = ` โฌก DANKGRINDER v${this._version || '?'} `;
|
|
332
|
+
out += `${this._at(2,1)}${C.border}${V} ${C.purple}${A.bold}${title}${rpad('', w - ansiLen(title) - 4)}${V}${A.reset}\n`;
|
|
441
333
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
334
|
+
// Subtitle with version info
|
|
335
|
+
const sub = `${C.textDim}24 commands ยท Auto-Recovery ยท Loss Limiter${A.reset}`;
|
|
336
|
+
out += `${this._at(3,1)}${C.border}${V} ${sub}${rpad('', w - ansiLen(sub) - 4)}${V}${A.reset}\n`;
|
|
445
337
|
|
|
446
|
-
|
|
447
|
-
_at(row, col) { return `\x1b[${row};${col}H`; }
|
|
448
|
-
_write(str) { if (str) process.stdout.write(str); }
|
|
338
|
+
out += `${this._at(4,1)}${C.border}${V}${'โ'.repeat(w-2)}${V}${A.reset}\n`;
|
|
449
339
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const s = Math.floor(ms / 1000);
|
|
453
|
-
if (s < 60) return `${s}s`;
|
|
454
|
-
const m = Math.floor(s / 60);
|
|
455
|
-
if (m < 60) return `${m}m ${s % 60}s`;
|
|
456
|
-
const h = Math.floor(m / 60);
|
|
457
|
-
if (h < 24) return `${h}h ${m % 60}m`;
|
|
458
|
-
return `${Math.floor(h / 24)}d ${h % 24}h`;
|
|
459
|
-
}
|
|
340
|
+
// Spinner + phase label (row 5)
|
|
341
|
+
out += `${this._at(5,1)}${C.border}${V}${rpad('', w-2)}${V}${A.reset}\n`;
|
|
460
342
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
464
|
-
return String(n);
|
|
465
|
-
}
|
|
343
|
+
// Progress bar (row 7)
|
|
344
|
+
out += `${this._at(7,1)}${C.border}${V}${rpad('', w-2)}${V}${A.reset}\n`;
|
|
466
345
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const ls = wk._lifesavers ?? '?';
|
|
470
|
-
const lsColor = ls === 0 ? C.lifesaversLow : ls <= 2 ? C.lifesaversMid : C.lifesavers;
|
|
471
|
-
const rate = wk.stats?.commands > 0
|
|
472
|
-
? `${((wk.stats.successes / wk.stats.commands) * 100).toFixed(0)}%`
|
|
473
|
-
: '0%';
|
|
346
|
+
// Checkmarks / status area (row 9)
|
|
347
|
+
out += `${this._at(9,1)}${C.border}${V}${rpad('', w-2)}${V}${A.reset}\n`;
|
|
474
348
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
statusDot = 'โซ'; statusText = 'offline';
|
|
478
|
-
} else if (wk.paused || wk.dashboardPaused) {
|
|
479
|
-
statusDot = '๐ด'; statusText = 'paused';
|
|
480
|
-
} else if (wk.lastStatus?.includes('claim') || wk.lastStatus?.includes('daily')) {
|
|
481
|
-
statusDot = '๐ก'; statusText = 'claiming';
|
|
482
|
-
} else {
|
|
483
|
-
statusDot = '๐ข'; statusText = 'grinding';
|
|
484
|
-
}
|
|
349
|
+
// Spacer
|
|
350
|
+
out += `${this._at(11,1)}${C.border}${V}${rpad('', w-2)}${V}${A.reset}\n`;
|
|
485
351
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
`${C.name}${A.bold}${(wk.username || '?').substring(0, 18).padEnd(19)}${A.reset}`,
|
|
489
|
-
`${C.coins}โฃ${this._fmtCoins(wk.stats?.coins || 0).padStart(7)}${A.reset}`,
|
|
490
|
-
`${C.level}Lv.${String(wk._level ?? '?').padStart(3)}${A.reset}`,
|
|
491
|
-
`${lsColor}โฅ${String(ls).padStart(2)}${A.reset}`,
|
|
492
|
-
`${C.statValue}${String(wk.stats?.commands || 0).padStart(4)}cmds${A.reset}`,
|
|
493
|
-
`${C.statValue}${rate.padStart(5)}${A.reset}`,
|
|
494
|
-
`${statusDot} ${statusText}`.padEnd(14),
|
|
495
|
-
`${A.dim}${(wk.lastStatus || '').substring(0, 22).padEnd(22)}${A.reset}`,
|
|
496
|
-
].join(' ');
|
|
352
|
+
// Bottom border
|
|
353
|
+
out += `${this._at(12,1)}${C.border}${BL}${'โ'.repeat(w-2)}${BR}${A.reset}\n`;
|
|
497
354
|
|
|
498
|
-
return this._rpad(line, w);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
_drawStartupScreen() {
|
|
502
|
-
const w = this._w;
|
|
503
|
-
const b = C.border;
|
|
504
|
-
const h = C.header;
|
|
505
|
-
const dim = A.dim;
|
|
506
|
-
const r = A.reset;
|
|
507
|
-
|
|
508
|
-
let out = '';
|
|
509
|
-
out += A.eraseAll + A.home;
|
|
510
|
-
out += `${this._at(1, 1)}${b}${C.topLeft}${'โ'.repeat(w - 2)}${C.topRight}${r}`;
|
|
511
|
-
out += `${this._at(2, 1)}${b} ${h}${A.bold} โฌก DANKGRINDER v${this._version || '?'} ${r}${'โ'.repeat(Math.max(0, w - 28 - (this._version || '').length))}${r}`;
|
|
512
|
-
out += `${this._at(3, 1)}${b}${C.h}${'โ'.repeat(w - 2)}${C.h}${r}`;
|
|
513
|
-
// Status bar placeholder
|
|
514
|
-
out += `${this._at(4, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
515
|
-
out += `${this._at(5, 1)}${b}${C.h}${'โ'.repeat(w - 2)}${C.h}${r}`;
|
|
516
|
-
// Spinner area
|
|
517
|
-
out += `${this._at(7, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
518
|
-
out += `${this._at(8, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
519
|
-
out += `${this._at(9, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
520
|
-
out += `${this._at(10, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
521
|
-
out += `${this._at(11, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
522
|
-
out += `${this._at(12, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
523
355
|
// Footer
|
|
524
|
-
|
|
525
|
-
out += `${this._at(
|
|
356
|
+
const hint = `${C.textDim}Initializing...${A.reset}`;
|
|
357
|
+
out += `${this._at(13,1)}${C.border}${V} ${hint}${rpad('', w - ansiLen(hint) - 4)}${V}${A.reset}\n`;
|
|
526
358
|
|
|
527
359
|
this._write(out);
|
|
528
360
|
}
|
|
529
361
|
|
|
530
|
-
|
|
531
|
-
if (!READY || !this.
|
|
532
|
-
const frame = SPIN[this.phaseFrame];
|
|
533
|
-
const line = ` ${frame} ${this.phaseName}...`;
|
|
362
|
+
_renderPhase() {
|
|
363
|
+
if (!READY || !this.phase) return;
|
|
534
364
|
const w = this._w;
|
|
535
|
-
const
|
|
536
|
-
const
|
|
365
|
+
const V = C.border;
|
|
366
|
+
const dot = SPIN_DOTS[this.phaseFrame];
|
|
367
|
+
const label = ` ${dot} ${this.phase} `;
|
|
368
|
+
const line = rpad(label, w - 3);
|
|
537
369
|
this._write(
|
|
538
|
-
A.save +
|
|
539
|
-
this.
|
|
540
|
-
`${
|
|
370
|
+
`${A.save}` +
|
|
371
|
+
`${this._at(5,1)}${A.clearLine}` +
|
|
372
|
+
`${V} ${line} ${V}${A.reset}` +
|
|
541
373
|
A.restore
|
|
542
374
|
);
|
|
543
375
|
}
|
|
544
376
|
|
|
545
|
-
|
|
546
|
-
if (!READY
|
|
547
|
-
const { done, total } = { done: this._phaseProgressDone, total: this._phaseProgressTotal };
|
|
377
|
+
_renderProgress() {
|
|
378
|
+
if (!READY) return;
|
|
548
379
|
const w = this._w;
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
const barW = Math.max(10, w - 35);
|
|
380
|
+
const V = C.border;
|
|
381
|
+
const { done, total } = { done: this.phaseDone, total: this.phaseTotal };
|
|
382
|
+
const barW = Math.max(16, w - 40);
|
|
554
383
|
const filled = total > 0 ? Math.round((done / total) * barW) : 0;
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
384
|
+
const block = SPIN_BLOCK[this.phaseFrame % SPIN_BLOCK.length];
|
|
385
|
+
|
|
386
|
+
// Filled bar with gradient: gold on left, purple on right
|
|
387
|
+
const filledPart = filled > 0
|
|
388
|
+
? `${C.gold}${'โ'.repeat(Math.max(1, filled - 1))}${C.green}${block}${A.reset}`
|
|
389
|
+
: '';
|
|
390
|
+
const emptyPart = barW - filled > 0
|
|
391
|
+
? `${C.borderDim}${'โ'.repeat(Math.max(0, barW - filled))}${A.reset}`
|
|
392
|
+
: '';
|
|
393
|
+
|
|
394
|
+
const pct = total > 0 ? `${Math.round((done / total) * 100)}%` : '';
|
|
395
|
+
const label = ` ${pct} `;
|
|
396
|
+
const bar = `${filledPart}${emptyPart}${C.textDim}${label}${A.reset}`;
|
|
397
|
+
const line = rpad(` ${bar}`, w - 3);
|
|
398
|
+
|
|
558
399
|
this._write(
|
|
559
|
-
A.save +
|
|
560
|
-
this.
|
|
561
|
-
`${
|
|
400
|
+
`${A.save}` +
|
|
401
|
+
`${this._at(7,1)}${A.clearLine}` +
|
|
402
|
+
`${V} ${line} ${V}${A.reset}` +
|
|
562
403
|
A.restore
|
|
563
404
|
);
|
|
564
405
|
}
|
|
565
406
|
|
|
407
|
+
// โโ Live View โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
408
|
+
|
|
566
409
|
_drawLiveView() {
|
|
567
|
-
|
|
568
|
-
this.
|
|
569
|
-
this.
|
|
570
|
-
this.
|
|
410
|
+
let out = A.eraseAll;
|
|
411
|
+
out += this._boxTop();
|
|
412
|
+
out += this._statsBar();
|
|
413
|
+
out += this._sep();
|
|
414
|
+
out += `${this._row(4, this._colHdr())}`;
|
|
415
|
+
out += this._sep();
|
|
416
|
+
this._topRow = 5;
|
|
417
|
+
out += this._accountRows();
|
|
418
|
+
const sepRow = this._topRow + Math.min(this.windowSize, this.workers.length);
|
|
419
|
+
out += `${this._at(sepRow,1)}${C.border}${V}${'โ'.repeat(this._w-2)}${V}${A.reset}\n`;
|
|
420
|
+
this._eventRow = sepRow + 1;
|
|
421
|
+
out += this._eventFeed();
|
|
422
|
+
out += this._boxBot();
|
|
423
|
+
this._write(out);
|
|
571
424
|
}
|
|
572
425
|
|
|
573
|
-
|
|
426
|
+
_boxTop() {
|
|
574
427
|
const w = this._w;
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
428
|
+
let o = '';
|
|
429
|
+
o += `${this._at(1,1)}${C.border}${TL}${'โ'.repeat(w-2)}${TR}${A.reset}\n`;
|
|
430
|
+
const title = ` โฌก DANKGRINDER v${this._version || '?'} `;
|
|
431
|
+
o += `${this._at(2,1)}${C.border}${V} ${C.purple}${A.bold}${title}${rpad('', w - ansiLen(title) - 4)}${V}${A.reset}\n`;
|
|
432
|
+
o += `${this._at(3,1)}${C.border}${V}${'โ'.repeat(w-2)}${V}${A.reset}\n`;
|
|
433
|
+
return o;
|
|
434
|
+
}
|
|
580
435
|
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
436
|
+
_boxBot() {
|
|
437
|
+
const w = this._w;
|
|
438
|
+
const hint = `${C.textDim}โโ scroll Ctrl+C quit${A.reset}`;
|
|
439
|
+
let o = '';
|
|
440
|
+
o += `${this._at(this._footerRow,1)}${C.border}${BL}${'โ'.repeat(w-2)}${BR}${A.reset}\n`;
|
|
441
|
+
o += `${this._at(this._footerRow+1,1)}${C.border}${V} ${hint}${rpad('', w - ansiLen(hint) - 4)}${V}${A.reset}\n`;
|
|
442
|
+
return o;
|
|
443
|
+
}
|
|
587
444
|
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
this._write(`${this._at(5, 1)}${b}${C.h}${'โ'.repeat(w - 2)}${C.h}${r}`);
|
|
445
|
+
_sep() {
|
|
446
|
+
const w = this._w;
|
|
447
|
+
return `${C.border}${V}${'โ'.repeat(w-2)}${V}${A.reset}\n`;
|
|
448
|
+
}
|
|
593
449
|
|
|
594
|
-
|
|
450
|
+
_row(r, content) {
|
|
451
|
+
const w = this._w;
|
|
452
|
+
return `${this._at(r,1)}${C.border}${V} ${content}${rpad('', w - ansiLen(content) - 4)} ${V}${A.reset}\n`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
_statsBar() {
|
|
456
|
+
const w = this._w;
|
|
457
|
+
const stats = this._buildStats();
|
|
458
|
+
return `${this._row(4, stats)}`;
|
|
595
459
|
}
|
|
596
460
|
|
|
597
|
-
|
|
461
|
+
_buildStats() {
|
|
598
462
|
let totalCoins = 0, totalCmds = 0, totalSuccess = 0, totalLs = 0;
|
|
599
463
|
let paused = 0, active = 0;
|
|
600
464
|
|
|
601
465
|
for (const wk of this.workers) {
|
|
602
|
-
totalCoins
|
|
603
|
-
totalCmds
|
|
604
|
-
totalSuccess += wk.stats?.successes
|
|
466
|
+
totalCoins += wk.stats?.coins || 0;
|
|
467
|
+
totalCmds += wk.stats?.commands || 0;
|
|
468
|
+
totalSuccess += wk.stats?.successes|| 0;
|
|
605
469
|
if (wk._lifesavers != null) totalLs += wk._lifesavers;
|
|
606
470
|
if (wk.running && !wk._tokenInvalid) {
|
|
607
|
-
if (wk.paused || wk.dashboardPaused) paused++;
|
|
608
|
-
else active++;
|
|
471
|
+
if (wk.paused || wk.dashboardPaused) paused++; else active++;
|
|
609
472
|
}
|
|
610
473
|
}
|
|
611
|
-
|
|
612
474
|
const uptime = this._fmtUptime(Date.now() - this._startTime);
|
|
613
|
-
const rate
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
`${
|
|
618
|
-
|
|
619
|
-
`${
|
|
620
|
-
`${
|
|
621
|
-
`${
|
|
622
|
-
`${
|
|
623
|
-
`${
|
|
624
|
-
]
|
|
475
|
+
const rate = totalCmds > 0 ? `${((totalSuccess / totalCmds) * 100).toFixed(0)}%` : '0%';
|
|
476
|
+
|
|
477
|
+
const items = [
|
|
478
|
+
[`โฑ`, uptime, C.textDim],
|
|
479
|
+
[`โฌก`, `${this.workers.length} accounts`, C.textDim],
|
|
480
|
+
[`โฃ`, totalCoins.toLocaleString(), C.gold],
|
|
481
|
+
[`โก`, `${totalCmds} cmds`, C.textDim],
|
|
482
|
+
[`๐`, `${rate} ok`, C.textDim],
|
|
483
|
+
[`โฅ`, `${totalLs}`, C.pink],
|
|
484
|
+
[`๐ข`, `${active}`, C.green],
|
|
485
|
+
[`๐ด`, `${paused}`, C.red],
|
|
486
|
+
];
|
|
487
|
+
|
|
488
|
+
return items.map(([icon, val, col]) =>
|
|
489
|
+
`${C.textDim}${icon}${A.reset} ${col}${val}${A.reset}`
|
|
490
|
+
).join(` ${C.borderDim}โ${A.reset} `);
|
|
625
491
|
}
|
|
626
492
|
|
|
627
|
-
|
|
628
|
-
const w = this._w;
|
|
629
|
-
const b = C.border;
|
|
630
|
-
const h = C.header;
|
|
631
|
-
const r = A.reset;
|
|
632
|
-
|
|
633
|
-
// Column headers
|
|
493
|
+
_colHdr() {
|
|
634
494
|
const cols = [
|
|
635
|
-
`${
|
|
636
|
-
`${
|
|
637
|
-
`${
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
495
|
+
`${C.purple}#`,
|
|
496
|
+
`${C.purple}ACCOUNT`,
|
|
497
|
+
`${C.purple}COINS`,
|
|
498
|
+
`${C.purple}LV`,
|
|
499
|
+
`${C.purple}โฅ`,
|
|
500
|
+
`${C.purple}OK%`,
|
|
501
|
+
`${C.purple}STATUS`,
|
|
502
|
+
];
|
|
503
|
+
return cols.join(' ');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
_accountRows() {
|
|
507
|
+
let out = '';
|
|
643
508
|
for (let i = 0; i < this.windowSize; i++) {
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
if (
|
|
647
|
-
|
|
648
|
-
|
|
509
|
+
const wkIdx = this.windowStart + i;
|
|
510
|
+
const row = this._topRow + i;
|
|
511
|
+
if (row > this._h - 5) break;
|
|
512
|
+
if (wkIdx < this.workers.length) {
|
|
513
|
+
out += `${this._row(row, this._accountLine(this.workers[wkIdx], wkIdx, this.workers))}`;
|
|
649
514
|
} else {
|
|
650
|
-
|
|
515
|
+
out += `${this._row(row, '')}`;
|
|
651
516
|
}
|
|
652
517
|
}
|
|
653
|
-
|
|
518
|
+
return out;
|
|
654
519
|
}
|
|
655
520
|
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
const
|
|
659
|
-
const
|
|
660
|
-
const
|
|
521
|
+
_accountLine(wk, idx, workers) {
|
|
522
|
+
const rank = coinRank(wk, workers);
|
|
523
|
+
const pos = rank - 1; // 0-indexed
|
|
524
|
+
const isActive = wk.running && !wk._tokenInvalid && !wk.paused && !wk.dashboardPaused;
|
|
525
|
+
const ls = wk._lifesavers ?? '?';
|
|
526
|
+
const rate = wk.stats?.commands > 0
|
|
527
|
+
? `${((wk.stats.successes / wk.stats.commands) * 100).toFixed(0)}%`
|
|
528
|
+
: '0%';
|
|
661
529
|
|
|
662
|
-
|
|
663
|
-
|
|
530
|
+
// Rank badge
|
|
531
|
+
const medal = pos < 3 ? MEDALS[pos] : null;
|
|
532
|
+
const rankColor = pos === 0 ? C.rank1 : pos === 1 ? C.rank2 : pos === 2 ? C.rank3 : null;
|
|
533
|
+
const acctColor = C.ACCT[idx % C.ACCT.length];
|
|
534
|
+
|
|
535
|
+
// Color based on rank (top 3 get medal color, rest get account color)
|
|
536
|
+
const mainColor = isActive
|
|
537
|
+
? (rankColor || acctColor)
|
|
538
|
+
: C.textFaint;
|
|
539
|
+
|
|
540
|
+
const dimColor = isActive ? C.textDim : C.textFaint;
|
|
541
|
+
const goldColor = isActive ? C.gold : C.textDim;
|
|
542
|
+
const cyanColor = isActive ? C.cyan : C.textDim;
|
|
543
|
+
const lsColor = isActive
|
|
544
|
+
? (ls === 0 ? C.red : ls <= 2 ? C.orange : C.pink)
|
|
545
|
+
: C.textDim;
|
|
546
|
+
|
|
547
|
+
// Status with pulse for active
|
|
548
|
+
let dot, statusText;
|
|
549
|
+
if (!wk.running || wk._tokenInvalid) {
|
|
550
|
+
dot = `${C.textFaint}โซ${A.reset}`; statusText = `${C.textFaint}offline${A.reset}`;
|
|
551
|
+
} else if (wk.paused || wk.dashboardPaused) {
|
|
552
|
+
dot = `${C.red}๐ด${A.reset}`; statusText = `${C.red}paused${A.reset}`;
|
|
553
|
+
} else {
|
|
554
|
+
const pulse = PULSE[this._pulseFrame];
|
|
555
|
+
dot = `${C.green}${pulse}${A.reset}`; statusText = `${C.green}active${A.reset}`;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Account name โ truncate with care
|
|
559
|
+
const rawName = wk.username || '?';
|
|
560
|
+
const nameDisplay = rawName.length > 20
|
|
561
|
+
? rawName.substring(0, 17) + '...'
|
|
562
|
+
: rawName;
|
|
563
|
+
|
|
564
|
+
// Coins display
|
|
565
|
+
const coins = (wk.stats?.coins || 0).toLocaleString();
|
|
566
|
+
const sign = (wk.stats?.coins || 0) >= 0 ? '+' : '';
|
|
567
|
+
const coinDisplay = `${goldColor}${sign}โฃ${coins}${A.reset}`;
|
|
568
|
+
|
|
569
|
+
// Current command (minimal)
|
|
570
|
+
const cmd = (wk.lastStatus || 'โ').replace(/\x1b\[[0-9;]*m/g, '').substring(0, 14);
|
|
571
|
+
|
|
572
|
+
// Build rank badge
|
|
573
|
+
const rankBadge = medal
|
|
574
|
+
? `${rankColor}${A.bold}${medal}${A.reset}`
|
|
575
|
+
: `${dimColor}${rank}th${A.reset}`;
|
|
576
|
+
|
|
577
|
+
// Account name with subtle glow for top 3
|
|
578
|
+
const nameBadge = pos < 3
|
|
579
|
+
? `${mainColor}${A.bold}${nameDisplay}${A.reset}`
|
|
580
|
+
: `${mainColor}${nameDisplay}${A.reset}`;
|
|
581
|
+
|
|
582
|
+
const parts = [
|
|
583
|
+
rankBadge,
|
|
584
|
+
nameBadge,
|
|
585
|
+
coinDisplay,
|
|
586
|
+
`${cyanColor}Lv.${String(wk._level ?? '?').padStart(3)}${A.reset}`,
|
|
587
|
+
`${lsColor}โฅ${String(ls).padStart(2)}${A.reset}`,
|
|
588
|
+
`${dimColor}${rate.padStart(5)}${A.reset}`,
|
|
589
|
+
`${dot} ${statusText}`,
|
|
590
|
+
`${dimColor}${cmd}`,
|
|
591
|
+
];
|
|
592
|
+
|
|
593
|
+
return parts.join(' ');
|
|
594
|
+
}
|
|
664
595
|
|
|
665
|
-
|
|
596
|
+
_eventFeed() {
|
|
597
|
+
let out = '';
|
|
598
|
+
const visible = this.events.slice(0, Math.min(this.MAX_EVENTS, this._h - this._eventRow - 3));
|
|
666
599
|
for (let i = 0; i < visible.length; i++) {
|
|
667
|
-
const row = this.
|
|
668
|
-
if (row > this._h -
|
|
600
|
+
const row = this._eventRow + i;
|
|
601
|
+
if (row > this._h - 3) break;
|
|
669
602
|
const e = visible[i];
|
|
670
|
-
const
|
|
671
|
-
: e.type === 'lowls'
|
|
672
|
-
: e.type === 'levelup'
|
|
673
|
-
: e.type === 'success'
|
|
674
|
-
: C.
|
|
675
|
-
|
|
676
|
-
this._write(`${this._at(row, 1)}${b} ${this._rpad(line, w - 4)} ${C.v}${r}`);
|
|
603
|
+
const color = e.type === 'death' ? C.red
|
|
604
|
+
: e.type === 'lowls' ? C.orange
|
|
605
|
+
: e.type === 'levelup'? C.cyan
|
|
606
|
+
: e.type === 'success'? C.green
|
|
607
|
+
: C.textDim;
|
|
608
|
+
out += this._row(row, ` ${e.ts} ${color}${e.msg}${A.reset}`);
|
|
677
609
|
}
|
|
678
|
-
this._footerRow = this.
|
|
610
|
+
this._footerRow = this._eventRow + visible.length;
|
|
611
|
+
return out;
|
|
679
612
|
}
|
|
680
613
|
|
|
681
|
-
|
|
682
|
-
const
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
this._write(`${this._at(this._footerRow + 1, 1)}${b} ${hint}${' '.repeat(Math.max(0, w - 2 - this._ansiLen(hint)))}${r}`);
|
|
614
|
+
_totalLine(totalCoins, totalCmds, totalSuccess, uptime, memMB) {
|
|
615
|
+
const rate = totalCmds > 0 ? `${((totalSuccess / totalCmds) * 100).toFixed(0)}%` : '0%';
|
|
616
|
+
const items = [
|
|
617
|
+
[`๐ฐ`, `${C.gold}${A.bold}TOTAL:${A.reset}`, `${C.gold}${A.bold}โฃ${totalCoins.toLocaleString()}${A.reset}`],
|
|
618
|
+
[`โก`, `${C.textDim}${totalCmds} cmds${A.reset}`, `${C.textDim}${rate} ok${A.reset}`],
|
|
619
|
+
[`โฑ`, `${C.textDim}${this._fmtUptime(uptime)}${A.reset}`, `${C.textDim}${memMB}MB${A.reset}`],
|
|
620
|
+
];
|
|
621
|
+
return items.map(([, label, val]) => `${label} ${val}`).join(' ');
|
|
690
622
|
}
|
|
691
623
|
|
|
624
|
+
// โโ Render loop โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
625
|
+
|
|
692
626
|
_render() {
|
|
693
627
|
if (!READY || this._shutdown || !this._active) return;
|
|
694
628
|
|
|
695
|
-
// Only update stats line (row 4) when dirty โ don't redraw whole header
|
|
696
629
|
if (this.dirtyStats) {
|
|
630
|
+
// Update stats bar (row 4)
|
|
631
|
+
const stats = this._buildStats();
|
|
697
632
|
const w = this._w;
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
633
|
+
const V = C.border;
|
|
634
|
+
this._write(
|
|
635
|
+
`${this._at(4,1)}${V} ${rpad(stats, w-4)} ${V}${A.reset}`
|
|
636
|
+
);
|
|
637
|
+
// Re-draw account rows to update pulse/status
|
|
638
|
+
for (let i = 0; i < this.windowSize; i++) {
|
|
639
|
+
const wkIdx = this.windowStart + i;
|
|
640
|
+
const row = this._topRow + i;
|
|
641
|
+
if (row > this._h - 5) break;
|
|
642
|
+
if (wkIdx < this.workers.length) {
|
|
643
|
+
const line = this._accountLine(this.workers[wkIdx], wkIdx, this.workers);
|
|
644
|
+
this._write(`${this._at(row,1)}${C.border}${V} ${rpad(line, w-4)} ${V}${A.reset}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
702
647
|
this.dirtyStats = false;
|
|
703
648
|
}
|
|
704
649
|
|
|
705
650
|
if (this.dirtyWorkers.size > 0) {
|
|
706
651
|
const w = this._w;
|
|
707
|
-
const
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
if (
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
this._write(`${this._at(row, 1)}${b} ${line} ${C.v}${r}`);
|
|
652
|
+
const V = C.border;
|
|
653
|
+
for (const wkIdx of this.dirtyWorkers) {
|
|
654
|
+
const localRow = wkIdx - this.windowStart;
|
|
655
|
+
const row = this._topRow + localRow;
|
|
656
|
+
if (row < this._topRow || row > this._h - 5) continue;
|
|
657
|
+
if (wkIdx < this.workers.length) {
|
|
658
|
+
const line = this._accountLine(this.workers[wkIdx], wkIdx, this.workers);
|
|
659
|
+
this._write(`${this._at(row,1)}${C.border}${V} ${rpad(line, w-4)} ${V}${A.reset}`);
|
|
716
660
|
} else {
|
|
717
|
-
this._write(`${this._at(row,
|
|
661
|
+
this._write(`${this._at(row,1)}${C.border}${V}${rpad('', w-2)}${V}${A.reset}`);
|
|
718
662
|
}
|
|
719
663
|
}
|
|
720
664
|
}
|
|
721
665
|
|
|
722
666
|
if (this.dirtyEvents) {
|
|
723
|
-
this.
|
|
667
|
+
this._write(this._eventFeed());
|
|
724
668
|
}
|
|
725
669
|
|
|
726
670
|
this.dirtyWorkers.clear();
|
|
@@ -729,10 +673,44 @@ class Terminal {
|
|
|
729
673
|
|
|
730
674
|
_startRenderLoop() {
|
|
731
675
|
if (this._renderTimer) clearInterval(this._renderTimer);
|
|
732
|
-
this._renderTimer = setInterval(() =>
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
676
|
+
this._renderTimer = setInterval(() => this._render(), 250);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// โโ Internals โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
680
|
+
|
|
681
|
+
_updateSize() {
|
|
682
|
+
try {
|
|
683
|
+
this._w = process.stdout.columns || 110;
|
|
684
|
+
this._h = process.stdout.rows || 35;
|
|
685
|
+
this.windowSize = Math.max(4, this._h - 11);
|
|
686
|
+
} catch (_) {
|
|
687
|
+
this._w = 110; this._h = 35; this.windowSize = 17;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
_onResize() {
|
|
692
|
+
clearTimeout(this._resizeTimer);
|
|
693
|
+
this._resizeTimer = setTimeout(() => {
|
|
694
|
+
this._updateSize();
|
|
695
|
+
if (this._active) {
|
|
696
|
+
this._write(A.eraseAll);
|
|
697
|
+
this._drawLiveView();
|
|
698
|
+
}
|
|
699
|
+
}, 100);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
_at(r, c) { return `\x1b[${r};${c}H`; }
|
|
703
|
+
_write(s) { if (s) process.stdout.write(s); }
|
|
704
|
+
|
|
705
|
+
_fmtUptime(ms) {
|
|
706
|
+
if (!ms) return '0s';
|
|
707
|
+
const s = Math.floor(ms / 1000);
|
|
708
|
+
if (s < 60) return `${s}s`;
|
|
709
|
+
const m = Math.floor(s / 60);
|
|
710
|
+
if (m < 60) return `${m}m ${s%60}s`;
|
|
711
|
+
const h = Math.floor(m / 60);
|
|
712
|
+
if (h < 24) return `${h}h ${m%60}m`;
|
|
713
|
+
return `${Math.floor(h/24)}d ${h%24}h`;
|
|
736
714
|
}
|
|
737
715
|
}
|
|
738
716
|
|