dankgrinder 7.66.0 → 7.68.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/dashboard.js +144 -166
- package/lib/grinder.js +1 -3
- package/package.json +1 -1
package/lib/dashboard.js
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DankGrinder CLI Dashboard
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Fresh rewrite. Simple line-by-line overwrite — no full clear, no cursor tricks.
|
|
5
|
+
* Every render writes N rows from line 1 onward. Previous content is overwritten.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* totalBalance, totalCoins, totalCommands, startTime,
|
|
14
|
-
* sessionPeakCoins, isNewHigh, recentLogs, globalCmdRate,
|
|
15
|
-
* earningsHistory, lastEarningsSample,
|
|
16
|
-
* CLOUD_MODE, CLUSTER_ENABLED, PKG_VERSION,
|
|
17
|
-
* AccountWorker, PULSE_CHARS, getSpinner, gradientText,
|
|
18
|
-
* rgb, c, BOX,
|
|
19
|
-
* }
|
|
7
|
+
* Context:
|
|
8
|
+
* workers, dashboardStarted, dashboardRendering, dashboardLines,
|
|
9
|
+
* totalBalance, totalCoins, totalCommands, startTime,
|
|
10
|
+
* sessionPeakCoins, isNewHigh, recentLogs, globalCmdRate,
|
|
11
|
+
* CLOUD_MODE, CLUSTER_ENABLED, PKG_VERSION,
|
|
12
|
+
* AccountWorker, PULSE_CHARS, getSpinner, gradientText,
|
|
13
|
+
* rgb, c
|
|
20
14
|
*/
|
|
21
15
|
|
|
22
16
|
'use strict';
|
|
23
17
|
|
|
24
|
-
//
|
|
25
|
-
let _c, _rgb,
|
|
18
|
+
// Local refs (set per render)
|
|
19
|
+
let _c, _rgb, _spinnerFn, _gradientFn;
|
|
26
20
|
|
|
27
|
-
function
|
|
21
|
+
function init(ctx) {
|
|
28
22
|
_c = ctx.c;
|
|
29
23
|
_rgb = ctx.rgb;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
_spinnerFn = ctx.getSpinner;
|
|
25
|
+
_gradientFn = ctx.gradientText;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Helpers ─────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function vis(s) {
|
|
31
|
+
return String(s).replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
function pad(str, width) {
|
|
36
|
-
|
|
37
|
-
return str + ' '.repeat(Math.max(0, width - raw.length));
|
|
35
|
+
return str + ' '.repeat(Math.max(0, width - vis(str)));
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
function clearLine(content) {
|
|
39
|
+
return `${_c.clearLine}\r${content}`;
|
|
40
|
+
}
|
|
41
41
|
|
|
42
42
|
function fmtCoins(n) {
|
|
43
43
|
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
@@ -59,152 +59,137 @@ function fmtUptime(startTime) {
|
|
|
59
59
|
// ── Render ──────────────────────────────────────────────────────
|
|
60
60
|
|
|
61
61
|
function renderDashboard(ctx) {
|
|
62
|
-
if (!ctx.dashboardStarted || ctx.workers.length === 0 || ctx.dashboardRendering
|
|
62
|
+
if (!ctx.dashboardStarted || ctx.workers.length === 0 || ctx.dashboardRendering) return;
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
init(ctx);
|
|
65
65
|
|
|
66
66
|
const tw = Math.max(process.stdout.columns || 80, 60);
|
|
67
|
+
const _ = _c.reset;
|
|
67
68
|
|
|
68
|
-
// Color
|
|
69
|
-
const A = _rgb(139, 92, 246);
|
|
70
|
-
const G = _rgb(52, 211, 153);
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
const Y = _rgb(251, 191, 36); // yellow
|
|
69
|
+
// Color shortcuts
|
|
70
|
+
const A = _rgb(139, 92, 246);
|
|
71
|
+
const G = _rgb(52, 211, 153);
|
|
72
|
+
const Au = _rgb(255, 215, 0);
|
|
73
|
+
const O = _rgb(251, 146, 60);
|
|
74
|
+
const Cy = _rgb(34, 211, 238);
|
|
75
|
+
const R = _rgb(239, 68, 68);
|
|
76
|
+
const Y = _rgb(251, 191, 36);
|
|
77
77
|
const W = _c.white;
|
|
78
78
|
const D = _c.dim;
|
|
79
|
-
const _ = _c.reset;
|
|
80
|
-
const BOX = _BOX;
|
|
81
|
-
|
|
82
|
-
const rows = [];
|
|
83
79
|
|
|
84
|
-
// ── Aggregate stats
|
|
85
|
-
let aggBalance = 0, aggCoins = 0,
|
|
80
|
+
// ── Aggregate stats ───────────────────────────────────────────
|
|
81
|
+
let aggBalance = 0, aggCoins = 0, aggCmds = 0, aggErrors = 0;
|
|
86
82
|
for (const w of ctx.workers) {
|
|
87
|
-
aggBalance
|
|
88
|
-
aggCoins
|
|
89
|
-
|
|
90
|
-
aggErrors
|
|
83
|
+
aggBalance += (w.stats.balance || 0) + (w.stats.bankBalance || 0);
|
|
84
|
+
aggCoins += w.stats.coins || 0;
|
|
85
|
+
aggCmds += w.stats.commands || 0;
|
|
86
|
+
aggErrors += w.stats.errors || 0;
|
|
91
87
|
}
|
|
92
|
-
const successRate =
|
|
93
|
-
|
|
94
|
-
const elapsedHrs
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
88
|
+
const successRate = aggCmds > 0 ? Math.round(((aggCmds - aggErrors) / aggCmds) * 100) : 100;
|
|
89
|
+
const elapsedHrs = (Date.now() - ctx.startTime) / 3_600_000;
|
|
90
|
+
const perHr = elapsedHrs > 0.01 ? Math.round(aggCoins / elapsedHrs) : 0;
|
|
91
|
+
const cpmVal = ctx.globalCmdRate.getRate().toFixed(1);
|
|
92
|
+
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
93
|
+
const spin = _spinnerFn('braille');
|
|
94
|
+
|
|
95
|
+
const activeCount = ctx.workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
96
|
+
const invalidCount = ctx.workers.filter(w => w._tokenInvalid).length;
|
|
97
|
+
const pausedCount = ctx.workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
98
|
+
const recovCount = ctx.workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
102
99
|
|
|
103
|
-
const netQ = ctx.workers.length > 0
|
|
104
|
-
? ctx.workers.reduce((s, w) => s + (w._lastPing < 200 ? 1 : w._lastPing < 500 ? 0.5 : 0), 0) / ctx.workers.length : 1;
|
|
105
|
-
const netDot = netQ > 0.8 ? `${G}●${_}` : netQ > 0.5 ? `${Y}●${_}` : `${R}●${_}`;
|
|
106
|
-
|
|
107
|
-
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
108
|
-
const spin = _getSpinner('braille');
|
|
109
|
-
|
|
110
|
-
// ── HEADER BOX ───────────────────────────────────────────────
|
|
111
|
-
const titleBar = `${_c.bold}${_gradientText(' DANK GRINDER ', [139, 92, 246], [52, 211, 153])}${_}`;
|
|
112
|
-
const versionTag = `${D}v${ctx.PKG_VERSION}${_}`;
|
|
113
100
|
const modeTag = ctx.CLOUD_MODE ? `${Cy}CLOUD${_}` : (ctx.CLUSTER_ENABLED ? `${Cy}CLUSTER${_}` : `${D}local${_}`);
|
|
114
101
|
|
|
115
|
-
//
|
|
116
|
-
|
|
102
|
+
// ── Column layout ─────────────────────────────────────────────
|
|
103
|
+
const colNum = 4;
|
|
104
|
+
const colSts = 3;
|
|
105
|
+
const colName = Math.max(16, Math.min(22, Math.floor(tw * 0.22)));
|
|
106
|
+
const colBal = 10;
|
|
107
|
+
const colLvl = 5;
|
|
108
|
+
const colLs = 4;
|
|
109
|
+
const colEarn = 9;
|
|
110
|
+
const colAct = Math.max(8, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn - 12);
|
|
111
|
+
const gap = ' ';
|
|
112
|
+
|
|
113
|
+
// ── Helpers to build rows ─────────────────────────────────────
|
|
114
|
+
const border = (char) => `${A}${char}${D}${'─'.repeat(tw - 2)}${A}${char}${_}`;
|
|
115
|
+
|
|
116
|
+
function mkRow(content, leftPad = ' ') {
|
|
117
|
+
const raw = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
118
|
+
const padLen = Math.max(0, tw - 4 - raw.length);
|
|
119
|
+
return `${A}│${_}${leftPad}${content}${' '.repeat(padLen)}${leftPad}${A}│${_}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Build all rows ────────────────────────────────────────────
|
|
123
|
+
const rows = [];
|
|
117
124
|
|
|
118
|
-
//
|
|
125
|
+
// HEADER
|
|
126
|
+
const title = _c.bold + _gradientFn(' DANK GRINDER ', [139, 92, 246], [52, 211, 153]) + _;
|
|
119
127
|
const h1 = [
|
|
120
|
-
|
|
128
|
+
title,
|
|
129
|
+
`${D}v${ctx.PKG_VERSION}${_}`,
|
|
130
|
+
`${G}${spin}${_}`,
|
|
121
131
|
`${D}up${_} ${W}${fmtUptime(ctx.startTime)}${_}`,
|
|
122
|
-
|
|
123
|
-
`${
|
|
124
|
-
`${G}${
|
|
125
|
-
`${G}${activeCount}${_}/${W}${ctx.workers.length}${_} ${D}active${_}`,
|
|
132
|
+
`${D}bal${_} ${Au}⏣${_}${W}${fmtCoins(aggBalance)}${_}`,
|
|
133
|
+
`${G}+⏣${fmtCoins(perHr)}${_}/h`,
|
|
134
|
+
`${G}${activeCount}${_}/${W}${ctx.workers.length}${_} active`,
|
|
126
135
|
invalidCount > 0 ? ` ${R}${invalidCount} inv${_}` : '',
|
|
127
136
|
pausedCount > 0 ? ` ${Y}${pausedCount} pause${_}` : '',
|
|
128
137
|
recovCount > 0 ? ` ${O}${recovCount} recov${_}` : '',
|
|
129
|
-
` ${D}${
|
|
138
|
+
` ${D}${aggCmds}${_}cmds`,
|
|
130
139
|
` ${D}${cpmVal}${_}/min`,
|
|
131
|
-
` ${D}
|
|
140
|
+
` ${D}${memMB}MB`,
|
|
132
141
|
modeTag,
|
|
133
142
|
].filter(Boolean).join(' ');
|
|
134
143
|
|
|
135
|
-
rows.push(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
rows.push(`${
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
`${G}${fmtCoins(aggCoins)}${_} ${D}coins${_}`,
|
|
143
|
-
`${G}${successRate}%${_} ${D}success${_}`,
|
|
144
|
-
`${W}${fmtCoins(ctx.sessionPeakCoins)}${_} ${D}peak${_}`,
|
|
145
|
-
ctx.isNewHigh ? `${Au}★ NEW HIGH${_}` : '',
|
|
146
|
-
].filter(Boolean).join(` ${D}│${_} `);
|
|
147
|
-
|
|
148
|
-
const statLine = ` ${D}session${_} ${statsRow}`;
|
|
149
|
-
rows.push(`${A}${BOX.dv}${_}${pad(statLine, tw - 4)}${A}${BOX.dv}${_}`);
|
|
150
|
-
|
|
151
|
-
// Bottom header border
|
|
152
|
-
rows.push(`${A}${BOX.dbl}${BOX.dh.repeat(tw - 2)}${BOX.dbr}${_}`);
|
|
153
|
-
|
|
154
|
-
// ── ACCOUNTS TABLE ──────────────────────────────────────────
|
|
155
|
-
const colNum = 4;
|
|
156
|
-
const colSts = 3;
|
|
157
|
-
const colName = Math.max(14, Math.min(22, Math.floor(tw * 0.20)));
|
|
158
|
-
const colBal = 10;
|
|
159
|
-
const colLvl = 5;
|
|
160
|
-
const colLs = 4;
|
|
161
|
-
const colEarn = 9;
|
|
162
|
-
const colAct = Math.max(6, tw - colNum - colSts - colName - colBal - colLvl - colLs - colEarn - 18);
|
|
163
|
-
const gap = 2;
|
|
164
|
-
const colGap = ' '.repeat(gap);
|
|
165
|
-
|
|
166
|
-
// Column header
|
|
144
|
+
rows.push(border('╔'));
|
|
145
|
+
rows.push(mkRow(h1));
|
|
146
|
+
rows.push(border('╠'));
|
|
147
|
+
rows.push(mkRow(`${D}session${_} ${G}${fmtCoins(aggCoins)}${_} coins ${G}${successRate}%${_} success ${W}${fmtCoins(ctx.sessionPeakCoins)}${_} peak${ctx.isNewHigh ? ` ${Au}★ NEW HIGH${_}` : ''}`));
|
|
148
|
+
rows.push(border('╚'));
|
|
149
|
+
|
|
150
|
+
// ACCOUNTS TABLE
|
|
167
151
|
const headers = [
|
|
168
152
|
`${D}${pad('#', colNum)}${_}`,
|
|
169
153
|
`${D}${pad('S', colSts)}${_}`,
|
|
170
|
-
`${
|
|
154
|
+
`${_gradientFn(pad('Account', colName), [139, 92, 246], [96, 165, 250])}${_}`,
|
|
171
155
|
`${D}${pad('Balance', colBal)}${_}`,
|
|
172
156
|
`${D}${pad('Lvl', colLvl)}${_}`,
|
|
173
157
|
`${D}${pad('LS', colLs)}${_}`,
|
|
174
158
|
`${D}${pad('Earned', colEarn)}${_}`,
|
|
175
159
|
`${D}${pad('Activity', colAct)}${_}`,
|
|
176
|
-
].join(
|
|
160
|
+
].join(gap);
|
|
177
161
|
|
|
178
|
-
rows.push(
|
|
179
|
-
rows.push(
|
|
180
|
-
rows.push(
|
|
162
|
+
rows.push(border('╔'));
|
|
163
|
+
rows.push(mkRow(headers));
|
|
164
|
+
rows.push(border('╟'));
|
|
181
165
|
|
|
182
|
-
const sorted
|
|
183
|
-
const maxRows
|
|
184
|
-
const visible
|
|
166
|
+
const sorted = [...ctx.workers].sort((a, b) => a.idx - b.idx);
|
|
167
|
+
const maxRows = Math.max(5, Math.min(sorted.length, Math.floor((process.stdout.rows || 24) - 12)));
|
|
168
|
+
const visible = sorted.slice(0, maxRows);
|
|
169
|
+
const extraRows = sorted.length - maxRows;
|
|
185
170
|
|
|
186
171
|
for (const wk of visible) {
|
|
187
172
|
const isRecov = wk._recoveryAttempts > 0 && wk._errorCooldownUntil > Date.now();
|
|
188
173
|
|
|
189
174
|
let stsIcon;
|
|
190
175
|
if (wk._tokenInvalid) stsIcon = `${R}✗${_}`;
|
|
191
|
-
else if (!wk.running)
|
|
192
|
-
else if (isRecov)
|
|
193
|
-
else if (wk.paused)
|
|
194
|
-
else if (wk.dashboardPaused)
|
|
195
|
-
else if (wk.busy)
|
|
196
|
-
else
|
|
197
|
-
|
|
198
|
-
const name
|
|
176
|
+
else if (!wk.running) stsIcon = `${D}○${_}`;
|
|
177
|
+
else if (isRecov) stsIcon = `${O}${_spinnerFn('braille').substring(0, 1)}${_}`;
|
|
178
|
+
else if (wk.paused) stsIcon = `${R}⏸${_}`;
|
|
179
|
+
else if (wk.dashboardPaused) stsIcon = `${Y}⏸${_}`;
|
|
180
|
+
else if (wk.busy) stsIcon = `${G}${_spinnerFn('pulse').substring(0, 1)}${_}`;
|
|
181
|
+
else stsIcon = `${G}●${_}`;
|
|
182
|
+
|
|
183
|
+
const name = (wk.username || wk.account.label || '?').substring(0, colName);
|
|
199
184
|
const nameStr = `${wk.color}${name}${_}`;
|
|
200
185
|
const balStr = wk.stats.balance > 0 ? `${Au}⏣${_}${W}${fmtCoins(wk.stats.balance)}${_}` : `${D}⏣-${_}`;
|
|
201
186
|
const lvl = wk._level || 0;
|
|
202
187
|
const lvlStr = lvl > 0 ? `${Cy}L${lvl}${_}` : `${D}L???${_}`;
|
|
203
188
|
const ls = wk._lifesavers;
|
|
204
189
|
let lsStr;
|
|
205
|
-
if (ls === 0)
|
|
190
|
+
if (ls === 0) lsStr = `${R}♥${ls}${_}`;
|
|
206
191
|
else if (ls != null && ls <= 2) lsStr = `${Y}♥${ls}${_}`;
|
|
207
|
-
else if (ls != null)
|
|
192
|
+
else if (ls != null) lsStr = `${G}♥${ls}${_}`;
|
|
208
193
|
else {
|
|
209
194
|
const p = ctx.PULSE_CHARS[Math.floor(Date.now() / 400) % ctx.PULSE_CHARS.length];
|
|
210
195
|
lsStr = `${D}${p}♥?${_}`;
|
|
@@ -212,41 +197,44 @@ function renderDashboard(ctx) {
|
|
|
212
197
|
|
|
213
198
|
const earn = wk.stats.coins || 0;
|
|
214
199
|
const earnStr = earn > 0 ? `${G}+${fmtCoins(earn)}${_}` : `${D}────${_}`;
|
|
215
|
-
const actRaw
|
|
216
|
-
const actStr
|
|
217
|
-
const numStr
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
numStr,
|
|
221
|
-
pad(
|
|
222
|
-
pad(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
200
|
+
const actRaw = (wk.lastStatus || 'ready').replace(/\x1b\[[0-9;]*m/g, '').substring(0, colAct);
|
|
201
|
+
const actStr = `${D}${pad(actRaw, colAct)}${_}`;
|
|
202
|
+
const numStr = `${D}${pad(String(wk.idx + 1), colNum)}${_}`;
|
|
203
|
+
|
|
204
|
+
const rowStr = [
|
|
205
|
+
numStr,
|
|
206
|
+
pad(stsIcon, colSts),
|
|
207
|
+
pad(nameStr, colName),
|
|
208
|
+
pad(balStr, colBal),
|
|
209
|
+
pad(lvlStr, colLvl),
|
|
210
|
+
pad(lsStr, colLs),
|
|
211
|
+
pad(earnStr, colEarn),
|
|
212
|
+
actStr,
|
|
213
|
+
].join(gap);
|
|
214
|
+
|
|
215
|
+
rows.push(mkRow(rowStr));
|
|
226
216
|
}
|
|
227
217
|
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
rows.push(`${A}${BOX.dv}${_} ${D}+${rest} more${_}${' '.repeat(Math.max(0, tw - 18))}${A}${BOX.dv}${_}`);
|
|
218
|
+
if (extraRows > 0) {
|
|
219
|
+
rows.push(mkRow(`${D}+${extraRows} more${_}`));
|
|
231
220
|
}
|
|
232
221
|
|
|
233
|
-
rows.push(
|
|
222
|
+
rows.push(border('╚'));
|
|
234
223
|
|
|
235
|
-
//
|
|
224
|
+
// LIVE FEED
|
|
236
225
|
const logs = ctx.recentLogs.toArray();
|
|
237
226
|
if (logs.length > 0) {
|
|
238
|
-
const feedTitle = `${
|
|
239
|
-
|
|
240
|
-
rows.push(
|
|
241
|
-
rows.push(
|
|
242
|
-
rows.push(`${A}${BOX.teeD}${BOX.h.repeat(tw - 2)}${BOX.teeU}${_}`);
|
|
227
|
+
const feedTitle = `${_gradientFn(' LIVE FEED ', [139, 92, 246], [52, 211, 153])}${_} ${G}${_spinnerFn('pulse')}${_}`;
|
|
228
|
+
rows.push(border('╔'));
|
|
229
|
+
rows.push(mkRow(feedTitle));
|
|
230
|
+
rows.push(border('╟'));
|
|
243
231
|
|
|
244
232
|
for (const entry of logs) {
|
|
245
233
|
let lineText;
|
|
246
234
|
if (typeof entry === 'string') {
|
|
247
235
|
lineText = entry;
|
|
248
236
|
} else if (entry && typeof entry === 'object') {
|
|
249
|
-
const ts = entry.ts ? new Date(entry.ts).toLocaleTimeString('en-US', { hour12: false
|
|
237
|
+
const ts = entry.ts ? new Date(entry.ts).toLocaleTimeString('en-US', { hour12: false }) : '';
|
|
250
238
|
const user = entry.username ? String(entry.username) : '';
|
|
251
239
|
const cmd = entry.command ? `[${entry.command}]` : '';
|
|
252
240
|
const resp = entry.response || '';
|
|
@@ -254,32 +242,22 @@ function renderDashboard(ctx) {
|
|
|
254
242
|
} else {
|
|
255
243
|
lineText = String(entry);
|
|
256
244
|
}
|
|
257
|
-
rows.push(`${
|
|
245
|
+
rows.push(mkRow(`${D}${lineText}${_}`));
|
|
258
246
|
}
|
|
259
|
-
rows.push(
|
|
247
|
+
rows.push(border('╚'));
|
|
260
248
|
}
|
|
261
249
|
|
|
262
|
-
//
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
250
|
+
// FOOTER
|
|
251
|
+
rows.push(border('╔'));
|
|
252
|
+
rows.push(mkRow(`${modeTag} ${D}P=pause R=resume S=status Q=quit${_}`));
|
|
253
|
+
rows.push(border('╚'));
|
|
266
254
|
|
|
267
|
-
rows
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
// ── Flush ────────────────────────────────────────────────────
|
|
272
|
-
// Clear entire screen buffer first so old startup logs don't bleed through
|
|
273
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
274
|
-
process.stdout.write(_c.hide);
|
|
255
|
+
// ── Flush: write all rows, each clears its line first ─────────
|
|
256
|
+
// This is how top/htop work — overwrite each row in place.
|
|
257
|
+
// No full-screen clear, no cursor tricks. Simple and reliable.
|
|
275
258
|
for (const row of rows) {
|
|
276
259
|
process.stdout.write(`${_c.clearLine}\r${row}\n`);
|
|
277
260
|
}
|
|
278
|
-
process.stdout.write(_c.show);
|
|
279
|
-
const leftover = ctx.dashboardLines - rows.length;
|
|
280
|
-
if (leftover > 0) {
|
|
281
|
-
process.stdout.write(`\x1b[${leftover}B\x1b[2K`);
|
|
282
|
-
}
|
|
283
261
|
|
|
284
262
|
return rows.length;
|
|
285
263
|
}
|
package/lib/grinder.js
CHANGED
|
@@ -3358,10 +3358,8 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
3358
3358
|
setDashboardActive(true);
|
|
3359
3359
|
// Setup keyboard shortcuts
|
|
3360
3360
|
setupKeyboardShortcuts();
|
|
3361
|
-
//
|
|
3362
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
3361
|
+
// Cursor hide to reduce visual noise during renders
|
|
3363
3362
|
process.stdout.write(c.hide);
|
|
3364
|
-
dashboardLines = 0;
|
|
3365
3363
|
|
|
3366
3364
|
// Re-render on terminal resize so layout adapts to window size
|
|
3367
3365
|
process.stdout.on('resize', () => {
|