qwenproxy-cli 1.0.28 → 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.28",
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"
@@ -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
+ }