cc-hub-cli 1.1.12 → 1.1.14
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 +130 -83
- 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 Command8 } from "commander";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
|
|
7
7
|
// src/profiles/commands.ts
|
|
@@ -258,9 +258,6 @@ import { randomUUID } from "crypto";
|
|
|
258
258
|
// src/provider/index.ts
|
|
259
259
|
import { Command } from "commander";
|
|
260
260
|
|
|
261
|
-
// src/provider/server.ts
|
|
262
|
-
import http from "http";
|
|
263
|
-
|
|
264
261
|
// src/provider/transform.ts
|
|
265
262
|
function sanitizeToolId(id) {
|
|
266
263
|
let sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
@@ -486,6 +483,7 @@ data: ${JSON.stringify(data)}
|
|
|
486
483
|
}
|
|
487
484
|
|
|
488
485
|
// src/provider/server.ts
|
|
486
|
+
import http from "http";
|
|
489
487
|
async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMappings = {}) {
|
|
490
488
|
const base = targetUrl.replace(/\/+$/, "");
|
|
491
489
|
const server = http.createServer(async (req, res) => {
|
|
@@ -631,9 +629,6 @@ function isAnthropicModel(model) {
|
|
|
631
629
|
}
|
|
632
630
|
return false;
|
|
633
631
|
}
|
|
634
|
-
function collect(value, previous) {
|
|
635
|
-
return previous.concat([value]);
|
|
636
|
-
}
|
|
637
632
|
function providerCommand() {
|
|
638
633
|
const cmd = new Command("provider").description("Manage provider types");
|
|
639
634
|
cmd.command("list").description("List available provider types").action(safeAction(() => {
|
|
@@ -646,48 +641,6 @@ function providerCommand() {
|
|
|
646
641
|
}));
|
|
647
642
|
return cmd;
|
|
648
643
|
}
|
|
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
644
|
|
|
692
645
|
// src/platform/profile-syncer.ts
|
|
693
646
|
function toDesktopProfile(p) {
|
|
@@ -970,6 +923,9 @@ function fixJsonFile(filePath, fallback = {}) {
|
|
|
970
923
|
}
|
|
971
924
|
}
|
|
972
925
|
|
|
926
|
+
// src/profiles/commands.ts
|
|
927
|
+
import path5 from "path";
|
|
928
|
+
|
|
973
929
|
// src/profiles/runner.ts
|
|
974
930
|
import { spawnSync as spawnSync2, spawn } from "child_process";
|
|
975
931
|
var BUILT_IN_DEFAULT = "__builtin__";
|
|
@@ -1117,13 +1073,13 @@ function formatModels(p) {
|
|
|
1117
1073
|
}
|
|
1118
1074
|
return p.model || "(unset)";
|
|
1119
1075
|
}
|
|
1120
|
-
function
|
|
1076
|
+
function collect(value, previous) {
|
|
1121
1077
|
return previous.concat([value]);
|
|
1122
1078
|
}
|
|
1123
1079
|
function profileCommand() {
|
|
1124
1080
|
const profile = new Command2("profile").description("Manage Claude CLI profiles");
|
|
1125
1081
|
const syncer = createProfileSyncer();
|
|
1126
|
-
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)",
|
|
1082
|
+
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) => {
|
|
1127
1083
|
const models = opts.model && opts.model.length > 0 ? opts.model : void 0;
|
|
1128
1084
|
if (models && models.length > 3) {
|
|
1129
1085
|
throw new Error("Error: A profile can have at most 3 models.");
|
|
@@ -1145,7 +1101,7 @@ function profileCommand() {
|
|
|
1145
1101
|
debug(`profile add: wrote ${PROFILES_FILE}`);
|
|
1146
1102
|
console.log(`Profile '${name}' saved.`);
|
|
1147
1103
|
}));
|
|
1148
|
-
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",
|
|
1104
|
+
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) => {
|
|
1149
1105
|
ensureProfilesFile();
|
|
1150
1106
|
const data = readJson(PROFILES_FILE);
|
|
1151
1107
|
if (!data.profiles[name]) {
|
|
@@ -1345,6 +1301,48 @@ function profileCommand() {
|
|
|
1345
1301
|
debug(`profile sync: wrote ${PROFILES_FILE}`);
|
|
1346
1302
|
console.log(`Synced ${names.length} profile(s) to the desktop app.`);
|
|
1347
1303
|
}));
|
|
1304
|
+
profile.command("export").description("Export a profile to a settings file").argument("<name>", "Profile name").action(safeAction((name) => {
|
|
1305
|
+
ensureProfilesFile();
|
|
1306
|
+
const data = readJson(PROFILES_FILE);
|
|
1307
|
+
const p = data.profiles[name];
|
|
1308
|
+
if (!p) {
|
|
1309
|
+
throw new Error(`Profile '${name}' not found.`);
|
|
1310
|
+
}
|
|
1311
|
+
ensureSettingsFile();
|
|
1312
|
+
const settings2 = readJson(SETTINGS_FILE);
|
|
1313
|
+
const exported = Object.fromEntries(
|
|
1314
|
+
Object.entries(settings2).filter(([key]) => !key.startsWith("_"))
|
|
1315
|
+
);
|
|
1316
|
+
const env = {
|
|
1317
|
+
...typeof exported.env === "object" && exported.env !== null ? exported.env : {}
|
|
1318
|
+
};
|
|
1319
|
+
if (p.token) env.ANTHROPIC_AUTH_TOKEN = p.token;
|
|
1320
|
+
if (p.url) env.ANTHROPIC_BASE_URL = p.url;
|
|
1321
|
+
const models = p.models || (p.model ? [p.model] : []);
|
|
1322
|
+
if (models.length > 0) {
|
|
1323
|
+
if (models[0]) {
|
|
1324
|
+
env.ANTHROPIC_DEFAULT_SONNET_MODEL = models[0];
|
|
1325
|
+
env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME = models[0];
|
|
1326
|
+
env.ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION = `Custom: ${models[0]}`;
|
|
1327
|
+
}
|
|
1328
|
+
if (models[1]) {
|
|
1329
|
+
env.ANTHROPIC_DEFAULT_OPUS_MODEL = models[1];
|
|
1330
|
+
env.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME = models[1];
|
|
1331
|
+
env.ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION = `Custom: ${models[1]}`;
|
|
1332
|
+
}
|
|
1333
|
+
if (models[2]) {
|
|
1334
|
+
env.ANTHROPIC_DEFAULT_HAIKU_MODEL = models[2];
|
|
1335
|
+
env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME = models[2];
|
|
1336
|
+
env.ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION = `Custom: ${models[2]}`;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = "1";
|
|
1340
|
+
env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
|
1341
|
+
exported.env = env;
|
|
1342
|
+
const exportPath = path5.join(CLAUDE_DIR, `settings.${name}.json`);
|
|
1343
|
+
writeJson(exportPath, exported);
|
|
1344
|
+
console.log(`Profile '${name}' exported to ${exportPath}`);
|
|
1345
|
+
}));
|
|
1348
1346
|
return profile;
|
|
1349
1347
|
}
|
|
1350
1348
|
function useCommand() {
|
|
@@ -1624,12 +1622,12 @@ function decodePath2(encoded) {
|
|
|
1624
1622
|
|
|
1625
1623
|
// src/sessions/stats.ts
|
|
1626
1624
|
import fs5 from "fs";
|
|
1627
|
-
import
|
|
1625
|
+
import path6 from "path";
|
|
1628
1626
|
function getDirSize(dir) {
|
|
1629
1627
|
let total = 0;
|
|
1630
1628
|
try {
|
|
1631
1629
|
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
1632
|
-
const fullPath =
|
|
1630
|
+
const fullPath = path6.join(dir, entry.name);
|
|
1633
1631
|
if (entry.isDirectory()) {
|
|
1634
1632
|
total += getDirSize(fullPath);
|
|
1635
1633
|
} else {
|
|
@@ -1653,7 +1651,7 @@ function formatSize(bytes) {
|
|
|
1653
1651
|
|
|
1654
1652
|
// src/sessions/utils.ts
|
|
1655
1653
|
import fs6 from "fs";
|
|
1656
|
-
import
|
|
1654
|
+
import path7 from "path";
|
|
1657
1655
|
function formatTimestamp(ms) {
|
|
1658
1656
|
const d = new Date(ms);
|
|
1659
1657
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -1662,7 +1660,7 @@ function formatTimestamp(ms) {
|
|
|
1662
1660
|
function findProjectDir(query) {
|
|
1663
1661
|
debug(`sessions: findProjectDir query="${query}"`);
|
|
1664
1662
|
const encoded = encodePath(query);
|
|
1665
|
-
if (fs6.existsSync(
|
|
1663
|
+
if (fs6.existsSync(path7.join(PROJECTS_DIR, encoded))) {
|
|
1666
1664
|
debug(`sessions: findProjectDir exact match ${encoded}`);
|
|
1667
1665
|
return encoded;
|
|
1668
1666
|
}
|
|
@@ -1740,10 +1738,10 @@ function findSessionFile(sessionQuery, projectQuery) {
|
|
|
1740
1738
|
if (!projDir) {
|
|
1741
1739
|
throw new Error(`No project matched: ${projectQuery}`);
|
|
1742
1740
|
}
|
|
1743
|
-
searchDirs.push(
|
|
1741
|
+
searchDirs.push(path7.join(PROJECTS_DIR, projDir));
|
|
1744
1742
|
} else {
|
|
1745
1743
|
try {
|
|
1746
|
-
searchDirs = fs6.readdirSync(PROJECTS_DIR).map((d) =>
|
|
1744
|
+
searchDirs = fs6.readdirSync(PROJECTS_DIR).map((d) => path7.join(PROJECTS_DIR, d));
|
|
1747
1745
|
} catch {
|
|
1748
1746
|
throw new Error(`No projects directory found at ${PROJECTS_DIR}`);
|
|
1749
1747
|
}
|
|
@@ -1759,7 +1757,7 @@ function findSessionFile(sessionQuery, projectQuery) {
|
|
|
1759
1757
|
for (const file of files) {
|
|
1760
1758
|
const sessionId = file.replace(/\.jsonl$/, "");
|
|
1761
1759
|
if (sessionId.toLowerCase().includes(sessionQuery.toLowerCase())) {
|
|
1762
|
-
matches.push({ filePath:
|
|
1760
|
+
matches.push({ filePath: path7.join(dir, file), project: path7.basename(dir) });
|
|
1763
1761
|
}
|
|
1764
1762
|
}
|
|
1765
1763
|
}
|
|
@@ -1767,7 +1765,7 @@ function findSessionFile(sessionQuery, projectQuery) {
|
|
|
1767
1765
|
return null;
|
|
1768
1766
|
}
|
|
1769
1767
|
if (matches.length > 1) {
|
|
1770
|
-
const lines = matches.map((m) => ` ${
|
|
1768
|
+
const lines = matches.map((m) => ` ${path7.basename(m.filePath)} in ${decodePath(m.project)}`).join("\n");
|
|
1771
1769
|
throw new Error(`Multiple sessions matched '${sessionQuery}':
|
|
1772
1770
|
${lines}
|
|
1773
1771
|
Use --project to disambiguate.`);
|
|
@@ -1778,7 +1776,7 @@ Use --project to disambiguate.`);
|
|
|
1778
1776
|
// src/sessions/commands.ts
|
|
1779
1777
|
import { Command as Command4 } from "commander";
|
|
1780
1778
|
import fs7 from "fs";
|
|
1781
|
-
import
|
|
1779
|
+
import path8 from "path";
|
|
1782
1780
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1783
1781
|
function sessionCommand() {
|
|
1784
1782
|
const session = new Command4("session").description("Manage Claude Code sessions");
|
|
@@ -1795,14 +1793,14 @@ function sessionCommand() {
|
|
|
1795
1793
|
return;
|
|
1796
1794
|
}
|
|
1797
1795
|
dirs.sort((a, b) => {
|
|
1798
|
-
const statA = fs7.statSync(
|
|
1799
|
-
const statB = fs7.statSync(
|
|
1796
|
+
const statA = fs7.statSync(path8.join(PROJECTS_DIR, a));
|
|
1797
|
+
const statB = fs7.statSync(path8.join(PROJECTS_DIR, b));
|
|
1800
1798
|
return statB.mtimeMs - statA.mtimeMs;
|
|
1801
1799
|
});
|
|
1802
1800
|
let count = 0;
|
|
1803
1801
|
for (const projDir of dirs) {
|
|
1804
1802
|
if (count >= limit) break;
|
|
1805
|
-
const fullPath =
|
|
1803
|
+
const fullPath = path8.join(PROJECTS_DIR, projDir);
|
|
1806
1804
|
let nSessions = 0;
|
|
1807
1805
|
try {
|
|
1808
1806
|
nSessions = fs7.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
|
|
@@ -1826,7 +1824,7 @@ function sessionCommand() {
|
|
|
1826
1824
|
if (!projDir) {
|
|
1827
1825
|
throw new Error(`No project matched: ${project}`);
|
|
1828
1826
|
}
|
|
1829
|
-
const fullPath =
|
|
1827
|
+
const fullPath = path8.join(PROJECTS_DIR, projDir);
|
|
1830
1828
|
console.log(`Project: ${decodePath2(projDir)}`);
|
|
1831
1829
|
console.log(`Dir: ${fullPath}`);
|
|
1832
1830
|
console.log("");
|
|
@@ -1840,7 +1838,7 @@ function sessionCommand() {
|
|
|
1840
1838
|
return;
|
|
1841
1839
|
}
|
|
1842
1840
|
for (const file of files) {
|
|
1843
|
-
const filePath =
|
|
1841
|
+
const filePath = path8.join(fullPath, file);
|
|
1844
1842
|
const sessionId = file.replace(/\.jsonl$/, "");
|
|
1845
1843
|
let msgCount = 0;
|
|
1846
1844
|
try {
|
|
@@ -1890,7 +1888,7 @@ function sessionCommand() {
|
|
|
1890
1888
|
if (!projDir) {
|
|
1891
1889
|
throw new Error(`No project matched: ${opts.project}`);
|
|
1892
1890
|
}
|
|
1893
|
-
searchRoots = [{ root:
|
|
1891
|
+
searchRoots = [{ root: path8.join(PROJECTS_DIR, projDir), label: "" }];
|
|
1894
1892
|
}
|
|
1895
1893
|
const limit = parseInt(opts.limit, 10);
|
|
1896
1894
|
let count = 0;
|
|
@@ -1904,7 +1902,7 @@ function sessionCommand() {
|
|
|
1904
1902
|
}
|
|
1905
1903
|
for (const entry of entries) {
|
|
1906
1904
|
if (count >= limit) break;
|
|
1907
|
-
const fullPath =
|
|
1905
|
+
const fullPath = path8.join(dir, entry.name);
|
|
1908
1906
|
if (entry.isDirectory()) {
|
|
1909
1907
|
searchDir(fullPath, label, baseDir);
|
|
1910
1908
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
@@ -1918,9 +1916,9 @@ function sessionCommand() {
|
|
|
1918
1916
|
const match = opts.ignoreCase ? line.toLowerCase().includes(query.toLowerCase()) : line.includes(query);
|
|
1919
1917
|
if (match) {
|
|
1920
1918
|
if (!found) {
|
|
1921
|
-
const relPath =
|
|
1922
|
-
const projEnc = relPath.split(
|
|
1923
|
-
const sessionId =
|
|
1919
|
+
const relPath = path8.relative(baseDir, fullPath);
|
|
1920
|
+
const projEnc = relPath.split(path8.sep)[0];
|
|
1921
|
+
const sessionId = path8.basename(fullPath, ".jsonl");
|
|
1924
1922
|
const projName = label ? projEnc : decodePath2(projEnc);
|
|
1925
1923
|
console.log(`${label}[${projName} \u2192 ${sessionId}]`);
|
|
1926
1924
|
found = true;
|
|
@@ -1972,7 +1970,7 @@ function sessionCommand() {
|
|
|
1972
1970
|
console.log(fmt("---", "----------", "-------", "---", ""));
|
|
1973
1971
|
for (const file of files) {
|
|
1974
1972
|
try {
|
|
1975
|
-
const data = JSON.parse(fs7.readFileSync(
|
|
1973
|
+
const data = JSON.parse(fs7.readFileSync(path8.join(SESSIONS_DIR, file), "utf-8"));
|
|
1976
1974
|
const pid = String(data.pid || "?");
|
|
1977
1975
|
const sessionId = data.sessionId || "?";
|
|
1978
1976
|
const cwd = data.cwd || "?";
|
|
@@ -2000,7 +1998,7 @@ function sessionCommand() {
|
|
|
2000
1998
|
const results = [];
|
|
2001
1999
|
try {
|
|
2002
2000
|
for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
|
|
2003
|
-
const fullPath =
|
|
2001
|
+
const fullPath = path8.join(dir, entry.name);
|
|
2004
2002
|
if (entry.isDirectory()) results.push(...walk(fullPath));
|
|
2005
2003
|
else if (entry.name.endsWith(".jsonl")) results.push(fullPath);
|
|
2006
2004
|
}
|
|
@@ -2077,7 +2075,7 @@ function sessionCommand() {
|
|
|
2077
2075
|
return;
|
|
2078
2076
|
}
|
|
2079
2077
|
for (const entry of entries) {
|
|
2080
|
-
const fullPath =
|
|
2078
|
+
const fullPath = path8.join(dir, entry.name);
|
|
2081
2079
|
if (entry.isDirectory()) {
|
|
2082
2080
|
walk(fullPath);
|
|
2083
2081
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
@@ -2183,6 +2181,7 @@ _cc-hub() {
|
|
|
2183
2181
|
'rename:Rename a profile'
|
|
2184
2182
|
'default:Set the default profile'
|
|
2185
2183
|
'sync:Synchronize all CLI profiles to the desktop app'
|
|
2184
|
+
'export:Export a profile to a settings file'
|
|
2186
2185
|
)
|
|
2187
2186
|
|
|
2188
2187
|
local -a hooks_subcmds
|
|
@@ -2237,7 +2236,7 @@ _cc-hub() {
|
|
|
2237
2236
|
profile)
|
|
2238
2237
|
if (( CURRENT == 2 )); then
|
|
2239
2238
|
_describe -t profile-subcmds 'profile subcommand' profile_subcmds
|
|
2240
|
-
elif [[ $words[2] == "view" || $words[2] == "remove" ]]; then
|
|
2239
|
+
elif [[ $words[2] == "view" || $words[2] == "remove" || $words[2] == "export" ]]; then
|
|
2241
2240
|
_cc_hub_profiles
|
|
2242
2241
|
elif [[ $words[2] == "default" ]]; then
|
|
2243
2242
|
_arguments -C -S '--built-in[Use official Anthropic models as default]' '*:profile:_cc_hub_profiles'
|
|
@@ -2346,7 +2345,7 @@ _cc-hub() {
|
|
|
2346
2345
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
2347
2346
|
commands="profile use run hook session provider cache completion help"
|
|
2348
2347
|
|
|
2349
|
-
local profile_subcmds="add update list view remove rename default sync"
|
|
2348
|
+
local profile_subcmds="add update list view remove rename default sync export"
|
|
2350
2349
|
local provider_subcmds="list"
|
|
2351
2350
|
local provider_types="anthropic openai"
|
|
2352
2351
|
local hooks_subcmds="list add remove enable disable"
|
|
@@ -2365,7 +2364,7 @@ _cc-hub() {
|
|
|
2365
2364
|
profile)
|
|
2366
2365
|
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
2367
2366
|
COMPREPLY=($(compgen -W "$profile_subcmds" -- "$cur"))
|
|
2368
|
-
elif [[ "$prev" == "view" || "$prev" == "remove" ]]; then
|
|
2367
|
+
elif [[ "$prev" == "view" || "$prev" == "remove" || "$prev" == "export" ]]; then
|
|
2369
2368
|
_cc-hub_profiles
|
|
2370
2369
|
elif [[ "$prev" == "default" ]]; then
|
|
2371
2370
|
COMPREPLY=($(compgen -W "--built-in $(_cc-hub_profile_names)" -- "$cur"))
|
|
@@ -2446,7 +2445,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2446
2445
|
'help:Display help for a command'
|
|
2447
2446
|
)
|
|
2448
2447
|
|
|
2449
|
-
$profileSubcmds = @('add', 'update', 'list', 'view', 'remove', 'rename', 'default', 'sync')
|
|
2448
|
+
$profileSubcmds = @('add', 'update', 'list', 'view', 'remove', 'rename', 'default', 'sync', 'export')
|
|
2450
2449
|
$hookSubcmds = @('list', 'add', 'remove', 'enable', 'disable')
|
|
2451
2450
|
$sessionSubcmds = @('list', 'show', 'search', 'ps', 'stats', 'clean', 'troubleshoot')
|
|
2452
2451
|
$cacheSubcmds = @('restore')
|
|
@@ -2529,10 +2528,58 @@ function completionCommand() {
|
|
|
2529
2528
|
}));
|
|
2530
2529
|
}
|
|
2531
2530
|
|
|
2532
|
-
// src/
|
|
2531
|
+
// src/proxy/commands.ts
|
|
2533
2532
|
import { Command as Command6 } from "commander";
|
|
2533
|
+
function collect2(value, previous) {
|
|
2534
|
+
return previous.concat([value]);
|
|
2535
|
+
}
|
|
2536
|
+
function proxyCommand() {
|
|
2537
|
+
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) => {
|
|
2538
|
+
let targetUrl = opts.url || "https://api.openai.com";
|
|
2539
|
+
let apiKey = opts.apiKey || "";
|
|
2540
|
+
let defaultModel = opts.model || "gpt-4o";
|
|
2541
|
+
let models = [];
|
|
2542
|
+
const modelMappings = {};
|
|
2543
|
+
if (opts.profile) {
|
|
2544
|
+
ensureProfilesFile();
|
|
2545
|
+
const data = readJson(PROFILES_FILE);
|
|
2546
|
+
const p = data.profiles[opts.profile];
|
|
2547
|
+
if (!p) {
|
|
2548
|
+
throw new Error(`Profile '${opts.profile}' not found.`);
|
|
2549
|
+
}
|
|
2550
|
+
targetUrl = p.url || targetUrl;
|
|
2551
|
+
apiKey = p.token || apiKey;
|
|
2552
|
+
models = p.models || (p.model ? [p.model] : []);
|
|
2553
|
+
defaultModel = models[0] || defaultModel;
|
|
2554
|
+
models.forEach((m, i) => {
|
|
2555
|
+
if (!isAnthropicModel(m)) {
|
|
2556
|
+
const alias = ANTHROPIC_ALIASES[Math.min(i, ANTHROPIC_ALIASES.length - 1)];
|
|
2557
|
+
modelMappings[alias] = m;
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
} else {
|
|
2561
|
+
for (const m of opts.mapping) {
|
|
2562
|
+
const [alias, actual] = m.split(":");
|
|
2563
|
+
if (alias && actual) {
|
|
2564
|
+
modelMappings[alias] = actual;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
models = [defaultModel];
|
|
2568
|
+
}
|
|
2569
|
+
const { baseUrl, stop } = await startOpenAIProxy(targetUrl, apiKey, defaultModel, models, modelMappings);
|
|
2570
|
+
console.log(`Proxy running at ${baseUrl}`);
|
|
2571
|
+
console.log("Press Ctrl+C to stop");
|
|
2572
|
+
process.on("SIGINT", () => {
|
|
2573
|
+
stop();
|
|
2574
|
+
process.exit(0);
|
|
2575
|
+
});
|
|
2576
|
+
}));
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
// src/cache/commands.ts
|
|
2580
|
+
import { Command as Command7 } from "commander";
|
|
2534
2581
|
import fs8 from "fs";
|
|
2535
|
-
import
|
|
2582
|
+
import path9 from "path";
|
|
2536
2583
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2537
2584
|
import { createInterface } from "readline/promises";
|
|
2538
2585
|
async function confirmPrompt(message) {
|
|
@@ -2605,9 +2652,9 @@ function killProcesses(pids) {
|
|
|
2605
2652
|
}
|
|
2606
2653
|
}
|
|
2607
2654
|
function cacheCommand() {
|
|
2608
|
-
const cache = new
|
|
2655
|
+
const cache = new Command7("cache").description("Manage Claude Code cache and backup files");
|
|
2609
2656
|
cache.command("restore").description("Restore ~/.claude/.claude.json.backup to ~/.claude.json").action(safeAction(async () => {
|
|
2610
|
-
const backupPath =
|
|
2657
|
+
const backupPath = path9.join(CLAUDE_DIR, ".claude.json.backup");
|
|
2611
2658
|
const targetPath = CLAUDE_JSON;
|
|
2612
2659
|
if (!fs8.existsSync(backupPath)) {
|
|
2613
2660
|
throw new Error(`Backup not found: ${backupPath}`);
|
|
@@ -2638,7 +2685,7 @@ ensureSettingsFile();
|
|
|
2638
2685
|
var settings = readJson(SETTINGS_FILE);
|
|
2639
2686
|
setLogLevel(settings._cc_hub_logLevel || "INFO");
|
|
2640
2687
|
installGlobalExceptionHandlers();
|
|
2641
|
-
var program = new
|
|
2688
|
+
var program = new Command8();
|
|
2642
2689
|
program.name("cc-hub").description("Manage Claude CLI profiles, hooks, and sessions").version(version);
|
|
2643
2690
|
program.addCommand(profileCommand());
|
|
2644
2691
|
program.addCommand(useCommand());
|