cc-hub-cli 1.1.16 → 1.1.18
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 +290 -22
- 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 Command9 } from "commander";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
|
|
7
7
|
// src/profiles/commands.ts
|
|
@@ -141,7 +141,7 @@ var MacOSDesktopApp = class {
|
|
|
141
141
|
if (fs2.existsSync(configLib)) return configLib;
|
|
142
142
|
return configLib;
|
|
143
143
|
}
|
|
144
|
-
findBinary() {
|
|
144
|
+
findBinary(pinnedVersion) {
|
|
145
145
|
debug(`desktop-app: searching for binary in ${this.supportDir}`);
|
|
146
146
|
const claudeCodeDir = path2.join(this.supportDir, "claude-code");
|
|
147
147
|
if (!fs2.existsSync(claudeCodeDir)) return void 0;
|
|
@@ -155,7 +155,17 @@ var MacOSDesktopApp = class {
|
|
|
155
155
|
}
|
|
156
156
|
if (versions.length === 0) return void 0;
|
|
157
157
|
versions.sort(sortSemverDesc);
|
|
158
|
-
|
|
158
|
+
let targetVersion = versions[0];
|
|
159
|
+
if (pinnedVersion) {
|
|
160
|
+
const match = versions.find((v) => v === pinnedVersion);
|
|
161
|
+
if (match) {
|
|
162
|
+
targetVersion = match;
|
|
163
|
+
debug(`desktop-app: using pinned version ${targetVersion}`);
|
|
164
|
+
} else {
|
|
165
|
+
warn(`desktop-app: pinned version ${pinnedVersion} not found locally; falling back to latest ${targetVersion}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const binary = path2.join(claudeCodeDir, targetVersion, "claude.app", "Contents", "MacOS", "claude");
|
|
159
169
|
debug(`desktop-app: found macOS binary ${binary}`);
|
|
160
170
|
return binary;
|
|
161
171
|
}
|
|
@@ -226,7 +236,7 @@ var WindowsDesktopApp = class {
|
|
|
226
236
|
const dir = this._findSupportDir();
|
|
227
237
|
return dir ? path2.join(dir, "local-agent-mode-sessions") : void 0;
|
|
228
238
|
}
|
|
229
|
-
findBinary() {
|
|
239
|
+
findBinary(_pinnedVersion) {
|
|
230
240
|
const win32Binary = path2.join(process.env.LOCALAPPDATA || "", "Programs", "Claude", "Claude.exe");
|
|
231
241
|
if (fs2.existsSync(win32Binary)) return win32Binary;
|
|
232
242
|
return void 0;
|
|
@@ -245,7 +255,7 @@ var NoOpDesktopApp = class {
|
|
|
245
255
|
getConfigLibrary() {
|
|
246
256
|
return void 0;
|
|
247
257
|
}
|
|
248
|
-
findBinary() {
|
|
258
|
+
findBinary(_pinnedVersion) {
|
|
249
259
|
return void 0;
|
|
250
260
|
}
|
|
251
261
|
};
|
|
@@ -674,6 +684,7 @@ function transformKimiResponseToAnthropic(kimiResponse, originalModel) {
|
|
|
674
684
|
tool_calls: "tool_use",
|
|
675
685
|
content_filter: "stop_sequence"
|
|
676
686
|
};
|
|
687
|
+
const cachedTokens = kimiResponse.usage?.prompt_tokens_details?.cached_tokens ?? kimiResponse.usage?.cached_tokens ?? 0;
|
|
677
688
|
return {
|
|
678
689
|
id: kimiResponse.id ?? `msg_${Date.now()}`,
|
|
679
690
|
type: "message",
|
|
@@ -683,9 +694,9 @@ function transformKimiResponseToAnthropic(kimiResponse, originalModel) {
|
|
|
683
694
|
stop_reason: finishMap[choice.finish_reason] ?? "end_turn",
|
|
684
695
|
stop_sequence: null,
|
|
685
696
|
usage: {
|
|
686
|
-
input_tokens: (kimiResponse.usage?.prompt_tokens ?? 0) -
|
|
697
|
+
input_tokens: (kimiResponse.usage?.prompt_tokens ?? 0) - cachedTokens,
|
|
687
698
|
output_tokens: kimiResponse.usage?.completion_tokens ?? 0,
|
|
688
|
-
cache_read_input_tokens:
|
|
699
|
+
cache_read_input_tokens: cachedTokens,
|
|
689
700
|
cache_creation_input_tokens: kimiResponse.usage?.prompt_tokens_details?.cache_creation_tokens ?? 0
|
|
690
701
|
}
|
|
691
702
|
};
|
|
@@ -694,17 +705,17 @@ function transformKimiResponseToAnthropic(kimiResponse, originalModel) {
|
|
|
694
705
|
// src/provider/server.ts
|
|
695
706
|
import http from "http";
|
|
696
707
|
import os3 from "os";
|
|
697
|
-
function getKimiHeaders() {
|
|
708
|
+
function getKimiHeaders(claudeVersion) {
|
|
698
709
|
const platform = os3.platform();
|
|
699
710
|
const stainlessOS = platform === "darwin" ? "MacOS" : platform === "win32" ? "Windows" : "Linux";
|
|
700
711
|
return {
|
|
701
712
|
"X-Stainless-OS": stainlessOS,
|
|
702
|
-
"X-Stainless-Package-Version": "1.1.
|
|
713
|
+
"X-Stainless-Package-Version": "1.1.18",
|
|
703
714
|
"X-Stainless-Runtime": "node",
|
|
704
|
-
"User-Agent":
|
|
715
|
+
"User-Agent": `claude-code/${claudeVersion}`
|
|
705
716
|
};
|
|
706
717
|
}
|
|
707
|
-
async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMappings = {}, provider = "openai") {
|
|
718
|
+
async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMappings = {}, provider = "openai", claudeVersion = "0.0.0") {
|
|
708
719
|
const base = targetUrl.replace(/\/+$/, "");
|
|
709
720
|
const server = http.createServer(async (req, res) => {
|
|
710
721
|
debug(`Proxy request: ${req.method} ${req.url}`);
|
|
@@ -747,7 +758,7 @@ async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMapp
|
|
|
747
758
|
headers: {
|
|
748
759
|
"Content-Type": "application/json",
|
|
749
760
|
"Authorization": `Bearer ${apiKey}`,
|
|
750
|
-
...provider === "kimi" ? getKimiHeaders() : {}
|
|
761
|
+
...provider === "kimi" ? getKimiHeaders(claudeVersion) : {}
|
|
751
762
|
},
|
|
752
763
|
body: JSON.stringify(openaiBody)
|
|
753
764
|
});
|
|
@@ -778,7 +789,7 @@ data: ${errText}
|
|
|
778
789
|
headers: {
|
|
779
790
|
"Content-Type": "application/json",
|
|
780
791
|
"Authorization": `Bearer ${apiKey}`,
|
|
781
|
-
...provider === "kimi" ? getKimiHeaders() : {}
|
|
792
|
+
...provider === "kimi" ? getKimiHeaders(claudeVersion) : {}
|
|
782
793
|
},
|
|
783
794
|
body: JSON.stringify(openaiBody)
|
|
784
795
|
});
|
|
@@ -992,12 +1003,48 @@ var DesktopProfileSyncer = class {
|
|
|
992
1003
|
|
|
993
1004
|
// src/platform/binary-resolver.ts
|
|
994
1005
|
import { spawnSync } from "child_process";
|
|
1006
|
+
var cachedVersion;
|
|
1007
|
+
function getClaudeVersion() {
|
|
1008
|
+
if (cachedVersion) return cachedVersion;
|
|
1009
|
+
debug("binary-resolver: detecting Claude version");
|
|
1010
|
+
try {
|
|
1011
|
+
const result = spawnSync("claude", ["--version"], {
|
|
1012
|
+
shell: process.platform === "win32",
|
|
1013
|
+
encoding: "utf-8"
|
|
1014
|
+
});
|
|
1015
|
+
if (result.status === 0 && result.stdout) {
|
|
1016
|
+
const match = result.stdout.trim().match(/^(\d+\.\d+\.\d+)/);
|
|
1017
|
+
if (match) {
|
|
1018
|
+
cachedVersion = match[1];
|
|
1019
|
+
debug(`binary-resolver: detected Claude version ${cachedVersion}`);
|
|
1020
|
+
return cachedVersion;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
} catch {
|
|
1024
|
+
}
|
|
1025
|
+
cachedVersion = "0.0.0";
|
|
1026
|
+
debug("binary-resolver: could not detect Claude version, using default");
|
|
1027
|
+
return cachedVersion;
|
|
1028
|
+
}
|
|
1029
|
+
function getPinnedVersion() {
|
|
1030
|
+
try {
|
|
1031
|
+
ensureSettingsFile();
|
|
1032
|
+
const settings2 = readJson(SETTINGS_FILE);
|
|
1033
|
+
if (settings2.minimumVersion && settings2.requiredMaximumVersion && settings2.minimumVersion === settings2.requiredMaximumVersion) {
|
|
1034
|
+
return settings2.minimumVersion;
|
|
1035
|
+
}
|
|
1036
|
+
return void 0;
|
|
1037
|
+
} catch {
|
|
1038
|
+
return void 0;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
995
1041
|
var SystemBinaryResolver = class {
|
|
996
1042
|
constructor(app) {
|
|
997
1043
|
this.app = app;
|
|
998
1044
|
}
|
|
999
1045
|
app;
|
|
1000
|
-
resolve() {
|
|
1046
|
+
resolve(pinnedVersion) {
|
|
1047
|
+
const pin = pinnedVersion ?? getPinnedVersion();
|
|
1001
1048
|
debug("binary-resolver: trying global 'claude' command");
|
|
1002
1049
|
try {
|
|
1003
1050
|
const result = spawnSync("claude", ["--version"], {
|
|
@@ -1006,12 +1053,22 @@ var SystemBinaryResolver = class {
|
|
|
1006
1053
|
});
|
|
1007
1054
|
if (result.status === 0) {
|
|
1008
1055
|
debug("binary-resolver: found global 'claude'");
|
|
1056
|
+
if (pin) {
|
|
1057
|
+
const currentVersion = getClaudeVersion();
|
|
1058
|
+
if (currentVersion !== pin) {
|
|
1059
|
+
warn(`binary-resolver: global claude version ${currentVersion} does not match pinned version ${pin}`);
|
|
1060
|
+
console.warn(`Warning: installed Claude version (${currentVersion}) does not match pinned version (${pin}).`);
|
|
1061
|
+
console.warn(`To install the pinned version, run: npm install -g @anthropic-ai/claude-code@${pin}`);
|
|
1062
|
+
} else {
|
|
1063
|
+
debug(`binary-resolver: global claude version matches pinned ${pin}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1009
1066
|
return "claude";
|
|
1010
1067
|
}
|
|
1011
1068
|
} catch {
|
|
1012
1069
|
}
|
|
1013
1070
|
debug("binary-resolver: trying desktop app binary");
|
|
1014
|
-
const desktopBinary = this.app.findBinary();
|
|
1071
|
+
const desktopBinary = this.app.findBinary(pin);
|
|
1015
1072
|
if (desktopBinary) {
|
|
1016
1073
|
debug(`binary-resolver: found desktop binary at ${desktopBinary}`);
|
|
1017
1074
|
return desktopBinary;
|
|
@@ -1234,7 +1291,8 @@ function execClaude(profileName, p, extraArgs) {
|
|
|
1234
1291
|
firstModel || "gpt-4o",
|
|
1235
1292
|
allModels,
|
|
1236
1293
|
{},
|
|
1237
|
-
p.provider
|
|
1294
|
+
p.provider,
|
|
1295
|
+
getClaudeVersion()
|
|
1238
1296
|
).then(({ baseUrl, stop }) => {
|
|
1239
1297
|
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
1240
1298
|
debug(`execClaude: proxy running at ${baseUrl}`);
|
|
@@ -2386,6 +2444,7 @@ _cc-hub() {
|
|
|
2386
2444
|
'session:Manage Claude Code sessions'
|
|
2387
2445
|
'provider:Manage provider types'
|
|
2388
2446
|
'cache:Manage Claude Code cache and backup files'
|
|
2447
|
+
'claude-version:Manage Claude Code CLI versions'
|
|
2389
2448
|
'completion:Print shell completion functions'
|
|
2390
2449
|
'help:Display help for a command'
|
|
2391
2450
|
)
|
|
@@ -2434,6 +2493,12 @@ _cc-hub() {
|
|
|
2434
2493
|
'troubleshoot:Launch Claude Code to troubleshoot a session file'
|
|
2435
2494
|
)
|
|
2436
2495
|
|
|
2496
|
+
local -a claude_version_subcmds
|
|
2497
|
+
claude_version_subcmds=(
|
|
2498
|
+
'list:List available Claude Code versions'
|
|
2499
|
+
'pin:Pin Claude Code to a specific version'
|
|
2500
|
+
)
|
|
2501
|
+
|
|
2437
2502
|
_cc_hub_profiles() {
|
|
2438
2503
|
local profiles_file="\${CLAUDE_PROFILES_FILE:-$HOME/.claude/profiles.json}"
|
|
2439
2504
|
if [[ -f "$profiles_file" ]]; then
|
|
@@ -2520,6 +2585,13 @@ _cc-hub() {
|
|
|
2520
2585
|
_arguments -C -S '(-i --interactive)'{-i,--interactive}'[Open an interactive Claude Code window instead of a one-shot prompt]'
|
|
2521
2586
|
fi
|
|
2522
2587
|
;;
|
|
2588
|
+
claude-version)
|
|
2589
|
+
if (( CURRENT == 2 )); then
|
|
2590
|
+
_describe -t claude-version-subcmds 'claude-version subcommand' claude_version_subcmds
|
|
2591
|
+
elif [[ $words[2] == "pin" ]]; then
|
|
2592
|
+
_arguments -C -S '--clear[Remove the version pin]' '*:version:'
|
|
2593
|
+
fi
|
|
2594
|
+
;;
|
|
2523
2595
|
esac
|
|
2524
2596
|
;;
|
|
2525
2597
|
esac
|
|
@@ -2573,7 +2645,7 @@ _cc-hub() {
|
|
|
2573
2645
|
COMPREPLY=()
|
|
2574
2646
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
2575
2647
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
2576
|
-
commands="profile use run hook session provider cache completion help"
|
|
2648
|
+
commands="profile use run hook session provider cache claude-version completion help"
|
|
2577
2649
|
|
|
2578
2650
|
local profile_subcmds="add update list view remove rename default sync export"
|
|
2579
2651
|
local provider_subcmds="list"
|
|
@@ -2581,6 +2653,7 @@ _cc-hub() {
|
|
|
2581
2653
|
local hooks_subcmds="list add remove enable disable"
|
|
2582
2654
|
local session_subcmds="list show search ps stats clean troubleshoot"
|
|
2583
2655
|
local cache_subcmds="restore"
|
|
2656
|
+
local claude_version_subcmds="list pin"
|
|
2584
2657
|
|
|
2585
2658
|
# Top-level command
|
|
2586
2659
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
@@ -2651,6 +2724,18 @@ _cc-hub() {
|
|
|
2651
2724
|
fi
|
|
2652
2725
|
fi
|
|
2653
2726
|
;;
|
|
2727
|
+
claude-version)
|
|
2728
|
+
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
2729
|
+
COMPREPLY=($(compgen -W "$claude_version_subcmds" -- "$cur"))
|
|
2730
|
+
elif [[ "\${COMP_WORDS[2]}" == "pin" ]]; then
|
|
2731
|
+
if [[ "$prev" == "--clear" ]]; then
|
|
2732
|
+
:
|
|
2733
|
+
else
|
|
2734
|
+
local pin_opts="--clear"
|
|
2735
|
+
COMPREPLY=($(compgen -W "$pin_opts" -- "$cur"))
|
|
2736
|
+
fi
|
|
2737
|
+
fi
|
|
2738
|
+
;;
|
|
2654
2739
|
esac
|
|
2655
2740
|
|
|
2656
2741
|
return 0
|
|
@@ -2671,6 +2756,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2671
2756
|
'session:Manage Claude Code sessions'
|
|
2672
2757
|
'provider:Manage provider types'
|
|
2673
2758
|
'cache:Manage Claude Code cache and backup files'
|
|
2759
|
+
'claude-version:Manage Claude Code CLI versions'
|
|
2674
2760
|
'completion:Print shell completion functions'
|
|
2675
2761
|
'help:Display help for a command'
|
|
2676
2762
|
)
|
|
@@ -2680,6 +2766,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2680
2766
|
$sessionSubcmds = @('list', 'show', 'search', 'ps', 'stats', 'clean', 'troubleshoot')
|
|
2681
2767
|
$cacheSubcmds = @('restore')
|
|
2682
2768
|
$providerSubcmds = @('list')
|
|
2769
|
+
$claudeVersionSubcmds = @('list', 'pin')
|
|
2683
2770
|
|
|
2684
2771
|
$tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
|
|
2685
2772
|
|
|
@@ -2725,6 +2812,16 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2725
2812
|
return
|
|
2726
2813
|
}
|
|
2727
2814
|
}
|
|
2815
|
+
'claude-version' {
|
|
2816
|
+
if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
|
|
2817
|
+
$claudeVersionSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
2818
|
+
return
|
|
2819
|
+
}
|
|
2820
|
+
if ($tokens[2] -eq 'pin' -and $tokens.Count -ge 3) {
|
|
2821
|
+
$opts = @('--clear')
|
|
2822
|
+
$opts | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2728
2825
|
'use' {
|
|
2729
2826
|
$opts = @('--built-in')
|
|
2730
2827
|
$opts | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
@@ -2796,7 +2893,15 @@ function proxyCommand() {
|
|
|
2796
2893
|
}
|
|
2797
2894
|
models = [defaultModel];
|
|
2798
2895
|
}
|
|
2799
|
-
const { baseUrl, stop } = await startOpenAIProxy(
|
|
2896
|
+
const { baseUrl, stop } = await startOpenAIProxy(
|
|
2897
|
+
targetUrl,
|
|
2898
|
+
apiKey,
|
|
2899
|
+
defaultModel,
|
|
2900
|
+
models,
|
|
2901
|
+
modelMappings,
|
|
2902
|
+
"openai",
|
|
2903
|
+
getClaudeVersion()
|
|
2904
|
+
);
|
|
2800
2905
|
console.log(`Proxy running at ${baseUrl}`);
|
|
2801
2906
|
console.log("Press Ctrl+C to stop");
|
|
2802
2907
|
process.on("SIGINT", () => {
|
|
@@ -2882,8 +2987,8 @@ function killProcesses(pids) {
|
|
|
2882
2987
|
}
|
|
2883
2988
|
}
|
|
2884
2989
|
function cacheCommand() {
|
|
2885
|
-
const
|
|
2886
|
-
|
|
2990
|
+
const cache2 = new Command7("cache").description("Manage Claude Code cache and backup files");
|
|
2991
|
+
cache2.command("restore").description("Restore ~/.claude/.claude.json.backup to ~/.claude.json").action(safeAction(async () => {
|
|
2887
2992
|
const backupPath = path9.join(CLAUDE_DIR, ".claude.json.backup");
|
|
2888
2993
|
const targetPath = CLAUDE_JSON;
|
|
2889
2994
|
if (!fs8.existsSync(backupPath)) {
|
|
@@ -2905,7 +3010,169 @@ function cacheCommand() {
|
|
|
2905
3010
|
debug(`cache restore: restored ${backupPath} -> ${targetPath}`);
|
|
2906
3011
|
console.log(`Restored ${backupPath} -> ${targetPath}`);
|
|
2907
3012
|
}));
|
|
2908
|
-
return
|
|
3013
|
+
return cache2;
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
// src/claude-version/commands.ts
|
|
3017
|
+
import { Command as Command8 } from "commander";
|
|
3018
|
+
import fs9 from "fs";
|
|
3019
|
+
import path10 from "path";
|
|
3020
|
+
|
|
3021
|
+
// src/claude-version/fetcher.ts
|
|
3022
|
+
var cache = null;
|
|
3023
|
+
var cacheTime = 0;
|
|
3024
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3025
|
+
async function fetchChangelogVersions() {
|
|
3026
|
+
if (cache && Date.now() - cacheTime < CACHE_TTL_MS) {
|
|
3027
|
+
return cache;
|
|
3028
|
+
}
|
|
3029
|
+
const url = "https://code.claude.com/docs/en/changelog";
|
|
3030
|
+
const response = await fetch(url, {
|
|
3031
|
+
headers: {
|
|
3032
|
+
"User-Agent": `cc-hub/${"1.1.18"}`
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
3035
|
+
if (!response.ok) {
|
|
3036
|
+
throw new Error(`Failed to fetch changelog: ${response.status} ${response.statusText}`);
|
|
3037
|
+
}
|
|
3038
|
+
const html = await response.text();
|
|
3039
|
+
const versions = parseChangelogHtml(html);
|
|
3040
|
+
cache = versions;
|
|
3041
|
+
cacheTime = Date.now();
|
|
3042
|
+
return versions;
|
|
3043
|
+
}
|
|
3044
|
+
function parseChangelogHtml(html) {
|
|
3045
|
+
const results = [];
|
|
3046
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3047
|
+
const componentRegex = /label:\s*"(\d+\.\d+\.\d+)"[\s\S]*?description:\s*"([^"]{5,60})"/g;
|
|
3048
|
+
let match;
|
|
3049
|
+
while ((match = componentRegex.exec(html)) !== null) {
|
|
3050
|
+
const version2 = match[1];
|
|
3051
|
+
const date = match[2].trim();
|
|
3052
|
+
if (seen.has(version2)) continue;
|
|
3053
|
+
seen.add(version2);
|
|
3054
|
+
results.push({ version: version2, date });
|
|
3055
|
+
}
|
|
3056
|
+
const versionRegex = /<div[^>]*contenteditable=["']?false["']?[^>]*>(\d+\.\d+\.\d+)<\/div>/gi;
|
|
3057
|
+
while ((match = versionRegex.exec(html)) !== null) {
|
|
3058
|
+
const version2 = match[1];
|
|
3059
|
+
if (seen.has(version2)) continue;
|
|
3060
|
+
seen.add(version2);
|
|
3061
|
+
const startIdx = match.index + match[0].length;
|
|
3062
|
+
const snippet2 = html.slice(startIdx, startIdx + 500);
|
|
3063
|
+
const dateMatch = snippet2.match(/<p>([^<]{5,60})<\/p>/) || snippet2.match(/data-component-part=["']?update-description["']?[^>]*>([^<]{5,60})<\/div>/);
|
|
3064
|
+
const date = dateMatch ? dateMatch[1].trim() : "";
|
|
3065
|
+
results.push({ version: version2, date });
|
|
3066
|
+
}
|
|
3067
|
+
const headingRegex = /<h[23][^>]*>(\d+\.\d+\.\d+)[^<]*<\/h[23]>/gi;
|
|
3068
|
+
while ((match = headingRegex.exec(html)) !== null) {
|
|
3069
|
+
const version2 = match[1];
|
|
3070
|
+
if (seen.has(version2)) continue;
|
|
3071
|
+
seen.add(version2);
|
|
3072
|
+
const startIdx = match.index + match[0].length;
|
|
3073
|
+
const snippet2 = html.slice(startIdx, startIdx + 500);
|
|
3074
|
+
const dateMatch = snippet2.match(/<p>([^<]{5,60})<\/p>/);
|
|
3075
|
+
const date = dateMatch ? dateMatch[1].trim() : "";
|
|
3076
|
+
results.push({ version: version2, date });
|
|
3077
|
+
}
|
|
3078
|
+
return results;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
// src/claude-version/utils.ts
|
|
3082
|
+
function getPinnedVersion2() {
|
|
3083
|
+
ensureSettingsFile();
|
|
3084
|
+
const settings2 = readJson(SETTINGS_FILE);
|
|
3085
|
+
return settings2.minimumVersion && settings2.requiredMaximumVersion && settings2.minimumVersion === settings2.requiredMaximumVersion ? settings2.minimumVersion : void 0;
|
|
3086
|
+
}
|
|
3087
|
+
function setPinnedVersion(version2) {
|
|
3088
|
+
ensureSettingsFile();
|
|
3089
|
+
const settings2 = readJson(SETTINGS_FILE);
|
|
3090
|
+
if (version2) {
|
|
3091
|
+
settings2.minimumVersion = version2;
|
|
3092
|
+
settings2.requiredMaximumVersion = version2;
|
|
3093
|
+
} else {
|
|
3094
|
+
delete settings2.minimumVersion;
|
|
3095
|
+
delete settings2.requiredMaximumVersion;
|
|
3096
|
+
}
|
|
3097
|
+
writeJson(SETTINGS_FILE, settings2);
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// src/claude-version/commands.ts
|
|
3101
|
+
function claudeVersionCommand() {
|
|
3102
|
+
const cmd = new Command8("claude-version").description("Manage Claude Code CLI versions");
|
|
3103
|
+
cmd.command("list").description("List available Claude Code versions").action(safeAction(async () => {
|
|
3104
|
+
const [remoteVersions, installedVersion, pinnedVersion] = await Promise.all([
|
|
3105
|
+
fetchChangelogVersions().catch(() => []),
|
|
3106
|
+
Promise.resolve(getClaudeVersion()).catch(() => "unknown"),
|
|
3107
|
+
Promise.resolve(getPinnedVersion2())
|
|
3108
|
+
]);
|
|
3109
|
+
if (remoteVersions.length === 0) {
|
|
3110
|
+
console.log("Could not fetch remote versions. Showing installed version only.");
|
|
3111
|
+
console.log(`Installed: ${installedVersion}`);
|
|
3112
|
+
if (pinnedVersion) {
|
|
3113
|
+
console.log(`Pinned: ${pinnedVersion}`);
|
|
3114
|
+
}
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
3117
|
+
console.log("Available Claude Code versions:");
|
|
3118
|
+
console.log("");
|
|
3119
|
+
const maxVersionLen = Math.max(...remoteVersions.map((v) => v.version.length), 10);
|
|
3120
|
+
const maxDateLen = Math.max(...remoteVersions.map((v) => v.date.length), 4);
|
|
3121
|
+
console.log(`${"Version".padEnd(maxVersionLen)} ${"Date".padEnd(maxDateLen)} Status`);
|
|
3122
|
+
console.log("-".repeat(maxVersionLen + maxDateLen + 10));
|
|
3123
|
+
for (const { version: version2, date } of remoteVersions.slice(0, 20)) {
|
|
3124
|
+
const markers = [];
|
|
3125
|
+
if (version2 === installedVersion) markers.push("installed");
|
|
3126
|
+
if (version2 === pinnedVersion) markers.push("pinned");
|
|
3127
|
+
const status = markers.length > 0 ? `(${markers.join(", ")})` : "";
|
|
3128
|
+
console.log(`${version2.padEnd(maxVersionLen)} ${(date || "\u2014").padEnd(maxDateLen)} ${status}`);
|
|
3129
|
+
}
|
|
3130
|
+
if (remoteVersions.length > 20) {
|
|
3131
|
+
console.log(`... and ${remoteVersions.length - 20} more versions`);
|
|
3132
|
+
}
|
|
3133
|
+
console.log("");
|
|
3134
|
+
console.log(`Installed: ${installedVersion}`);
|
|
3135
|
+
if (pinnedVersion) {
|
|
3136
|
+
console.log(`Pinned: ${pinnedVersion}`);
|
|
3137
|
+
} else {
|
|
3138
|
+
console.log("No version pinned (using latest)");
|
|
3139
|
+
}
|
|
3140
|
+
}));
|
|
3141
|
+
cmd.command("pin [version]").description("Pin Claude Code to a specific version (omit or --clear to unpin)").option("--clear", "Remove the version pin").action(safeAction(async (version2, opts) => {
|
|
3142
|
+
if (opts.clear || !version2) {
|
|
3143
|
+
setPinnedVersion(void 0);
|
|
3144
|
+
console.log("Version pin cleared. cc-hub will use the latest available Claude Code version.");
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
3147
|
+
if (!/^\d+\.\d+\.\d+$/.test(version2)) {
|
|
3148
|
+
console.error(`Invalid version format: ${version2}. Expected format: x.y.z`);
|
|
3149
|
+
process.exit(1);
|
|
3150
|
+
}
|
|
3151
|
+
let localAvailable = false;
|
|
3152
|
+
if (process.platform === "darwin") {
|
|
3153
|
+
const desktopApp2 = new MacOSDesktopApp();
|
|
3154
|
+
const supportDir = desktopApp2.getSupportDir();
|
|
3155
|
+
if (supportDir) {
|
|
3156
|
+
const claudeCodeDir = path10.join(supportDir, "claude-code");
|
|
3157
|
+
if (fs9.existsSync(claudeCodeDir)) {
|
|
3158
|
+
const versions = fs9.readdirSync(claudeCodeDir).filter(
|
|
3159
|
+
(d) => fs9.existsSync(path10.join(claudeCodeDir, d, "claude.app", "Contents", "MacOS", "claude"))
|
|
3160
|
+
);
|
|
3161
|
+
localAvailable = versions.includes(version2);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
setPinnedVersion(version2);
|
|
3166
|
+
console.log(`Pinned Claude Code version: ${version2}`);
|
|
3167
|
+
if (process.platform === "darwin" && !localAvailable) {
|
|
3168
|
+
console.warn(`Warning: version ${version2} is not installed in the desktop app. The pin will take effect once that version is available, or you can install it via:`);
|
|
3169
|
+
console.warn(` npm install -g @anthropic-ai/claude-code@${version2}`);
|
|
3170
|
+
} else if (process.platform !== "darwin") {
|
|
3171
|
+
console.log(`Note: on ${process.platform}, ensure the global install matches the pinned version:`);
|
|
3172
|
+
console.log(` npm install -g @anthropic-ai/claude-code@${version2}`);
|
|
3173
|
+
}
|
|
3174
|
+
}));
|
|
3175
|
+
return cmd;
|
|
2909
3176
|
}
|
|
2910
3177
|
|
|
2911
3178
|
// src/index.ts
|
|
@@ -2915,7 +3182,7 @@ ensureSettingsFile();
|
|
|
2915
3182
|
var settings = readJson(SETTINGS_FILE);
|
|
2916
3183
|
setLogLevel(settings._cc_hub_logLevel || "INFO");
|
|
2917
3184
|
installGlobalExceptionHandlers();
|
|
2918
|
-
var program = new
|
|
3185
|
+
var program = new Command9();
|
|
2919
3186
|
program.name("cc-hub").description("Manage Claude CLI profiles, hooks, and sessions").version(version);
|
|
2920
3187
|
program.addCommand(profileCommand());
|
|
2921
3188
|
program.addCommand(useCommand());
|
|
@@ -2926,6 +3193,7 @@ program.addCommand(completionCommand());
|
|
|
2926
3193
|
program.addCommand(providerCommand());
|
|
2927
3194
|
program.addCommand(proxyCommand());
|
|
2928
3195
|
program.addCommand(cacheCommand());
|
|
3196
|
+
program.addCommand(claudeVersionCommand());
|
|
2929
3197
|
try {
|
|
2930
3198
|
program.parse();
|
|
2931
3199
|
} catch (err) {
|