cc-hub-cli 1.1.10 → 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 +517 -400
- 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
|
|
4
|
+
import { Command as Command7 } from "commander";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
|
|
7
7
|
// src/profiles/commands.ts
|
|
@@ -250,300 +250,10 @@ var NoOpDesktopApp = class {
|
|
|
250
250
|
}
|
|
251
251
|
};
|
|
252
252
|
|
|
253
|
-
// src/platform/profile-syncer.ts
|
|
254
|
-
import fs3 from "fs";
|
|
255
|
-
import path3 from "path";
|
|
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";
|
|
253
|
+
// src/platform/profile-syncer.ts
|
|
254
|
+
import fs3 from "fs";
|
|
255
|
+
import path3 from "path";
|
|
256
|
+
import { randomUUID } from "crypto";
|
|
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
|
-
}
|
|
887
|
-
});
|
|
888
|
-
});
|
|
889
|
-
server.on("error", reject);
|
|
890
|
-
});
|
|
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"
|
|
824
|
+
});
|
|
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)";
|
|
@@ -2168,6 +2157,7 @@ _cc-hub() {
|
|
|
2168
2157
|
'hook:Manage Claude Code hooks in settings.json'
|
|
2169
2158
|
'session:Manage Claude Code sessions'
|
|
2170
2159
|
'provider:Manage provider types'
|
|
2160
|
+
'cache:Manage Claude Code cache and backup files'
|
|
2171
2161
|
'completion:Print shell completion functions'
|
|
2172
2162
|
'help:Display help for a command'
|
|
2173
2163
|
)
|
|
@@ -2177,6 +2167,11 @@ _cc-hub() {
|
|
|
2177
2167
|
'list:List available provider types'
|
|
2178
2168
|
)
|
|
2179
2169
|
|
|
2170
|
+
local -a cache_subcmds
|
|
2171
|
+
cache_subcmds=(
|
|
2172
|
+
'restore:Restore ~/.claude/.claude.json.backup to ~/.claude.json'
|
|
2173
|
+
)
|
|
2174
|
+
|
|
2180
2175
|
|
|
2181
2176
|
local -a profile_subcmds
|
|
2182
2177
|
profile_subcmds=(
|
|
@@ -2284,6 +2279,11 @@ _cc-hub() {
|
|
|
2284
2279
|
_describe -t hooks-subcmds 'hook subcommand' hooks_subcmds
|
|
2285
2280
|
fi
|
|
2286
2281
|
;;
|
|
2282
|
+
cache)
|
|
2283
|
+
if (( CURRENT == 2 )); then
|
|
2284
|
+
_describe -t cache-subcmds 'cache subcommand' cache_subcmds
|
|
2285
|
+
fi
|
|
2286
|
+
;;
|
|
2287
2287
|
session)
|
|
2288
2288
|
if (( CURRENT == 2 )); then
|
|
2289
2289
|
_describe -t session-subcmds 'session subcommand' session_subcmds
|
|
@@ -2344,13 +2344,14 @@ _cc-hub() {
|
|
|
2344
2344
|
COMPREPLY=()
|
|
2345
2345
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
2346
2346
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
2347
|
-
commands="profile use run hook session provider completion help"
|
|
2347
|
+
commands="profile use run hook session provider cache completion help"
|
|
2348
2348
|
|
|
2349
2349
|
local profile_subcmds="add update list view remove rename default sync"
|
|
2350
2350
|
local provider_subcmds="list"
|
|
2351
2351
|
local provider_types="anthropic openai"
|
|
2352
2352
|
local hooks_subcmds="list add remove enable disable"
|
|
2353
2353
|
local session_subcmds="list show search ps stats clean troubleshoot"
|
|
2354
|
+
local cache_subcmds="restore"
|
|
2354
2355
|
|
|
2355
2356
|
# Top-level command
|
|
2356
2357
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
@@ -2404,6 +2405,11 @@ _cc-hub() {
|
|
|
2404
2405
|
COMPREPLY=($(compgen -W "$hooks_subcmds" -- "$cur"))
|
|
2405
2406
|
fi
|
|
2406
2407
|
;;
|
|
2408
|
+
cache)
|
|
2409
|
+
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
2410
|
+
COMPREPLY=($(compgen -W "$cache_subcmds" -- "$cur"))
|
|
2411
|
+
fi
|
|
2412
|
+
;;
|
|
2407
2413
|
session)
|
|
2408
2414
|
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
2409
2415
|
COMPREPLY=($(compgen -W "$session_subcmds" -- "$cur"))
|
|
@@ -2435,6 +2441,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2435
2441
|
'hook:Manage Claude Code hooks in settings.json'
|
|
2436
2442
|
'session:Manage Claude Code sessions'
|
|
2437
2443
|
'provider:Manage provider types'
|
|
2444
|
+
'cache:Manage Claude Code cache and backup files'
|
|
2438
2445
|
'completion:Print shell completion functions'
|
|
2439
2446
|
'help:Display help for a command'
|
|
2440
2447
|
)
|
|
@@ -2442,6 +2449,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2442
2449
|
$profileSubcmds = @('add', 'update', 'list', 'view', 'remove', 'rename', 'default', 'sync')
|
|
2443
2450
|
$hookSubcmds = @('list', 'add', 'remove', 'enable', 'disable')
|
|
2444
2451
|
$sessionSubcmds = @('list', 'show', 'search', 'ps', 'stats', 'clean', 'troubleshoot')
|
|
2452
|
+
$cacheSubcmds = @('restore')
|
|
2445
2453
|
$providerSubcmds = @('list')
|
|
2446
2454
|
|
|
2447
2455
|
$tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
|
|
@@ -2482,6 +2490,12 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2482
2490
|
return
|
|
2483
2491
|
}
|
|
2484
2492
|
}
|
|
2493
|
+
'cache' {
|
|
2494
|
+
if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
|
|
2495
|
+
$cacheSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
2496
|
+
return
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2485
2499
|
'use' {
|
|
2486
2500
|
$opts = @('--built-in')
|
|
2487
2501
|
$opts | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
@@ -2515,6 +2529,108 @@ function completionCommand() {
|
|
|
2515
2529
|
}));
|
|
2516
2530
|
}
|
|
2517
2531
|
|
|
2532
|
+
// src/cache/commands.ts
|
|
2533
|
+
import { Command as Command6 } from "commander";
|
|
2534
|
+
import fs8 from "fs";
|
|
2535
|
+
import path8 from "path";
|
|
2536
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2537
|
+
import { createInterface } from "readline/promises";
|
|
2538
|
+
async function confirmPrompt(message) {
|
|
2539
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2540
|
+
const answer = await rl.question(message);
|
|
2541
|
+
rl.close();
|
|
2542
|
+
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
|
|
2543
|
+
}
|
|
2544
|
+
function findClaudeProcesses() {
|
|
2545
|
+
const pids = [];
|
|
2546
|
+
const platform = process.platform;
|
|
2547
|
+
if (platform === "win32") {
|
|
2548
|
+
debug("cache restore: scanning Windows processes via tasklist");
|
|
2549
|
+
const result = spawnSync4("tasklist", ["/FO", "CSV", "/NH"], {
|
|
2550
|
+
encoding: "utf-8",
|
|
2551
|
+
shell: true
|
|
2552
|
+
});
|
|
2553
|
+
if (result.status === 0 && result.stdout) {
|
|
2554
|
+
const lines = result.stdout.trim().split("\n");
|
|
2555
|
+
for (const line of lines) {
|
|
2556
|
+
const parts = line.split('","');
|
|
2557
|
+
if (parts.length >= 2) {
|
|
2558
|
+
const imageName = parts[0].replace(/^"/, "").trim();
|
|
2559
|
+
const pidStr = parts[1].replace(/"$/, "").trim();
|
|
2560
|
+
const lowerName = imageName.toLowerCase();
|
|
2561
|
+
if (lowerName === "claude.exe" || lowerName === "claude") {
|
|
2562
|
+
const pid = parseInt(pidStr, 10);
|
|
2563
|
+
if (!isNaN(pid) && pid !== process.pid) {
|
|
2564
|
+
pids.push(pid);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
} else {
|
|
2571
|
+
debug("cache restore: scanning Unix processes via ps");
|
|
2572
|
+
const result = spawnSync4("ps", ["-eo", "pid,comm"], {
|
|
2573
|
+
encoding: "utf-8"
|
|
2574
|
+
});
|
|
2575
|
+
if (result.status === 0 && result.stdout) {
|
|
2576
|
+
const lines = result.stdout.trim().split("\n");
|
|
2577
|
+
for (let i = 1; i < lines.length; i++) {
|
|
2578
|
+
const line = lines[i].trim();
|
|
2579
|
+
const match = line.match(/^(\d+)\s+(.+)$/);
|
|
2580
|
+
if (match) {
|
|
2581
|
+
const pid = parseInt(match[1], 10);
|
|
2582
|
+
const comm = match[2].trim();
|
|
2583
|
+
if ((comm === "claude" || comm === "Claude") && pid !== process.pid) {
|
|
2584
|
+
pids.push(pid);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
debug(`cache restore: found ${pids.length} Claude process(es)`);
|
|
2591
|
+
return pids;
|
|
2592
|
+
}
|
|
2593
|
+
function killProcesses(pids) {
|
|
2594
|
+
if (pids.length === 0) return;
|
|
2595
|
+
if (process.platform === "win32") {
|
|
2596
|
+
for (const pid of pids) {
|
|
2597
|
+
debug(`cache restore: killing PID ${pid} via taskkill`);
|
|
2598
|
+
spawnSync4("taskkill", ["/F", "/PID", String(pid)], { shell: true });
|
|
2599
|
+
}
|
|
2600
|
+
} else {
|
|
2601
|
+
for (const pid of pids) {
|
|
2602
|
+
debug(`cache restore: killing PID ${pid} via kill`);
|
|
2603
|
+
spawnSync4("kill", ["-9", String(pid)]);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
function cacheCommand() {
|
|
2608
|
+
const cache = new Command6("cache").description("Manage Claude Code cache and backup files");
|
|
2609
|
+
cache.command("restore").description("Restore ~/.claude/.claude.json.backup to ~/.claude.json").action(safeAction(async () => {
|
|
2610
|
+
const backupPath = path8.join(CLAUDE_DIR, ".claude.json.backup");
|
|
2611
|
+
const targetPath = CLAUDE_JSON;
|
|
2612
|
+
if (!fs8.existsSync(backupPath)) {
|
|
2613
|
+
throw new Error(`Backup not found: ${backupPath}`);
|
|
2614
|
+
}
|
|
2615
|
+
const confirmed = await confirmPrompt(
|
|
2616
|
+
"This will terminate all running Claude Code processes to prevent file conflicts. Continue? (y/N) "
|
|
2617
|
+
);
|
|
2618
|
+
if (!confirmed) {
|
|
2619
|
+
console.log("Restore cancelled.");
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
const pids = findClaudeProcesses();
|
|
2623
|
+
if (pids.length > 0) {
|
|
2624
|
+
console.log(`Terminating ${pids.length} Claude process(es)...`);
|
|
2625
|
+
killProcesses(pids);
|
|
2626
|
+
}
|
|
2627
|
+
fs8.copyFileSync(backupPath, targetPath);
|
|
2628
|
+
debug(`cache restore: restored ${backupPath} -> ${targetPath}`);
|
|
2629
|
+
console.log(`Restored ${backupPath} -> ${targetPath}`);
|
|
2630
|
+
}));
|
|
2631
|
+
return cache;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2518
2634
|
// src/index.ts
|
|
2519
2635
|
var _require = createRequire(import.meta.url);
|
|
2520
2636
|
var { version } = _require("../package.json");
|
|
@@ -2522,7 +2638,7 @@ ensureSettingsFile();
|
|
|
2522
2638
|
var settings = readJson(SETTINGS_FILE);
|
|
2523
2639
|
setLogLevel(settings._cc_hub_logLevel || "INFO");
|
|
2524
2640
|
installGlobalExceptionHandlers();
|
|
2525
|
-
var program = new
|
|
2641
|
+
var program = new Command7();
|
|
2526
2642
|
program.name("cc-hub").description("Manage Claude CLI profiles, hooks, and sessions").version(version);
|
|
2527
2643
|
program.addCommand(profileCommand());
|
|
2528
2644
|
program.addCommand(useCommand());
|
|
@@ -2532,6 +2648,7 @@ program.addCommand(sessionCommand());
|
|
|
2532
2648
|
program.addCommand(completionCommand());
|
|
2533
2649
|
program.addCommand(providerCommand());
|
|
2534
2650
|
program.addCommand(proxyCommand());
|
|
2651
|
+
program.addCommand(cacheCommand());
|
|
2535
2652
|
try {
|
|
2536
2653
|
program.parse();
|
|
2537
2654
|
} catch (err) {
|