1414import sys
1515import tempfile
1616import time
17+ import urllib .error
18+ import urllib .request
1719from pathlib import Path
1820from typing import Any
1921from urllib .request import urlopen
@@ -83,14 +85,21 @@ def banner() -> None:
8385def run_command (cmd : list , capture_output : bool = True , ** kwargs : Any ) -> subprocess .CompletedProcess :
8486 """Run a command and return the result."""
8587 try :
88+ # Debug: print command being run if not capturing output
89+ if not capture_output :
90+ info (f'Executing: { ", " .join (cmd )} ' )
8691 return subprocess .run (
8792 cmd ,
8893 capture_output = capture_output ,
8994 text = True ,
9095 ** kwargs ,
9196 )
92- except FileNotFoundError :
97+ except FileNotFoundError as e :
98+ error (f'Command not found: { cmd [0 ]} - { e } ' )
9399 return subprocess .CompletedProcess (cmd , 1 , '' , f'Command not found: { cmd [0 ]} ' )
100+ except Exception as e :
101+ error (f'Error running command { cmd [0 ]} : { e } ' )
102+ return subprocess .CompletedProcess (cmd , 1 , '' , str (e ))
94103
95104
96105def is_admin () -> bool :
@@ -197,13 +206,16 @@ def install_git_windows_download() -> bool:
197206 try :
198207 with urlopen (GIT_WINDOWS_URL ) as response :
199208 html = response .read ().decode ('utf-8' )
200- except ssl .SSLError :
201- warning ('SSL certificate verification failed, trying with unverified context' )
202- ctx = ssl .create_default_context ()
203- ctx .check_hostname = False
204- ctx .verify_mode = ssl .CERT_NONE
205- with urlopen (GIT_WINDOWS_URL , context = ctx ) as response :
206- html = response .read ().decode ('utf-8' )
209+ except urllib .error .URLError as e :
210+ if 'SSL' in str (e ) or 'certificate' in str (e ).lower ():
211+ warning ('SSL certificate verification failed, trying with unverified context' )
212+ ctx = ssl .create_default_context ()
213+ ctx .check_hostname = False
214+ ctx .verify_mode = ssl .CERT_NONE
215+ with urlopen (GIT_WINDOWS_URL , context = ctx ) as response :
216+ html = response .read ().decode ('utf-8' )
217+ else :
218+ raise
207219
208220 # Find the installer link
209221 match = re .search (r'href="([^"]+Git-[\d.]+-64-bit\.exe)"' , html )
@@ -221,15 +233,17 @@ def install_git_windows_download() -> bool:
221233 info (f'Downloading { installer_url } ' )
222234 try :
223235 urlretrieve (installer_url , temp_path )
224- except ssl .SSLError :
225- warning ('SSL certificate verification failed, trying with unverified context' )
226- ctx = ssl .create_default_context ()
227- ctx .check_hostname = False
228- ctx .verify_mode = ssl .CERT_NONE
229- import urllib .request
230- opener = urllib .request .build_opener (urllib .request .HTTPSHandler (context = ctx ))
231- urllib .request .install_opener (opener )
232- urlretrieve (installer_url , temp_path )
236+ except urllib .error .URLError as e :
237+ if 'SSL' in str (e ) or 'certificate' in str (e ).lower ():
238+ warning ('SSL certificate verification failed, trying with unverified context' )
239+ ctx = ssl .create_default_context ()
240+ ctx .check_hostname = False
241+ ctx .verify_mode = ssl .CERT_NONE
242+ opener = urllib .request .build_opener (urllib .request .HTTPSHandler (context = ctx ))
243+ urllib .request .install_opener (opener )
244+ urlretrieve (installer_url , temp_path )
245+ else :
246+ raise
233247
234248 # Run installer silently
235249 info ('Running Git installer silently...' )
@@ -383,13 +397,16 @@ def install_nodejs_direct() -> bool:
383397 try :
384398 with urlopen (NODE_LTS_API ) as response :
385399 versions = json .loads (response .read ())
386- except ssl .SSLError :
387- warning ('SSL certificate verification failed, trying with unverified context' )
388- ctx = ssl .create_default_context ()
389- ctx .check_hostname = False
390- ctx .verify_mode = ssl .CERT_NONE
391- with urlopen (NODE_LTS_API , context = ctx ) as response :
392- versions = json .loads (response .read ())
400+ except urllib .error .URLError as e :
401+ if 'SSL' in str (e ) or 'certificate' in str (e ).lower ():
402+ warning ('SSL certificate verification failed, trying with unverified context' )
403+ ctx = ssl .create_default_context ()
404+ ctx .check_hostname = False
405+ ctx .verify_mode = ssl .CERT_NONE
406+ with urlopen (NODE_LTS_API , context = ctx ) as response :
407+ versions = json .loads (response .read ())
408+ else :
409+ raise
393410
394411 lts_version = None
395412 for v in versions :
@@ -424,15 +441,17 @@ def install_nodejs_direct() -> bool:
424441 info (f'Downloading { installer_url } ' )
425442 try :
426443 urlretrieve (installer_url , temp_path )
427- except ssl .SSLError :
428- warning ('SSL certificate verification failed, trying with unverified context' )
429- ctx = ssl .create_default_context ()
430- ctx .check_hostname = False
431- ctx .verify_mode = ssl .CERT_NONE
432- import urllib .request
433- opener = urllib .request .build_opener (urllib .request .HTTPSHandler (context = ctx ))
434- urllib .request .install_opener (opener )
435- urlretrieve (installer_url , temp_path )
444+ except urllib .error .URLError as e :
445+ if 'SSL' in str (e ) or 'certificate' in str (e ).lower ():
446+ warning ('SSL certificate verification failed, trying with unverified context' )
447+ ctx = ssl .create_default_context ()
448+ ctx .check_hostname = False
449+ ctx .verify_mode = ssl .CERT_NONE
450+ opener = urllib .request .build_opener (urllib .request .HTTPSHandler (context = ctx ))
451+ urllib .request .install_opener (opener )
452+ urlretrieve (installer_url , temp_path )
453+ else :
454+ raise
436455
437456 # Install based on OS
438457 if system == 'Windows' :
@@ -602,22 +621,40 @@ def ensure_nodejs() -> bool:
602621def install_claude_npm () -> bool :
603622 """Install Claude Code using npm."""
604623 # On Windows, check if npm is in Program Files even if not in PATH
605- if platform .system () == 'Windows' and not find_command ( 'npm' ) :
624+ if platform .system () == 'Windows' :
606625 nodejs_path = r'C:\Program Files\nodejs'
607626 if Path (nodejs_path ).exists ():
608627 current_path = os .environ .get ('PATH' , '' )
609628 if nodejs_path not in current_path :
610629 os .environ ['PATH' ] = f'{ nodejs_path } ;{ current_path } '
611630 info (f'Added { nodejs_path } to PATH for npm access' )
612631
613- if not find_command ('npm' ):
614- error ('npm not found. Please install Node.js with npm' )
615- return False
632+ # Also check for npm.cmd specifically on Windows
633+ npm_cmd = Path (nodejs_path ) / 'npm.cmd'
634+ if npm_cmd .exists ():
635+ info (f'Found npm.cmd at { npm_cmd } ' )
636+
637+ npm_path = find_command ('npm' )
638+ if not npm_path :
639+ # On Windows, try to find npm.cmd explicitly
640+ if platform .system () == 'Windows' :
641+ npm_cmd_path = Path (r'C:\Program Files\nodejs\npm.cmd' )
642+ if npm_cmd_path .exists ():
643+ npm_path = str (npm_cmd_path )
644+ info (f'Using npm.cmd at { npm_path } ' )
645+ else :
646+ error ('npm not found. Please install Node.js with npm' )
647+ return False
648+ else :
649+ error ('npm not found. Please install Node.js with npm' )
650+ return False
616651
617- info ('Installing Claude Code CLI via npm...' )
652+ info (f 'Installing Claude Code CLI via npm (npm path: { npm_path } ) ...' )
618653
619654 # Try without sudo first (show output for debugging)
620- result = run_command (['npm' , 'install' , '-g' , CLAUDE_NPM_PACKAGE ], capture_output = False )
655+ cmd = [npm_path , 'install' , '-g' , CLAUDE_NPM_PACKAGE ]
656+ info (f'Running command: { " " .join (cmd )} ' )
657+ result = run_command (cmd , capture_output = False )
621658
622659 if result .returncode == 0 :
623660 success ('Claude Code installed successfully' )
@@ -647,14 +684,17 @@ def install_claude_native() -> bool:
647684 try :
648685 with urlopen (CLAUDE_INSTALLER_URL ) as response :
649686 installer_script = response .read ().decode ('utf-8' )
650- except ssl .SSLError :
651- # Fallback: create unverified SSL context for corporate environments
652- warning ('SSL certificate verification failed, trying with unverified context' )
653- ctx = ssl .create_default_context ()
654- ctx .check_hostname = False
655- ctx .verify_mode = ssl .CERT_NONE
656- with urlopen (CLAUDE_INSTALLER_URL , context = ctx ) as response :
657- installer_script = response .read ().decode ('utf-8' )
687+ except urllib .error .URLError as e :
688+ if 'SSL' in str (e ) or 'certificate' in str (e ).lower ():
689+ # Fallback: create unverified SSL context for corporate environments
690+ warning ('SSL certificate verification failed, trying with unverified context' )
691+ ctx = ssl .create_default_context ()
692+ ctx .check_hostname = False
693+ ctx .verify_mode = ssl .CERT_NONE
694+ with urlopen (CLAUDE_INSTALLER_URL , context = ctx ) as response :
695+ installer_script = response .read ().decode ('utf-8' )
696+ else :
697+ raise
658698
659699 # Save to temp file and execute
660700 with tempfile .NamedTemporaryFile (suffix = '.ps1' , delete = False , mode = 'w' ) as tmp :
0 commit comments