cc-hub-cli 1.1.11 → 1.1.13

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.
Files changed (2) hide show
  1. package/dist/index.js +393 -403
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command7 } from "commander";
4
+ import { Command as Command8 } from "commander";
5
5
  import { createRequire } from "module";
6
6
 
7
7
  // src/profiles/commands.ts
@@ -254,303 +254,10 @@ 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";
550
260
 
551
- // src/provider/server.ts
552
- import http from "http";
553
-
554
261
  // src/provider/transform.ts
555
262
  function sanitizeToolId(id) {
556
263
  let sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_");
@@ -776,6 +483,7 @@ data: ${JSON.stringify(data)}
776
483
  }
777
484
 
778
485
  // src/provider/server.ts
486
+ import http from "http";
779
487
  async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMappings = {}) {
780
488
  const base = targetUrl.replace(/\/+$/, "");
781
489
  const server = http.createServer(async (req, res) => {
@@ -872,110 +580,351 @@ data: ${errText}
872
580
  res.end(JSON.stringify({ error: { type: "internal_error", message: String(err) } }));
873
581
  }
874
582
  }
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
- }
887
- });
888
- });
889
- server.on("error", reject);
890
- });
583
+ });
584
+ return new Promise((resolve, reject) => {
585
+ server.listen(0, "127.0.0.1", () => {
586
+ const addr = server.address();
587
+ const baseUrl = `http://127.0.0.1:${addr.port}`;
588
+ debug(`OpenAI proxy listening on ${baseUrl}`);
589
+ resolve({
590
+ baseUrl,
591
+ stop: () => {
592
+ debug("OpenAI proxy stopped");
593
+ server.close();
594
+ }
595
+ });
596
+ });
597
+ server.on("error", reject);
598
+ });
599
+ }
600
+ function readBody(req) {
601
+ return new Promise((resolve, reject) => {
602
+ const chunks = [];
603
+ req.on("data", (c) => chunks.push(c));
604
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
605
+ req.on("error", reject);
606
+ });
607
+ }
608
+
609
+ // src/provider/index.ts
610
+ var PROVIDERS = [
611
+ {
612
+ name: "anthropic",
613
+ description: "Default \u2014 sends Anthropic-format requests directly to the configured URL"
614
+ },
615
+ {
616
+ name: "openai",
617
+ description: "Embedded proxy \u2014 translates Anthropic requests to OpenAI Chat Completions format"
618
+ }
619
+ ];
620
+ var ANTHROPIC_ALIASES = ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5"];
621
+ function isAnthropicModel(model) {
622
+ const anthropicAliases = ["opus", "sonnet", "haiku", "best", "default", "opusplan", "opus[1m]", "sonnet[1m]"];
623
+ const lower = model.toLowerCase();
624
+ if (anthropicAliases.includes(lower)) return true;
625
+ if (lower.startsWith("claude-")) return true;
626
+ for (let index = 0; index < anthropicAliases.length; index++) {
627
+ const element = anthropicAliases[index];
628
+ if (lower.includes(element)) return true;
629
+ }
630
+ return false;
631
+ }
632
+ function providerCommand() {
633
+ const cmd = new Command("provider").description("Manage provider types");
634
+ cmd.command("list").description("List available provider types").action(safeAction(() => {
635
+ const fmt = (name, desc) => `${name.padEnd(12)} ${desc}`;
636
+ console.log(fmt("NAME", "DESCRIPTION"));
637
+ console.log(fmt("----", "-----------"));
638
+ for (const p of PROVIDERS) {
639
+ console.log(fmt(p.name, p.description));
640
+ }
641
+ }));
642
+ return cmd;
643
+ }
644
+
645
+ // src/platform/profile-syncer.ts
646
+ function toDesktopProfile(p) {
647
+ const models = p.models || (p.model ? [p.model] : []);
648
+ const isAnthropic = p.provider === "anthropic" || !p.provider && !p.url;
649
+ if (isAnthropic && !p.url) {
650
+ return {
651
+ inferenceProvider: "1p",
652
+ inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
653
+ };
654
+ }
655
+ const mappings = [];
656
+ const mappedModels = models.map((m, index) => {
657
+ if (isAnthropicModel(m)) return m;
658
+ const alias = ANTHROPIC_ALIASES[Math.min(index, ANTHROPIC_ALIASES.length - 1)];
659
+ mappings.push({ alias, actual: m });
660
+ return alias;
661
+ });
662
+ const result = {
663
+ inferenceProvider: "gateway",
664
+ inferenceGatewayBaseUrl: p.url || void 0,
665
+ inferenceGatewayApiKey: p.token || void 0,
666
+ inferenceGatewayAuthScheme: "bearer",
667
+ inferenceModels: mappedModels.map((m) => ({ name: m, supports1m: true }))
668
+ };
669
+ if (mappings.length > 0) {
670
+ result.inferenceModelMappings = mappings;
671
+ }
672
+ return result;
673
+ }
674
+ var DesktopProfileSyncer = class {
675
+ constructor(app) {
676
+ this.app = app;
677
+ }
678
+ app;
679
+ isSupported() {
680
+ return this.app.isInstalled();
681
+ }
682
+ sync(name, p) {
683
+ const configLib = this.app.getConfigLibrary();
684
+ debug(`profile-syncer: sync '${name}' to ${configLib || "(none)"}`);
685
+ if (!configLib) return;
686
+ if (!fs3.existsSync(configLib)) {
687
+ fs3.mkdirSync(configLib, { recursive: true });
688
+ }
689
+ const meta = this.readMeta();
690
+ const entries = meta.entries || [];
691
+ let id = p.desktopId;
692
+ if (!id) {
693
+ const existingByName = entries.find((e) => e.name === name);
694
+ if (existingByName) {
695
+ id = existingByName.id;
696
+ } else {
697
+ id = randomUUID();
698
+ }
699
+ p.desktopId = id;
700
+ }
701
+ const existingIndex = entries.findIndex((e) => e.id === id);
702
+ if (existingIndex !== -1) {
703
+ entries[existingIndex].name = name;
704
+ } else {
705
+ entries.push({ id, name });
706
+ }
707
+ meta.entries = entries;
708
+ this.writeMeta(meta);
709
+ this.writeProfile(id, configLib, toDesktopProfile(p));
710
+ debug(`profile-syncer: synced '${name}' id=${id}`);
711
+ }
712
+ remove(name, p) {
713
+ const configLib = this.app.getConfigLibrary();
714
+ debug(`profile-syncer: remove '${name}' id=${p.desktopId || "(none)"} from ${configLib || "(none)"}`);
715
+ if (!configLib || !p.desktopId) return;
716
+ const meta = this.readMeta();
717
+ if (meta.entries) {
718
+ meta.entries = meta.entries.filter((e) => e.id !== p.desktopId);
719
+ }
720
+ if (meta.appliedId === p.desktopId) {
721
+ delete meta.appliedId;
722
+ }
723
+ this.writeMeta(meta);
724
+ const filePath = path3.join(configLib, `${p.desktopId}.json`);
725
+ if (fs3.existsSync(filePath)) {
726
+ fs3.unlinkSync(filePath);
727
+ }
728
+ }
729
+ setActive(p) {
730
+ const configLib = this.app.getConfigLibrary();
731
+ debug(`profile-syncer: setActive id=${p.desktopId || "(none)"} in ${configLib || "(none)"}`);
732
+ if (!configLib || !p.desktopId) return;
733
+ const meta = this.readMeta();
734
+ meta.appliedId = p.desktopId;
735
+ const entries = meta.entries || [];
736
+ if (!entries.some((e) => e.id === p.desktopId)) {
737
+ entries.push({ id: p.desktopId, name: "unknown" });
738
+ meta.entries = entries;
739
+ }
740
+ this.writeMeta(meta);
741
+ }
742
+ metaFile() {
743
+ const configLib = this.app.getConfigLibrary();
744
+ return configLib ? path3.join(configLib, "_meta.json") : void 0;
745
+ }
746
+ readMeta() {
747
+ const file = this.metaFile();
748
+ if (!file || !fs3.existsSync(file)) return {};
749
+ try {
750
+ return readJson(file);
751
+ } catch {
752
+ return {};
753
+ }
754
+ }
755
+ writeMeta(meta) {
756
+ const file = this.metaFile();
757
+ if (file) writeJson(file, meta);
758
+ }
759
+ writeProfile(id, configLib, data) {
760
+ writeJson(path3.join(configLib, `${id}.json`), data);
761
+ }
762
+ };
763
+
764
+ // src/platform/binary-resolver.ts
765
+ import { spawnSync } from "child_process";
766
+ var SystemBinaryResolver = class {
767
+ constructor(app) {
768
+ this.app = app;
769
+ }
770
+ app;
771
+ resolve() {
772
+ debug("binary-resolver: trying global 'claude' command");
773
+ try {
774
+ const result = spawnSync("claude", ["--version"], {
775
+ shell: process.platform === "win32",
776
+ encoding: "utf-8"
777
+ });
778
+ if (result.status === 0) {
779
+ debug("binary-resolver: found global 'claude'");
780
+ return "claude";
781
+ }
782
+ } catch {
783
+ }
784
+ debug("binary-resolver: trying desktop app binary");
785
+ const desktopBinary = this.app.findBinary();
786
+ if (desktopBinary) {
787
+ debug(`binary-resolver: found desktop binary at ${desktopBinary}`);
788
+ return desktopBinary;
789
+ }
790
+ throw new Error("Could not find Claude Code CLI. Install it globally or install the Claude Code desktop app.");
791
+ }
792
+ };
793
+
794
+ // src/platform/path-codec.ts
795
+ var UnixPathCodec = class {
796
+ encode(p) {
797
+ return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
798
+ }
799
+ decode(encoded) {
800
+ return encoded.replace(/--/g, "/.").replace(/-/g, "/");
801
+ }
802
+ };
803
+ var WindowsPathCodec = class {
804
+ encode(p) {
805
+ return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
806
+ }
807
+ decode(encoded) {
808
+ const decoded = encoded.replace(/--/g, "\\.").replace(/-/g, "\\");
809
+ if (/^[A-Za-z]\\/.test(decoded)) {
810
+ return decoded[0] + ":" + decoded.slice(1);
811
+ }
812
+ return decoded;
813
+ }
814
+ };
815
+
816
+ // src/platform/index.ts
817
+ function createDesktopApp() {
818
+ if (process.platform === "darwin") return new MacOSDesktopApp();
819
+ if (process.platform === "win32") return new WindowsDesktopApp();
820
+ return new NoOpDesktopApp();
891
821
  }
