cc-hub-cli 1.1.17 → 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 +248 -13
  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
  };
@@ -700,7 +710,7 @@ function getKimiHeaders(claudeVersion) {
700
710
  const stainlessOS = platform === "darwin" ? "MacOS" : platform === "win32" ? "Windows" : "Linux";
701
711
  return {
702
712
  "X-Stainless-OS": stainlessOS,
703
- "X-Stainless-Package-Version": "1.1.17",
713
+ "X-Stainless-Package-Version": "1.1.18",
704
714
  "X-Stainless-Runtime": "node",
705
715
  "User-Agent": `claude-code/${claudeVersion}`
706
716
  };
@@ -1016,12 +1026,25 @@ function getClaudeVersion() {
1016
1026
  debug("binary-resolver: could not detect Claude version, using default");
1017
1027
  return cachedVersion;
1018
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
+ }
1019
1041
  var SystemBinaryResolver = class {
1020
1042
  constructor(app) {
1021
1043
  this.app = app;
1022
1044
  }
1023
1045
  app;
1024
- resolve() {
1046
+ resolve(pinnedVersion) {
1047
+ const pin = pinnedVersion ?? getPinnedVersion();
1025
1048
  debug("binary-resolver: trying global 'claude' command");
1026
1049
  try {
1027
1050
  const result = spawnSync("claude", ["--version"], {
@@ -1030,12 +1053,22 @@ var SystemBinaryResolver = class {
1030
1053
  });
1031
1054
  if (result.status === 0) {
1032
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
+ }
1033
1066
  return "claude";
1034
1067
  }
1035
1068
  } catch {
1036
1069
  }
1037
1070
  debug("binary-resolver: trying desktop app binary");
1038
- const desktopBinary = this.app.findBinary();
1071
+ const desktopBinary = this.app.findBinary(pin);
1039
1072
  if (desktopBinary) {
1040
1073
  debug(`binary-resolver: found desktop binary at ${desktopBinary}`);
1041
1074
  return desktopBinary;
@@ -2411,6 +2444,7 @@ _cc-hub() {
2411
2444
  'session:Manage Claude Code sessions'
2412
2445
  'provider:Manage provider types'
2413
2446
  'cache:Manage Claude Code cache and backup files'
2447
+ 'claude-version:Manage Claude Code CLI versions'
2414
2448
  'completion:Print shell completion functions'
2415
2449
  'help:Display help for a command'
2416
2450
  )
@@ -2459,6 +2493,12 @@ _cc-hub() {
2459
2493
  'troubleshoot:Launch Claude Code to troubleshoot a session file'
2460
2494
  )
2461
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
+
2462
2502
  _cc_hub_profiles() {
2463
2503
  local profiles_file="\${CLAUDE_PROFILES_FILE:-$HOME/.claude/profiles.json}"
2464
2504
  if [[ -f "$profiles_file" ]]; then
@@ -2545,6 +2585,13 @@ _cc-hub() {
2545
2585
  _arguments -C -S '(-i --interactive)'{-i,--interactive}'[Open an interactive Claude Code window instead of a one-shot prompt]'
2546
2586
  fi
2547
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
+ ;;
2548
2595
  esac
2549
2596
  ;;
2550
2597
  esac
@@ -2598,7 +2645,7 @@ _cc-hub() {
2598
2645
  COMPREPLY=()
2599
2646
  cur="\${COMP_WORDS[COMP_CWORD]}"
2600
2647
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
2601
- commands="profile use run hook session provider cache completion help"
2648
+ commands="profile use run hook session provider cache claude-version completion help"
2602
2649
 
2603
2650
  local profile_subcmds="add update list view remove rename default sync export"
2604
2651
  local provider_subcmds="list"
@@ -2606,6 +2653,7 @@ _cc-hub() {
2606
2653
  local hooks_subcmds="list add remove enable disable"
2607
2654
  local session_subcmds="list show search ps stats clean troubleshoot"
2608
2655
  local cache_subcmds="restore"
2656
+ local claude_version_subcmds="list pin"
2609
2657
 
2610
2658
  # Top-level command
2611
2659
  if [[ \${COMP_CWORD} -eq 1 ]]; then
@@ -2676,6 +2724,18 @@ _cc-hub() {
2676
2724
  fi
2677
2725
  fi
2678
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
+ ;;
2679
2739
  esac
2680
2740
 
2681
2741
  return 0
@@ -2696,6 +2756,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
2696
2756
  'session:Manage Claude Code sessions'
2697
2757
  'provider:Manage provider types'
2698
2758
  'cache:Manage Claude Code cache and backup files'
2759
+ 'claude-version:Manage Claude Code CLI versions'
2699
2760
  'completion:Print shell completion functions'
2700
2761
  'help:Display help for a command'
2701
2762
  )
@@ -2705,6 +2766,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
2705
2766
  $sessionSubcmds = @('list', 'show', 'search', 'ps', 'stats', 'clean', 'troubleshoot')
2706
2767
  $cacheSubcmds = @('restore')
2707
2768
  $providerSubcmds = @('list')
2769
+ $claudeVersionSubcmds = @('list', 'pin')
2708
2770
 
2709
2771
  $tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
2710
2772
 
@@ -2750,6 +2812,16 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
2750
2812
  return
2751
2813
  }
2752
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
+ }
2753
2825
  'use' {
2754
2826
  $opts = @('--built-in')
2755
2827
  $opts | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
@@ -2915,8 +2987,8 @@ function killProcesses(pids) {
2915
2987
  }
2916
2988
  }
2917
2989
  function cacheCommand() {
2918
- const cache = new Command7("cache").description("Manage Claude Code cache and backup files");
2919
- 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 () => {
2920
2992
  const backupPath = path9.join(CLAUDE_DIR, ".claude.json.backup");
2921
2993
  const targetPath = CLAUDE_JSON;
2922
2994
  if (!fs8.existsSync(backupPath)) {
@@ -2938,7 +3010,169 @@ function cacheCommand() {
2938
3010
  debug(`cache restore: restored ${backupPath} -> ${targetPath}`);
2939
3011
  console.log(`Restored ${backupPath} -> ${targetPath}`);
2940
3012
  }));
2941
- 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;
2942
3176
  }
2943
3177
 
2944
3178
  // src/index.ts
@@ -2948,7 +3182,7 @@ ensureSettingsFile();
2948
3182
  var settings = readJson(SETTINGS_FILE);
2949
3183
  setLogLevel(settings._cc_hub_logLevel || "INFO");
2950
3184
  installGlobalExceptionHandlers();
2951
- var program = new Command8();
3185
+ var program = new Command9();
2952
3186
  program.name("cc-hub").description("Manage Claude CLI profiles, hooks, and sessions").version(version);
2953
3187
  program.addCommand(profileCommand());
2954
3188
  program.addCommand(useCommand());
@@ -2959,6 +3193,7 @@ program.addCommand(completionCommand());
2959
3193
  program.addCommand(providerCommand());
2960
3194
  program.addCommand(proxyCommand());
2961
3195
  program.addCommand(cacheCommand());
3196
+ program.addCommand(claudeVersionCommand());
2962
3197
  try {
2963
3198
  program.parse();
2964
3199
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hub-cli",
3
- "version": "1.1.17",
3
+ "version": "1.1.18",
4
4
  "description": "Manage Claude CLI profiles, hooks, and sessions",
5
5
  "type": "module",
6
6
  "bin": {