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.
Files changed (2) hide show
  1. package/dist/index.js +290 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command8 } from "commander";
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
- const binary = path2.join(claudeCodeDir, versions[0], "claude.app", "Contents", "MacOS", "claude");
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) - (kimiResponse.usage?.prompt_tokens_details?.cached_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: kimiResponse.usage?.prompt_tokens_details?.cached_tokens ?? 0,
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.16",
713
+ "X-Stainless-Package-Version": "1.1.18",
703
714
  "X-Stainless-Runtime": "node",
704
- "User-Agent": "claude-code/1.0"
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(targetUrl, apiKey, defaultModel, models, modelMappings);
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 cache = new Command7("cache").description("Manage Claude Code cache and backup files");
2886
- cache.command("restore").description("Restore ~/.claude/.claude.json.backup to ~/.claude.json").action(safeAction(async () => {
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 cache;
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 Command8();
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hub-cli",
3
- "version": "1.1.16",
3
+ "version": "1.1.18",
4
4
  "description": "Manage Claude CLI profiles, hooks, and sessions",
5
5
  "type": "module",
6
6
  "bin": {