892
- function readBody(req) {
893
- return new Promise((resolve, reject) => {
894
- const chunks = [];
895
- req.on("data", (c) => chunks.push(c));
896
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
897
- req.on("error", reject);
898
- });
822
+ function createProfileSyncer() {
823
+ return new DesktopProfileSyncer(createDesktopApp());
824
+ }
825
+ function createBinaryResolver() {
826
+ return new SystemBinaryResolver(createDesktopApp());
827
+ }
828
+ function createPathCodec() {
829
+ if (process.platform === "win32") return new WindowsPathCodec();
830
+ return new UnixPathCodec();
899
831
  }
900
832
 
901
- // src/provider/index.ts
902
- var PROVIDERS = [
903
- {
904
- name: "anthropic",
905
- description: "Default \u2014 sends Anthropic-format requests directly to the configured URL"
906
- },
907
- {
908
- name: "openai",
909
- description: "Embedded proxy \u2014 translates Anthropic requests to OpenAI Chat Completions format"
833
+ // src/config.ts
834
+ var CLAUDE_DIR = process.env.CLAUDE_DIR || path4.join(os3.homedir(), ".claude");
835
+ var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE || path4.join(CLAUDE_DIR, "profiles.json");
836
+ var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path4.join(CLAUDE_DIR, "settings.json");
837
+ var CLAUDE_JSON = path4.join(os3.homedir(), ".claude.json");
838
+ var PROJECTS_DIR = path4.join(CLAUDE_DIR, "projects");
839
+ var SESSIONS_DIR = path4.join(CLAUDE_DIR, "sessions");
840
+ var desktopApp = createDesktopApp();
841
+ var DESKTOP_CONFIG_LIBRARY = desktopApp.getConfigLibrary() || "";
842
+ var DESKTOP_META_FILE = DESKTOP_CONFIG_LIBRARY ? path4.join(DESKTOP_CONFIG_LIBRARY, "_meta.json") : "";
843
+ var DESKTOP_SESSIONS_DIR = desktopApp.getSessionsDir() || "";
844
+ function isDesktopAppInstalled() {
845
+ return desktopApp.isInstalled();
846
+ }
847
+ function ensureFile(filePath, defaultContent) {
848
+ if (!fs4.existsSync(filePath)) {
849
+ debug(`ensureFile: creating ${filePath}`);
850
+ fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
851
+ fs4.writeFileSync(filePath, defaultContent, "utf-8");
910
852
  }
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
853
  }
920
- function collect(value, previous) {
921
- return previous.concat([value]);
854
+ function readJson(filePath) {
855
+ debug(`readJson: ${filePath}`);
856
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
922
857
  }
923
- function providerCommand() {
924
- const cmd = new Command("provider").description("Manage provider types");
925
- cmd.command("list").description("List available provider types").action(safeAction(() => {
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;
858
+ function writeJson(filePath, data) {
859
+ debug(`writeJson: ${filePath}`);
860
+ fs4.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
934
861
  }
935
- function proxyCommand() {
936
- 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) => {
937
- let targetUrl = opts.url || "https://api.openai.com";
938
- let apiKey = opts.apiKey || "";
939
- let defaultModel = opts.model || "gpt-4o";
940
- let models = [];
941
- const modelMappings = {};
942
- if (opts.profile) {
943
- ensureProfilesFile();
944
- const data = readJson(PROFILES_FILE);
945
- const p = data.profiles[opts.profile];
946
- if (!p) {
947
- throw new Error(`Profile '${opts.profile}' not found.`);
948
- }
949
- targetUrl = p.url || targetUrl;
950
- apiKey = p.token || apiKey;
951
- models = p.models || (p.model ? [p.model] : []);
952
- defaultModel = models[0] || defaultModel;
953
- models.forEach((m, i) => {
954
- if (!isAnthropicModel2(m)) {
955
- const alias = ANTHROPIC_ALIASES2[Math.min(i, ANTHROPIC_ALIASES2.length - 1)];
956
- modelMappings[alias] = m;
957
- }
958
- });
959
- } else {
960
- for (const m of opts.mapping) {
961
- const [alias, actual] = m.split(":");
962
- if (alias && actual) {
963
- modelMappings[alias] = actual;
964
- }
862
+ function ensureProfilesFile() {
863
+ ensureFile(PROFILES_FILE, '{"profiles":{}}\n');
864
+ }
865
+ function ensureSettingsFile() {
866
+ ensureFile(SETTINGS_FILE, "{}\n");
867
+ }
868
+ function fixJsonFile(filePath, fallback = {}) {
869
+ if (!fs4.existsSync(filePath)) return;
870
+ const backupPath = path4.join(CLAUDE_DIR, path4.basename(filePath) + ".backup");
871
+ const raw = fs4.readFileSync(filePath, "utf-8");
872
+ try {
873
+ JSON.parse(raw);
874
+ fs4.mkdirSync(CLAUDE_DIR, { recursive: true });
875
+ fs4.copyFileSync(filePath, backupPath);
876
+ return;
877
+ } catch {
878
+ }
879
+ let text = raw.trim();
880
+ if (text.charCodeAt(0) === 65279) {
881
+ text = text.slice(1).trim();
882
+ }
883
+ text = text.replace(/,\s*$/, "");
884
+ text = text.replace(/,\s*([}\]])/g, "$1");
885
+ const lastBrace = Math.max(text.lastIndexOf("}"), text.lastIndexOf("]"));
886
+ if (lastBrace !== -1 && lastBrace < text.length - 1) {
887
+ text = text.slice(0, lastBrace + 1);
888
+ }
889
+ let openCurly = 0, openSquare = 0;
890
+ for (const ch of text) {
891
+ if (ch === "{") openCurly++;
892
+ else if (ch === "}") openCurly--;
893
+ else if (ch === "[") openSquare++;
894
+ else if (ch === "]") openSquare--;
895
+ }
896
+ if (openSquare > 0) text += "]".repeat(openSquare);
897
+ if (openCurly > 0) text += "}".repeat(openCurly);
898
+ try {
899
+ JSON.parse(text);
900
+ fs4.writeFileSync(filePath, text + "\n", "utf-8");
901
+ warn(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
902
+ console.error(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
903
+ } catch {
904
+ let restored = false;
905
+ if (fs4.existsSync(backupPath)) {
906
+ try {
907
+ const backupRaw = fs4.readFileSync(backupPath, "utf-8");
908
+ JSON.parse(backupRaw);
909
+ fs4.copyFileSync(backupPath, filePath);
910
+ restored = true;
911
+ warn(`Restored ${path4.basename(filePath)} from backup.`);
912
+ console.error(`Restored ${path4.basename(filePath)} from backup.`);
913
+ } catch {
914
+ error(`Backup ${path4.basename(backupPath)} is also corrupt; using fallback.`);
915
+ console.error(`Backup ${path4.basename(backupPath)} is also corrupt; using fallback.`);
965
916
  }
966
- models = [defaultModel];
967
917
  }
968
- const { baseUrl, stop } = await startOpenAIProxy(targetUrl, apiKey, defaultModel, models, modelMappings);
969
- console.log(`Proxy running at ${baseUrl}`);
970
- console.log("Press Ctrl+C to stop");
971
- process.on("SIGINT", () => {
972
- stop();
973
- process.exit(0);
974
- });
975
- }));
918
+ if (!restored) {
919
+ writeJson(filePath, fallback);
920
+ error(`Could not fix ${path4.basename(filePath)}, no valid backup found, reset to default.`);
921
+ console.error(`Could not fix ${path4.basename(filePath)}, no valid backup found, reset to default.`);
922
+ }
923
+ }
976
924
  }
977
925
 
978
926
  // src/profiles/runner.ts
927
+ import { spawnSync as spawnSync2, spawn } from "child_process";
979
928
  var BUILT_IN_DEFAULT = "__builtin__";
980
929
  function resolveClaudeBinary() {
981
930
  return createBinaryResolver().resolve();
@@ -1100,10 +1049,10 @@ function maskToken(token) {
1100
1049
  }
1101
1050
  function formatModels(p) {
1102
1051
  if (p.models && p.models.length > 0) {
1103
- const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel3(m));
1052
+ const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel(m));
1104
1053
  const parts = [];
1105
1054
  p.models.forEach((m, i) => {
1106
- if (!isAnthropicModel3(m)) {
1055
+ if (!isAnthropicModel(m)) {
1107
1056
  const aliasIndex = nonAnthropicModels.indexOf(m);
1108
1057
  if (aliasIndex === 0) parts.push(`${m} (sonnet)`);
1109
1058
  else if (aliasIndex === 1) parts.push(`${m} (opus)`);
@@ -1121,20 +1070,13 @@ function formatModels(p) {
1121
1070
  }
1122
1071
  return p.model || "(unset)";
1123
1072
  }
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
- function collect2(value, previous) {
1073
+ function collect(value, previous) {
1132
1074
  return previous.concat([value]);
1133
1075
  }
1134
1076
  function profileCommand() {
1135
1077
  const profile = new Command2("profile").description("Manage Claude CLI profiles");
1136
1078
  const syncer = createProfileSyncer();
1137
- profile.command("add").description("Add or update a profile").argument("<name>", "Profile name").option("-m, --model <model>", "Model ID - can be used multiple times (max 3)", collect2, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action(safeAction((name, opts) => {
1079
+ profile.command("add").description("Add or update a profile").argument("<name>", "Profile name").option("-m, --model <model>", "Model ID - can be used multiple times (max 3)", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action(safeAction((name, opts) => {
1138
1080
  const models = opts.model && opts.model.length > 0 ? opts.model : void 0;
1139
1081
  if (models && models.length > 3) {
1140
1082
  throw new Error("Error: A profile can have at most 3 models.");
@@ -1156,7 +1098,7 @@ function profileCommand() {
1156
1098
  debug(`profile add: wrote ${PROFILES_FILE}`);
1157
1099
  console.log(`Profile '${name}' saved.`);
1158
1100
  }));
1159
- profile.command("update").description("Update fields of an existing profile").argument("<name>", "Profile name (must already exist)").option("-m, --model <model>", "Model ID - can be used multiple times", collect2, []).option("-d, --delete-model <model>", "Remove model ID - can be used multiple times", collect2, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type").action(safeAction((name, opts) => {
1101
+ profile.command("update").description("Update fields of an existing profile").argument("<name>", "Profile name (must already exist)").option("-m, --model <model>", "Model ID - can be used multiple times", collect, []).option("-d, --delete-model <model>", "Remove model ID - can be used multiple times", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type").action(safeAction((name, opts) => {
1160
1102
  ensureProfilesFile();
1161
1103
  const data = readJson(PROFILES_FILE);
1162
1104
  if (!data.profiles[name]) {
@@ -1262,10 +1204,10 @@ function profileCommand() {
1262
1204
  console.log(`Name: ${name}`);
1263
1205
  console.log(`Model: ${p.model || "(unset)"}`);
1264
1206
  if (p.models && p.models.length > 0) {
1265
- const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel3(m));
1207
+ const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel(m));
1266
1208
  console.log(`Models:`);
1267
1209
  for (const m of p.models) {
1268
- if (!isAnthropicModel3(m)) {
1210
+ if (!isAnthropicModel(m)) {
1269
1211
  const aliasIndex = nonAnthropicModels.indexOf(m);
1270
1212
  let alias = "";
1271
1213
  if (aliasIndex === 0) alias = " (sonnet)";
@@ -2540,8 +2482,56 @@ function completionCommand() {
2540
2482
  }));
2541
2483
  }
2542
2484
 
2543
- // src/cache/commands.ts
2485
+ // src/proxy/commands.ts
2544
2486
  import { Command as Command6 } from "commander";
2487
+ function collect2(value, previous) {
2488
+ return previous.concat([value]);
2489
+ }
2490
+ function proxyCommand() {
2491
+ return new Command6("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)", collect2, []).action(safeAction(async (opts) => {
2492
+ let targetUrl = opts.url || "https://api.openai.com";
2493
+ let apiKey = opts.apiKey || "";
2494
+ let defaultModel = opts.model || "gpt-4o";
2495
+ let models = [];
2496
+ const modelMappings = {};
2497
+ if (opts.profile) {
2498
+ ensureProfilesFile();
2499
+ const data = readJson(PROFILES_FILE);
2500
+ const p = data.profiles[opts.profile];
2501
+ if (!p) {
2502
+ throw new Error(`Profile '${opts.profile}' not found.`);
2503
+ }
2504
+ targetUrl = p.url || targetUrl;
2505
+ apiKey = p.token || apiKey;
2506
+ models = p.models || (p.model ? [p.model] : []);
2507
+ defaultModel = models[0] || defaultModel;
2508
+ models.forEach((m, i) => {
2509
+ if (!isAnthropicModel(m)) {
2510
+ const alias = ANTHROPIC_ALIASES[Math.min(i, ANTHROPIC_ALIASES.length - 1)];
2511
+ modelMappings[alias] = m;
2512
+ }
2513
+ });
2514
+ } else {
2515
+ for (const m of opts.mapping) {
2516
+ const [alias, actual] = m.split(":");
2517
+ if (alias && actual) {
2518
+ modelMappings[alias] = actual;
2519
+ }
2520
+ }
2521
+ models = [defaultModel];
2522
+ }
2523
+ const { baseUrl, stop } = await startOpenAIProxy(targetUrl, apiKey, defaultModel, models, modelMappings);
2524
+ console.log(`Proxy running at ${baseUrl}`);
2525
+ console.log("Press Ctrl+C to stop");
2526
+ process.on("SIGINT", () => {
2527
+ stop();
2528
+ process.exit(0);
2529
+ });
2530
+ }));
2531
+ }
2532
+
2533
+ // src/cache/commands.ts
2534
+ import { Command as Command7 } from "commander";
2545
2535
  import fs8 from "fs";
2546
2536
  import path8 from "path";
2547
2537
  import { spawnSync as spawnSync4 } from "child_process";
@@ -2616,7 +2606,7 @@ function killProcesses(pids) {
2616
2606
  }
2617
2607
  }
2618
2608
  function cacheCommand() {
2619
- const cache = new Command6("cache").description("Manage Claude Code cache and backup files");
2609
+ const cache = new Command7("cache").description("Manage Claude Code cache and backup files");
2620
2610
  cache.command("restore").description("Restore ~/.claude/.claude.json.backup to ~/.claude.json").action(safeAction(async () => {
2621
2611
  const backupPath = path8.join(CLAUDE_DIR, ".claude.json.backup");
2622
2612
  const targetPath = CLAUDE_JSON;
@@ -2649,7 +2639,7 @@ ensureSettingsFile();
2649
2639
  var settings = readJson(SETTINGS_FILE);
2650
2640
  setLogLevel(settings._cc_hub_logLevel || "INFO");
2651
2641
  installGlobalExceptionHandlers();
2652
- var program = new Command7();
2642
+ var program = new Command8();
2653
2643
  program.name("cc-hub").description("Manage Claude CLI profiles, hooks, and sessions").version(version);
2654
2644
  program.addCommand(profileCommand());
2655
2645
  program.addCommand(useCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hub-cli",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "description": "Manage Claude CLI profiles, hooks, and sessions",
5
5
  "type": "module",
6
6
  "bin": {