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.
Files changed (2) hide show
  1. package/dist/index.js +381 -392
  2. 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
- server.on("error", reject);
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 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
- });
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/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"
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 collect(value, previous) {
921
- return previous.concat([value]);
901
+ function readJson(filePath) {
902
+ debug(`readJson: ${filePath}`);
903
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
922
904
  }
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;
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 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
- }
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
- 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
- }));
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) => !isAnthropicModel3(m));
1099
+ const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel(m));
1104
1100
  const parts = [];
1105
1101
  p.models.forEach((m, i) => {
1106
- if (!isAnthropicModel3(m)) {
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) => !isAnthropicModel3(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 (!isAnthropicModel3(m)) {
1257
+ if (!isAnthropicModel(m)) {
1269
1258
  const aliasIndex = nonAnthropicModels.indexOf(m);
1270
1259
  let alias = "";
1271
1260
  if (aliasIndex === 0) alias = " (sonnet)";
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.12",
4
4
  "description": "Manage Claude CLI profiles, hooks, and sessions",
5
5
  "type": "module",
6
6
  "bin": {