dankgrinder 7.78.0 → 7.79.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 +11 -0
- package/lib/terminal.js +348 -429
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -339,6 +339,17 @@ function colorBanner() {
|
|
|
339
339
|
|
|
340
340
|
// ── Simple Logging ─────────────────────────────────────────────
|
|
341
341
|
function log(type, msg, label) {
|
|
342
|
+
// Route grinding logs through terminal flash events when active
|
|
343
|
+
if (terminal._active) {
|
|
344
|
+
const clean = stripAnsi(String(msg || '')).substring(0, 120);
|
|
345
|
+
const tagClean = stripAnsi(String(label || ''));
|
|
346
|
+
terminal.flashEvent(
|
|
347
|
+
type === 'error' ? 'death' : type === 'warn' ? 'warn' : 'info',
|
|
348
|
+
`${tagClean ? tagClean + ' ' : ''}${clean}`
|
|
349
|
+
);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
342
353
|
const colorIcons = {
|
|
343
354
|
info: `${c.dim}·${c.reset}`, success: `${rgb(52, 211, 153)}✓${c.reset}`,
|
|
344
355
|
error: `${rgb(239, 68, 68)}✗${c.reset}`, warn: `${rgb(251, 191, 36)}!${c.reset}`,
|
package/lib/terminal.js
CHANGED
|
@@ -1,29 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* terminal.js — Modern animated terminal renderer for DankGrinder
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
4
|
+
* Key design:
|
|
5
|
+
* - stdout capture during startup → buffer prevents bleed-through
|
|
6
|
+
* - When setActive() called: clear screen + replay buffer, then normal
|
|
7
|
+
* - After activation: all w.log() routed through flashEvent()
|
|
8
|
+
* - Virtual window: single-line per account (scales to 10k+)
|
|
7
9
|
* - 4 FPS render loop with dirty-row tracking (no flicker)
|
|
8
|
-
* - Graceful degradation: falls back to console.log if not
|
|
9
|
-
* - Graceful mode: Ctrl+C always restores cursor + shows summary
|
|
10
|
-
*
|
|
11
|
-
* Layout (fixed row heights, live-updated):
|
|
12
|
-
*
|
|
13
|
-
* ┌─ DANKGRINDER v7.77.0 ─────────────────────────────────────────────────────┐
|
|
14
|
-
* │ ⏱ 00:32 ⬡ 5 accounts ⏣ 47,230 ⚡ 12 cmd/m 📊 89% success │
|
|
15
|
-
* ├─ ACCOUNTS ──────────────────────────────────────────────────────────────────┤
|
|
16
|
-
* │ 💎 alice_99 ⏣ 12,450 L:24 ♥3 34cmds 🟢 grinding ⚔ adventure │
|
|
17
|
-
* │ 💎 bob_trades ⏣ 8,920 L:18 ♥5 28cmds 🟢 grinding 🐟 fishing │
|
|
18
|
-
* │ 💎 crypto_king ⏣ 5,100 L:31 ♥0 19cmds 🟡 depositing 💰 beg │
|
|
19
|
-
* │ 💎 diamond_h.. ⏣ 3,780 L:12 ♥8 14cmds 🟢 grinding 🌾 farm │
|
|
20
|
-
* │ 💎 moon_wallet ⏣ 1,200 L:9 ♥2 8cmds 🔴 paused ⚠ captcha │
|
|
21
|
-
* ├─ EVENTS ────────────────────────────────────────────────────────────────────┤
|
|
22
|
-
* │ 00:32 ⚔ alice_99 adventure: +⏣ 850 │
|
|
23
|
-
* │ 00:31 💀 crypto_king DEATH — 0 lifesavers! crime/search disabled │
|
|
24
|
-
* │ 00:30 ⬆️ bob_trades leveled up to Lv.19 │
|
|
25
|
-
* │ 00:28 🌾 diamond_hands farm: +⏣ 2,100 │
|
|
26
|
-
* └──────────────────────────────────────────────────────────────────────────────┘
|
|
10
|
+
* - Graceful degradation: falls back to plain console.log if not TTY
|
|
27
11
|
*/
|
|
28
12
|
|
|
29
13
|
const READY = (() => {
|
|
@@ -40,51 +24,39 @@ const A = {
|
|
|
40
24
|
dim: '\x1b[2m',
|
|
41
25
|
italic: '\x1b[3m',
|
|
42
26
|
|
|
43
|
-
black: '\x1b[30m',
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
bgBlack: '\x1b[40m',
|
|
53
|
-
bgRed: '\x1b[41m',
|
|
54
|
-
bgGreen: '\x1b[42m',
|
|
55
|
-
bgYellow: '\x1b[43m',
|
|
56
|
-
bgBlue: '\x1b[44m',
|
|
57
|
-
bgMagenta: '\x1b[45m',
|
|
58
|
-
bgCyan: '\x1b[46m',
|
|
59
|
-
bgWhite: '\x1b[47m',
|
|
60
|
-
|
|
61
|
-
// 256-color RGB shortcuts
|
|
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
|
|
62
36
|
rgb: (r, g, b) => `\x1b[38;2;${r};${g};${b}m`,
|
|
63
37
|
|
|
64
38
|
// Cursor
|
|
65
|
-
save: '\x1b7',
|
|
66
|
-
|
|
67
|
-
hide: '\x1b[?25l',
|
|
68
|
-
show: '\x1b[?25h',
|
|
39
|
+
save: '\x1b7', restore: '\x1b8',
|
|
40
|
+
hide: '\x1b[?25l', show: '\x1b[?25h',
|
|
69
41
|
up: (n = 1) => `\x1b[${n}A`,
|
|
70
42
|
down: (n = 1) => `\x1b[${n}B`,
|
|
71
|
-
right: (n = 1) => `\x1b[${n}C`,
|
|
72
|
-
left: (n = 1) => `\x1b[${n}D`,
|
|
73
|
-
col: (n) => `\x1b[${n}G`,
|
|
74
43
|
clear: '\x1b[2J',
|
|
75
44
|
clearLine: '\x1b[2K',
|
|
76
45
|
home: '\x1b[H',
|
|
77
46
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
47
|
+
// Erase in display (clears scrollback too)
|
|
48
|
+
eraseAll: '\x1b[3J\x1b[2J\x1b[H',
|
|
49
|
+
|
|
50
|
+
// 256-color shortcuts
|
|
51
|
+
purple: '\x1b[38;5;141m',
|
|
52
|
+
pink: '\x1b[38;5;205m',
|
|
53
|
+
orange: '\x1b[38;5;214m',
|
|
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',
|
|
88
60
|
};
|
|
89
61
|
|
|
90
62
|
// ── Color scheme ────────────────────────────────────────────────────────────
|
|
@@ -92,60 +64,63 @@ const A = {
|
|
|
92
64
|
const C = {
|
|
93
65
|
header: A.purple,
|
|
94
66
|
headerDim: A.rgb(100, 70, 180),
|
|
95
|
-
border: A.rgb(
|
|
96
|
-
borderDim: A.rgb(
|
|
67
|
+
border: A.rgb(55, 45, 85),
|
|
68
|
+
borderDim: A.rgb(35, 30, 55),
|
|
97
69
|
|
|
98
|
-
|
|
99
|
-
rowAlt: A.rgb(30, 28, 45),
|
|
100
|
-
rowHighlight: A.rgb(25, 23, 40),
|
|
101
|
-
|
|
102
|
-
name: A.rgb(255, 255, 255),
|
|
70
|
+
name: A.rgb(220, 215, 255),
|
|
103
71
|
nameDim: A.slate,
|
|
104
|
-
|
|
105
72
|
coins: A.gold,
|
|
106
73
|
level: A.cyan,
|
|
107
74
|
lifesavers: A.pink,
|
|
108
75
|
lifesaversLow: A.crimson,
|
|
109
76
|
lifesaversMid: A.orange,
|
|
110
77
|
|
|
111
|
-
statusActive: A.emerald,
|
|
112
|
-
statusPaused: A.crimson,
|
|
113
|
-
statusWarning: A.orange,
|
|
114
|
-
statusOffline: A.slate,
|
|
115
|
-
statusConnecting: A.yellow,
|
|
78
|
+
statusActive: A.emerald,
|
|
79
|
+
statusPaused: A.crimson,
|
|
80
|
+
statusWarning: A.orange,
|
|
81
|
+
statusOffline: A.slate,
|
|
82
|
+
statusConnecting: A.yellow,
|
|
116
83
|
|
|
117
84
|
cmdSuccess: A.emerald,
|
|
118
85
|
cmdError: A.crimson,
|
|
119
86
|
cmdEvent: A.purple,
|
|
120
87
|
cmdWarn: A.orange,
|
|
88
|
+
cmdInfo: A.slate,
|
|
121
89
|
|
|
122
90
|
statLabel: A.slate,
|
|
123
91
|
statValue: A.white,
|
|
124
92
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
rowBgWarning: A.rgb(40, 35, 15),
|
|
130
|
-
rowBgDeath: A.rgb(50, 15, 15),
|
|
93
|
+
// Box styles
|
|
94
|
+
topLeft: '╭', topRight: '╮',
|
|
95
|
+
botLeft: '╰', botRight: '╯',
|
|
96
|
+
h: '─', v: '│',
|
|
131
97
|
};
|
|
132
98
|
|
|
133
99
|
// ── Spinner frames ──────────────────────────────────────────────────────────
|
|
134
100
|
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
101
|
+
const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠿'];
|
|
102
|
+
|
|
103
|
+
// ── stdout capture during startup ─────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
let _origWrite = null;
|
|
106
|
+
let _captureActive = false;
|
|
107
|
+
let _captureBuf = [];
|
|
108
|
+
|
|
109
|
+
function _captureWrite(chunk) {
|
|
110
|
+
if (_captureActive) {
|
|
111
|
+
_captureBuf.push(String(chunk));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
return _origWrite.call(process.stdout, chunk);
|
|
115
|
+
}
|
|
141
116
|
|
|
142
117
|
// ── Terminal Renderer ────────────────────────────────────────────────────────
|
|
143
118
|
|
|
144
119
|
class Terminal {
|
|
145
120
|
constructor() {
|
|
146
121
|
this.workers = [];
|
|
147
|
-
this.events = [];
|
|
148
|
-
this.MAX_EVENTS =
|
|
122
|
+
this.events = [];
|
|
123
|
+
this.MAX_EVENTS = 4;
|
|
149
124
|
|
|
150
125
|
this.phaseName = '';
|
|
151
126
|
this.phaseFrame = 0;
|
|
@@ -157,44 +132,54 @@ class Terminal {
|
|
|
157
132
|
this._renderTimer = null;
|
|
158
133
|
this._startTime = 0;
|
|
159
134
|
|
|
160
|
-
// Virtual window state
|
|
161
135
|
this.windowStart = 0;
|
|
162
|
-
this.windowSize =
|
|
163
|
-
this.
|
|
136
|
+
this.windowSize = 8;
|
|
137
|
+
this._followIdx = -1;
|
|
164
138
|
|
|
165
139
|
this._lineCount = 0;
|
|
166
140
|
this._active = false;
|
|
167
141
|
this._shutdown = false;
|
|
168
142
|
|
|
169
|
-
// Row offsets (set during render)
|
|
170
|
-
this._headerRow = 0;
|
|
171
|
-
this._statsRow = 0;
|
|
172
|
-
this._accountsRow = 0;
|
|
173
|
-
this._eventsRow = 0;
|
|
174
|
-
this._footerRow = 0;
|
|
175
|
-
|
|
176
|
-
// ── Scroll position state ──
|
|
177
|
-
this._followWorkerIdx = -1; // -1 = follow newest active worker
|
|
178
|
-
|
|
179
143
|
this._w = 80;
|
|
180
144
|
this._h = 24;
|
|
181
145
|
this._resizeTimer = null;
|
|
182
|
-
|
|
146
|
+
|
|
147
|
+
// ── Startup capture ──
|
|
148
|
+
this._capturing = false;
|
|
149
|
+
this._origLog = null;
|
|
150
|
+
this._phaseProgressDone = 0;
|
|
151
|
+
this._phaseProgressTotal = 0;
|
|
183
152
|
}
|
|
184
153
|
|
|
185
154
|
// ── Public API ──────────────────────────────────────────────────────────
|
|
186
155
|
|
|
187
156
|
init(opts = {}) {
|
|
188
|
-
|
|
157
|
+
this._startTime = opts.startTime || Date.now();
|
|
189
158
|
this.workers = opts.workers || [];
|
|
190
159
|
this._updateSize();
|
|
191
160
|
|
|
192
161
|
if (READY) {
|
|
193
|
-
|
|
162
|
+
// Override stdout.write to capture everything during startup
|
|
163
|
+
_origWrite = process.stdout.write.bind(process.stdout);
|
|
164
|
+
process.stdout.write = _captureWrite;
|
|
165
|
+
this._capturing = true;
|
|
166
|
+
_captureActive = true;
|
|
167
|
+
_captureBuf = [];
|
|
168
|
+
|
|
169
|
+
// Also override console.log temporarily
|
|
170
|
+
this._origLog = console.log;
|
|
171
|
+
console.log = (...args) => {
|
|
172
|
+
if (this._active) {
|
|
173
|
+
// After activation, route to flashEvent
|
|
174
|
+
const msg = args.join(' ').replace(/\x1b\[[0-9;]*m/g, '').substring(0, 120);
|
|
175
|
+
if (msg.trim()) this.flashEvent('info', msg);
|
|
176
|
+
} else {
|
|
177
|
+
_captureWrite(args.join(' ') + '\n');
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
194
181
|
process.stdout.on('resize', this._onResize.bind(this));
|
|
195
182
|
this._drawStartupScreen();
|
|
196
|
-
} else {
|
|
197
|
-
console.log(`${C.header}🚀 DankGrinder starting...${A.reset}`);
|
|
198
183
|
}
|
|
199
184
|
}
|
|
200
185
|
|
|
@@ -203,46 +188,56 @@ class Terminal {
|
|
|
203
188
|
startPhase(name) {
|
|
204
189
|
this.phaseName = name;
|
|
205
190
|
this.phaseFrame = 0;
|
|
191
|
+
this._phaseProgressDone = 0;
|
|
192
|
+
this._phaseProgressTotal = 0;
|
|
193
|
+
|
|
206
194
|
if (!READY) {
|
|
207
|
-
console.log(`
|
|
195
|
+
console.log(` ⟳ ${name}...`);
|
|
208
196
|
return;
|
|
209
197
|
}
|
|
210
198
|
if (this.phaseTimer) clearInterval(this.phaseTimer);
|
|
211
199
|
this.phaseTimer = setInterval(() => {
|
|
212
|
-
this.phaseFrame = (this.phaseFrame + 1) %
|
|
200
|
+
this.phaseFrame = (this.phaseFrame + 1) % SPIN.length;
|
|
213
201
|
this._redrawPhaseSpinner();
|
|
214
202
|
}, 120);
|
|
215
203
|
this._redrawPhaseSpinner();
|
|
216
204
|
}
|
|
217
205
|
|
|
218
206
|
updateProgress(done, total) {
|
|
207
|
+
this._phaseProgressDone = done;
|
|
208
|
+
this._phaseProgressTotal = total;
|
|
219
209
|
if (!READY) return;
|
|
220
|
-
this.
|
|
210
|
+
this._redrawProgressBar();
|
|
221
211
|
}
|
|
222
212
|
|
|
223
213
|
endPhase(name, ok = true) {
|
|
224
214
|
if (this.phaseTimer) { clearInterval(this.phaseTimer); this.phaseTimer = null; }
|
|
215
|
+
this.phaseName = '';
|
|
216
|
+
this._phaseProgressDone = 0;
|
|
217
|
+
this._phaseProgressTotal = 0;
|
|
218
|
+
|
|
225
219
|
if (!READY) {
|
|
226
|
-
const icon = ok ?
|
|
227
|
-
console.log(` ${icon}
|
|
220
|
+
const icon = ok ? `✓ ${name}` : `✗ ${name}`;
|
|
221
|
+
console.log(` ${icon}`);
|
|
228
222
|
return;
|
|
229
223
|
}
|
|
230
224
|
const icon = ok ? `${C.header}✓${A.reset}` : `${A.crimson}✗${A.reset}`;
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
225
|
+
const line = ` ${icon} ${name}`;
|
|
226
|
+
const w = this._w;
|
|
227
|
+
|
|
228
|
+
// Clear spinner + progress rows, show result
|
|
229
|
+
this._write(
|
|
236
230
|
A.save +
|
|
237
|
-
this._cursor(
|
|
238
|
-
A.clearLine +
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
231
|
+
this._cursor(6, 1) + A.clearLine +
|
|
232
|
+
this._cursor(7, 1) + A.clearLine +
|
|
233
|
+
this._cursor(8, 1) + A.clearLine +
|
|
234
|
+
this._cursor(9, 1) + A.clearLine +
|
|
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)}` +
|
|
242
239
|
A.restore
|
|
243
240
|
);
|
|
244
|
-
// Clear spinner line
|
|
245
|
-
process.stdout.write(this._cursor(3, 1) + A.clearLine);
|
|
246
241
|
}
|
|
247
242
|
|
|
248
243
|
flashEvent(type, msg) {
|
|
@@ -266,50 +261,75 @@ class Terminal {
|
|
|
266
261
|
setActive() {
|
|
267
262
|
if (this._active) return;
|
|
268
263
|
this._active = true;
|
|
269
|
-
if (!READY) return;
|
|
270
264
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
265
|
+
if (READY) {
|
|
266
|
+
// ── Stop capturing, fully clear screen, draw live view ──
|
|
267
|
+
_captureActive = false;
|
|
268
|
+
this._capturing = false;
|
|
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
|
|
281
|
+
_captureBuf = [];
|
|
282
|
+
|
|
283
|
+
// Full screen clear + scrollback clear
|
|
284
|
+
this._write(A.eraseAll);
|
|
285
|
+
|
|
286
|
+
// Draw live view
|
|
287
|
+
this._drawLiveView();
|
|
288
|
+
|
|
289
|
+
// Reset dirty flags so we don't redraw header every frame
|
|
290
|
+
this.dirtyWorkers.clear();
|
|
291
|
+
this.dirtyEvents = false;
|
|
292
|
+
this.dirtyStats = false;
|
|
293
|
+
|
|
294
|
+
this._startRenderLoop();
|
|
295
|
+
} else {
|
|
296
|
+
// Non-TTY: restore console.log
|
|
297
|
+
if (this._origLog) {
|
|
298
|
+
console.log = this._origLog;
|
|
299
|
+
this._origLog = null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
275
302
|
}
|
|
276
303
|
|
|
277
304
|
scrollBy(delta) {
|
|
278
305
|
if (!READY || this._shutdown) return;
|
|
279
306
|
const max = Math.max(0, this.workers.length - this.windowSize);
|
|
280
307
|
this.windowStart = Math.max(0, Math.min(max, this.windowStart + delta));
|
|
281
|
-
this.
|
|
308
|
+
this._followIdx = -1;
|
|
282
309
|
this.dirtyWorkers = new Set();
|
|
283
310
|
for (let i = this.windowStart; i < this.windowStart + this.windowSize; i++) {
|
|
284
311
|
if (i < this.workers.length) this.dirtyWorkers.add(i);
|
|
285
312
|
}
|
|
286
313
|
}
|
|
287
314
|
|
|
288
|
-
followWorker(idx) {
|
|
289
|
-
this._followWorkerIdx = idx;
|
|
290
|
-
this.scrollLocked = true;
|
|
291
|
-
// Ensure the worker is visible
|
|
292
|
-
if (idx < this.windowStart) {
|
|
293
|
-
this.windowStart = idx;
|
|
294
|
-
} else if (idx >= this.windowStart + this.windowSize) {
|
|
295
|
-
this.windowStart = idx - this.windowSize + 1;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
315
|
shutdown(summary = {}) {
|
|
300
316
|
this._shutdown = true;
|
|
301
317
|
if (this._renderTimer) { clearInterval(this._renderTimer); this._renderTimer = null; }
|
|
302
318
|
if (this._phaseTimer) { clearInterval(this._phaseTimer); this._phaseTimer = null; }
|
|
303
319
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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;
|
|
309
328
|
}
|
|
310
329
|
|
|
311
|
-
|
|
312
|
-
|
|
330
|
+
this._write(A.show);
|
|
331
|
+
|
|
332
|
+
const w = this._w;
|
|
313
333
|
const { totalCoins = 0, totalCmds = 0, totalSuccess = 0,
|
|
314
334
|
workers = [], uptime = 0, memMB = 0 } = summary;
|
|
315
335
|
|
|
@@ -319,45 +339,68 @@ class Terminal {
|
|
|
319
339
|
const dim = A.dim;
|
|
320
340
|
const r = A.reset;
|
|
321
341
|
|
|
322
|
-
const sep = (c) => `${b}${c.repeat(cols - 2)}${r}`;
|
|
323
|
-
const bar = `${b}${'─'.repeat(cols - 2)}${r}`;
|
|
324
|
-
const icon = (e) => `${h}${e}${r}`;
|
|
325
|
-
|
|
326
342
|
let out = '';
|
|
327
|
-
out +=
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
out += `${this._at(
|
|
332
|
-
out += `${this._at(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
343
|
+
out += A.eraseAll + A.home + A.save;
|
|
344
|
+
|
|
345
|
+
// Box top
|
|
346
|
+
out += `${this._at(1, 1)}${b}${C.topLeft}${'─'.repeat(w - 2)}${C.topRight}${r}`;
|
|
347
|
+
out += `${this._at(2, 1)}${b}${C.v} ${h}${A.bold} ⬡ DANKGRINDER — Session Summary ${r}${' '.repeat(Math.max(0, w - 40))}${C.v}${r}`;
|
|
348
|
+
out += `${this._at(3, 1)}${b}${C.h}${'─'.repeat(w - 2)}${C.h}${r}`;
|
|
349
|
+
|
|
350
|
+
// Column headers
|
|
351
|
+
const hdr = [
|
|
352
|
+
`${h}#${r}`, `${h}ACCOUNT${r}`, `${h}COINS${r}`,
|
|
353
|
+
`${h}LV${r}`, `${h}♥${r}`, `${h}CMDS${r}`,
|
|
354
|
+
`${h}OK%${r}`, `${h}STATUS${r}`,
|
|
355
|
+
].join(` `);
|
|
356
|
+
out += `${this._at(4, 1)}${b} ${this._rpad(hdr, w - 4)} ${C.v}${r}`;
|
|
357
|
+
out += `${this._at(5, 1)}${b}${C.h}${'─'.repeat(w - 2)}${C.h}${r}`;
|
|
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];
|
|
340
363
|
const rate = wk.stats?.commands > 0
|
|
341
364
|
? `${((wk.stats.successes / wk.stats.commands) * 100).toFixed(0)}%`
|
|
342
365
|
: '0%';
|
|
343
366
|
const ls = wk._lifesavers ?? '?';
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
|
|
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}`;
|
|
347
383
|
}
|
|
348
384
|
|
|
349
|
-
row++; // blank
|
|
350
|
-
out += `${this._at(row++, 1)}${
|
|
351
|
-
|
|
352
|
-
// Totals
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
385
|
+
row++; // blank
|
|
386
|
+
out += `${this._at(row++, 1)}${b}${C.h}${'─'.repeat(w - 2)}${C.h}${r}`;
|
|
387
|
+
|
|
388
|
+
// Totals row
|
|
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;
|
|
358
402
|
|
|
359
|
-
|
|
360
|
-
setTimeout(() => {}, 100);
|
|
403
|
+
this._write(out);
|
|
361
404
|
}
|
|
362
405
|
|
|
363
406
|
// ── Internal ────────────────────────────────────────────────────────────
|
|
@@ -366,10 +409,9 @@ class Terminal {
|
|
|
366
409
|
try {
|
|
367
410
|
this._w = process.stdout.columns || 80;
|
|
368
411
|
this._h = process.stdout.rows || 24;
|
|
369
|
-
|
|
370
|
-
this.windowSize = Math.max(3, this._h - 12);
|
|
412
|
+
this.windowSize = Math.max(3, this._h - 11);
|
|
371
413
|
} catch (_) {
|
|
372
|
-
this._w = 80; this._h = 24; this.windowSize =
|
|
414
|
+
this._w = 80; this._h = 24; this.windowSize = 8;
|
|
373
415
|
}
|
|
374
416
|
}
|
|
375
417
|
|
|
@@ -378,52 +420,32 @@ class Terminal {
|
|
|
378
420
|
this._resizeTimer = setTimeout(() => {
|
|
379
421
|
this._updateSize();
|
|
380
422
|
if (this._active) {
|
|
381
|
-
this.
|
|
423
|
+
this._write(A.eraseAll);
|
|
382
424
|
this._drawLiveView();
|
|
383
425
|
}
|
|
384
426
|
}, 100);
|
|
385
427
|
}
|
|
386
428
|
|
|
387
429
|
_ansiLen(s) {
|
|
388
|
-
|
|
389
|
-
let len = 0;
|
|
390
|
-
let i = 0;
|
|
430
|
+
let len = 0, i = 0;
|
|
391
431
|
const str = String(s);
|
|
392
432
|
while (i < str.length) {
|
|
393
433
|
if (str.charCodeAt(i) === 0x1b && str[i + 1] === '[') {
|
|
394
434
|
let j = i + 2;
|
|
395
435
|
while (j < str.length && str[j] !== 'm') j++;
|
|
396
436
|
i = j + 1;
|
|
397
|
-
} else {
|
|
398
|
-
len++;
|
|
399
|
-
i++;
|
|
400
|
-
}
|
|
437
|
+
} else { len++; i++; }
|
|
401
438
|
}
|
|
402
439
|
return len;
|
|
403
440
|
}
|
|
404
441
|
|
|
405
442
|
_rpad(s, width) {
|
|
406
|
-
|
|
407
|
-
const pad = width > len ? width - len : 0;
|
|
408
|
-
return s + (pad > 0 ? ' '.repeat(pad) : '');
|
|
443
|
+
return s + ' '.repeat(Math.max(0, width - this._ansiLen(s)));
|
|
409
444
|
}
|
|
410
445
|
|
|
411
|
-
_cursor(row
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
_at(row, col) {
|
|
417
|
-
return `\x1b[${row};${col}H`;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
_write(str) {
|
|
421
|
-
process.stdout.write(str);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
_ansi(s, code) { return `${code}${s}${A.reset}`; }
|
|
425
|
-
_bold(s) { return `${A.bold}${s}${A.reset}`; }
|
|
426
|
-
_dim(s) { return `${A.dim}${s}${A.reset}`; }
|
|
446
|
+
_cursor(row) { return `\x1b[${row};1H`; }
|
|
447
|
+
_at(row, col) { return `\x1b[${row};${col}H`; }
|
|
448
|
+
_write(str) { if (str) process.stdout.write(str); }
|
|
427
449
|
|
|
428
450
|
_fmtUptime(ms) {
|
|
429
451
|
if (!ms) return '0s';
|
|
@@ -433,8 +455,7 @@ class Terminal {
|
|
|
433
455
|
if (m < 60) return `${m}m ${s % 60}s`;
|
|
434
456
|
const h = Math.floor(m / 60);
|
|
435
457
|
if (h < 24) return `${h}h ${m % 60}m`;
|
|
436
|
-
|
|
437
|
-
return `${d}d ${h % 24}h`;
|
|
458
|
+
return `${Math.floor(h / 24)}d ${h % 24}h`;
|
|
438
459
|
}
|
|
439
460
|
|
|
440
461
|
_fmtCoins(n) {
|
|
@@ -445,53 +466,35 @@ class Terminal {
|
|
|
445
466
|
|
|
446
467
|
_buildAccountRow(wk, idx) {
|
|
447
468
|
const w = this._w;
|
|
448
|
-
// Row layout (all in one line):
|
|
449
|
-
// #N 💎 username ⏣coins Lv.N ♥N Ncmds ● status ⚔ current_cmd
|
|
450
|
-
// We need to fit within `w` columns, truncate username if needed
|
|
451
|
-
|
|
452
|
-
const num = `${idx + 1}.`.padEnd(3);
|
|
453
|
-
const username = (wk.username || '?').substring(0, 16).padEnd(17);
|
|
454
|
-
const coins = `⏣${this._fmtCoins(wk.stats?.coins || 0)}`.padEnd(8);
|
|
455
|
-
const level = `Lv.${wk._level ?? '?'}`.padEnd(5);
|
|
456
469
|
const ls = wk._lifesavers ?? '?';
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const cmds = `${wk.stats?.commands || 0}cmds`.padEnd(7);
|
|
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%';
|
|
462
474
|
|
|
463
|
-
// Status dot + text
|
|
464
475
|
let statusDot, statusText, rowBg;
|
|
465
476
|
if (!wk.running || wk._tokenInvalid) {
|
|
466
|
-
statusDot = '⚫'; statusText = 'offline';
|
|
467
|
-
} else if (wk.paused) {
|
|
468
|
-
statusDot = '🔴'; statusText = 'paused';
|
|
469
|
-
} else if (wk.
|
|
470
|
-
statusDot = '
|
|
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';
|
|
471
482
|
} else {
|
|
472
|
-
statusDot = '🟢'; statusText = 'grinding';
|
|
483
|
+
statusDot = '🟢'; statusText = 'grinding';
|
|
473
484
|
}
|
|
474
485
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
`${C.
|
|
483
|
-
`${C.name}${username}${A.reset}`,
|
|
484
|
-
`${C.coins}${coins}${A.reset}`,
|
|
485
|
-
`${C.level}${level}${A.reset}`,
|
|
486
|
-
`${lifesaversColor}${lifesavers}${A.reset}`,
|
|
487
|
-
`${C.statValue}${cmds}${A.reset}`,
|
|
488
|
-
`${C.statValue}${successRate}`.padEnd(5) + A.reset,
|
|
486
|
+
const line = [
|
|
487
|
+
`${C.header}${idx + 1}.${A.reset}`,
|
|
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}`,
|
|
489
494
|
`${statusDot} ${statusText}`.padEnd(14),
|
|
490
|
-
`${
|
|
491
|
-
];
|
|
495
|
+
`${A.dim}${(wk.lastStatus || '').substring(0, 22).padEnd(22)}${A.reset}`,
|
|
496
|
+
].join(' ');
|
|
492
497
|
|
|
493
|
-
// Join and pad to screen width
|
|
494
|
-
const line = parts.join(' ');
|
|
495
498
|
return this._rpad(line, w);
|
|
496
499
|
}
|
|
497
500
|
|
|
@@ -499,94 +502,68 @@ class Terminal {
|
|
|
499
502
|
const w = this._w;
|
|
500
503
|
const b = C.border;
|
|
501
504
|
const h = C.header;
|
|
502
|
-
const g = C.statValue;
|
|
503
505
|
const dim = A.dim;
|
|
504
506
|
const r = A.reset;
|
|
505
507
|
|
|
506
|
-
const sep = (c) => `${b}${c.repeat(w - 2)}${r}`;
|
|
507
|
-
|
|
508
|
-
// Figure out how many rows we need for the spinner
|
|
509
|
-
const spinnerRow = 3;
|
|
510
|
-
const progressRow = 5;
|
|
511
|
-
const readyRow = 7;
|
|
512
|
-
|
|
513
508
|
let out = '';
|
|
514
|
-
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
|
+
// Footer
|
|
524
|
+
out += `${this._at(14, 1)}${b}${C.botLeft}${'─'.repeat(w - 2)}${C.botRight}${r}`;
|
|
525
|
+
out += `${this._at(15, 1)}${b} ${dim}Starting up...${r}${' '.repeat(Math.max(0, w - 18))}${r}`;
|
|
515
526
|
|
|
516
|
-
// Title box
|
|
517
|
-
const titleText = ` ⬡ DANKGRINDER v${this._version || '?'} `;
|
|
518
|
-
const titlePad = w - 2 - this._ansiLen(titleText);
|
|
519
|
-
out += `${this._at(1, 1)}${sep('─')}`;
|
|
520
|
-
out += `${this._at(2, 1)}${b} ${h}${A.bold}${titleText}${r}${b}${'─'.repeat(Math.max(0, titlePad))}${r}`;
|
|
521
|
-
out += `${this._at(3, 1)}${sep('─')}`;
|
|
522
|
-
|
|
523
|
-
// Spinner + phase text
|
|
524
|
-
out += `${this._at(5, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
525
|
-
out += `${this._at(6, 1)}${b}${' '.repeat(w - 2)}${r}`;
|
|
526
|
-
|
|
527
|
-
// Footer hint
|
|
528
|
-
out += `${this._at(8, 1)}${sep('─')}`;
|
|
529
|
-
const hint = `${dim}Starting up...${r}`;
|
|
530
|
-
out += `${this._at(9, 1)}${b} ${hint}${' '.repeat(Math.max(0, w - 2 - this._ansiLen(hint)))}${r}`;
|
|
531
|
-
out += `${this._at(10, 1)}${sep('─')}`;
|
|
532
|
-
|
|
533
|
-
this._lineCount = 10;
|
|
534
527
|
this._write(out);
|
|
535
528
|
}
|
|
536
529
|
|
|
537
530
|
_redrawPhaseSpinner() {
|
|
538
|
-
if (!READY) return;
|
|
539
|
-
const frame =
|
|
540
|
-
const
|
|
531
|
+
if (!READY || !this._phaseName) return;
|
|
532
|
+
const frame = SPIN[this.phaseFrame];
|
|
533
|
+
const line = ` ${frame} ${this.phaseName}...`;
|
|
541
534
|
const w = this._w;
|
|
542
|
-
|
|
543
|
-
const line = ` ${frame} ${label}...`;
|
|
544
|
-
const pad = w - this._ansiLen(line) - 2;
|
|
545
|
-
const padded = line + (pad > 0 ? ' '.repeat(pad) : '');
|
|
546
|
-
|
|
547
535
|
const b = C.border;
|
|
548
536
|
const r = A.reset;
|
|
549
537
|
this._write(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
`${b}${
|
|
553
|
-
|
|
538
|
+
A.save +
|
|
539
|
+
this._cursor(8, 1) + A.clearLine +
|
|
540
|
+
`${b} ${line}${' '.repeat(Math.max(0, w - this._ansiLen(line) - 3))}${r}` +
|
|
541
|
+
A.restore
|
|
554
542
|
);
|
|
555
543
|
}
|
|
556
544
|
|
|
557
|
-
|
|
558
|
-
if (!READY)
|
|
559
|
-
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
545
|
+
_redrawProgressBar() {
|
|
546
|
+
if (!READY || !this._phaseName) return;
|
|
547
|
+
const { done, total } = { done: this._phaseProgressDone, total: this._phaseProgressTotal };
|
|
562
548
|
const w = this._w;
|
|
563
|
-
const b = C.border;
|
|
564
549
|
const h = C.header;
|
|
565
550
|
const dim = A.dim;
|
|
566
551
|
const r = A.reset;
|
|
567
|
-
const barLen = Math.max(10, w - 30);
|
|
568
|
-
const filled = Math.round((done / total) * barLen);
|
|
569
|
-
const empty = barLen - filled;
|
|
570
552
|
|
|
571
|
-
const
|
|
553
|
+
const barW = Math.max(10, w - 35);
|
|
554
|
+
const filled = total > 0 ? Math.round((done / total) * barW) : 0;
|
|
555
|
+
const bar = `${h}${'█'.repeat(filled)}${dim}${'░'.repeat(barW - filled)}${r}`;
|
|
572
556
|
const label = ` ${done}/${total} `;
|
|
573
|
-
const line =
|
|
574
|
-
const pad = w - this._ansiLen(line) - 2;
|
|
575
|
-
|
|
557
|
+
const line = bar + label;
|
|
576
558
|
this._write(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
`${
|
|
580
|
-
|
|
559
|
+
A.save +
|
|
560
|
+
this._cursor(9, 1) + A.clearLine +
|
|
561
|
+
`${C.border} ${line}${' '.repeat(Math.max(0, w - this._ansiLen(line) - 3))}${r}` +
|
|
562
|
+
A.restore
|
|
581
563
|
);
|
|
582
564
|
}
|
|
583
565
|
|
|
584
|
-
_clearScreen() {
|
|
585
|
-
this._write(`${A.clear}${A.home}`);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
566
|
_drawLiveView() {
|
|
589
|
-
if (!READY) return;
|
|
590
567
|
this._drawHeader();
|
|
591
568
|
this._drawAccounts();
|
|
592
569
|
this._drawEvents();
|
|
@@ -601,94 +578,79 @@ class Terminal {
|
|
|
601
578
|
const dim = A.dim;
|
|
602
579
|
const r = A.reset;
|
|
603
580
|
|
|
604
|
-
|
|
605
|
-
const
|
|
606
|
-
const titlePad = w - 2 - this._ansiLen(
|
|
607
|
-
|
|
608
|
-
this.
|
|
609
|
-
this._write(`${this._at(
|
|
610
|
-
this._write(`${this._at(2, 1)}${b} ${h}${A.bold}${title}${r}${b}${'─'.repeat(Math.max(0, titlePad))}${r}`);
|
|
611
|
-
this._write(`${this._at(3, 1)}${sep}`);
|
|
612
|
-
this._statsRow = 4;
|
|
581
|
+
// Title bar
|
|
582
|
+
const titleText = ` ⬡ DANKGRINDER v${this._version || '?'} `;
|
|
583
|
+
const titlePad = Math.max(0, w - 2 - this._ansiLen(titleText));
|
|
584
|
+
this._write(`${this._at(1, 1)}${b}${C.topLeft}${'─'.repeat(w - 2)}${C.topRight}${r}`);
|
|
585
|
+
this._write(`${this._at(2, 1)}${b} ${h}${A.bold}${titleText}${r}${' '.repeat(titlePad)} ${C.v}${r}`);
|
|
586
|
+
this._write(`${this._at(3, 1)}${b}${C.h}${'─'.repeat(w - 2)}${C.h}${r}`);
|
|
613
587
|
|
|
614
588
|
// Stats bar
|
|
615
589
|
const stats = this._buildStatsLine();
|
|
616
|
-
|
|
617
|
-
this.
|
|
618
|
-
this._write(`${this._at(5, 1)}${
|
|
590
|
+
const statsPad = Math.max(0, w - 2 - this._ansiLen(stats) - 2);
|
|
591
|
+
this._write(`${this._at(4, 1)}${b} ${stats}${' '.repeat(statsPad)} ${C.v}${r}`);
|
|
592
|
+
this._write(`${this._at(5, 1)}${b}${C.h}${'─'.repeat(w - 2)}${C.h}${r}`);
|
|
593
|
+
|
|
594
|
+
this._accountsRow = 6;
|
|
619
595
|
}
|
|
620
596
|
|
|
621
597
|
_buildStatsLine() {
|
|
622
|
-
const g = C.statValue;
|
|
623
|
-
const dim = A.dim;
|
|
624
|
-
const h = C.header;
|
|
625
|
-
const coins = C.coins;
|
|
626
|
-
|
|
627
598
|
let totalCoins = 0, totalCmds = 0, totalSuccess = 0, totalLs = 0;
|
|
628
|
-
let
|
|
599
|
+
let paused = 0, active = 0;
|
|
629
600
|
|
|
630
601
|
for (const wk of this.workers) {
|
|
631
602
|
totalCoins += wk.stats?.coins || 0;
|
|
632
603
|
totalCmds += wk.stats?.commands || 0;
|
|
633
604
|
totalSuccess += wk.stats?.successes || 0;
|
|
634
605
|
if (wk._lifesavers != null) totalLs += wk._lifesavers;
|
|
635
|
-
if (
|
|
636
|
-
|
|
637
|
-
|
|
606
|
+
if (wk.running && !wk._tokenInvalid) {
|
|
607
|
+
if (wk.paused || wk.dashboardPaused) paused++;
|
|
608
|
+
else active++;
|
|
609
|
+
}
|
|
638
610
|
}
|
|
639
611
|
|
|
640
|
-
const uptime = this._fmtUptime(Date.now() -
|
|
641
|
-
const rate = totalCmds > 0 ? ((totalSuccess / totalCmds) * 100).toFixed(0) : 0;
|
|
612
|
+
const uptime = this._fmtUptime(Date.now() - this._startTime);
|
|
613
|
+
const rate = totalCmds > 0 ? ((totalSuccess / totalCmds) * 100).toFixed(0) : '0';
|
|
642
614
|
|
|
643
615
|
return [
|
|
644
|
-
`${dim}⏱${A.reset} ${
|
|
645
|
-
`${dim}⬡${A.reset} ${
|
|
646
|
-
`${coins}⏣${A.reset} ${
|
|
647
|
-
`${dim}⚡${A.reset} ${
|
|
648
|
-
`${dim}📊${A.reset} ${
|
|
649
|
-
`${C.lifesavers}♥${A.reset} ${
|
|
650
|
-
`${
|
|
651
|
-
|
|
616
|
+
`${A.dim}⏱${A.reset} ${C.statValue}${uptime}${A.reset}`,
|
|
617
|
+
`${A.dim}⬡${A.reset} ${C.statValue}${this.workers.length}${A.reset} ${A.dim}accounts${A.reset}`,
|
|
618
|
+
`${C.coins}⏣${A.reset} ${C.statValue}${totalCoins.toLocaleString()}${A.reset}`,
|
|
619
|
+
`${A.dim}⚡${A.reset} ${C.statValue}${totalCmds}${A.reset} ${A.dim}cmds${A.reset}`,
|
|
620
|
+
`${A.dim}📊${A.reset} ${C.statValue}${rate}%${A.reset}`,
|
|
621
|
+
`${C.lifesavers}♥${A.reset} ${C.statValue}${totalLs}${A.reset}`,
|
|
622
|
+
`${C.statusActive}🟢${A.reset} ${C.statValue}${active}${A.reset}`,
|
|
623
|
+
`${C.statusPaused}🔴${A.reset} ${C.statValue}${paused}${A.reset}`,
|
|
624
|
+
].join(` │ `);
|
|
652
625
|
}
|
|
653
626
|
|
|
654
627
|
_drawAccounts() {
|
|
655
628
|
const w = this._w;
|
|
656
629
|
const b = C.border;
|
|
657
|
-
const
|
|
630
|
+
const h = C.header;
|
|
658
631
|
const r = A.reset;
|
|
659
|
-
const sep = `${b}${'─'.repeat(w - 2)}${r}`;
|
|
660
|
-
|
|
661
|
-
const visible = this.workers.slice(this.windowStart, this.windowStart + this.windowSize);
|
|
662
632
|
|
|
663
|
-
// Column
|
|
633
|
+
// Column headers
|
|
664
634
|
const cols = [
|
|
665
|
-
`${
|
|
666
|
-
`${
|
|
667
|
-
`${
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
`${C.header}OK%${A.reset}`,
|
|
672
|
-
`${C.header}STATUS${A.reset}`,
|
|
673
|
-
`${C.header}CURRENT${A.reset}`,
|
|
674
|
-
].join(' ');
|
|
675
|
-
this._write(`${this._at(this._accountsRow, 1)}${b}${this._rpad(' ' + cols, w - 2)}${r}`);
|
|
676
|
-
this._write(`${this._at(this._accountsRow + 1, 1)}${sep}`);
|
|
635
|
+
`${h}#${r}`, `${h}ACCOUNT${r}`, `${h}COINS${r}`,
|
|
636
|
+
`${h}LV${r}`, `${h}♥${r}`, `${h}CMDS${r}`,
|
|
637
|
+
`${h}OK%${r}`, `${h}STATUS${r}`,
|
|
638
|
+
].join(` `);
|
|
639
|
+
this._write(`${this._at(this._accountsRow, 1)}${b} ${this._rpad(cols, w - 4)} ${C.v}${r}`);
|
|
640
|
+
this._write(`${this._at(this._accountsRow + 1, 1)}${b}${C.h}${'─'.repeat(w - 2)}${C.h}${r}`);
|
|
677
641
|
|
|
678
|
-
|
|
642
|
+
const visible = this.workers.slice(this.windowStart, this.windowStart + this.windowSize);
|
|
679
643
|
for (let i = 0; i < this.windowSize; i++) {
|
|
680
644
|
const row = this._accountsRow + 2 + i;
|
|
681
645
|
if (row > this._h - 4) break;
|
|
682
|
-
|
|
683
646
|
if (i < visible.length) {
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
this._write(`${this._at(row, 1)}${b}${line}${r}`);
|
|
647
|
+
const line = this._buildAccountRow(visible[i], this.windowStart + i);
|
|
648
|
+
this._write(`${this._at(row, 1)}${b} ${line} ${C.v}${r}`);
|
|
687
649
|
} else {
|
|
688
650
|
this._write(`${this._at(row, 1)}${b}${' '.repeat(w - 2)}${r}`);
|
|
689
651
|
}
|
|
690
652
|
}
|
|
691
|
-
this._eventsRow = this._accountsRow + 2 + this.windowSize
|
|
653
|
+
this._eventsRow = this._accountsRow + 2 + Math.min(this.windowSize, this.workers.length);
|
|
692
654
|
}
|
|
693
655
|
|
|
694
656
|
_drawEvents() {
|
|
@@ -696,25 +658,24 @@ class Terminal {
|
|
|
696
658
|
const b = C.border;
|
|
697
659
|
const dim = A.dim;
|
|
698
660
|
const r = A.reset;
|
|
699
|
-
const sep = `${b}${'─'.repeat(w - 2)}${r}`;
|
|
700
661
|
|
|
701
662
|
if (this._eventsRow > this._h - 4) return;
|
|
702
|
-
this._write(`${this._at(this._eventsRow, 1)}${
|
|
663
|
+
this._write(`${this._at(this._eventsRow, 1)}${b}${C.h}${'─'.repeat(w - 2)}${C.h}${r}`);
|
|
703
664
|
|
|
704
|
-
const
|
|
705
|
-
for (let i = 0; i <
|
|
665
|
+
const visible = this.events.slice(0, Math.min(this.MAX_EVENTS, this._h - this._eventsRow - 3));
|
|
666
|
+
for (let i = 0; i < visible.length; i++) {
|
|
706
667
|
const row = this._eventsRow + 1 + i;
|
|
707
668
|
if (row > this._h - 2) break;
|
|
708
|
-
const
|
|
709
|
-
const typeColor =
|
|
710
|
-
:
|
|
711
|
-
:
|
|
712
|
-
: C.
|
|
713
|
-
|
|
714
|
-
const line = ` ${dim}${
|
|
715
|
-
this._write(`${this._at(row, 1)}${b}${this._rpad(line, w -
|
|
669
|
+
const e = visible[i];
|
|
670
|
+
const typeColor = e.type === 'death' ? C.cmdError
|
|
671
|
+
: e.type === 'lowls' ? C.cmdWarn
|
|
672
|
+
: e.type === 'levelup' ? C.cmdSuccess
|
|
673
|
+
: e.type === 'success' ? C.cmdSuccess
|
|
674
|
+
: C.cmdInfo;
|
|
675
|
+
const line = ` ${dim}${e.ts}${A.reset} ${typeColor}${e.msg}${A.reset}`;
|
|
676
|
+
this._write(`${this._at(row, 1)}${b} ${this._rpad(line, w - 4)} ${C.v}${r}`);
|
|
716
677
|
}
|
|
717
|
-
this._footerRow = this._eventsRow + 1 +
|
|
678
|
+
this._footerRow = this._eventsRow + 1 + visible.length;
|
|
718
679
|
}
|
|
719
680
|
|
|
720
681
|
_drawFooter() {
|
|
@@ -722,56 +683,36 @@ class Terminal {
|
|
|
722
683
|
const b = C.border;
|
|
723
684
|
const dim = A.dim;
|
|
724
685
|
const r = A.reset;
|
|
725
|
-
const sep = `${b}${'─'.repeat(w - 2)}${r}`;
|
|
726
|
-
|
|
727
686
|
if (this._footerRow > this._h - 1) this._footerRow = this._h - 2;
|
|
728
|
-
this._write(`${this._at(this._footerRow, 1)}${
|
|
729
|
-
|
|
730
|
-
const hint = `${dim}↑↓ scroll · j/k navigate · Ctrl+C quit${r}`;
|
|
687
|
+
this._write(`${this._at(this._footerRow, 1)}${b}${C.botLeft}${'─'.repeat(w - 2)}${C.botRight}${r}`);
|
|
688
|
+
const hint = `${dim}↑↓ scroll${r}`;
|
|
731
689
|
this._write(`${this._at(this._footerRow + 1, 1)}${b} ${hint}${' '.repeat(Math.max(0, w - 2 - this._ansiLen(hint)))}${r}`);
|
|
732
690
|
}
|
|
733
691
|
|
|
734
692
|
_render() {
|
|
735
693
|
if (!READY || this._shutdown || !this._active) return;
|
|
736
694
|
|
|
737
|
-
//
|
|
738
|
-
if (this._followWorkerIdx >= 0 && this._followWorkerIdx < this.workers.length) {
|
|
739
|
-
const target = this._followWorkerIdx;
|
|
740
|
-
if (target < this.windowStart) {
|
|
741
|
-
this.windowStart = target;
|
|
742
|
-
this.dirtyWorkers = new Set();
|
|
743
|
-
for (let i = this.windowStart; i < this.windowStart + this.windowSize; i++) {
|
|
744
|
-
if (i < this.workers.length) this.dirtyWorkers.add(i);
|
|
745
|
-
}
|
|
746
|
-
} else if (target >= this.windowStart + this.windowSize) {
|
|
747
|
-
this.windowStart = target - this.windowSize + 1;
|
|
748
|
-
this.dirtyWorkers = new Set();
|
|
749
|
-
for (let i = this.windowStart; i < this.windowStart + this.windowSize; i++) {
|
|
750
|
-
if (i < this.workers.length) this.dirtyWorkers.add(i);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// Redraw everything on resize changes
|
|
695
|
+
// Only update stats line (row 4) when dirty — don't redraw whole header
|
|
756
696
|
if (this.dirtyStats) {
|
|
757
|
-
this.
|
|
758
|
-
|
|
697
|
+
const w = this._w;
|
|
698
|
+
const b = C.border;
|
|
699
|
+
const stats = this._buildStatsLine();
|
|
700
|
+
const statsPad = Math.max(0, w - 2 - this._ansiLen(stats) - 2);
|
|
701
|
+
this._write(`${this._at(4, 1)}${b} ${stats}${' '.repeat(statsPad)} ${C.v}${A.reset}`);
|
|
702
|
+
this.dirtyStats = false;
|
|
759
703
|
}
|
|
760
704
|
|
|
761
705
|
if (this.dirtyWorkers.size > 0) {
|
|
762
706
|
const w = this._w;
|
|
763
707
|
const b = C.border;
|
|
764
708
|
const r = A.reset;
|
|
765
|
-
const sep = `${b}${'─'.repeat(w - 2)}${r}`;
|
|
766
|
-
|
|
767
709
|
for (const idx of this.dirtyWorkers) {
|
|
768
710
|
const localIdx = idx - this.windowStart;
|
|
769
711
|
const row = this._accountsRow + 2 + localIdx;
|
|
770
712
|
if (row < this._accountsRow + 2 || row > this._h - 4) continue;
|
|
771
|
-
|
|
772
713
|
if (idx < this.workers.length) {
|
|
773
714
|
const line = this._buildAccountRow(this.workers[idx], idx);
|
|
774
|
-
this._write(`${this._at(row, 1)}${b}${line}${r}`);
|
|
715
|
+
this._write(`${this._at(row, 1)}${b} ${line} ${C.v}${r}`);
|
|
775
716
|
} else {
|
|
776
717
|
this._write(`${this._at(row, 1)}${b}${' '.repeat(w - 2)}${r}`);
|
|
777
718
|
}
|
|
@@ -780,40 +721,18 @@ class Terminal {
|
|
|
780
721
|
|
|
781
722
|
if (this.dirtyEvents) {
|
|
782
723
|
this._drawEvents();
|
|
783
|
-
this._drawFooter();
|
|
784
724
|
}
|
|
785
725
|
|
|
786
726
|
this.dirtyWorkers.clear();
|
|
787
727
|
this.dirtyEvents = false;
|
|
788
|
-
this.dirtyStats = false;
|
|
789
728
|
}
|
|
790
729
|
|
|
791
730
|
_startRenderLoop() {
|
|
792
731
|
if (this._renderTimer) clearInterval(this._renderTimer);
|
|
793
|
-
this._renderTimer = setInterval(() =>
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const { totalCoins = 0, totalCmds = 0, totalSuccess = 0,
|
|
798
|
-
workers = [], uptime = 0, memMB = 0 } = summary;
|
|
799
|
-
const b = '═'.repeat(60);
|
|
800
|
-
console.log('');
|
|
801
|
-
console.log(` ${'═'.repeat(60)}`);
|
|
802
|
-
console.log(` ⬡ DANKGRINDER — Session Summary`);
|
|
803
|
-
console.log(` ${'─'.repeat(60)}`);
|
|
804
|
-
for (const wk of workers) {
|
|
805
|
-
const rate = wk.stats?.commands > 0
|
|
806
|
-
? `${((wk.stats.successes / wk.stats.commands) * 100).toFixed(0)}%`
|
|
807
|
-
: '0%';
|
|
808
|
-
console.log(
|
|
809
|
-
` 💎 ${(wk.username || '?').padEnd(18)} +⏣ ${(wk.stats?.coins || 0).toLocaleString().padStart(8)} ` +
|
|
810
|
-
`${(wk.stats?.commands || 0)}cmds ${rate} ♥${wk._lifesavers ?? '?'} Lv.${wk._level ?? '?'}`
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
console.log(` ${'─'.repeat(60)}`);
|
|
814
|
-
console.log(` 💰 Total: ⏣ ${totalCoins.toLocaleString()} ${totalCmds}cmds ${totalSuccess}%OK ${this._fmtUptime(uptime)} ${memMB}MB`);
|
|
815
|
-
console.log(` ${'═'.repeat(60)}`);
|
|
816
|
-
console.log('');
|
|
732
|
+
this._renderTimer = setInterval(() => {
|
|
733
|
+
this.dirtyStats = true; // always refresh uptime/stats
|
|
734
|
+
this._render();
|
|
735
|
+
}, 250);
|
|
817
736
|
}
|
|
818
737
|
}
|
|
819
738
|
|