qwenproxy-cli 1.0.26 → 1.0.28

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.
@@ -8,6 +8,48 @@ import { theme, glyphs, drawBox, pad, truncate } from "../theme.ts";
8
8
  import { fetchProxyStatus, resetAllCooldowns, formatUptime } from "../proxy-client.ts";
9
9
  import { ServerManager } from "../server-manager.ts";
10
10
 
11
+ export function renderProgressBar(
12
+ pct: number,
13
+ width = 8,
14
+ colorFn: (s: string) => string = theme.cyan,
15
+ ): string {
16
+ const safePct = Math.max(0, Math.min(100, isNaN(pct) ? 0 : pct));
17
+ const rawFilled = Math.round((safePct / 100) * width);
18
+ const filled = safePct > 0.05 ? Math.max(1, rawFilled) : 0;
19
+ const empty = Math.max(0, width - filled);
20
+ return colorFn("█".repeat(filled)) + theme.muted("░".repeat(empty));
21
+ }
22
+ export function getAccountReadinessScore(acc: {
23
+ onCooldown: boolean;
24
+ headersReady: boolean;
25
+ activeStreams?: number;
26
+ isInitialized?: boolean;
27
+ cooldownReason?: string | null;
28
+ }): number {
29
+ if (acc.onCooldown) {
30
+ const reason = acc.cooldownReason || "";
31
+ if (
32
+ reason.startsWith("AuthFailed") ||
33
+ reason.startsWith("AuthPermanentFailure") ||
34
+ reason.includes("login methods exhausted")
35
+ ) {
36
+ return 0; // Auth Fail at the very bottom
37
+ }
38
+ return 10; // Other cooldowns (RateLimited, WafChallenge, etc.)
39
+ }
40
+ if (acc.activeStreams && acc.activeStreams > 0 && acc.headersReady) {
41
+ return 100; // Actively generating code
42
+ }
43
+ if (acc.headersReady) {
44
+ return 80; // Ready / Warm
45
+ }
46
+ if (acc.isInitialized) {
47
+ return 60; // Warming up
48
+ }
49
+ return 40; // Healthy standby
50
+ }
51
+
52
+
11
53
  export class StatusView implements TuiView {
12
54
  public readonly id = "status";
13
55
  public readonly title = "Status";
@@ -17,8 +59,9 @@ export class StatusView implements TuiView {
17
59
  private actionMessage = "";
18
60
  private actionMessageTimeout: NodeJS.Timeout | null = null;
19
61
  private hoveredActionRow: number | null = null;
20
- private lastLeftW = 34;
21
-
62
+ private lastLeftW = 38;
63
+ private lastActionRecarregarRow = 20;
64
+ private lastActionZerarRow = 21;
22
65
  constructor() {
23
66
  this.refresh();
24
67
  }
@@ -58,8 +101,12 @@ export class StatusView implements TuiView {
58
101
  // Mouse hover over quick actions
59
102
  if (key.name === "hover" && key.mouse) {
60
103
  const { row, col } = key.mouse;
61
- const leftW = this.lastLeftW || 34;
62
- if (col >= 2 && col <= leftW - 1 && (row === 13 || row === 14)) {
104
+ const leftW = this.lastLeftW || 38;
105
+ if (
106
+ col >= 2 &&
107
+ col <= leftW - 1 &&
108
+ (row === this.lastActionRecarregarRow || row === this.lastActionZerarRow)
109
+ ) {
63
110
  if (this.hoveredActionRow !== row) {
64
111
  this.hoveredActionRow = row;
65
112
  return true;
@@ -73,14 +120,14 @@ export class StatusView implements TuiView {
73
120
  // Mouse click interactions
74
121
  if (key.name === "click" && key.mouse) {
75
122
  const { row, col } = key.mouse;
76
- const leftW = this.lastLeftW || 34;
123
+ const leftW = this.lastLeftW || 38;
77
124
  if (col >= 2 && col <= leftW - 1) {
78
- if (row === 13) {
125
+ if (row === this.lastActionRecarregarRow) {
79
126
  await this.refresh();
80
127
  this.setMessage(theme.green("✓ Status atualizado"));
81
128
  return true;
82
129
  }
83
- if (row === 14) {
130
+ if (row === this.lastActionZerarRow) {
84
131
  const cleared = resetAllCooldowns();
85
132
  await this.refresh();
86
133
  this.setMessage(theme.green(`✓ Cooldowns zerados: ${cleared} conta(s) liberada(s)`));
@@ -108,12 +155,13 @@ export class StatusView implements TuiView {
108
155
  const isOnline = data?.online ?? false;
109
156
  const contentH = Math.max(10, height);
110
157
 
111
- // Two-column layout
112
- const leftW = Math.max(34, Math.floor(width * 0.42));
158
+ // Two-column layout: give left box 48-52 cols so rich metrics never truncate,
159
+ // and right box takes the remaining width (at least 38 cols).
160
+ const leftW = width >= 96
161
+ ? Math.min(52, Math.max(48, Math.floor(width * 0.48)))
162
+ : Math.min(46, Math.max(38, Math.floor(width * 0.50)));
113
163
  this.lastLeftW = leftW;
114
- const rightW = Math.max(34, width - leftW - 1);
115
-
116
- // Left Column: System & Proxy Status
164
+ const rightW = Math.max(36, width - leftW - 1);
117
165
  const serverState = ServerManager.getInstance().getState();
118
166
  let onlineBadge: string;
119
167
  if (isOnline || serverState === "online") {
@@ -128,67 +176,158 @@ export class StatusView implements TuiView {
128
176
 
129
177
  const uptimeSecs = data?.uptimeSeconds || Math.floor(process.uptime());
130
178
  const uptimeStr = formatUptime(uptimeSecs);
131
-
132
179
  const baseUrl = `http://${data?.host || "127.0.0.1"}:${data?.port || 7936}/v1`;
133
180
 
134
- const leftContent = [
135
- "",
136
- ` ${theme.bold("Status:")} ${onlineBadge}`,
137
- ` ${theme.bold("Base URL:")} ${theme.cyan(baseUrl)}`,
138
- ` ${theme.bold("Uptime:")} ${theme.cyan(uptimeStr)}`,
139
- ` ${theme.bold("RAM:")} ${theme.cyan(String(data?.rssMb || 0) + " MB")}`,
140
- ` ${theme.bold("Conexões:")} ${data?.activeStreams ? theme.yellow(String(data.activeStreams) + " ativas") : "0 ativas"}`,
141
- "",
181
+ const m = data?.metrics;
182
+ const reqsTotal = m?.requestsTotal ?? 0;
183
+ const reqsErrors = m?.requestsErrors ?? 0;
184
+ const successPct = m?.successRate ?? (reqsTotal > 0 ? Number((((reqsTotal - reqsErrors) / reqsTotal) * 100).toFixed(1)) : 100);
185
+ const latencyAvg = m?.latencyAvgMs ? `${m.latencyAvgMs}ms` : "–";
186
+ const deltasCount = m?.deltasCount ?? 0;
187
+ const fullCount = m?.fullReplaysCount ?? 0;
188
+ const deltaRatio = m?.deltaRatio != null ? `${m.deltaRatio}%` : "";
189
+ const toolCalls = m?.toolCallsCount ?? 0;
190
+ const toolRecovered = m?.toolCallsRecovered ?? 0;
191
+ const captchasDetected = m?.captchasDetected ?? 0;
192
+ const captchasSolved = m?.captchasSolved ?? 0;
193
+ const chatsCleaned = m?.chatsCleaned ?? 0;
194
+ const lbl = (s: string) => pad(s, 13);
195
+ const innerLeftW = Math.max(30, leftW - 2);
196
+ const deltaDetail = innerLeftW < 44
197
+ ? `(${deltasCount}d / ${fullCount}f)`
198
+ : `(${deltasCount} delta / ${fullCount} full)`;
199
+ const ramPct = data?.systemMemoryPct || 0;
200
+ const ramBarColor = ramPct >= 80 ? theme.red : ramPct >= 60 ? theme.yellow : theme.cyan;
201
+ const ramBar = renderProgressBar(ramPct, 7, ramBarColor);
202
+ const ramStr = `${data?.rssMb || 0} MB [${ramBar}] ${ramPct}%`;
203
+
204
+ let connsStr = "0 ativas";
205
+ if (data?.activeStreams && data.activeStreams > 0) {
206
+ connsStr = theme.yellow(`⚡ ${data.activeStreams} ativa(s)`);
207
+ } else {
208
+ connsStr = theme.dim("0 ativas");
209
+ }
210
+ if (data?.waitingStreams && data.waitingStreams > 0) {
211
+ connsStr += theme.peach(` (${data.waitingStreams} na fila)`);
212
+ }
213
+
214
+ const leftContent: string[] = [
215
+ ` ${theme.bold(lbl("Status:"))} ${onlineBadge}`,
216
+ ` ${theme.bold(lbl("Base URL:"))} ${theme.cyan(baseUrl)}`,
217
+ ` ${theme.bold(lbl("Uptime:"))} ${theme.cyan(uptimeStr)}`,
218
+ ` ${theme.bold(lbl("Memória:"))} ${theme.cyan(ramStr)}`,
219
+ ` ${theme.bold(lbl("Conexões:"))} ${connsStr}`,
220
+ ` ${theme.dim("───────────────────────────────────────")}`,
221
+ ` ${theme.bold("Tráfego & Performance:")}`,
222
+ ` ${theme.dim(lbl("Requisições:"))} ${theme.cyan(String(reqsTotal))} ${theme.green(`(${successPct}% ok)`)} · ${reqsErrors > 0 ? theme.red(`${reqsErrors} err`) : theme.dim("0 err")}`,
223
+ ` ${theme.dim(lbl("Latência:"))} ${theme.yellow(latencyAvg)} méd`,
224
+ ` ${theme.dim(lbl("Deltas:"))} ${theme.green(deltaRatio)} ${theme.dim(deltaDetail)}`,
225
+ ` ${theme.dim("───────────────────────────────────────")}`,
226
+ ` ${theme.bold("Agentes & Operações:")}`,
227
+ ` ${theme.dim(lbl("Tool Calls:"))} ${theme.cyan(String(toolCalls))} ${toolRecovered > 0 ? theme.green(`(${toolRecovered} curadas)`) : ""}`,
228
+ ` ${theme.dim(lbl("Captchas:"))} ${captchasSolved > 0 ? theme.green(`${captchasSolved}/${captchasDetected} resolvidos`) : theme.dim(`${captchasDetected} detectados`)}`,
229
+ ` ${theme.dim(lbl("Chats Limpos:"))} ${theme.cyan(String(chatsCleaned))} ${theme.dim("excluídos (>24h)")}`,
230
+ ` ${theme.dim("───────────────────────────────────────")}`,
142
231
  ` ${theme.bold("Ações:")}`,
143
- ` ${this.hoveredActionRow === 13 ? theme.bgHover(` ${theme.cyan("[ R ] Recarregar")} `) : `${theme.cyan("[ R ]")} Recarregar`}`,
144
- ` ${this.hoveredActionRow === 14 ? theme.bgHover(` ${theme.yellow("[ Z ] Zerar Cooldowns")} `) : `${theme.yellow("[ Z ]")} Zerar Cooldowns`}`,
145
- "",
146
- this.actionMessage ? ` ${this.actionMessage}` : "",
147
232
  ];
148
233
 
234
+ const recarregarIdx = leftContent.length;
235
+ const zerarIdx = leftContent.length + 1;
236
+ this.lastActionRecarregarRow = 5 + recarregarIdx;
237
+ this.lastActionZerarRow = 5 + zerarIdx;
238
+
239
+ leftContent.push(
240
+ ` ${this.hoveredActionRow === this.lastActionRecarregarRow ? theme.bgHover(` ${theme.cyan("[ R ] Recarregar")} `) : ` ${theme.cyan("[ R ]")} Recarregar`}`,
241
+ ` ${this.hoveredActionRow === this.lastActionZerarRow ? theme.bgHover(` ${theme.yellow("[ Z ] Zerar Cooldowns")} `) : ` ${theme.yellow("[ Z ]")} Zerar Cooldowns`}`,
242
+ );
243
+
244
+ const boxHeight = Math.max(contentH, leftContent.length + 2);
245
+
149
246
  const leftBox = drawBox({
150
- title: "Sistema",
247
+ title: "Sistema & Performance",
151
248
  width: leftW,
152
- height: contentH,
249
+ height: boxHeight,
153
250
  borderColor: theme.borderInactive,
154
251
  titleColor: theme.cyan,
252
+ footer: this.actionMessage || undefined,
155
253
  content: leftContent,
156
254
  });
157
-
158
255
  // Right Column: Accounts Pool Status
159
- const accounts = data?.accounts || [];
256
+ const rawAccounts = data?.accounts || [];
257
+ const accounts = [...rawAccounts].sort((a, b) => {
258
+ const scoreA = getAccountReadinessScore(a);
259
+ const scoreB = getAccountReadinessScore(b);
260
+ if (scoreB !== scoreA) {
261
+ return scoreB - scoreA;
262
+ }
263
+ return 0;
264
+ });
265
+ const availableCount = accounts.filter((a) => !a.onCooldown).length;
160
266
  const readyCount = accounts.filter((a) => !a.onCooldown && a.headersReady).length;
267
+ const standbyCount = accounts.filter((a) => !a.onCooldown && !a.headersReady).length;
268
+ const cooldownCount = accounts.filter((a) => a.onCooldown).length;
269
+ const poolPct = accounts.length > 0 ? Math.round((availableCount / accounts.length) * 100) : 0;
270
+ const poolColor = poolPct >= 70 ? theme.green : poolPct >= 40 ? theme.yellow : theme.red;
271
+ const poolBar = renderProgressBar(poolPct, 8, poolColor);
272
+ const innerRightW = Math.max(30, rightW - 2);
273
+ const emailWidth = Math.max(16, Math.min(20, innerRightW - 27));
161
274
  const rightContent: string[] = [
162
- "",
163
- ` ${theme.dim("# Conta Status")}`,
164
- ` ${theme.dim("───────────────────────────────────────")}`,
275
+ ` ${theme.bold("Disponibilidade:")} [${poolBar}] ${poolColor(`${availableCount}/${accounts.length} (${poolPct}%)`)}`,
276
+ ` ${theme.dim("".repeat(Math.max(32, innerRightW - 2)))}`,
277
+ ` ${theme.dim(`# ${pad("Conta", emailWidth)} Carga Status`)}`,
278
+ ` ${theme.dim("─".repeat(Math.max(32, innerRightW - 2)))}`,
165
279
  ];
166
280
 
167
281
  if (accounts.length === 0) {
168
282
  rightContent.push(` ${theme.muted("Nenhuma conta adicionada. (Vá em [5] Contas)")}`);
169
283
  } else {
170
- accounts.slice(0, contentH - 5).forEach((acc, idx) => {
171
- const num = pad(String(idx + 1), 3);
172
- const name = pad(truncate(acc.emailOrName, 20), 20);
284
+ const maxVisibleAccounts = Math.max(6, boxHeight - 7);
285
+ accounts.slice(0, maxVisibleAccounts).forEach((acc, idx) => {
286
+ const num = pad(String(idx + 1) + ".", 4);
287
+ const name = pad(truncate(acc.emailOrName, emailWidth - 1), emailWidth);
288
+ const active = acc.activeStreams || 0;
289
+ const limit = acc.streamLimit || 1;
290
+ const loadBadge = active > 0 ? theme.yellow(`[${active}/${limit}]`) : theme.dim(`[0/${limit}]`);
291
+
173
292
  let status = theme.green(`${glyphs.bullet} Pronto`);
174
- if (acc.onCooldown) {
175
- const mins = Math.max(1, Math.round(acc.remainingCooldownMs / 60000));
176
- status = theme.yellow(`⚠️ Cooldown ${mins}m`);
293
+ if (active > 0 && !acc.onCooldown && acc.headersReady) {
294
+ status = theme.yellow(`● Gerando `);
295
+ } else if (acc.onCooldown) {
296
+ const reason = acc.cooldownReason || "";
297
+ if (
298
+ reason.startsWith("AuthFailed") ||
299
+ reason.startsWith("AuthPermanentFailure") ||
300
+ reason.includes("login methods exhausted")
301
+ ) {
302
+ status = theme.red(`❌ Auth Fail`);
303
+ } else if (reason === "WafChallenge") {
304
+ status = theme.peach(`🛡️ WAF Block`);
305
+ } else {
306
+ const mins = Math.max(1, Math.round(acc.remainingCooldownMs / 60000));
307
+ status = theme.yellow(`⚠️ ${mins}m cd`);
308
+ }
177
309
  } else if (!acc.headersReady) {
178
310
  status = acc.isInitialized
179
311
  ? theme.yellow(`◐ Aquecendo...`)
180
312
  : theme.muted(`○ Standby`);
181
313
  }
182
- rightContent.push(` ${num} ${name} ${status}`);
314
+ rightContent.push(` ${num}${name} ${loadBadge} ${status}`);
183
315
  });
184
316
  }
185
317
 
318
+ const summaryParts: string[] = [];
319
+ if (readyCount > 0) summaryParts.push(`${readyCount} warm`);
320
+ if (standbyCount > 0) summaryParts.push(`${standbyCount} standby`);
321
+ if (cooldownCount > 0) summaryParts.push(`${cooldownCount} cd`);
322
+ const rightFooter = summaryParts.length > 0 ? `💡 ${summaryParts.join(" · ")} · [5] Contas` : undefined;
323
+
186
324
  const rightBox = drawBox({
187
- title: `Contas (${readyCount}/${accounts.length})`,
325
+ title: `Contas Pool (${availableCount}/${accounts.length})`,
188
326
  width: rightW,
189
- height: contentH,
327
+ height: boxHeight,
190
328
  borderColor: theme.borderInactive,
191
329
  titleColor: theme.lavender,
330
+ footer: rightFooter,
192
331
  content: rightContent,
193
332
  });
194
333