qwenproxy-cli 1.0.27 → 1.0.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -24,8 +24,8 @@
24
24
  "reset": "npx tsx src/reset-cooldowns.ts",
25
25
  "purge": "npx tsx src/delete-chats.ts",
26
26
  "test": "npm run test:mock && npm run test:live",
27
- "test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts",
28
- "test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts",
27
+ "test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts",
28
+ "test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chat-cleanup-auto.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/sync-top10-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/tui-settings.test.ts src/tests/personalization-deadline.test.ts",
29
29
  "test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
30
30
  "benchmark:proxy": "npx tsx src/benchmarks/proxy-baseline.ts",
31
31
  "typecheck": "tsc --noEmit"
@@ -101,7 +101,7 @@ export function clearAllAccountCooldowns(): number {
101
101
  count++;
102
102
  }
103
103
  }
104
- cooldowns.delete("global");
104
+ cooldowns.clear();
105
105
  return count;
106
106
  }
107
107
 
@@ -29,7 +29,7 @@ export function recordServerLog(level: "INFO" | "WARN" | "ERROR", text: string):
29
29
 
30
30
  const lines = clean.split(/\r?\n/);
31
31
  for (const raw of lines) {
32
- const line = raw.trim();
32
+ let line = raw.trim();
33
33
  if (!line) continue;
34
34
  // Filter out raw ASCII box frames and border rows
35
35
  if (/^[+\-=#]{5,}$/.test(line)) continue;
@@ -49,6 +49,12 @@ export function recordServerLog(level: "INFO" | "WARN" | "ERROR", text: string):
49
49
  continue;
50
50
  }
51
51
 
52
+ // Clean redundant leading level tags (e.g. "WARN [Qwen]" -> "[Qwen]")
53
+ // and normalize multi-space gaps after emojis
54
+ line = line
55
+ .replace(/^(?:\[?(?:INFO|WARN|WARNING|ERROR|ERR|DEBUG)\]?\s+)+/i, "")
56
+ .replace(/([\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2300}-\u{23FF}]\uFE0F?)\s{2,}/gu, "$1 ");
57
+ if (!line) continue;
52
58
  const entry: ServerLogMessage = { time, level, message: line };
53
59
  logHistory.push(entry);
54
60
  if (logHistory.length > MAX_LOG_HISTORY) {
@@ -116,7 +116,7 @@ export function logMediaDebug(message: string): void {
116
116
  }
117
117
 
118
118
  export function logMediaWarn(message: string): void {
119
- console.warn(`⚠️ ${message}`);
119
+ console.warn(`⚠️ ${message}`);
120
120
  }
121
121
 
122
122
  export function logMediaError(message: string): void {
@@ -658,7 +658,7 @@ async function requestCompletionsWithBrowserFallback(params: {
658
658
  headers,
659
659
  referrer: qwenUrl(`/c/${encodeURIComponent(chatId)}`),
660
660
  streaming,
661
- timeoutMs: Math.min(timeoutMs, IMAGE_TIMEOUT_MS),
661
+ timeoutMs: Math.min(timeoutMs, kind === "video" ? VIDEO_TIMEOUT_MS : IMAGE_TIMEOUT_MS),
662
662
  });
663
663
 
664
664
  if (browserResult.error) {
@@ -2676,7 +2676,10 @@ export async function withAccountPage<T>(
2676
2676
  return result;
2677
2677
  } catch (error) {
2678
2678
  const message = getErrorMessage(error);
2679
- if (message.includes("Playwright page operation timed out")) {
2679
+ if (
2680
+ recoverOnTimeout &&
2681
+ message.includes("Playwright page operation timed out")
2682
+ ) {
2680
2683
  console.warn(
2681
2684
  `⏱️ [Playwright] Resetting account context after a stuck page operation: ${accountId}`,
2682
2685
  );
@@ -0,0 +1,118 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { ClientSyncResult, SyncOptions } from "./types.ts";
4
+ import { createTimestampBackup, restoreFromBackup } from "./utils.ts";
5
+
6
+ function updateYamlKeyValue(content: string, key: string, value: string): string {
7
+ const regex = new RegExp(`^${key}:.*$`, "m");
8
+ const line = `${key}: ${value}`;
9
+ if (regex.test(content)) {
10
+ return content.replace(regex, line);
11
+ }
12
+ return content.trimEnd() + (content.length > 0 ? "\n" : "") + line + "\n";
13
+ }
14
+
15
+ export function syncAider(options: SyncOptions): ClientSyncResult {
16
+ const {
17
+ filePath,
18
+ apiKey,
19
+ baseUrl,
20
+ model = "qwen3.8-max",
21
+ reasoningEffort = "high",
22
+ modelSettingsPath,
23
+ } = options;
24
+
25
+ try {
26
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
27
+
28
+ let backupPath: string | undefined;
29
+ let confContent = "";
30
+
31
+ if (fs.existsSync(filePath)) {
32
+ backupPath = createTimestampBackup(filePath);
33
+ confContent = fs.readFileSync(filePath, "utf-8");
34
+ }
35
+
36
+ const fullModelName = model.startsWith("openai/") ? model : `openai/${model}`;
37
+ const weakModelName = "openai/qwen3.7-plus";
38
+
39
+ confContent = updateYamlKeyValue(confContent, "openai-api-base", baseUrl);
40
+ confContent = updateYamlKeyValue(confContent, "openai-api-key", apiKey);
41
+ confContent = updateYamlKeyValue(confContent, "model", fullModelName);
42
+ confContent = updateYamlKeyValue(confContent, "editor-model", fullModelName);
43
+ confContent = updateYamlKeyValue(confContent, "weak-model", weakModelName);
44
+
45
+ fs.writeFileSync(filePath, confContent.trimEnd() + "\n", "utf-8");
46
+
47
+ // Configure model settings YAML if path provided
48
+ let extraBackupPath: string | undefined;
49
+ const resolvedSettingsPath =
50
+ modelSettingsPath || path.join(path.dirname(filePath), ".aider.model.settings.yml");
51
+
52
+ if (resolvedSettingsPath) {
53
+ fs.mkdirSync(path.dirname(resolvedSettingsPath), { recursive: true });
54
+ let settingsContent = "";
55
+
56
+ if (fs.existsSync(resolvedSettingsPath)) {
57
+ extraBackupPath = createTimestampBackup(resolvedSettingsPath);
58
+ settingsContent = fs.readFileSync(resolvedSettingsPath, "utf-8");
59
+ }
60
+
61
+ const modelBlock = `- name: ${fullModelName}
62
+ extra_params:
63
+ extra_body:
64
+ reasoning_effort: ${reasoningEffort}
65
+ streaming: true
66
+ `;
67
+
68
+ const existingModelRegex = new RegExp(`- name: ${fullModelName}[\\s\\S]*?(?=(?:^-[ \\t]+name:|\\Z))`, "m");
69
+ if (existingModelRegex.test(settingsContent)) {
70
+ settingsContent = settingsContent.replace(existingModelRegex, modelBlock.trimEnd() + "\n");
71
+ } else {
72
+ settingsContent = settingsContent.trimEnd() + (settingsContent.length > 0 ? "\n\n" : "") + modelBlock;
73
+ }
74
+
75
+ fs.writeFileSync(resolvedSettingsPath, settingsContent.trimEnd() + "\n", "utf-8");
76
+ }
77
+
78
+ return {
79
+ client: "aider",
80
+ filePath,
81
+ backupPath,
82
+ extraBackupPath,
83
+ success: true,
84
+ action: backupPath ? "updated" : "created",
85
+ message: `Configured Aider with model ${fullModelName} and reasoning effort ${reasoningEffort}`,
86
+ };
87
+ } catch (err: any) {
88
+ return {
89
+ client: "aider",
90
+ filePath,
91
+ success: false,
92
+ action: "failed",
93
+ error: err?.message || String(err),
94
+ };
95
+ }
96
+ }
97
+
98
+ export function restoreAider(
99
+ filePath: string,
100
+ backupPath?: string,
101
+ modelSettingsPath?: string,
102
+ extraBackupPath?: string,
103
+ ): ClientSyncResult {
104
+ const mainRestored = restoreFromBackup(filePath, backupPath);
105
+ if (modelSettingsPath && extraBackupPath) {
106
+ restoreFromBackup(modelSettingsPath, extraBackupPath);
107
+ }
108
+
109
+ return {
110
+ client: "aider",
111
+ filePath,
112
+ backupPath,
113
+ extraBackupPath,
114
+ success: mainRestored,
115
+ action: mainRestored ? "restored" : "failed",
116
+ message: mainRestored ? "Restored Aider settings from backup" : "Backup file not found",
117
+ };
118
+ }
@@ -0,0 +1,110 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import Database from "better-sqlite3";
4
+ import type { ClientSyncResult, SyncOptions } from "./types.ts";
5
+ import { createTimestampBackup, restoreFromBackup } from "./utils.ts";
6
+
7
+ const TARGET_KEYS = [
8
+ "saoudrizwan.claude-dev",
9
+ "ZooCodeOrganization.zoo-code",
10
+ "RooVeterinaryInc.roo-cline",
11
+ ];
12
+
13
+ export function syncCline(options: SyncOptions): ClientSyncResult {
14
+ const { filePath, baseUrl, model = "qwen3.8-max", reasoningEffort = "high" } = options;
15
+ try {
16
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
17
+
18
+ let backupPath: string | undefined;
19
+ if (fs.existsSync(filePath)) {
20
+ backupPath = createTimestampBackup(filePath);
21
+ }
22
+
23
+ const db = new Database(filePath);
24
+ db.exec(`CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value TEXT)`);
25
+
26
+ const selectStmt = db.prepare(`SELECT value FROM ItemTable WHERE key = ?`);
27
+ const insertStmt = db.prepare(`INSERT INTO ItemTable (key, value) VALUES (?, ?)`);
28
+
29
+ const updateConfig = (existingVal?: string): string => {
30
+ let parsed: Record<string, any> = {};
31
+ if (existingVal) {
32
+ try {
33
+ parsed = JSON.parse(existingVal);
34
+ } catch {
35
+ parsed = {};
36
+ }
37
+ }
38
+
39
+ parsed.apiProvider = "openai";
40
+ parsed.openAiBaseUrl = baseUrl;
41
+ parsed.openAiModelId = model;
42
+ parsed.enableReasoningEffort = true;
43
+ parsed.reasoningEffort = reasoningEffort;
44
+ parsed.openAiCustomModelInfo = {
45
+ ...(parsed.openAiCustomModelInfo || {}),
46
+ contextWindow: 1000000,
47
+ maxTokens: -1,
48
+ supportsImages: true,
49
+ supportsPromptCache: false,
50
+ inputPrice: 0,
51
+ outputPrice: 0,
52
+ };
53
+
54
+ if (Array.isArray(parsed.listApiConfigMeta)) {
55
+ if (!parsed.listApiConfigMeta.some((p: any) => p?.name === "QwenProxy")) {
56
+ parsed.listApiConfigMeta.push({
57
+ name: "QwenProxy",
58
+ id: "qwenproxy-local",
59
+ apiProvider: "openai",
60
+ modelId: model,
61
+ });
62
+ }
63
+ parsed.currentApiConfigName = "QwenProxy";
64
+ }
65
+
66
+ return JSON.stringify(parsed);
67
+ };
68
+
69
+ // Update keys
70
+ for (const key of TARGET_KEYS) {
71
+ const row = selectStmt.get(key) as { value: string } | undefined;
72
+ // If row exists, update it. If it's saoudrizwan.claude-dev or ZooCode, ensure it is written.
73
+ if (row || key === "saoudrizwan.claude-dev" || key === "ZooCodeOrganization.zoo-code") {
74
+ const newVal = updateConfig(row?.value);
75
+ insertStmt.run(key, newVal);
76
+ }
77
+ }
78
+
79
+ db.close();
80
+
81
+ return {
82
+ client: "cline",
83
+ filePath,
84
+ backupPath,
85
+ success: true,
86
+ action: backupPath ? "updated" : "created",
87
+ message: `Configured Cline & Zoo Code in state.vscdb with model ${model} and reasoning effort ${reasoningEffort}`,
88
+ };
89
+ } catch (err: any) {
90
+ return {
91
+ client: "cline",
92
+ filePath,
93
+ success: false,
94
+ action: "failed",
95
+ error: err?.message || String(err),
96
+ };
97
+ }
98
+ }
99
+
100
+ export function restoreCline(filePath: string, backupPath?: string): ClientSyncResult {
101
+ const restored = restoreFromBackup(filePath, backupPath);
102
+ return {
103
+ client: "cline",
104
+ filePath,
105
+ backupPath,
106
+ success: restored,
107
+ action: restored ? "restored" : "failed",
108
+ message: restored ? "Restored Cline settings from backup" : "Backup file not found",
109
+ };
110
+ }
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { ClientSyncResult, SyncOptions } from "./types.ts";
4
+ import { createTimestampBackup, restoreFromBackup } from "./utils.ts";
5
+
6
+ function buildHermesModelBlock(
7
+ baseUrl: string,
8
+ apiKey: string,
9
+ model: string = "qwen3.8-max",
10
+ reasoningEffort: string = "high",
11
+ ): string {
12
+ return `model:
13
+ default: "${model}"
14
+ provider: "custom"
15
+ base_url: "${baseUrl}"
16
+ api_key: "${apiKey}"
17
+ reasoning_effort: "${reasoningEffort}"
18
+ reasoning_overrides:
19
+ "qwen3.8-max": "${reasoningEffort}"
20
+ "qwen3.7-plus": "medium"
21
+ `;
22
+ }
23
+
24
+ function buildHermesProviderBlock(baseUrl: string, apiKey: string): string {
25
+ return ` qwenproxy:
26
+ base_url: "${baseUrl}"
27
+ api_mode: chat_completions
28
+ api_key: "${apiKey}"
29
+ `;
30
+ }
31
+
32
+ export function syncHermes(options: SyncOptions): ClientSyncResult {
33
+ const { filePath, apiKey, baseUrl, model = "qwen3.8-max", reasoningEffort = "high" } = options;
34
+ try {
35
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
36
+
37
+ let backupPath: string | undefined;
38
+ let content = "";
39
+
40
+ if (fs.existsSync(filePath)) {
41
+ backupPath = createTimestampBackup(filePath);
42
+ content = fs.readFileSync(filePath, "utf-8");
43
+ }
44
+
45
+ const modelBlock = buildHermesModelBlock(baseUrl, apiKey, model, reasoningEffort);
46
+ const providerBlock = buildHermesProviderBlock(baseUrl, apiKey);
47
+
48
+ // Replace or insert model: block
49
+ const modelRegex = /^model:[\s\S]*?(?=(?:^[a-zA-Z0-9_-]+:|\Z))/m;
50
+ if (modelRegex.test(content)) {
51
+ content = content.replace(modelRegex, modelBlock);
52
+ } else {
53
+ content = modelBlock + "\n" + content.trimStart();
54
+ }
55
+
56
+ // Replace or insert qwenproxy under providers:
57
+ if (!content.includes("providers:")) {
58
+ content = content.trimEnd() + "\n\nproviders:\n" + providerBlock;
59
+ } else {
60
+ const existingQwenRegex = /^ {2}qwenproxy:[\s\S]*?(?=(?:^ {2}[a-zA-Z0-9_-]+:|^[a-zA-Z0-9_-]+:|\Z))/m;
61
+ if (existingQwenRegex.test(content)) {
62
+ content = content.replace(existingQwenRegex, providerBlock);
63
+ } else {
64
+ const providersMatch = content.match(/^providers:\s*$/m);
65
+ if (providersMatch && providersMatch.index !== undefined) {
66
+ const insertIdx = providersMatch.index + providersMatch[0].length;
67
+ content = content.slice(0, insertIdx) + "\n" + providerBlock + content.slice(insertIdx);
68
+ } else {
69
+ content = content.trimEnd() + "\n\nproviders:\n" + providerBlock;
70
+ }
71
+ }
72
+ }
73
+
74
+ fs.writeFileSync(filePath, content.trimEnd() + "\n", "utf-8");
75
+
76
+ return {
77
+ client: "hermes",
78
+ filePath,
79
+ backupPath,
80
+ success: true,
81
+ action: backupPath ? "updated" : "created",
82
+ message: `Configured Hermes Agent with model ${model} and reasoning effort ${reasoningEffort}`,
83
+ };
84
+ } catch (err: any) {
85
+ return {
86
+ client: "hermes",
87
+ filePath,
88
+ success: false,
89
+ action: "failed",
90
+ error: err?.message || String(err),
91
+ };
92
+ }
93
+ }
94
+
95
+ export function restoreHermes(filePath: string, backupPath?: string): ClientSyncResult {
96
+ const restored = restoreFromBackup(filePath, backupPath);
97
+ return {
98
+ client: "hermes",
99
+ filePath,
100
+ backupPath,
101
+ success: restored,
102
+ action: restored ? "restored" : "failed",
103
+ message: restored ? "Restored Hermes config from backup" : "Backup file not found",
104
+ };
105
+ }