qwenproxy-cli 1.1.0 → 1.2.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.
@@ -12,7 +12,7 @@ import {
12
12
  inspectClientSyncStatus,
13
13
  } from "../../sync/index.ts";
14
14
  import type { SyncClientName } from "../../sync/types.ts";
15
- import { fetchLiveModels } from "../proxy-client.ts";
15
+ import { fetchLiveModels, DEFAULT_FALLBACK_MODELS } from "../proxy-client.ts";
16
16
 
17
17
  interface ClientOption {
18
18
  id: SyncClientName;
@@ -30,18 +30,17 @@ export class SyncView implements TuiView {
30
30
  private clients: ClientOption[] = [];
31
31
  private selectedRowIndex = 0; // 0..9 for clients, 10 for model, 11 for scope, 12 for sync, 13 for restore
32
32
  private hoveredActionRow: number | null = null;
33
- private availableModels = [
34
- "qwen3.8-max",
35
- "qwen3.7-plus",
36
- "qwen3.7-max",
37
- "z-image-turbo",
38
- "qwen-image-3.0-pro",
39
- "wan3.0-video",
40
- ];
33
+ private availableModels = [...DEFAULT_FALLBACK_MODELS];
41
34
  private modelIndex = 0;
42
35
  private syncAllModels = true;
43
36
  private actionLog: string[] = [];
44
37
  private lastLeftW = 46;
38
+ private lastClientStartRow = 8;
39
+ private lastClientEndRow = 17;
40
+ private lastModelRow = 20;
41
+ private lastScopeRow = 21;
42
+ private lastSyncRow = 24;
43
+ private lastRestoreRow = 25;
45
44
  constructor() {
46
45
  this.detectClients();
47
46
  }
@@ -50,11 +49,14 @@ export class SyncView implements TuiView {
50
49
  this.detectClients();
51
50
  void this.refreshModels();
52
51
  }
53
- private async refreshModels(): Promise<void> {
52
+ public async refreshModels(): Promise<void> {
54
53
  try {
55
54
  const live = await fetchLiveModels();
56
55
  if (live && live.length > 0) {
56
+ const current = this.availableModels[this.modelIndex];
57
57
  this.availableModels = live;
58
+ const foundIdx = this.availableModels.indexOf(current);
59
+ this.modelIndex = foundIdx !== -1 ? foundIdx : 0;
58
60
  }
59
61
  } catch {}
60
62
  }
@@ -103,27 +105,62 @@ export class SyncView implements TuiView {
103
105
  const { row, col } = key.mouse;
104
106
  const leftW = this.lastLeftW || 46;
105
107
  if (col >= 2 && col <= leftW - 1) {
106
- if (row >= 8 && row <= 17) {
107
- const targetRow = row - 8;
108
+ if (row >= this.lastClientStartRow && row <= this.lastClientEndRow) {
109
+ const targetRow = row - this.lastClientStartRow;
110
+ let changed = false;
108
111
  if (this.selectedRowIndex !== targetRow) {
109
112
  this.selectedRowIndex = targetRow;
110
- return true;
113
+ changed = true;
114
+ }
115
+ if (this.hoveredActionRow !== null) {
116
+ this.hoveredActionRow = null;
117
+ changed = true;
111
118
  }
112
- } else if (row === 20 || row === 14) {
119
+ if (changed) return true;
120
+ } else if (row === this.lastModelRow) {
121
+ let changed = false;
113
122
  if (this.selectedRowIndex !== 10) {
114
123
  this.selectedRowIndex = 10;
115
- return true;
124
+ changed = true;
116
125
  }
117
- } else if (row === 21 || row === 15) {
126
+ if (this.hoveredActionRow !== null) {
127
+ this.hoveredActionRow = null;
128
+ changed = true;
129
+ }
130
+ if (changed) return true;
131
+ } else if (row === this.lastScopeRow) {
132
+ let changed = false;
118
133
  if (this.selectedRowIndex !== 11) {
119
134
  this.selectedRowIndex = 11;
120
- return true;
135
+ changed = true;
121
136
  }
122
- } else if (row === 24 || row === 18 || row === 25 || row === 19) {
123
- if (this.hoveredActionRow !== row) {
124
- this.hoveredActionRow = row;
125
- return true;
137
+ if (this.hoveredActionRow !== null) {
138
+ this.hoveredActionRow = null;
139
+ changed = true;
140
+ }
141
+ if (changed) return true;
142
+ } else if (row === this.lastSyncRow) {
143
+ let changed = false;
144
+ if (this.selectedRowIndex !== 12) {
145
+ this.selectedRowIndex = 12;
146
+ changed = true;
147
+ }
148
+ if (this.hoveredActionRow !== this.lastSyncRow) {
149
+ this.hoveredActionRow = this.lastSyncRow;
150
+ changed = true;
151
+ }
152
+ if (changed) return true;
153
+ } else if (row === this.lastRestoreRow) {
154
+ let changed = false;
155
+ if (this.selectedRowIndex !== 13) {
156
+ this.selectedRowIndex = 13;
157
+ changed = true;
126
158
  }
159
+ if (this.hoveredActionRow !== this.lastRestoreRow) {
160
+ this.hoveredActionRow = this.lastRestoreRow;
161
+ changed = true;
162
+ }
163
+ if (changed) return true;
127
164
  } else if (this.hoveredActionRow !== null) {
128
165
  this.hoveredActionRow = null;
129
166
  return true;
@@ -140,35 +177,40 @@ export class SyncView implements TuiView {
140
177
  const leftW = this.lastLeftW || 46;
141
178
  if (col >= 2 && col <= leftW - 1) {
142
179
  // Rows 8..17: Toggle client
143
- if (row >= 8 && row <= 17) {
144
- const client = this.clients[row - 8];
180
+ if (row >= this.lastClientStartRow && row <= this.lastClientEndRow) {
181
+ const client = this.clients[row - this.lastClientStartRow];
145
182
  if (client) {
146
183
  client.selected = !client.selected;
147
- this.selectedRowIndex = row - 8;
184
+ this.selectedRowIndex = row - this.lastClientStartRow;
185
+ this.hoveredActionRow = null;
148
186
  return true;
149
187
  }
150
188
  }
151
189
  // Model selector
152
- if (row === 20 || row === 14) {
190
+ if (row === this.lastModelRow) {
153
191
  this.modelIndex = (this.modelIndex + 1) % this.availableModels.length;
154
192
  this.selectedRowIndex = 10;
193
+ this.hoveredActionRow = null;
155
194
  return true;
156
195
  }
157
196
  // Scope selector
158
- if (row === 21 || row === 15) {
197
+ if (row === this.lastScopeRow) {
159
198
  this.syncAllModels = !this.syncAllModels;
160
199
  this.selectedRowIndex = 11;
200
+ this.hoveredActionRow = null;
161
201
  return true;
162
202
  }
163
203
  // Sincronizar button
164
- if (row === 24 || row === 18) {
204
+ if (row === this.lastSyncRow) {
165
205
  this.selectedRowIndex = 12;
206
+ this.hoveredActionRow = this.lastSyncRow;
166
207
  this.executeSync();
167
208
  return true;
168
209
  }
169
210
  // Restaurar button
170
- if (row === 25 || row === 19) {
211
+ if (row === this.lastRestoreRow) {
171
212
  this.selectedRowIndex = 13;
213
+ this.hoveredActionRow = this.lastRestoreRow;
172
214
  this.executeRollback();
173
215
  return true;
174
216
  }
@@ -282,9 +324,16 @@ export class SyncView implements TuiView {
282
324
  }
283
325
 
284
326
  private executeRollback(): void {
285
- this.actionLog.unshift(theme.yellow("⏳ Restaurando backups anteriores de configuração..."));
327
+ const selectedTargets = this.clients
328
+ .filter((c) => c.selected)
329
+ .map((c) => c.id);
330
+
331
+ const targetDesc = selectedTargets.length > 0 ? `[${selectedTargets.join(", ")}]` : "todos os clientes";
332
+ this.actionLog.unshift(theme.yellow(`⏳ Restaurando backups anteriores de ${targetDesc}...`));
286
333
  try {
287
- const res = restoreAllClients();
334
+ const res = restoreAllClients({
335
+ targets: selectedTargets.length > 0 ? selectedTargets : undefined,
336
+ });
288
337
  this.actionLog.unshift(
289
338
  theme.green(`✓ Rollback concluído: ${res.restoredCount} arquivo(s) restaurados com sucesso.`),
290
339
  );
@@ -298,6 +347,12 @@ export class SyncView implements TuiView {
298
347
  const contentH = Math.max(22, height);
299
348
  const leftW = Math.max(48, Math.floor(width * 0.52));
300
349
  this.lastLeftW = leftW;
350
+ this.lastClientStartRow = 8;
351
+ this.lastClientEndRow = 8 + this.clients.length - 1;
352
+ this.lastModelRow = this.lastClientEndRow + 3;
353
+ this.lastScopeRow = this.lastModelRow + 1;
354
+ this.lastSyncRow = this.lastScopeRow + 3;
355
+ this.lastRestoreRow = this.lastSyncRow + 1;
301
356
  const rightW = Math.max(30, width - leftW - 1);
302
357
 
303
358
  // Left Panel: Options and Selectors
@@ -351,13 +406,13 @@ export class SyncView implements TuiView {
351
406
  leftContent.push(` ${theme.bold("Ações:")}`);
352
407
  // Row index 12: Sincronizar
353
408
  const isSyncFocused = this.selectedRowIndex === 12;
354
- const isSyncHovered = this.hoveredActionRow === 24 || this.hoveredActionRow === 18;
409
+ const isSyncHovered = this.hoveredActionRow === this.lastSyncRow;
355
410
  const syncLine = ` ${isSyncHovered || isSyncFocused ? theme.bgHover(` ${theme.cyan("[ Enter ] Sincronizar")} `) : `${theme.cyan("[ Enter ]")} Sincronizar`}`;
356
411
  leftContent.push(syncLine);
357
412
 
358
413
  // Row index 13: Restaurar
359
414
  const isRestoreFocused = this.selectedRowIndex === 13;
360
- const isRestoreHovered = this.hoveredActionRow === 25 || this.hoveredActionRow === 19;
415
+ const isRestoreHovered = this.hoveredActionRow === this.lastRestoreRow;
361
416
  const restoreLine = ` ${isRestoreHovered || isRestoreFocused ? theme.bgHover(` ${theme.yellow("[ R ] Restaurar")} `) : `${theme.yellow("[ R ]")} Restaurar`}`;
362
417
  leftContent.push(restoreLine);
363
418
 
package/src/update-cli.ts CHANGED
@@ -1,161 +1,17 @@
1
- import { spawnSync } from "node:child_process";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
- const packageRoot = path.resolve(__dirname, "..");
8
- const packageJsonPath = path.join(packageRoot, "package.json");
9
-
10
1
  export type PackageManager = "bun" | "pnpm" | "yarn" | "npm";
11
2
 
12
- export function detectPackageManager(): PackageManager {
13
- const userAgent = process.env.npm_config_user_agent || "";
14
- if (userAgent.startsWith("bun")) return "bun";
15
- if (userAgent.startsWith("pnpm")) return "pnpm";
16
- if (userAgent.startsWith("yarn")) return "yarn";
17
-
18
- // Check which executable is actually running the script or installed in PATH
19
- const execPath = process.execPath.toLowerCase();
20
- if (execPath.includes("bun")) return "bun";
21
-
22
- // Check if installation path contains pnpm / bun / yarn markers
23
- const currentPath = packageRoot.toLowerCase();
24
- if (currentPath.includes(".pnpm") || currentPath.includes("pnpm")) return "pnpm";
25
- if (currentPath.includes(".bun") || currentPath.includes("bun")) return "bun";
26
- if (currentPath.includes("yarn")) return "yarn";
27
-
28
- return "npm";
29
- }
30
- export function getUpdateArgs(
31
- pm: PackageManager,
32
- packageName: string,
33
- targetVersion?: string,
34
- ): { cmd: string; args: string[] } {
35
- const versionTag = targetVersion ? `@${targetVersion}` : "@latest";
36
- switch (pm) {
37
- case "bun":
38
- return { cmd: "bun", args: ["add", "-g", `${packageName}${versionTag}`] };
39
- case "pnpm":
40
- return {
41
- cmd: "pnpm",
42
- args: targetVersion
43
- ? ["add", "-g", `${packageName}@${targetVersion}`]
44
- : ["update", "-g", packageName],
45
- };
46
- case "yarn":
47
- return {
48
- cmd: "yarn",
49
- args: targetVersion
50
- ? ["global", "add", `${packageName}@${targetVersion}`]
51
- : ["global", "upgrade", packageName],
52
- };
53
- case "npm":
54
- default:
55
- return { cmd: "npm", args: ["install", "-g", `${packageName}${versionTag}`] };
56
- }
57
- }
58
-
59
- export function isNewerVersion(current: string, latest: string): boolean {
60
- const c = current.replace(/^v/i, "").split(".").map((n) => parseInt(n, 10));
61
- const l = latest.replace(/^v/i, "").split(".").map((n) => parseInt(n, 10));
62
- for (let i = 0; i < 3; i++) {
63
- const cv = c[i] || 0;
64
- const lv = l[i] || 0;
65
- if (lv > cv) return true;
66
- if (lv < cv) return false;
67
- }
68
- return false;
69
- }
70
-
71
- export async function fetchLatestNpmVersion(packageName: string): Promise<string> {
72
- // 1. Direct fast HTTP query to registry.npmjs.org (bypasses child process & Windows npm.cmd cold start)
73
- try {
74
- const controller = new AbortController();
75
- const timer = setTimeout(() => controller.abort(), 6000);
76
- const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
77
- headers: { Accept: "application/json" },
78
- signal: controller.signal,
79
- });
80
- clearTimeout(timer);
81
- if (res.ok) {
82
- const data = (await res.json()) as { version?: string };
83
- if (data.version && typeof data.version === "string") {
84
- return data.version.trim();
85
- }
86
- }
87
- } catch {}
88
-
89
- // 2. Fallback: npm view via CLI with a generous 30s timeout and clean semver extraction
90
- try {
91
- const fullCmd = `npm view ${packageName} version`;
92
- const res = spawnSync(fullCmd, {
93
- encoding: "utf-8",
94
- shell: true,
95
- timeout: 30000,
96
- });
97
- if (res.stdout) {
98
- const lines = res.stdout.trim().split(/\r?\n/);
99
- for (const line of lines.reverse()) {
100
- const cleaned = line.trim().replace(/^v/i, "");
101
- if (/^\d+\.\d+\.\d+/.test(cleaned)) {
102
- return cleaned;
103
- }
104
- }
105
- }
106
- } catch {}
3
+ export {
4
+ detectPackageManager,
5
+ getUpdateArgs,
6
+ isNewerVersion,
7
+ fetchLatestNpmVersion,
8
+ runUpdateCommand,
9
+ } from "../bin/update.js";
107
10
 
108
- return "";
109
- }
110
-
111
- export async function runUpdateCommand(): Promise<void> {
112
- let pkg: any = { name: "qwenproxy-cli", version: "1.0.0" };
113
- try {
114
- pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
115
- } catch {}
116
-
117
- const currentVersion = pkg.version || "1.0.0";
118
- const packageName = pkg.name || "qwenproxy-cli";
119
- const pm = detectPackageManager();
120
-
121
- console.log(`\n📦 [QwenProxy] Versão local instalada: v${currentVersion}`);
122
- console.log(`⚙️ [QwenProxy] Gerenciador de pacotes detectado: ${pm}`);
123
- console.log(`🔍 [QwenProxy] Verificando se há novas versões de ${packageName} no npm registry...`);
124
-
125
- const latestVersion = await fetchLatestNpmVersion(packageName);
126
-
127
- if (!latestVersion) {
128
- console.warn("⚠️ [QwenProxy] Não foi possível consultar o registro online.");
129
- const manual = getUpdateArgs(pm, packageName);
130
- console.log(`👉 Você pode forçar a atualização manualmente com:\n ${manual.cmd} ${manual.args.join(" ")}\n`);
131
- return;
132
- }
133
-
134
- console.log(`🌐 [QwenProxy] Versão mais recente disponível: v${latestVersion}`);
135
-
136
- if (!isNewerVersion(currentVersion, latestVersion)) {
137
- console.log(`\n✨ Você já está utilizando a versão mais recente (v${currentVersion})!\n`);
138
- return;
139
- }
140
-
141
- console.log(`\n🚀 Nova versão disponível: v${currentVersion} ➔ v${latestVersion}`);
142
- const { cmd, args } = getUpdateArgs(pm, packageName, latestVersion);
143
- console.log(`⏳ Atualizando globalmente via ${pm} (${cmd} ${args.join(" ")})...`);
144
- const fullUpdateCmd = `${cmd} ${args.join(" ")}`;
145
- const updateProc = spawnSync(fullUpdateCmd, {
146
- stdio: "inherit",
147
- shell: true,
148
- });
149
- if (updateProc.status === 0) {
150
- console.log(`\n✅ [QwenProxy] Atualizado com sucesso para a versão v${latestVersion}!`);
151
- console.log("👉 Digite 'qpx' para iniciar a nova versão.\n");
152
- } else {
153
- console.error(`\n❌ [QwenProxy] Falha ao atualizar automaticamente com ${cmd}.`);
154
- console.log(`👉 Tente executar manualmente:\n ${cmd} ${args.join(" ")}\n`);
155
- }
156
- }
11
+ import { runUpdateCommand } from "../bin/update.js";
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
157
14
 
158
- // Execute if run directly via CLI runner, not when imported in unit tests
159
15
  const isDirectRun =
160
16
  Boolean(process.argv[1]) &&
161
17
  (fileURLToPath(import.meta.url) === path.resolve(process.argv[1]) ||