feat: patch ALL installed cli.js locations for multi-install support
- Add find_all_cli_js() — discovers all cli.js paths across npm global dirs, which claude symlink resolution, and NVM per-user installs - Update cmd_update() to iterate all found paths and patch each one - Update ensure_claude_code() to use find_all_cli_js() for install detection - Fixes: `claude --version` showing old version when multiple Claude Code installs exist (e.g. /usr/lib vs /usr/local/lib vs NVM) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -218,7 +218,8 @@ def ensure_claude_code(target_version=None):
|
|||||||
If target_version is set and the installed version doesn't match,
|
If target_version is set and the installed version doesn't match,
|
||||||
reinstall to the exact version so cli.js patch is compatible.
|
reinstall to the exact version so cli.js patch is compatible.
|
||||||
"""
|
"""
|
||||||
cli_js = find_cli_js()
|
all_paths = find_all_cli_js()
|
||||||
|
cli_js = all_paths[0] if all_paths else None
|
||||||
|
|
||||||
# If already installed, check version compatibility
|
# If already installed, check version compatibility
|
||||||
if cli_js and target_version:
|
if cli_js and target_version:
|
||||||
@@ -241,8 +242,8 @@ def ensure_claude_code(target_version=None):
|
|||||||
capture_output=True, text=True, timeout=300,
|
capture_output=True, text=True, timeout=300,
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
cli_js = find_cli_js()
|
found = find_all_cli_js()
|
||||||
if cli_js:
|
if found:
|
||||||
new_ver, _ = get_installed_version()
|
new_ver, _ = get_installed_version()
|
||||||
print(f" {G}Claude Code {new_ver or ''} installed{D}")
|
print(f" {G}Claude Code {new_ver or ''} installed{D}")
|
||||||
return True
|
return True
|
||||||
@@ -296,6 +297,58 @@ def find_cli_js():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_all_cli_js():
|
||||||
|
"""Find ALL installed Claude Code cli.js paths (for multi-install patching)."""
|
||||||
|
candidates = set()
|
||||||
|
|
||||||
|
if IS_WINDOWS:
|
||||||
|
for env_key in ("APPDATA", "LOCALAPPDATA", "PROGRAMFILES"):
|
||||||
|
base = os.environ.get(env_key, "")
|
||||||
|
if base:
|
||||||
|
candidates.add(os.path.join(base, "npm", "node_modules",
|
||||||
|
"@anthropic-ai", "claude-code", "cli.js"))
|
||||||
|
else:
|
||||||
|
# Static well-known paths
|
||||||
|
for prefix in ("/usr/lib", "/usr/local/lib", "/opt/homebrew/lib"):
|
||||||
|
candidates.add(os.path.join(prefix, "node_modules",
|
||||||
|
"@anthropic-ai", "claude-code", "cli.js"))
|
||||||
|
|
||||||
|
# npm root -g (primary install path)
|
||||||
|
try:
|
||||||
|
r = subprocess.run(["npm", "root", "-g"], capture_output=True, text=True, timeout=10)
|
||||||
|
if r.returncode == 0:
|
||||||
|
candidates.add(os.path.join(r.stdout.strip(),
|
||||||
|
"@anthropic-ai", "claude-code", "cli.js"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Resolve `which claude` → follow symlinks → find cli.js
|
||||||
|
try:
|
||||||
|
r = subprocess.run(["which", "claude"], capture_output=True, text=True, timeout=5)
|
||||||
|
if r.returncode == 0:
|
||||||
|
claude_bin = os.path.realpath(r.stdout.strip())
|
||||||
|
# claude binary is at node_modules/.bin/claude
|
||||||
|
# cli.js is in node_modules/@anthropic-ai/claude-code/cli.js
|
||||||
|
nm = os.path.dirname(os.path.dirname(claude_bin)) # node_modules/
|
||||||
|
candidates.add(os.path.join(nm, "@anthropic-ai", "claude-code", "cli.js"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# NVM installs: /root/.nvm, /home/*/.nvm
|
||||||
|
nvm_bases = ["/root/.nvm"]
|
||||||
|
if os.path.isdir("/home"):
|
||||||
|
for user in os.listdir("/home"):
|
||||||
|
nvm_bases.append(f"/home/{user}/.nvm")
|
||||||
|
for nvm_base in nvm_bases:
|
||||||
|
versions_dir = os.path.join(nvm_base, "versions", "node")
|
||||||
|
if os.path.isdir(versions_dir):
|
||||||
|
for node_ver in os.listdir(versions_dir):
|
||||||
|
candidates.add(os.path.join(versions_dir, node_ver, "lib",
|
||||||
|
"node_modules", "@anthropic-ai", "claude-code", "cli.js"))
|
||||||
|
|
||||||
|
return [p for p in candidates if os.path.isfile(p)]
|
||||||
|
|
||||||
|
|
||||||
def get_installed_version():
|
def get_installed_version():
|
||||||
"""Get currently installed Claude Code version.
|
"""Get currently installed Claude Code version.
|
||||||
|
|
||||||
@@ -879,9 +932,12 @@ def cmd_update(force=False, settings_only=False):
|
|||||||
|
|
||||||
# Install cli.js
|
# Install cli.js
|
||||||
if not settings_only:
|
if not settings_only:
|
||||||
if not cli_js:
|
all_paths = find_all_cli_js()
|
||||||
cli_js = find_cli_js()
|
if not all_paths:
|
||||||
if not cli_js:
|
cli_js_single = find_cli_js()
|
||||||
|
if cli_js_single:
|
||||||
|
all_paths = [cli_js_single]
|
||||||
|
if not all_paths:
|
||||||
eprint(f" {R}Claude Code cli.js not found even after install attempt.{D}")
|
eprint(f" {R}Claude Code cli.js not found even after install attempt.{D}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
@@ -890,9 +946,16 @@ def cmd_update(force=False, settings_only=False):
|
|||||||
eprint(f" Run with: sudo python3 {sys.argv[0]}")
|
eprint(f" Run with: sudo python3 {sys.argv[0]}")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print(f"\n{W}--- Installing cli.js v{latest} ---{D}")
|
print(f"\n{W}--- Installing cli.js v{latest} (found {len(all_paths)} location(s)) ---{D}")
|
||||||
ok = install_cli_js(latest, cli_js)
|
any_ok = False
|
||||||
if not ok:
|
for path in all_paths:
|
||||||
|
print(f" Patching: {path}")
|
||||||
|
ok = install_cli_js(latest, path)
|
||||||
|
if ok:
|
||||||
|
any_ok = True
|
||||||
|
else:
|
||||||
|
eprint(f" {Y}Failed to patch {path}, continuing...{D}")
|
||||||
|
if not any_ok:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Patch settings
|
# Patch settings
|
||||||
|
|||||||
Reference in New Issue
Block a user