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.
package/src/sync/codex.ts CHANGED
@@ -111,13 +111,38 @@ experimental_bearer_token = "${apiKey}"
111
111
  }
112
112
 
113
113
  export function restoreCodex(filePath: string, backupPath?: string): ClientSyncResult {
114
- const restored = restoreFromBackup(filePath, backupPath);
114
+ const restoredFromBackup = restoreFromBackup(filePath, backupPath);
115
+
116
+ // If backup was restored but still had qwenproxy (or if no backup was found),
117
+ // strip the QwenProxy provider block cleanly so the file is guaranteed un-synced.
118
+ let manuallyCleaned = false;
119
+ if (fs.existsSync(filePath)) {
120
+ try {
121
+ let content = fs.readFileSync(filePath, "utf-8");
122
+ if (content.includes("[model_providers.qwenproxy]") || /^model_provider\s*=\s*["']qwenproxy["']/m.test(content)) {
123
+ const providerRegex = /\[model_providers\.qwenproxy\][\s\S]*?(?=(?:^\[|\Z))/m;
124
+ content = content.replace(providerRegex, "").trimEnd();
125
+ content = content.replace(/^model_provider\s*=\s*["']qwenproxy["']\r?\n?/m, "");
126
+ if (/^model\s*=\s*["']qwen/m.test(content)) {
127
+ content = content.replace(/^model\s*=\s*["']qwen[^"']*["']\r?\n?/m, "");
128
+ }
129
+ fs.writeFileSync(filePath, content.trimEnd() + "\n", "utf-8");
130
+ manuallyCleaned = true;
131
+ }
132
+ } catch {}
133
+ }
134
+
135
+ const success = restoredFromBackup || manuallyCleaned;
115
136
  return {
116
137
  client: "codex",
117
138
  filePath,
118
139
  backupPath,
119
- success: restored,
120
- action: restored ? "restored" : "failed",
121
- message: restored ? "Restored Codex config from backup" : "Backup file not found",
140
+ success,
141
+ action: success ? "restored" : "failed",
142
+ message: success
143
+ ? restoredFromBackup
144
+ ? "Restored Codex config from backup"
145
+ : "Removed QwenProxy configuration from Codex config"
146
+ : "Backup file not found",
122
147
  };
123
148
  }
package/src/sync/index.ts CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  ClientSyncResult,
11
11
  SyncAllOptions,
12
12
  SyncClientName,
13
+ SyncRecord,
13
14
  SyncStateFile,
14
15
  } from "./types.ts";
15
16
  import { syncClaudeCode, restoreClaudeCode } from "./claude-code.ts";
@@ -23,6 +24,28 @@ import { syncCline, restoreCline } from "./cline.ts";
23
24
  import { syncZed, restoreZed } from "./zed.ts";
24
25
  import { syncAider, restoreAider } from "./aider.ts";
25
26
 
27
+ export {
28
+ syncClaudeCode,
29
+ restoreClaudeCode,
30
+ syncCodex,
31
+ restoreCodex,
32
+ syncOpenCode,
33
+ restoreOpenCode,
34
+ syncOmp,
35
+ restoreOmp,
36
+ syncHermes,
37
+ restoreHermes,
38
+ syncOpenClaw,
39
+ restoreOpenClaw,
40
+ syncKilo,
41
+ restoreKilo,
42
+ syncCline,
43
+ restoreCline,
44
+ syncZed,
45
+ restoreZed,
46
+ syncAider,
47
+ restoreAider,
48
+ };
26
49
  export function resolveApiKey(overrideKey?: string, configKey?: string): string {
27
50
  if (overrideKey && overrideKey.trim().length > 0) {
28
51
  return overrideKey.trim();
@@ -646,16 +669,29 @@ export function syncAllClients(options: SyncAllOptions = {}): SyncAllResult {
646
669
  }
647
670
  }
648
671
 
649
- // Persist sync state
672
+ // Persist sync state (merge with existing state if present)
650
673
  try {
651
674
  fs.mkdirSync(path.dirname(stateFilePath), { recursive: true });
675
+ let existingClients: SyncStateFile["clients"] = {};
676
+ if (fs.existsSync(stateFilePath)) {
677
+ try {
678
+ const raw = fs.readFileSync(stateFilePath, "utf-8");
679
+ const parsed = JSON.parse(raw);
680
+ if (parsed?.clients && typeof parsed.clients === "object") {
681
+ existingClients = parsed.clients;
682
+ }
683
+ } catch {}
684
+ }
652
685
  const stateContent: SyncStateFile = {
653
686
  version: 1,
654
687
  updatedAt: new Date().toISOString(),
655
688
  apiKey,
656
689
  port,
657
690
  host,
658
- clients: stateRecords,
691
+ clients: {
692
+ ...existingClients,
693
+ ...stateRecords,
694
+ },
659
695
  };
660
696
  fs.writeFileSync(stateFilePath, JSON.stringify(stateContent, null, 2) + "\n", "utf-8");
661
697
  } catch (err) {
@@ -670,87 +706,79 @@ export interface RestoreAllResult {
670
706
  details: ClientSyncResult[];
671
707
  }
672
708
 
673
- export function restoreAllClients(options: { stateFilePath?: string } = {}): RestoreAllResult {
709
+ export interface RestoreAllOptions {
710
+ stateFilePath?: string;
711
+ targets?: SyncClientName[];
712
+ }
713
+
714
+ export function restoreAllClients(options: RestoreAllOptions = {}): RestoreAllResult {
715
+ const defaultPaths = getDefaultPaths();
674
716
  const stateFilePath = options.stateFilePath || getDefaultStateFilePath();
675
717
  const details: ClientSyncResult[] = [];
676
718
  let restoredCount = 0;
677
719
 
678
- if (!fs.existsSync(stateFilePath)) {
679
- return { restoredCount: 0, details };
720
+ let state: SyncStateFile | null = null;
721
+ if (fs.existsSync(stateFilePath)) {
722
+ try {
723
+ const raw = fs.readFileSync(stateFilePath, "utf-8");
724
+ state = JSON.parse(raw);
725
+ } catch {}
680
726
  }
681
727
 
682
- try {
683
- const raw = fs.readFileSync(stateFilePath, "utf-8");
684
- const state: SyncStateFile = JSON.parse(raw);
685
-
686
- if (state.clients.claudeCode?.backupPath) {
687
- const res = restoreClaudeCode(state.clients.claudeCode.filePath, state.clients.claudeCode.backupPath);
688
- details.push(res);
689
- if (res.success) restoredCount++;
690
- }
691
-
692
- if (state.clients.codex?.backupPath) {
693
- const res = restoreCodex(state.clients.codex.filePath, state.clients.codex.backupPath);
694
- details.push(res);
695
- if (res.success) restoredCount++;
696
- }
697
-
698
- if (state.clients.openCode?.backupPath) {
699
- const res = restoreOpenCode(state.clients.openCode.filePath, state.clients.openCode.backupPath);
700
- details.push(res);
701
- if (res.success) restoredCount++;
702
- }
703
-
704
- if (state.clients.omp?.backupPath) {
705
- const res = restoreOmp(state.clients.omp.filePath, state.clients.omp.backupPath);
706
- details.push(res);
707
- if (res.success) restoredCount++;
708
- }
709
-
710
- if (state.clients.hermes?.backupPath) {
711
- const res = restoreHermes(state.clients.hermes.filePath, state.clients.hermes.backupPath);
712
- details.push(res);
713
- if (res.success) restoredCount++;
714
- }
715
-
716
- if (state.clients.openClaw?.backupPath) {
717
- const res = restoreOpenClaw(state.clients.openClaw.filePath, state.clients.openClaw.backupPath);
718
- details.push(res);
719
- if (res.success) restoredCount++;
720
- }
728
+ const shouldRestore = (client: SyncClientName) => {
729
+ if (!options.targets || options.targets.length === 0) return true;
730
+ return options.targets.includes(client);
731
+ };
721
732
 
722
- if (state.clients.kilo?.backupPath) {
723
- const res = restoreKilo(state.clients.kilo.filePath, state.clients.kilo.backupPath);
724
- details.push(res);
725
- if (res.success) restoredCount++;
726
- }
733
+ const restoreClientsList: Array<{
734
+ id: SyncClientName;
735
+ stateKey: keyof SyncStateFile["clients"];
736
+ defaultPath: string;
737
+ stateRecord?: SyncRecord;
738
+ restoreFn: (filePath: string, backupPath?: string) => ClientSyncResult;
739
+ }> = [
740
+ { id: "claude-code", stateKey: "claudeCode", defaultPath: defaultPaths.claudeCode, stateRecord: state?.clients?.claudeCode, restoreFn: restoreClaudeCode },
741
+ { id: "codex", stateKey: "codex", defaultPath: defaultPaths.codex, stateRecord: state?.clients?.codex, restoreFn: restoreCodex },
742
+ { id: "opencode", stateKey: "openCode", defaultPath: defaultPaths.openCode, stateRecord: state?.clients?.openCode, restoreFn: restoreOpenCode },
743
+ { id: "omp", stateKey: "omp", defaultPath: defaultPaths.omp, stateRecord: state?.clients?.omp, restoreFn: restoreOmp },
744
+ { id: "hermes", stateKey: "hermes", defaultPath: defaultPaths.hermes, stateRecord: state?.clients?.hermes, restoreFn: restoreHermes },
745
+ { id: "openclaw", stateKey: "openClaw", defaultPath: defaultPaths.openClaw, stateRecord: state?.clients?.openClaw, restoreFn: restoreOpenClaw },
746
+ { id: "kilo", stateKey: "kilo", defaultPath: defaultPaths.kilo, stateRecord: state?.clients?.kilo, restoreFn: restoreKilo },
747
+ { id: "cline", stateKey: "cline", defaultPath: defaultPaths.cline, stateRecord: state?.clients?.cline, restoreFn: restoreCline },
748
+ { id: "zed", stateKey: "zed", defaultPath: defaultPaths.zed, stateRecord: state?.clients?.zed, restoreFn: restoreZed },
749
+ { id: "aider", stateKey: "aider", defaultPath: defaultPaths.aider, stateRecord: state?.clients?.aider, restoreFn: restoreAider },
750
+ ];
727
751
 
728
- if (state.clients.cline?.backupPath) {
729
- const res = restoreCline(state.clients.cline.filePath, state.clients.cline.backupPath);
730
- details.push(res);
731
- if (res.success) restoredCount++;
732
- }
752
+ for (const c of restoreClientsList) {
753
+ if (!shouldRestore(c.id)) continue;
754
+ const filePath = c.stateRecord?.filePath || c.defaultPath;
755
+ const backupPath = c.stateRecord?.backupPath;
733
756
 
734
- if (state.clients.zed?.backupPath) {
735
- const res = restoreZed(state.clients.zed.filePath, state.clients.zed.backupPath);
736
- details.push(res);
737
- if (res.success) restoredCount++;
738
- }
757
+ const canAttempt = options.stateFilePath
758
+ ? Boolean(c.stateRecord)
759
+ : Boolean(c.stateRecord || fs.existsSync(filePath));
739
760
 
740
- if (state.clients.aider?.backupPath) {
741
- const res = restoreAider(state.clients.aider.filePath, state.clients.aider.backupPath);
761
+ if (canAttempt) {
762
+ const res = c.restoreFn(filePath, backupPath);
742
763
  details.push(res);
743
- if (res.success) restoredCount++;
764
+ if (res.success) {
765
+ restoredCount++;
766
+ if (state?.clients) {
767
+ delete state.clients[c.stateKey];
768
+ delete (state.clients as any)[c.id];
769
+ }
770
+ }
744
771
  }
745
-
746
- // Remove state file after successful restoration
772
+ }
773
+ // Update or delete state file
774
+ if (stateFilePath && fs.existsSync(stateFilePath)) {
747
775
  try {
748
- fs.unlinkSync(stateFilePath);
749
- } catch {
750
- // Ignore
751
- }
752
- } catch (err) {
753
- console.error("Error reading sync state file:", err);
776
+ if (state && state.clients && Object.keys(state.clients).length > 0) {
777
+ fs.writeFileSync(stateFilePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
778
+ } else {
779
+ fs.unlinkSync(stateFilePath);
780
+ }
781
+ } catch {}
754
782
  }
755
783
 
756
784
  return { restoredCount, details };
package/src/sync/omp.ts CHANGED
@@ -99,13 +99,32 @@ export function syncOmp(options: SyncOptions): ClientSyncResult {
99
99
  }
100
100
 
101
101
  export function restoreOmp(filePath: string, backupPath?: string): ClientSyncResult {
102
- const restored = restoreFromBackup(filePath, backupPath);
102
+ const restoredFromBackup = restoreFromBackup(filePath, backupPath);
103
+
104
+ let manuallyCleaned = false;
105
+ if (fs.existsSync(filePath)) {
106
+ try {
107
+ let content = fs.readFileSync(filePath, "utf-8");
108
+ if (content.includes("qwenproxy:")) {
109
+ const regex = /^\s*qwenproxy:\s*\r?\n(?:^[ \t].*\r?\n?)*/m;
110
+ content = content.replace(regex, "");
111
+ fs.writeFileSync(filePath, content, "utf-8");
112
+ manuallyCleaned = true;
113
+ }
114
+ } catch {}
115
+ }
116
+
117
+ const success = restoredFromBackup || manuallyCleaned;
103
118
  return {
104
119
  client: "omp",
105
120
  filePath,
106
121
  backupPath,
107
- success: restored,
108
- action: restored ? "restored" : "failed",
109
- message: restored ? "Restored OMP models config from backup" : "Backup file not found",
122
+ success,
123
+ action: success ? "restored" : "failed",
124
+ message: success
125
+ ? restoredFromBackup
126
+ ? "Restored OMP models config from backup"
127
+ : "Removed QwenProxy configuration from OMP config"
128
+ : "Backup file not found",
110
129
  };
111
130
  }
@@ -206,13 +206,37 @@ export function syncOpenCode(options: SyncOptions): ClientSyncResult {
206
206
  }
207
207
 
208
208
  export function restoreOpenCode(filePath: string, backupPath?: string): ClientSyncResult {
209
- const restored = restoreFromBackup(filePath, backupPath);
209
+ const restoredFromBackup = restoreFromBackup(filePath, backupPath);
210
+
211
+ let manuallyCleaned = false;
212
+ if (fs.existsSync(filePath)) {
213
+ try {
214
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
215
+ if (data.provider && data.provider.qwenproxy) {
216
+ delete data.provider.qwenproxy;
217
+ if (data.model && data.model.includes("qwen")) {
218
+ delete data.model;
219
+ }
220
+ if (Object.keys(data.provider).length === 0) {
221
+ delete data.provider;
222
+ }
223
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
224
+ manuallyCleaned = true;
225
+ }
226
+ } catch {}
227
+ }
228
+
229
+ const success = restoredFromBackup || manuallyCleaned;
210
230
  return {
211
231
  client: "opencode",
212
232
  filePath,
213
233
  backupPath,
214
- success: restored,
215
- action: restored ? "restored" : "failed",
216
- message: restored ? "Restored OpenCode config from backup" : "Backup file not found",
234
+ success,
235
+ action: success ? "restored" : "failed",
236
+ message: success
237
+ ? restoredFromBackup
238
+ ? "Restored OpenCode config from backup"
239
+ : "Removed QwenProxy configuration from OpenCode config"
240
+ : "Backup file not found",
217
241
  };
218
242
  }
package/src/sync/utils.ts CHANGED
@@ -13,13 +13,42 @@ export function createTimestampBackup(filePath: string): string {
13
13
  return backupPath;
14
14
  }
15
15
 
16
+ export function findLatestBackup(filePath: string): string | undefined {
17
+ const dir = path.dirname(filePath);
18
+ const ext = path.extname(filePath);
19
+ const base = path.basename(filePath, ext);
20
+ if (!fs.existsSync(dir)) return undefined;
21
+
22
+ try {
23
+ const files = fs.readdirSync(dir);
24
+ const candidates = files
25
+ .filter((f) => f.startsWith(base) && f.includes("qwenproxy") && f.endsWith(".bak"))
26
+ .map((f) => path.join(dir, f))
27
+ .sort((a, b) => {
28
+ try {
29
+ return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs;
30
+ } catch {
31
+ return 0;
32
+ }
33
+ });
34
+
35
+ return candidates[0];
36
+ } catch {
37
+ return undefined;
38
+ }
39
+ }
40
+
16
41
  export function restoreFromBackup(filePath: string, backupPath?: string): boolean {
17
- if (!backupPath || !fs.existsSync(backupPath)) {
42
+ const targetBackup = (backupPath && fs.existsSync(backupPath))
43
+ ? backupPath
44
+ : findLatestBackup(filePath);
45
+
46
+ if (!targetBackup || !fs.existsSync(targetBackup)) {
18
47
  return false;
19
48
  }
20
- fs.copyFileSync(backupPath, filePath);
49
+ fs.copyFileSync(targetBackup, filePath);
21
50
  try {
22
- fs.unlinkSync(backupPath);
51
+ fs.unlinkSync(targetBackup);
23
52
  } catch {
24
53
  // Ignore cleanup error
25
54
  }
package/src/sync/zed.ts CHANGED
@@ -198,13 +198,37 @@ export function syncZed(options: SyncOptions): ClientSyncResult {
198
198
  }
199
199
 
200
200
  export function restoreZed(filePath: string, backupPath?: string): ClientSyncResult {
201
- const restored = restoreFromBackup(filePath, backupPath);
201
+ const restoredFromBackup = restoreFromBackup(filePath, backupPath);
202
+
203
+ let manuallyCleaned = false;
204
+ if (fs.existsSync(filePath)) {
205
+ try {
206
+ let content = fs.readFileSync(filePath, "utf-8");
207
+ if (content.includes("127.0.0.1:7936") || content.includes("qwen3.8-max")) {
208
+ const data = parseJsonWithComments(content);
209
+ if (data.language_models?.openai?.api_url?.includes("7936")) {
210
+ delete data.language_models.openai;
211
+ if (Object.keys(data.language_models).length === 0) {
212
+ delete data.language_models;
213
+ }
214
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
215
+ manuallyCleaned = true;
216
+ }
217
+ }
218
+ } catch {}
219
+ }
220
+
221
+ const success = restoredFromBackup || manuallyCleaned;
202
222
  return {
203
223
  client: "zed",
204
224
  filePath,
205
225
  backupPath,
206
- success: restored,
207
- action: restored ? "restored" : "failed",
208
- message: restored ? "Restored Zed settings from backup" : "Backup file not found",
226
+ success,
227
+ action: success ? "restored" : "failed",
228
+ message: success
229
+ ? restoredFromBackup
230
+ ? "Restored Zed settings from backup"
231
+ : "Removed QwenProxy configuration from Zed settings"
232
+ : "Backup file not found",
209
233
  };
210
234
  }
@@ -73,8 +73,8 @@ Exemplos:
73
73
  npm run sync zed # Sincroniza apenas o Zed Editor
74
74
  npm run sync aider # Sincroniza apenas o Aider
75
75
  npm run sync claude codex # Sincroniza múltiplos clientes específicos
76
- npm run sync -- --list # Lista status de detecção de todos os 10 clientes
77
- npm run sync -- --restore # Restaura as configurações originais (rollback)
76
+ npm run sync --list # Lista status de detecção de todos os 10 clientes (ou qpx sync --list)
77
+ npm run sync --restore # Restaura as configurações originais (ou qpx sync --restore)
78
78
 
79
79
  Opções:
80
80
  --client <nome> Nome do cliente (hermes, opencode, claude, openclaw, kilo, cline, omp, codex, zed, aider)
@@ -193,8 +193,7 @@ async function main() {
193
193
  console.log("--------------------------------------------------");
194
194
  console.log(`✨ ${count} cliente(s) sincronizado(s) com zero perda de outras configs/provedores!`);
195
195
  console.log("💡 Para desfazer e restaurar a qualquer momento:");
196
- console.log(" npm run sync -- --restore");
197
- console.log("==================================================\n");
196
+ console.log(" qpx sync --restore (ou npm run sync --restore)");
198
197
  }
199
198
 
200
199
  main().catch((err) => {
package/src/tui/app.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import { Screen, type KeyEvent } from "./screen.ts";
6
6
  import type { TuiView, ProxyStatusSnapshot } from "./types.ts";
7
7
  import { theme, glyphs, drawBox, stringWidth } from "./theme.ts";
8
- import { fetchProxyStatus } from "./proxy-client.ts";
8
+ import { fetchProxyStatus, fetchLiveModels, getCachedLiveModels } from "./proxy-client.ts";
9
9
  import { ServerManager } from "./server-manager.ts";
10
10
  import fs from "node:fs";
11
11
  import path from "node:path";
@@ -128,10 +128,28 @@ export class TuiApp {
128
128
  } catch {}
129
129
 
130
130
  // Background polling every 1s for live status updates and model catalog synchronization
131
+ let pollCount = 0;
131
132
  this.pollInterval = setInterval(async () => {
132
133
  if (!this.isRunning) return;
134
+ pollCount++;
133
135
  try {
134
136
  this.statusSnapshot = await fetchProxyStatus();
137
+
138
+ // Sync live models catalog when server is running:
139
+ // Try every 5s until first successful catalog load, then refresh periodically every 30s
140
+ const hasLiveModels = getCachedLiveModels() !== null;
141
+ const shouldSync = !hasLiveModels ? pollCount % 5 === 1 : pollCount % 30 === 1;
142
+ if (shouldSync) {
143
+ const models = await fetchLiveModels(hasLiveModels);
144
+ if (models.length > 0) {
145
+ for (const view of this.views) {
146
+ if ("refreshModels" in view && typeof (view as any).refreshModels === "function") {
147
+ void (view as any).refreshModels();
148
+ }
149
+ }
150
+ }
151
+ }
152
+
135
153
  this.requestRender();
136
154
  } catch {}
137
155
  }, 1000);
@@ -336,18 +336,17 @@ export async function streamChatCompletions(
336
336
  let cachedLiveModels: string[] | null = null;
337
337
  let liveModelsPromise: Promise<string[]> | null = null;
338
338
 
339
- const DEFAULT_FALLBACK_MODELS = [
340
- "qwen3.8-max",
341
- "qwen3.7-plus",
342
- "qwen3.7-max",
343
- "z-image-turbo",
344
- "qwen-image-3.0-pro",
345
- "qwen-image-3.0",
346
- "wan2.7-image-pro",
347
- "wan2.7-image",
348
- "wan3.0-video",
349
- "wan2.7-t2v",
350
- ];
339
+ export { DEFAULT_FALLBACK_MODELS } from "../core/model-alias.ts";
340
+ import { DEFAULT_FALLBACK_MODELS } from "../core/model-alias.ts";
341
+
342
+ export function getCachedLiveModels(): string[] | null {
343
+ return cachedLiveModels;
344
+ }
345
+
346
+ export function resetCachedLiveModelsForTests(): void {
347
+ cachedLiveModels = null;
348
+ liveModelsPromise = null;
349
+ }
351
350
 
352
351
  export async function fetchLiveModels(forceRefresh = false): Promise<string[]> {
353
352
  if (!forceRefresh && cachedLiveModels && cachedLiveModels.length > 0) {
@@ -365,7 +364,9 @@ export async function fetchLiveModels(forceRefresh = false): Promise<string[]> {
365
364
 
366
365
  liveModelsPromise = (async () => {
367
366
  const controller = new AbortController();
368
- const timeout = setTimeout(() => controller.abort(), 3000);
367
+ // Cold start with Playwright navigation and bx security token acquisition can take 5-12s
368
+ const timeoutMs = cachedLiveModels ? 4000 : 12000;
369
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
369
370
  try {
370
371
  const resp = await fetch(`http://${host}:${port}/v1/models`, {
371
372
  headers: { Authorization: `Bearer ${apiKey}` },
@@ -389,7 +390,9 @@ export async function fetchLiveModels(forceRefresh = false): Promise<string[]> {
389
390
  }
390
391
  }
391
392
  }
392
- } catch {} finally {
393
+ } catch {
394
+ // Don't poison cachedLiveModels on network error so subsequent polls can retry
395
+ } finally {
393
396
  clearTimeout(timeout);
394
397
  liveModelsPromise = null;
395
398
  }
package/src/tui/theme.ts CHANGED
@@ -39,6 +39,7 @@ export const theme = {
39
39
  borderInactive: (s: string) => `\x1b[38;2;53;53;61m${s}\x1b[39m`, // #35353d (Qwen Line Primary Border)
40
40
  bgSelected: (s: string) => `\x1b[48;2;45;35;85m\x1b[38;2;247;248;252m${s}\x1b[49m\x1b[39m`, // #2d2355 Deep Violet + #f7f8fc White
41
41
  bgHover: (s: string) => `\x1b[48;2;58;46;110m\x1b[38;2;247;248;252m${s}\x1b[49m\x1b[39m`, // #3a2e6e Violet Hover + #f7f8fc White
42
+ bgUserCard: (s: string) => `\x1b[48;2;38;40;58m${s}\x1b[49m`, // #26283a (Catppuccin Surface / Distinct Lighter User Message Card)
42
43
  bold: (s: string) => `\x1b[1m${s}\x1b[22m`,
43
44
  italic: (s: string) => `\x1b[3m${s}\x1b[23m`,
44
45
  dim: (s: string) => `\x1b[2m${s}\x1b[22m`,