qwenproxy-cli 1.0.31 → 1.0.32
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 +1 -1
- package/src/sync/index.ts +124 -80
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
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": {
|
package/src/sync/index.ts
CHANGED
|
@@ -151,6 +151,98 @@ export interface ClientDetectionStatus {
|
|
|
151
151
|
url?: string;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
export function isExecutableInPath(name: string): boolean {
|
|
155
|
+
const envPath = process.env.PATH || "";
|
|
156
|
+
const dirs = envPath.split(path.delimiter).filter(Boolean);
|
|
157
|
+
const extensions = process.platform === "win32"
|
|
158
|
+
? (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.toLowerCase())
|
|
159
|
+
: [""];
|
|
160
|
+
|
|
161
|
+
for (const dir of dirs) {
|
|
162
|
+
for (const ext of extensions) {
|
|
163
|
+
const fullPath = path.join(dir, name + ext);
|
|
164
|
+
try {
|
|
165
|
+
if (fs.existsSync(fullPath) && !fs.statSync(fullPath).isDirectory()) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function isVscodeExtensionInstalled(pattern: RegExp): boolean {
|
|
175
|
+
const home = os.homedir();
|
|
176
|
+
const candidateDirs = [
|
|
177
|
+
path.join(home, ".vscode", "extensions"),
|
|
178
|
+
path.join(home, ".vscode-insiders", "extensions"),
|
|
179
|
+
path.join(home, ".cursor", "extensions"),
|
|
180
|
+
path.join(home, ".windsurf", "extensions"),
|
|
181
|
+
];
|
|
182
|
+
for (const d of candidateDirs) {
|
|
183
|
+
if (fs.existsSync(d)) {
|
|
184
|
+
try {
|
|
185
|
+
const entries = fs.readdirSync(d);
|
|
186
|
+
if (entries.some((e) => pattern.test(e))) return true;
|
|
187
|
+
} catch {}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function isZedInstalled(): boolean {
|
|
194
|
+
if (isExecutableInPath("zed")) return true;
|
|
195
|
+
if (process.platform === "win32") {
|
|
196
|
+
const local = process.env.LOCALAPPDATA || "";
|
|
197
|
+
if (fs.existsSync(path.join(local, "Programs", "Zed"))) return true;
|
|
198
|
+
} else if (process.platform === "darwin") {
|
|
199
|
+
if (fs.existsSync("/Applications/Zed.app")) return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function isMatchingLocalHost(text: string, port = 7936): boolean {
|
|
205
|
+
if (!text) return false;
|
|
206
|
+
const configuredPort = config.server?.port || 7936;
|
|
207
|
+
const ports = new Set([String(port), String(configuredPort), "7936", "3000"]);
|
|
208
|
+
const isLocal = text.includes("127.0.0.1") || text.includes("localhost") || text.includes("0.0.0.0");
|
|
209
|
+
return isLocal && Array.from(ports).some((p) => text.includes(`:${p}`));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function isClientToolInstalled(
|
|
213
|
+
id: SyncClientName,
|
|
214
|
+
targetPath: string,
|
|
215
|
+
isCustomTestPath = false,
|
|
216
|
+
): boolean {
|
|
217
|
+
if (isCustomTestPath) {
|
|
218
|
+
return fs.existsSync(targetPath);
|
|
219
|
+
}
|
|
220
|
+
switch (id) {
|
|
221
|
+
case "hermes":
|
|
222
|
+
return isExecutableInPath("hermes");
|
|
223
|
+
case "openclaw":
|
|
224
|
+
return isExecutableInPath("openclaw") || isExecutableInPath("clawdbot") || isExecutableInPath("moltbot");
|
|
225
|
+
case "aider":
|
|
226
|
+
return isExecutableInPath("aider");
|
|
227
|
+
case "kilo":
|
|
228
|
+
return isExecutableInPath("kilo") || isVscodeExtensionInstalled(/kilo/i);
|
|
229
|
+
case "cline":
|
|
230
|
+
return isExecutableInPath("cline") || isVscodeExtensionInstalled(/cline|zoo-code|roo-cline/i);
|
|
231
|
+
case "zed":
|
|
232
|
+
return isZedInstalled();
|
|
233
|
+
case "claude-code":
|
|
234
|
+
return isExecutableInPath("claude") || fs.existsSync(targetPath);
|
|
235
|
+
case "codex":
|
|
236
|
+
return isExecutableInPath("codex") || fs.existsSync(targetPath);
|
|
237
|
+
case "opencode":
|
|
238
|
+
return isExecutableInPath("opencode") || fs.existsSync(targetPath);
|
|
239
|
+
case "omp":
|
|
240
|
+
return isExecutableInPath("omp") || fs.existsSync(targetPath);
|
|
241
|
+
default:
|
|
242
|
+
return fs.existsSync(targetPath);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
154
246
|
/**
|
|
155
247
|
* Inspects a client configuration file to determine whether the client is installed
|
|
156
248
|
* and whether it is actively configured to route to QwenProxy.
|
|
@@ -161,37 +253,26 @@ export function inspectClientSyncStatus(
|
|
|
161
253
|
port = 7936,
|
|
162
254
|
): ClientDetectionStatus {
|
|
163
255
|
const defaultPaths = getDefaultPaths();
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
? defaultPaths.codex
|
|
170
|
-
: id === "opencode"
|
|
171
|
-
? defaultPaths.openCode
|
|
172
|
-
: id === "omp"
|
|
173
|
-
? defaultPaths.omp
|
|
174
|
-
: id === "hermes"
|
|
175
|
-
? defaultPaths.hermes
|
|
176
|
-
: id === "openclaw"
|
|
177
|
-
? defaultPaths.openClaw
|
|
178
|
-
: id === "kilo"
|
|
179
|
-
? defaultPaths.kilo
|
|
180
|
-
: id === "cline"
|
|
181
|
-
? defaultPaths.cline
|
|
182
|
-
: id === "zed"
|
|
183
|
-
? defaultPaths.zed
|
|
184
|
-
: defaultPaths.aider);
|
|
256
|
+
const defaultTarget = (defaultPaths as Record<string, string>)[id] || "";
|
|
257
|
+
const isCustomTestPath = Boolean(
|
|
258
|
+
filePath && defaultTarget && path.resolve(filePath) !== path.resolve(defaultTarget),
|
|
259
|
+
);
|
|
260
|
+
const targetPath = filePath || defaultTarget;
|
|
185
261
|
|
|
186
262
|
if (!fs.existsSync(targetPath)) {
|
|
187
263
|
return { id, installed: false, synced: false };
|
|
188
264
|
}
|
|
189
265
|
|
|
266
|
+
const toolInstalled = isClientToolInstalled(id, targetPath, isCustomTestPath);
|
|
267
|
+
if (!toolInstalled) {
|
|
268
|
+
return { id, installed: false, synced: false };
|
|
269
|
+
}
|
|
270
|
+
|
|
190
271
|
try {
|
|
191
272
|
if (id === "cline") {
|
|
192
|
-
// Inspect SQLite DB
|
|
193
273
|
let isSynced = false;
|
|
194
274
|
let model: string | undefined;
|
|
275
|
+
let rowExists = false;
|
|
195
276
|
try {
|
|
196
277
|
const db = new Database(targetPath, { readonly: true });
|
|
197
278
|
const row = db
|
|
@@ -201,18 +282,19 @@ export function inspectClientSyncStatus(
|
|
|
201
282
|
.get() as { value: string } | undefined;
|
|
202
283
|
db.close();
|
|
203
284
|
if (row && row.value) {
|
|
285
|
+
rowExists = true;
|
|
204
286
|
const parsed = JSON.parse(row.value);
|
|
205
287
|
const url = parsed.openAiBaseUrl || "";
|
|
206
288
|
model = parsed.openAiModelId;
|
|
207
|
-
isSynced =
|
|
208
|
-
url &&
|
|
209
|
-
(url.includes(String(port)) ||
|
|
210
|
-
url.includes(`127.0.0.1:${port}`) ||
|
|
211
|
-
url.includes(`localhost:${port}`)),
|
|
212
|
-
);
|
|
289
|
+
isSynced = isMatchingLocalHost(url, port);
|
|
213
290
|
}
|
|
214
291
|
} catch {}
|
|
215
|
-
|
|
292
|
+
|
|
293
|
+
const isInstalled = isCustomTestPath
|
|
294
|
+
? true
|
|
295
|
+
: rowExists || isExecutableInPath("cline") || isVscodeExtensionInstalled(/cline|zoo-code|roo-cline/i);
|
|
296
|
+
|
|
297
|
+
return { id, installed: isInstalled, synced: isInstalled && isSynced, model };
|
|
216
298
|
}
|
|
217
299
|
|
|
218
300
|
const raw = fs.readFileSync(targetPath, "utf-8");
|
|
@@ -221,12 +303,8 @@ export function inspectClientSyncStatus(
|
|
|
221
303
|
const data = JSON.parse(raw);
|
|
222
304
|
const url = data?.env?.ANTHROPIC_BASE_URL || "";
|
|
223
305
|
const model = data?.env?.ANTHROPIC_MODEL || data?.model || "";
|
|
224
|
-
const isLocalHost =
|
|
225
|
-
url.includes(String(port)) ||
|
|
226
|
-
url.includes(`127.0.0.1:${port}`) ||
|
|
227
|
-
url.includes(`localhost:${port}`);
|
|
228
306
|
const isSynced =
|
|
229
|
-
|
|
307
|
+
isMatchingLocalHost(url, port) &&
|
|
230
308
|
(model.toLowerCase().includes("qwen") || Boolean(data?.env?.ANTHROPIC_AUTH_TOKEN));
|
|
231
309
|
return {
|
|
232
310
|
id,
|
|
@@ -244,13 +322,11 @@ export function inspectClientSyncStatus(
|
|
|
244
322
|
const model = modelMatch ? modelMatch[1] : undefined;
|
|
245
323
|
const urlMatch = raw.match(/\[model_providers\.qwenproxy\][\s\S]*?base_url\s*=\s*["']([^"']+)["']/);
|
|
246
324
|
const url = urlMatch ? urlMatch[1] : "";
|
|
247
|
-
const
|
|
248
|
-
url && (url.includes(String(port)) || url.includes(`127.0.0.1:${port}`) || url.includes(`localhost:${port}`)),
|
|
249
|
-
);
|
|
325
|
+
const isSynced = hasProvider && isProviderActive && isMatchingLocalHost(url, port);
|
|
250
326
|
return {
|
|
251
327
|
id,
|
|
252
328
|
installed: true,
|
|
253
|
-
synced:
|
|
329
|
+
synced: isSynced,
|
|
254
330
|
model,
|
|
255
331
|
};
|
|
256
332
|
}
|
|
@@ -261,20 +337,13 @@ export function inspectClientSyncStatus(
|
|
|
261
337
|
const data = JSON.parse(raw);
|
|
262
338
|
const provider = data?.provider?.qwenproxy;
|
|
263
339
|
const url = provider?.options?.baseURL || "";
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
url.includes(`127.0.0.1:${port}`) ||
|
|
267
|
-
url.includes(`localhost:${port}`);
|
|
268
|
-
isSynced = Boolean(provider && isLocalHost);
|
|
340
|
+
const isSynced = Boolean(provider && isMatchingLocalHost(url, port));
|
|
341
|
+
return { id, installed: true, synced: isSynced };
|
|
269
342
|
} catch {
|
|
270
343
|
const qwenBlockMatch = raw.match(/"qwenproxy"\s*:\s*\{[\s\S]*?"baseURL"\s*:\s*"([^"]+)"/);
|
|
271
344
|
const url = qwenBlockMatch ? qwenBlockMatch[1] : "";
|
|
272
|
-
isSynced = Boolean(
|
|
273
|
-
|
|
274
|
-
(url.includes(String(port)) ||
|
|
275
|
-
url.includes(`127.0.0.1:${port}`) ||
|
|
276
|
-
url.includes(`localhost:${port}`)),
|
|
277
|
-
);
|
|
345
|
+
const isSynced = Boolean(url && isMatchingLocalHost(url, port));
|
|
346
|
+
return { id, installed: true, synced: isSynced };
|
|
278
347
|
}
|
|
279
348
|
return {
|
|
280
349
|
id,
|
|
@@ -290,12 +359,7 @@ export function inspectClientSyncStatus(
|
|
|
290
359
|
if (ompMatch) {
|
|
291
360
|
const urlMatch = ompMatch[1].match(/baseUrl:\s*(\S+)/);
|
|
292
361
|
url = urlMatch ? urlMatch[1].replace(/['"]/g, "") : undefined;
|
|
293
|
-
isSynced = Boolean(
|
|
294
|
-
url &&
|
|
295
|
-
(url.includes(String(port)) ||
|
|
296
|
-
url.includes(`127.0.0.1:${port}`) ||
|
|
297
|
-
url.includes(`localhost:${port}`)),
|
|
298
|
-
);
|
|
362
|
+
isSynced = Boolean(url && isMatchingLocalHost(url, port));
|
|
299
363
|
}
|
|
300
364
|
return {
|
|
301
365
|
id,
|
|
@@ -306,47 +370,27 @@ export function inspectClientSyncStatus(
|
|
|
306
370
|
}
|
|
307
371
|
|
|
308
372
|
if (id === "hermes") {
|
|
309
|
-
const
|
|
310
|
-
raw.includes(String(port)) ||
|
|
311
|
-
raw.includes(`127.0.0.1:${port}`) ||
|
|
312
|
-
raw.includes(`localhost:${port}`);
|
|
313
|
-
const isSynced = isLocalHost && (raw.includes("qwenproxy") || raw.includes("qwen"));
|
|
373
|
+
const isSynced = isMatchingLocalHost(raw, port) && (raw.includes("qwenproxy") || raw.includes("qwen"));
|
|
314
374
|
return { id, installed: true, synced: isSynced };
|
|
315
375
|
}
|
|
316
376
|
|
|
317
377
|
if (id === "openclaw") {
|
|
318
|
-
const
|
|
319
|
-
raw.includes(String(port)) ||
|
|
320
|
-
raw.includes(`127.0.0.1:${port}`) ||
|
|
321
|
-
raw.includes(`localhost:${port}`);
|
|
322
|
-
const isSynced = isLocalHost && raw.includes("qwenproxy");
|
|
378
|
+
const isSynced = isMatchingLocalHost(raw, port) && raw.includes("qwenproxy");
|
|
323
379
|
return { id, installed: true, synced: isSynced };
|
|
324
380
|
}
|
|
325
381
|
|
|
326
382
|
if (id === "kilo") {
|
|
327
|
-
const
|
|
328
|
-
raw.includes(String(port)) ||
|
|
329
|
-
raw.includes(`127.0.0.1:${port}`) ||
|
|
330
|
-
raw.includes(`localhost:${port}`);
|
|
331
|
-
const isSynced = isLocalHost && raw.includes("qwenproxy");
|
|
383
|
+
const isSynced = isMatchingLocalHost(raw, port) && raw.includes("qwenproxy");
|
|
332
384
|
return { id, installed: true, synced: isSynced };
|
|
333
385
|
}
|
|
334
386
|
|
|
335
387
|
if (id === "zed") {
|
|
336
|
-
const
|
|
337
|
-
raw.includes(String(port)) ||
|
|
338
|
-
raw.includes(`127.0.0.1:${port}`) ||
|
|
339
|
-
raw.includes(`localhost:${port}`);
|
|
340
|
-
const isSynced = isLocalHost && raw.includes("QwenProxy");
|
|
388
|
+
const isSynced = isMatchingLocalHost(raw, port) && raw.includes("QwenProxy");
|
|
341
389
|
return { id, installed: true, synced: isSynced };
|
|
342
390
|
}
|
|
343
391
|
|
|
344
392
|
if (id === "aider") {
|
|
345
|
-
const
|
|
346
|
-
raw.includes(String(port)) ||
|
|
347
|
-
raw.includes(`127.0.0.1:${port}`) ||
|
|
348
|
-
raw.includes(`localhost:${port}`);
|
|
349
|
-
const isSynced = isLocalHost && raw.includes("qwen");
|
|
393
|
+
const isSynced = isMatchingLocalHost(raw, port) && raw.includes("qwen");
|
|
350
394
|
return { id, installed: true, synced: isSynced };
|
|
351
395
|
}
|
|
352
396
|
} catch {
|