cc-hub-cli 1.1.11 → 1.1.12
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/dist/index.js +381 -392
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -254,296 +254,6 @@ var NoOpDesktopApp = class {
|
|
|
254
254
|
import fs3 from "fs";
|
|
255
255
|
import path3 from "path";
|
|
256
256
|
import { randomUUID } from "crypto";
|
|
257
|
-
var ANTHROPIC_ALIASES = ["claude-sonnet-4-5", "claude-opus-4-7", "claude-haiku-4-5-20251001"];
|
|
258
|
-
function isAnthropicModel(model) {
|
|
259
|
-
const anthropicAliases = ["opus", "sonnet", "haiku", "best", "default", "opusplan", "opus[1m]", "sonnet[1m]"];
|
|
260
|
-
const lower = model.toLowerCase();
|
|
261
|
-
if (anthropicAliases.includes(lower)) return true;
|
|
262
|
-
if (lower.startsWith("claude-")) return true;
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
function toDesktopProfile(p) {
|
|
266
|
-
const models = p.models || (p.model ? [p.model] : []);
|
|
267
|
-
const isAnthropic = p.provider === "anthropic" || !p.provider && !p.url;
|
|
268
|
-
if (isAnthropic && !p.url) {
|
|
269
|
-
return {
|
|
270
|
-
inferenceProvider: "1p",
|
|
271
|
-
inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
const mappings = [];
|
|
275
|
-
const mappedModels = models.map((m, index) => {
|
|
276
|
-
if (isAnthropicModel(m)) return m;
|
|
277
|
-
const alias = ANTHROPIC_ALIASES[Math.min(index, ANTHROPIC_ALIASES.length - 1)];
|
|
278
|
-
mappings.push({ alias, actual: m });
|
|
279
|
-
return alias;
|
|
280
|
-
});
|
|
281
|
-
const result = {
|
|
282
|
-
inferenceProvider: "gateway",
|
|
283
|
-
inferenceGatewayBaseUrl: p.url || void 0,
|
|
284
|
-
inferenceGatewayApiKey: p.token || void 0,
|
|
285
|
-
inferenceGatewayAuthScheme: "bearer",
|
|
286
|
-
inferenceModels: mappedModels.map((m) => ({ name: m, supports1m: true }))
|
|
287
|
-
};
|
|
288
|
-
if (mappings.length > 0) {
|
|
289
|
-
result.inferenceModelMappings = mappings;
|
|
290
|
-
}
|
|
291
|
-
return result;
|
|
292
|
-
}
|
|
293
|
-
var DesktopProfileSyncer = class {
|
|
294
|
-
constructor(app) {
|
|
295
|
-
this.app = app;
|
|
296
|
-
}
|
|
297
|
-
app;
|
|
298
|
-
isSupported() {
|
|
299
|
-
return this.app.isInstalled();
|
|
300
|
-
}
|
|
301
|
-
sync(name, p) {
|
|
302
|
-
const configLib = this.app.getConfigLibrary();
|
|
303
|
-
debug(`profile-syncer: sync '${name}' to ${configLib || "(none)"}`);
|
|
304
|
-
if (!configLib) return;
|
|
305
|
-
if (!fs3.existsSync(configLib)) {
|
|
306
|
-
fs3.mkdirSync(configLib, { recursive: true });
|
|
307
|
-
}
|
|
308
|
-
const meta = this.readMeta();
|
|
309
|
-
const entries = meta.entries || [];
|
|
310
|
-
let id = p.desktopId;
|
|
311
|
-
if (!id) {
|
|
312
|
-
const existingByName = entries.find((e) => e.name === name);
|
|
313
|
-
if (existingByName) {
|
|
314
|
-
id = existingByName.id;
|
|
315
|
-
} else {
|
|
316
|
-
id = randomUUID();
|
|
317
|
-
}
|
|
318
|
-
p.desktopId = id;
|
|
319
|
-
}
|
|
320
|
-
const existingIndex = entries.findIndex((e) => e.id === id);
|
|
321
|
-
if (existingIndex !== -1) {
|
|
322
|
-
entries[existingIndex].name = name;
|
|
323
|
-
} else {
|
|
324
|
-
entries.push({ id, name });
|
|
325
|
-
}
|
|
326
|
-
meta.entries = entries;
|
|
327
|
-
this.writeMeta(meta);
|
|
328
|
-
this.writeProfile(id, configLib, toDesktopProfile(p));
|
|
329
|
-
debug(`profile-syncer: synced '${name}' id=${id}`);
|
|
330
|
-
}
|
|
331
|
-
remove(name, p) {
|
|
332
|
-
const configLib = this.app.getConfigLibrary();
|
|
333
|
-
debug(`profile-syncer: remove '${name}' id=${p.desktopId || "(none)"} from ${configLib || "(none)"}`);
|
|
334
|
-
if (!configLib || !p.desktopId) return;
|
|
335
|
-
const meta = this.readMeta();
|
|
336
|
-
if (meta.entries) {
|
|
337
|
-
meta.entries = meta.entries.filter((e) => e.id !== p.desktopId);
|
|
338
|
-
}
|
|
339
|
-
if (meta.appliedId === p.desktopId) {
|
|
340
|
-
delete meta.appliedId;
|
|
341
|
-
}
|
|
342
|
-
this.writeMeta(meta);
|
|
343
|
-
const filePath = path3.join(configLib, `${p.desktopId}.json`);
|
|
344
|
-
if (fs3.existsSync(filePath)) {
|
|
345
|
-
fs3.unlinkSync(filePath);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
setActive(p) {
|
|
349
|
-
const configLib = this.app.getConfigLibrary();
|
|
350
|
-
debug(`profile-syncer: setActive id=${p.desktopId || "(none)"} in ${configLib || "(none)"}`);
|
|
351
|
-
if (!configLib || !p.desktopId) return;
|
|
352
|
-
const meta = this.readMeta();
|
|
353
|
-
meta.appliedId = p.desktopId;
|
|
354
|
-
const entries = meta.entries || [];
|
|
355
|
-
if (!entries.some((e) => e.id === p.desktopId)) {
|
|
356
|
-
entries.push({ id: p.desktopId, name: "unknown" });
|
|
357
|
-
meta.entries = entries;
|
|
358
|
-
}
|
|
359
|
-
this.writeMeta(meta);
|
|
360
|
-
}
|
|
361
|
-
metaFile() {
|
|
362
|
-
const configLib = this.app.getConfigLibrary();
|
|
363
|
-
return configLib ? path3.join(configLib, "_meta.json") : void 0;
|
|
364
|
-
}
|
|
365
|
-
readMeta() {
|
|
366
|
-
const file = this.metaFile();
|
|
367
|
-
if (!file || !fs3.existsSync(file)) return {};
|
|
368
|
-
try {
|
|
369
|
-
return readJson(file);
|
|
370
|
-
} catch {
|
|
371
|
-
return {};
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
writeMeta(meta) {
|
|
375
|
-
const file = this.metaFile();
|
|
376
|
-
if (file) writeJson(file, meta);
|
|
377
|
-
}
|
|
378
|
-
writeProfile(id, configLib, data) {
|
|
379
|
-
writeJson(path3.join(configLib, `${id}.json`), data);
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
// src/platform/binary-resolver.ts
|
|
384
|
-
import { spawnSync } from "child_process";
|
|
385
|
-
var SystemBinaryResolver = class {
|
|
386
|
-
constructor(app) {
|
|
387
|
-
this.app = app;
|
|
388
|
-
}
|
|
389
|
-
app;
|
|
390
|
-
resolve() {
|
|
391
|
-
debug("binary-resolver: trying global 'claude' command");
|
|
392
|
-
try {
|
|
393
|
-
const result = spawnSync("claude", ["--version"], {
|
|
394
|
-
shell: process.platform === "win32",
|
|
395
|
-
encoding: "utf-8"
|
|
396
|
-
});
|
|
397
|
-
if (result.status === 0) {
|
|
398
|
-
debug("binary-resolver: found global 'claude'");
|
|
399
|
-
return "claude";
|
|
400
|
-
}
|
|
401
|
-
} catch {
|
|
402
|
-
}
|
|
403
|
-
debug("binary-resolver: trying desktop app binary");
|
|
404
|
-
const desktopBinary = this.app.findBinary();
|
|
405
|
-
if (desktopBinary) {
|
|
406
|
-
debug(`binary-resolver: found desktop binary at ${desktopBinary}`);
|
|
407
|
-
return desktopBinary;
|
|
408
|
-
}
|
|
409
|
-
throw new Error("Could not find Claude Code CLI. Install it globally or install the Claude Code desktop app.");
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
// src/platform/path-codec.ts
|
|
414
|
-
var UnixPathCodec = class {
|
|
415
|
-
encode(p) {
|
|
416
|
-
return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
|
|
417
|
-
}
|
|
418
|
-
decode(encoded) {
|
|
419
|
-
return encoded.replace(/--/g, "/.").replace(/-/g, "/");
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
|
-
var WindowsPathCodec = class {
|
|
423
|
-
encode(p) {
|
|
424
|
-
return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
|
|
425
|
-
}
|
|
426
|
-
decode(encoded) {
|
|
427
|
-
const decoded = encoded.replace(/--/g, "\\.").replace(/-/g, "\\");
|
|
428
|
-
if (/^[A-Za-z]\\/.test(decoded)) {
|
|
429
|
-
return decoded[0] + ":" + decoded.slice(1);
|
|
430
|
-
}
|
|
431
|
-
return decoded;
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
// src/platform/index.ts
|
|
436
|
-
function createDesktopApp() {
|
|
437
|
-
if (process.platform === "darwin") return new MacOSDesktopApp();
|
|
438
|
-
if (process.platform === "win32") return new WindowsDesktopApp();
|
|
439
|
-
return new NoOpDesktopApp();
|
|
440
|
-
}
|
|
441
|
-
function createProfileSyncer() {
|
|
442
|
-
return new DesktopProfileSyncer(createDesktopApp());
|
|
443
|
-
}
|
|
444
|
-
function createBinaryResolver() {
|
|
445
|
-
return new SystemBinaryResolver(createDesktopApp());
|
|
446
|
-
}
|
|
447
|
-
function createPathCodec() {
|
|
448
|
-
if (process.platform === "win32") return new WindowsPathCodec();
|
|
449
|
-
return new UnixPathCodec();
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// src/config.ts
|
|
453
|
-
var CLAUDE_DIR = process.env.CLAUDE_DIR || path4.join(os3.homedir(), ".claude");
|
|
454
|
-
var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE || path4.join(CLAUDE_DIR, "profiles.json");
|
|
455
|
-
var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path4.join(CLAUDE_DIR, "settings.json");
|
|
456
|
-
var CLAUDE_JSON = path4.join(os3.homedir(), ".claude.json");
|
|
457
|
-
var PROJECTS_DIR = path4.join(CLAUDE_DIR, "projects");
|
|
458
|
-
var SESSIONS_DIR = path4.join(CLAUDE_DIR, "sessions");
|
|
459
|
-
var desktopApp = createDesktopApp();
|
|
460
|
-
var DESKTOP_CONFIG_LIBRARY = desktopApp.getConfigLibrary() || "";
|
|
461
|
-
var DESKTOP_META_FILE = DESKTOP_CONFIG_LIBRARY ? path4.join(DESKTOP_CONFIG_LIBRARY, "_meta.json") : "";
|
|
462
|
-
var DESKTOP_SESSIONS_DIR = desktopApp.getSessionsDir() || "";
|
|
463
|
-
function isDesktopAppInstalled() {
|
|
464
|
-
return desktopApp.isInstalled();
|
|
465
|
-
}
|
|
466
|
-
function ensureFile(filePath, defaultContent) {
|
|
467
|
-
if (!fs4.existsSync(filePath)) {
|
|
468
|
-
debug(`ensureFile: creating ${filePath}`);
|
|
469
|
-
fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
470
|
-
fs4.writeFileSync(filePath, defaultContent, "utf-8");
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
function readJson(filePath) {
|
|
474
|
-
debug(`readJson: ${filePath}`);
|
|
475
|
-
return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
|
|
476
|
-
}
|
|
477
|
-
function writeJson(filePath, data) {
|
|
478
|
-
debug(`writeJson: ${filePath}`);
|
|
479
|
-
fs4.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
480
|
-
}
|
|
481
|
-
function ensureProfilesFile() {
|
|
482
|
-
ensureFile(PROFILES_FILE, '{"profiles":{}}\n');
|
|
483
|
-
}
|
|
484
|
-
function ensureSettingsFile() {
|
|
485
|
-
ensureFile(SETTINGS_FILE, "{}\n");
|
|
486
|
-
}
|
|
487
|
-
function fixJsonFile(filePath, fallback = {}) {
|
|
488
|
-
if (!fs4.existsSync(filePath)) return;
|
|
489
|
-
const backupPath = path4.join(CLAUDE_DIR, path4.basename(filePath) + ".backup");
|
|
490
|
-
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
491
|
-
try {
|
|
492
|
-
JSON.parse(raw);
|
|
493
|
-
fs4.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
494
|
-
fs4.copyFileSync(filePath, backupPath);
|
|
495
|
-
return;
|
|
496
|
-
} catch {
|
|
497
|
-
}
|
|
498
|
-
let text = raw.trim();
|
|
499
|
-
if (text.charCodeAt(0) === 65279) {
|
|
500
|
-
text = text.slice(1).trim();
|
|
501
|
-
}
|
|
502
|
-
text = text.replace(/,\s*$/, "");
|
|
503
|
-
text = text.replace(/,\s*([}\]])/g, "$1");
|
|
504
|
-
const lastBrace = Math.max(text.lastIndexOf("}"), text.lastIndexOf("]"));
|
|
505
|
-
if (lastBrace !== -1 && lastBrace < text.length - 1) {
|
|
506
|
-
text = text.slice(0, lastBrace + 1);
|
|
507
|
-
}
|
|
508
|
-
let openCurly = 0, openSquare = 0;
|
|
509
|
-
for (const ch of text) {
|
|
510
|
-
if (ch === "{") openCurly++;
|
|
511
|
-
else if (ch === "}") openCurly--;
|
|
512
|
-
else if (ch === "[") openSquare++;
|
|
513
|
-
else if (ch === "]") openSquare--;
|
|
514
|
-
}
|
|
515
|
-
if (openSquare > 0) text += "]".repeat(openSquare);
|
|
516
|
-
if (openCurly > 0) text += "}".repeat(openCurly);
|
|
517
|
-
try {
|
|
518
|
-
JSON.parse(text);
|
|
519
|
-
fs4.writeFileSync(filePath, text + "\n", "utf-8");
|
|
520
|
-
warn(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
|
|
521
|
-
console.error(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
|
|
522
|
-
} catch {
|
|
523
|
-
let restored = false;
|
|
524
|
-
if (fs4.existsSync(backupPath)) {
|
|
525
|
-
try {
|
|
526
|
-
const backupRaw = fs4.readFileSync(backupPath, "utf-8");
|
|
527
|
-
JSON.parse(backupRaw);
|
|
528
|
-
fs4.copyFileSync(backupPath, filePath);
|
|
529
|
-
restored = true;
|
|
530
|
-
warn(`Restored ${path4.basename(filePath)} from backup.`);
|
|
531
|
-
console.error(`Restored ${path4.basename(filePath)} from backup.`);
|
|
532
|
-
} catch {
|
|
533
|
-
error(`Backup ${path4.basename(backupPath)} is also corrupt; using fallback.`);
|
|
534
|
-
console.error(`Backup ${path4.basename(backupPath)} is also corrupt; using fallback.`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (!restored) {
|
|
538
|
-
writeJson(filePath, fallback);
|
|
539
|
-
error(`Could not fix ${path4.basename(filePath)}, no valid backup found, reset to default.`);
|
|
540
|
-
console.error(`Could not fix ${path4.basename(filePath)}, no valid backup found, reset to default.`);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// src/profiles/runner.ts
|
|
546
|
-
import { spawnSync as spawnSync2, spawn } from "child_process";
|
|
547
257
|
|
|
548
258
|
// src/provider/index.ts
|
|
549
259
|
import { Command } from "commander";
|
|
@@ -872,110 +582,396 @@ data: ${errText}
|
|
|
872
582
|
res.end(JSON.stringify({ error: { type: "internal_error", message: String(err) } }));
|
|
873
583
|
}
|
|
874
584
|
}
|
|
875
|
-
});
|
|
876
|
-
return new Promise((resolve, reject) => {
|
|
877
|
-
server.listen(0, "127.0.0.1", () => {
|
|
878
|
-
const addr = server.address();
|
|
879
|
-
const baseUrl = `http://127.0.0.1:${addr.port}`;
|
|
880
|
-
debug(`OpenAI proxy listening on ${baseUrl}`);
|
|
881
|
-
resolve({
|
|
882
|
-
baseUrl,
|
|
883
|
-
stop: () => {
|
|
884
|
-
debug("OpenAI proxy stopped");
|
|
885
|
-
server.close();
|
|
886
|
-
}
|
|
585
|
+
});
|
|
586
|
+
return new Promise((resolve, reject) => {
|
|
587
|
+
server.listen(0, "127.0.0.1", () => {
|
|
588
|
+
const addr = server.address();
|
|
589
|
+
const baseUrl = `http://127.0.0.1:${addr.port}`;
|
|
590
|
+
debug(`OpenAI proxy listening on ${baseUrl}`);
|
|
591
|
+
resolve({
|
|
592
|
+
baseUrl,
|
|
593
|
+
stop: () => {
|
|
594
|
+
debug("OpenAI proxy stopped");
|
|
595
|
+
server.close();
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
server.on("error", reject);
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
function readBody(req) {
|
|
603
|
+
return new Promise((resolve, reject) => {
|
|
604
|
+
const chunks = [];
|
|
605
|
+
req.on("data", (c) => chunks.push(c));
|
|
606
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
607
|
+
req.on("error", reject);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/provider/index.ts
|
|
612
|
+
var PROVIDERS = [
|
|
613
|
+
{
|
|
614
|
+
name: "anthropic",
|
|
615
|
+
description: "Default \u2014 sends Anthropic-format requests directly to the configured URL"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
name: "openai",
|
|
619
|
+
description: "Embedded proxy \u2014 translates Anthropic requests to OpenAI Chat Completions format"
|
|
620
|
+
}
|
|
621
|
+
];
|
|
622
|
+
var ANTHROPIC_ALIASES = ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5"];
|
|
623
|
+
function isAnthropicModel(model) {
|
|
624
|
+
const anthropicAliases = ["opus", "sonnet", "haiku", "best", "default", "opusplan", "opus[1m]", "sonnet[1m]"];
|
|
625
|
+
const lower = model.toLowerCase();
|
|
626
|
+
if (anthropicAliases.includes(lower)) return true;
|
|
627
|
+
if (lower.startsWith("claude-")) return true;
|
|
628
|
+
for (let index = 0; index < anthropicAliases.length; index++) {
|
|
629
|
+
const element = anthropicAliases[index];
|
|
630
|
+
if (lower.includes(element)) return true;
|
|
631
|
+
}
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
function collect(value, previous) {
|
|
635
|
+
return previous.concat([value]);
|
|
636
|
+
}
|
|
637
|
+
function providerCommand() {
|
|
638
|
+
const cmd = new Command("provider").description("Manage provider types");
|
|
639
|
+
cmd.command("list").description("List available provider types").action(safeAction(() => {
|
|
640
|
+
const fmt = (name, desc) => `${name.padEnd(12)} ${desc}`;
|
|
641
|
+
console.log(fmt("NAME", "DESCRIPTION"));
|
|
642
|
+
console.log(fmt("----", "-----------"));
|
|
643
|
+
for (const p of PROVIDERS) {
|
|
644
|
+
console.log(fmt(p.name, p.description));
|
|
645
|
+
}
|
|
646
|
+
}));
|
|
647
|
+
return cmd;
|
|
648
|
+
}
|
|
649
|
+
function proxyCommand() {
|
|
650
|
+
return new Command("proxy").description("Start a standalone OpenAI proxy for the desktop app").option("--profile <name>", "Use configuration from a saved profile").option("-u, --url <url>", "Upstream base URL (e.g., https://api.openai.com)").option("-k, --api-key <key>", "Upstream API key").option("-m, --model <model>", "Default model", "gpt-4o").option("--mapping <mapping>", "Model alias mapping (format: alias:actual, can be used multiple times)", collect, []).action(safeAction(async (opts) => {
|
|
651
|
+
let targetUrl = opts.url || "https://api.openai.com";
|
|
652
|
+
let apiKey = opts.apiKey || "";
|
|
653
|
+
let defaultModel = opts.model || "gpt-4o";
|
|
654
|
+
let models = [];
|
|
655
|
+
const modelMappings = {};
|
|
656
|
+
if (opts.profile) {
|
|
657
|
+
ensureProfilesFile();
|
|
658
|
+
const data = readJson(PROFILES_FILE);
|
|
659
|
+
const p = data.profiles[opts.profile];
|
|
660
|
+
if (!p) {
|
|
661
|
+
throw new Error(`Profile '${opts.profile}' not found.`);
|
|
662
|
+
}
|
|
663
|
+
targetUrl = p.url || targetUrl;
|
|
664
|
+
apiKey = p.token || apiKey;
|
|
665
|
+
models = p.models || (p.model ? [p.model] : []);
|
|
666
|
+
defaultModel = models[0] || defaultModel;
|
|
667
|
+
models.forEach((m, i) => {
|
|
668
|
+
if (!isAnthropicModel(m)) {
|
|
669
|
+
const alias = ANTHROPIC_ALIASES[Math.min(i, ANTHROPIC_ALIASES.length - 1)];
|
|
670
|
+
modelMappings[alias] = m;
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
} else {
|
|
674
|
+
for (const m of opts.mapping) {
|
|
675
|
+
const [alias, actual] = m.split(":");
|
|
676
|
+
if (alias && actual) {
|
|
677
|
+
modelMappings[alias] = actual;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
models = [defaultModel];
|
|
681
|
+
}
|
|
682
|
+
const { baseUrl, stop } = await startOpenAIProxy(targetUrl, apiKey, defaultModel, models, modelMappings);
|
|
683
|
+
console.log(`Proxy running at ${baseUrl}`);
|
|
684
|
+
console.log("Press Ctrl+C to stop");
|
|
685
|
+
process.on("SIGINT", () => {
|
|
686
|
+
stop();
|
|
687
|
+
process.exit(0);
|
|
688
|
+
});
|
|
689
|
+
}));
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/platform/profile-syncer.ts
|
|
693
|
+
function toDesktopProfile(p) {
|
|
694
|
+
const models = p.models || (p.model ? [p.model] : []);
|
|
695
|
+
const isAnthropic = p.provider === "anthropic" || !p.provider && !p.url;
|
|
696
|
+
if (isAnthropic && !p.url) {
|
|
697
|
+
return {
|
|
698
|
+
inferenceProvider: "1p",
|
|
699
|
+
inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const mappings = [];
|
|
703
|
+
const mappedModels = models.map((m, index) => {
|
|
704
|
+
if (isAnthropicModel(m)) return m;
|
|
705
|
+
const alias = ANTHROPIC_ALIASES[Math.min(index, ANTHROPIC_ALIASES.length - 1)];
|
|
706
|
+
mappings.push({ alias, actual: m });
|
|
707
|
+
return alias;
|
|
708
|
+
});
|
|
709
|
+
const result = {
|
|
710
|
+
inferenceProvider: "gateway",
|
|
711
|
+
inferenceGatewayBaseUrl: p.url || void 0,
|
|
712
|
+
inferenceGatewayApiKey: p.token || void 0,
|
|
713
|
+
inferenceGatewayAuthScheme: "bearer",
|
|
714
|
+
inferenceModels: mappedModels.map((m) => ({ name: m, supports1m: true }))
|
|
715
|
+
};
|
|
716
|
+
if (mappings.length > 0) {
|
|
717
|
+
result.inferenceModelMappings = mappings;
|
|
718
|
+
}
|
|
719
|
+
return result;
|
|
720
|
+
}
|
|
721
|
+
var DesktopProfileSyncer = class {
|
|
722
|
+
constructor(app) {
|
|
723
|
+
this.app = app;
|
|
724
|
+
}
|
|
725
|
+
app;
|
|
726
|
+
isSupported() {
|
|
727
|
+
return this.app.isInstalled();
|
|
728
|
+
}
|
|
729
|
+
sync(name, p) {
|
|
730
|
+
const configLib = this.app.getConfigLibrary();
|
|
731
|
+
debug(`profile-syncer: sync '${name}' to ${configLib || "(none)"}`);
|
|
732
|
+
if (!configLib) return;
|
|
733
|
+
if (!fs3.existsSync(configLib)) {
|
|
734
|
+
fs3.mkdirSync(configLib, { recursive: true });
|
|
735
|
+
}
|
|
736
|
+
const meta = this.readMeta();
|
|
737
|
+
const entries = meta.entries || [];
|
|
738
|
+
let id = p.desktopId;
|
|
739
|
+
if (!id) {
|
|
740
|
+
const existingByName = entries.find((e) => e.name === name);
|
|
741
|
+
if (existingByName) {
|
|
742
|
+
id = existingByName.id;
|
|
743
|
+
} else {
|
|
744
|
+
id = randomUUID();
|
|
745
|
+
}
|
|
746
|
+
p.desktopId = id;
|
|
747
|
+
}
|
|
748
|
+
const existingIndex = entries.findIndex((e) => e.id === id);
|
|
749
|
+
if (existingIndex !== -1) {
|
|
750
|
+
entries[existingIndex].name = name;
|
|
751
|
+
} else {
|
|
752
|
+
entries.push({ id, name });
|
|
753
|
+
}
|
|
754
|
+
meta.entries = entries;
|
|
755
|
+
this.writeMeta(meta);
|
|
756
|
+
this.writeProfile(id, configLib, toDesktopProfile(p));
|
|
757
|
+
debug(`profile-syncer: synced '${name}' id=${id}`);
|
|
758
|
+
}
|
|
759
|
+
remove(name, p) {
|
|
760
|
+
const configLib = this.app.getConfigLibrary();
|
|
761
|
+
debug(`profile-syncer: remove '${name}' id=${p.desktopId || "(none)"} from ${configLib || "(none)"}`);
|
|
762
|
+
if (!configLib || !p.desktopId) return;
|
|
763
|
+
const meta = this.readMeta();
|
|
764
|
+
if (meta.entries) {
|
|
765
|
+
meta.entries = meta.entries.filter((e) => e.id !== p.desktopId);
|
|
766
|
+
}
|
|
767
|
+
if (meta.appliedId === p.desktopId) {
|
|
768
|
+
delete meta.appliedId;
|
|
769
|
+
}
|
|
770
|
+
this.writeMeta(meta);
|
|
771
|
+
const filePath = path3.join(configLib, `${p.desktopId}.json`);
|
|
772
|
+
if (fs3.existsSync(filePath)) {
|
|
773
|
+
fs3.unlinkSync(filePath);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
setActive(p) {
|
|
777
|
+
const configLib = this.app.getConfigLibrary();
|
|
778
|
+
debug(`profile-syncer: setActive id=${p.desktopId || "(none)"} in ${configLib || "(none)"}`);
|
|
779
|
+
if (!configLib || !p.desktopId) return;
|
|
780
|
+
const meta = this.readMeta();
|
|
781
|
+
meta.appliedId = p.desktopId;
|
|
782
|
+
const entries = meta.entries || [];
|
|
783
|
+
if (!entries.some((e) => e.id === p.desktopId)) {
|
|
784
|
+
entries.push({ id: p.desktopId, name: "unknown" });
|
|
785
|
+
meta.entries = entries;
|
|
786
|
+
}
|
|
787
|
+
this.writeMeta(meta);
|
|
788
|
+
}
|
|
789
|
+
metaFile() {
|
|
790
|
+
const configLib = this.app.getConfigLibrary();
|
|
791
|
+
return configLib ? path3.join(configLib, "_meta.json") : void 0;
|
|
792
|
+
}
|
|
793
|
+
readMeta() {
|
|
794
|
+
const file = this.metaFile();
|
|
795
|
+
if (!file || !fs3.existsSync(file)) return {};
|
|
796
|
+
try {
|
|
797
|
+
return readJson(file);
|
|
798
|
+
} catch {
|
|
799
|
+
return {};
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
writeMeta(meta) {
|
|
803
|
+
const file = this.metaFile();
|
|
804
|
+
if (file) writeJson(file, meta);
|
|
805
|
+
}
|
|
806
|
+
writeProfile(id, configLib, data) {
|
|
807
|
+
writeJson(path3.join(configLib, `${id}.json`), data);
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// src/platform/binary-resolver.ts
|
|
812
|
+
import { spawnSync } from "child_process";
|
|
813
|
+
var SystemBinaryResolver = class {
|
|
814
|
+
constructor(app) {
|
|
815
|
+
this.app = app;
|
|
816
|
+
}
|
|
817
|
+
app;
|
|
818
|
+
resolve() {
|
|
819
|
+
debug("binary-resolver: trying global 'claude' command");
|
|
820
|
+
try {
|
|
821
|
+
const result = spawnSync("claude", ["--version"], {
|
|
822
|
+
shell: process.platform === "win32",
|
|
823
|
+
encoding: "utf-8"
|
|
887
824
|
});
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
825
|
+
if (result.status === 0) {
|
|
826
|
+
debug("binary-resolver: found global 'claude'");
|
|
827
|
+
return "claude";
|
|
828
|
+
}
|
|
829
|
+
} catch {
|
|
830
|
+
}
|
|
831
|
+
debug("binary-resolver: trying desktop app binary");
|
|
832
|
+
const desktopBinary = this.app.findBinary();
|
|
833
|
+
if (desktopBinary) {
|
|
834
|
+
debug(`binary-resolver: found desktop binary at ${desktopBinary}`);
|
|
835
|
+
return desktopBinary;
|
|
836
|
+
}
|
|
837
|
+
throw new Error("Could not find Claude Code CLI. Install it globally or install the Claude Code desktop app.");
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
|
|
841
|
+
// src/platform/path-codec.ts
|
|
842
|
+
var UnixPathCodec = class {
|
|
843
|
+
encode(p) {
|
|
844
|
+
return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
|
|
845
|
+
}
|
|
846
|
+
decode(encoded) {
|
|
847
|
+
return encoded.replace(/--/g, "/.").replace(/-/g, "/");
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
var WindowsPathCodec = class {
|
|
851
|
+
encode(p) {
|
|
852
|
+
return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
|
|
853
|
+
}
|
|
854
|
+
decode(encoded) {
|
|
855
|
+
const decoded = encoded.replace(/--/g, "\\.").replace(/-/g, "\\");
|
|
856
|
+
if (/^[A-Za-z]\\/.test(decoded)) {
|
|
857
|
+
return decoded[0] + ":" + decoded.slice(1);
|
|
858
|
+
}
|
|
859
|
+
return decoded;
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// src/platform/index.ts
|
|
864
|
+
function createDesktopApp() {
|
|
865
|
+
if (process.platform === "darwin") return new MacOSDesktopApp();
|
|
866
|
+
if (process.platform === "win32") return new WindowsDesktopApp();
|
|
867
|
+
return new NoOpDesktopApp();
|
|
891
868
|
}
|
|
892
|
-
function
|
|
893
|
-
return new
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
869
|
+
function createProfileSyncer() {
|
|
870
|
+
return new DesktopProfileSyncer(createDesktopApp());
|
|
871
|
+
}
|
|
872
|
+
function createBinaryResolver() {
|
|
873
|
+
return new SystemBinaryResolver(createDesktopApp());
|
|
874
|
+
}
|
|
875
|
+
function createPathCodec() {
|
|
876
|
+
if (process.platform === "win32") return new WindowsPathCodec();
|
|
877
|
+
return new UnixPathCodec();
|
|
899
878
|
}
|
|
900
879
|
|
|
901
|
-
// src/
|
|
902
|
-
var
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
880
|
+
// src/config.ts
|
|
881
|
+
var CLAUDE_DIR = process.env.CLAUDE_DIR || path4.join(os3.homedir(), ".claude");
|
|
882
|
+
var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE || path4.join(CLAUDE_DIR, "profiles.json");
|
|
883
|
+
var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path4.join(CLAUDE_DIR, "settings.json");
|
|
884
|
+
var CLAUDE_JSON = path4.join(os3.homedir(), ".claude.json");
|
|
885
|
+
var PROJECTS_DIR = path4.join(CLAUDE_DIR, "projects");
|
|
886
|
+
var SESSIONS_DIR = path4.join(CLAUDE_DIR, "sessions");
|
|
887
|
+
var desktopApp = createDesktopApp();
|
|
888
|
+
var DESKTOP_CONFIG_LIBRARY = desktopApp.getConfigLibrary() || "";
|
|
889
|
+
var DESKTOP_META_FILE = DESKTOP_CONFIG_LIBRARY ? path4.join(DESKTOP_CONFIG_LIBRARY, "_meta.json") : "";
|
|
890
|
+
var DESKTOP_SESSIONS_DIR = desktopApp.getSessionsDir() || "";
|
|
891
|
+
function isDesktopAppInstalled() {
|
|
892
|
+
return desktopApp.isInstalled();
|
|
893
|
+
}
|
|
894
|
+
function ensureFile(filePath, defaultContent) {
|
|
895
|
+
if (!fs4.existsSync(filePath)) {
|
|
896
|
+
debug(`ensureFile: creating ${filePath}`);
|
|
897
|
+
fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
898
|
+
fs4.writeFileSync(filePath, defaultContent, "utf-8");
|
|
910
899
|
}
|
|
911
|
-
];
|
|
912
|
-
var ANTHROPIC_ALIASES2 = ["claude-sonnet-4-5", "claude-opus-4-7", "claude-haiku-4-5-20251001"];
|
|
913
|
-
function isAnthropicModel2(model) {
|
|
914
|
-
const anthropicAliases = ["opus", "sonnet", "haiku", "best", "default", "opusplan", "opus[1m]", "sonnet[1m]"];
|
|
915
|
-
const lower = model.toLowerCase();
|
|
916
|
-
if (anthropicAliases.includes(lower)) return true;
|
|
917
|
-
if (lower.startsWith("claude-")) return true;
|
|
918
|
-
return false;
|
|
919
900
|
}
|
|
920
|
-
function
|
|
921
|
-
|
|
901
|
+
function readJson(filePath) {
|
|
902
|
+
debug(`readJson: ${filePath}`);
|
|
903
|
+
return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
|
|
922
904
|
}
|
|
923
|
-
function
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const fmt = (name, desc) => `${name.padEnd(12)} ${desc}`;
|
|
927
|
-
console.log(fmt("NAME", "DESCRIPTION"));
|
|
928
|
-
console.log(fmt("----", "-----------"));
|
|
929
|
-
for (const p of PROVIDERS) {
|
|
930
|
-
console.log(fmt(p.name, p.description));
|
|
931
|
-
}
|
|
932
|
-
}));
|
|
933
|
-
return cmd;
|
|
905
|
+
function writeJson(filePath, data) {
|
|
906
|
+
debug(`writeJson: ${filePath}`);
|
|
907
|
+
fs4.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
934
908
|
}
|
|
935
|
-
function
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
909
|
+
function ensureProfilesFile() {
|
|
910
|
+
ensureFile(PROFILES_FILE, '{"profiles":{}}\n');
|
|
911
|
+
}
|
|
912
|
+
function ensureSettingsFile() {
|
|
913
|
+
ensureFile(SETTINGS_FILE, "{}\n");
|
|
914
|
+
}
|
|
915
|
+
function fixJsonFile(filePath, fallback = {}) {
|
|
916
|
+
if (!fs4.existsSync(filePath)) return;
|
|
917
|
+
const backupPath = path4.join(CLAUDE_DIR, path4.basename(filePath) + ".backup");
|
|
918
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
919
|
+
try {
|
|
920
|
+
JSON.parse(raw);
|
|
921
|
+
fs4.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
922
|
+
fs4.copyFileSync(filePath, backupPath);
|
|
923
|
+
return;
|
|
924
|
+
} catch {
|
|
925
|
+
}
|
|
926
|
+
let text = raw.trim();
|
|
927
|
+
if (text.charCodeAt(0) === 65279) {
|
|
928
|
+
text = text.slice(1).trim();
|
|
929
|
+
}
|
|
930
|
+
text = text.replace(/,\s*$/, "");
|
|
931
|
+
text = text.replace(/,\s*([}\]])/g, "$1");
|
|
932
|
+
const lastBrace = Math.max(text.lastIndexOf("}"), text.lastIndexOf("]"));
|
|
933
|
+
if (lastBrace !== -1 && lastBrace < text.length - 1) {
|
|
934
|
+
text = text.slice(0, lastBrace + 1);
|
|
935
|
+
}
|
|
936
|
+
let openCurly = 0, openSquare = 0;
|
|
937
|
+
for (const ch of text) {
|
|
938
|
+
if (ch === "{") openCurly++;
|
|
939
|
+
else if (ch === "}") openCurly--;
|
|
940
|
+
else if (ch === "[") openSquare++;
|
|
941
|
+
else if (ch === "]") openSquare--;
|
|
942
|
+
}
|
|
943
|
+
if (openSquare > 0) text += "]".repeat(openSquare);
|
|
944
|
+
if (openCurly > 0) text += "}".repeat(openCurly);
|
|
945
|
+
try {
|
|
946
|
+
JSON.parse(text);
|
|
947
|
+
fs4.writeFileSync(filePath, text + "\n", "utf-8");
|
|
948
|
+
warn(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
|
|
949
|
+
console.error(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
|
|
950
|
+
} catch {
|
|
951
|
+
let restored = false;
|
|
952
|
+
if (fs4.existsSync(backupPath)) {
|
|
953
|
+
try {
|
|
954
|
+
const backupRaw = fs4.readFileSync(backupPath, "utf-8");
|
|
955
|
+
JSON.parse(backupRaw);
|
|
956
|
+
fs4.copyFileSync(backupPath, filePath);
|
|
957
|
+
restored = true;
|
|
958
|
+
warn(`Restored ${path4.basename(filePath)} from backup.`);
|
|
959
|
+
console.error(`Restored ${path4.basename(filePath)} from backup.`);
|
|
960
|
+
} catch {
|
|
961
|
+
error(`Backup ${path4.basename(backupPath)} is also corrupt; using fallback.`);
|
|
962
|
+
console.error(`Backup ${path4.basename(backupPath)} is also corrupt; using fallback.`);
|
|
965
963
|
}
|
|
966
|
-
models = [defaultModel];
|
|
967
964
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
});
|
|
975
|
-
}));
|
|
965
|
+
if (!restored) {
|
|
966
|
+
writeJson(filePath, fallback);
|
|
967
|
+
error(`Could not fix ${path4.basename(filePath)}, no valid backup found, reset to default.`);
|
|
968
|
+
console.error(`Could not fix ${path4.basename(filePath)}, no valid backup found, reset to default.`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
976
971
|
}
|
|
977
972
|
|
|
978
973
|
// src/profiles/runner.ts
|
|
974
|
+
import { spawnSync as spawnSync2, spawn } from "child_process";
|
|
979
975
|
var BUILT_IN_DEFAULT = "__builtin__";
|
|
980
976
|
function resolveClaudeBinary() {
|
|
981
977
|
return createBinaryResolver().resolve();
|
|
@@ -1100,10 +1096,10 @@ function maskToken(token) {
|
|
|
1100
1096
|
}
|
|
1101
1097
|
function formatModels(p) {
|
|
1102
1098
|
if (p.models && p.models.length > 0) {
|
|
1103
|
-
const nonAnthropicModels = p.models.filter((m) => !
|
|
1099
|
+
const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel(m));
|
|
1104
1100
|
const parts = [];
|
|
1105
1101
|
p.models.forEach((m, i) => {
|
|
1106
|
-
if (!
|
|
1102
|
+
if (!isAnthropicModel(m)) {
|
|
1107
1103
|
const aliasIndex = nonAnthropicModels.indexOf(m);
|
|
1108
1104
|
if (aliasIndex === 0) parts.push(`${m} (sonnet)`);
|
|
1109
1105
|
else if (aliasIndex === 1) parts.push(`${m} (opus)`);
|
|
@@ -1121,13 +1117,6 @@ function formatModels(p) {
|
|
|
1121
1117
|
}
|
|
1122
1118
|
return p.model || "(unset)";
|
|
1123
1119
|
}
|
|
1124
|
-
function isAnthropicModel3(model) {
|
|
1125
|
-
const anthropicAliases = ["opus", "sonnet", "haiku", "best", "default", "opusplan", "opus[1m]", "sonnet[1m]"];
|
|
1126
|
-
const lower = model.toLowerCase();
|
|
1127
|
-
if (anthropicAliases.includes(lower)) return true;
|
|
1128
|
-
if (lower.startsWith("claude-")) return true;
|
|
1129
|
-
return false;
|
|
1130
|
-
}
|
|
1131
1120
|
function collect2(value, previous) {
|
|
1132
1121
|
return previous.concat([value]);
|
|
1133
1122
|
}
|
|
@@ -1262,10 +1251,10 @@ function profileCommand() {
|
|
|
1262
1251
|
console.log(`Name: ${name}`);
|
|
1263
1252
|
console.log(`Model: ${p.model || "(unset)"}`);
|
|
1264
1253
|
if (p.models && p.models.length > 0) {
|
|
1265
|
-
const nonAnthropicModels = p.models.filter((m) => !
|
|
1254
|
+
const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel(m));
|
|
1266
1255
|
console.log(`Models:`);
|
|
1267
1256
|
for (const m of p.models) {
|
|
1268
|
-
if (!
|
|
1257
|
+
if (!isAnthropicModel(m)) {
|
|
1269
1258
|
const aliasIndex = nonAnthropicModels.indexOf(m);
|
|
1270
1259
|
let alias = "";
|
|
1271
1260
|
if (aliasIndex === 0) alias = " (sonnet)";
|