3030import shutil
3131import json
3232from pathlib import Path
33- from typing import Optional
33+ from typing import Optional , Tuple
3434
3535import typer
3636import httpx
@@ -402,7 +402,7 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> bool:
402402 os .chdir (original_cwd )
403403
404404
405- def download_template_from_github (ai_assistant : str , download_dir : Path , * , script_type : str = "sh" , verbose : bool = True , show_progress : bool = True , client : httpx .Client = None ) :
405+ def download_template_from_github (ai_assistant : str , download_dir : Path , * , script_type : str = "sh" , verbose : bool = True , show_progress : bool = True , client : httpx .Client = None , debug : bool = False ) -> Tuple [ Path , dict ] :
406406 repo_owner = "github"
407407 repo_name = "spec-kit"
408408 if client is None :
@@ -414,11 +414,19 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
414414
415415 try :
416416 response = client .get (api_url , timeout = 30 , follow_redirects = True )
417- response .raise_for_status ()
418- release_data = response .json ()
419- except httpx .RequestError as e :
420- if verbose :
421- console .print (f"[red]Error fetching release information:[/red] { e } " )
417+ status = response .status_code
418+ if status != 200 :
419+ msg = f"GitHub API returned { status } for { api_url } "
420+ if debug :
421+ msg += f"\n Response headers: { response .headers } \n Body (truncated 500): { response .text [:500 ]} "
422+ raise RuntimeError (msg )
423+ try :
424+ release_data = response .json ()
425+ except ValueError as je :
426+ raise RuntimeError (f"Failed to parse release JSON: { je } \n Raw (truncated 400): { response .text [:400 ]} " )
427+ except Exception as e :
428+ console .print (f"[red]Error fetching release information[/red]" )
429+ console .print (Panel (str (e ), title = "Fetch Error" , border_style = "red" ))
422430 raise typer .Exit (1 )
423431
424432 # Find the template asset for the specified AI assistant
@@ -429,11 +437,9 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
429437 ]
430438
431439 if not matching_assets :
432- if verbose :
433- console .print (f"[red]Error:[/red] No template found for AI assistant '{ ai_assistant } '" )
434- console .print (f"[yellow]Available assets:[/yellow]" )
435- for asset in release_data .get ("assets" , []):
436- console .print (f" - { asset ['name' ]} " )
440+ console .print (f"[red]No matching release asset found[/red] for pattern: [bold]{ pattern } [/bold]" )
441+ asset_names = [a .get ('name' ,'?' ) for a in release_data .get ('assets' , [])]
442+ console .print (Panel ("\n " .join (asset_names ) or "(no assets)" , title = "Available Assets" , border_style = "yellow" ))
437443 raise typer .Exit (1 )
438444
439445 # Use the first matching asset
@@ -453,8 +459,10 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
453459 console .print (f"[cyan]Downloading template...[/cyan]" )
454460
455461 try :
456- with client .stream ("GET" , download_url , timeout = 30 , follow_redirects = True ) as response :
457- response .raise_for_status ()
462+ with client .stream ("GET" , download_url , timeout = 60 , follow_redirects = True ) as response :
463+ if response .status_code != 200 :
464+ body_sample = response .text [:400 ]
465+ raise RuntimeError (f"Download failed with { response .status_code } \n Headers: { response .headers } \n Body (truncated): { body_sample } " )
458466 total_size = int (response .headers .get ('content-length' , 0 ))
459467 with open (zip_path , 'wb' ) as f :
460468 if total_size == 0 :
@@ -477,11 +485,12 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
477485 else :
478486 for chunk in response .iter_bytes (chunk_size = 8192 ):
479487 f .write (chunk )
480- except httpx . RequestError as e :
481- if verbose :
482- console . print ( f"[red]Error downloading template:[/red] { e } " )
488+ except Exception as e :
489+ console . print ( f"[red]Error downloading template[/red]" )
490+ detail = str ( e )
483491 if zip_path .exists ():
484492 zip_path .unlink ()
493+ console .print (Panel (detail , title = "Download Error" , border_style = "red" ))
485494 raise typer .Exit (1 )
486495 if verbose :
487496 console .print (f"Downloaded: { filename } " )
@@ -494,7 +503,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
494503 return zip_path , metadata
495504
496505
497- def download_and_extract_template (project_path : Path , ai_assistant : str , script_type : str , is_current_dir : bool = False , * , verbose : bool = True , tracker : StepTracker | None = None , client : httpx .Client = None ) -> Path :
506+ def download_and_extract_template (project_path : Path , ai_assistant : str , script_type : str , is_current_dir : bool = False , * , verbose : bool = True , tracker : StepTracker | None = None , client : httpx .Client = None , debug : bool = False ) -> Path :
498507 """Download the latest release and extract it to create a new project.
499508 Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
500509 """
@@ -510,7 +519,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
510519 script_type = script_type ,
511520 verbose = verbose and tracker is None ,
512521 show_progress = (tracker is None ),
513- client = client
522+ client = client ,
523+ debug = debug
514524 )
515525 if tracker :
516526 tracker .complete ("fetch" , f"release { meta ['release' ]} ({ meta ['size' ]:,} bytes)" )
@@ -627,6 +637,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
627637 else :
628638 if verbose :
629639 console .print (f"[red]Error extracting template:[/red] { e } " )
640+ if debug :
641+ console .print (Panel (str (e ), title = "Extraction Error" , border_style = "red" ))
630642 # Clean up project directory if created and not current directory
631643 if not is_current_dir and project_path .exists ():
632644 shutil .rmtree (project_path )
@@ -702,6 +714,7 @@ def init(
702714 no_git : bool = typer .Option (False , "--no-git" , help = "Skip git repository initialization" ),
703715 here : bool = typer .Option (False , "--here" , help = "Initialize project in the current directory instead of creating a new one" ),
704716 skip_tls : bool = typer .Option (False , "--skip-tls" , help = "Skip SSL/TLS verification (not recommended)" ),
717+ debug : bool = typer .Option (False , "--debug" , help = "Show verbose diagnostic output for network and extraction failures" ),
705718):
706719 """
707720 Initialize a new Specify project from the latest template.
@@ -856,7 +869,7 @@ def init(
856869 local_ssl_context = ssl_context if verify else False
857870 local_client = httpx .Client (verify = local_ssl_context )
858871
859- download_and_extract_template (project_path , selected_ai , selected_script , here , verbose = False , tracker = tracker , client = local_client )
872+ download_and_extract_template (project_path , selected_ai , selected_script , here , verbose = False , tracker = tracker , client = local_client , debug = debug )
860873
861874 # Ensure scripts are executable (POSIX)
862875 ensure_executable_scripts (project_path , tracker = tracker )
@@ -879,6 +892,14 @@ def init(
879892 tracker .complete ("final" , "project ready" )
880893 except Exception as e :
881894 tracker .error ("final" , str (e ))
895+ console .print (Panel (f"Initialization failed: { e } " , title = "Failure" , border_style = "red" ))
896+ if debug :
897+ env_info = [
898+ f"Python: { sys .version .split ()[0 ]} " ,
899+ f"Platform: { sys .platform } " ,
900+ f"CWD: { Path .cwd ()} " ,
901+ ]
902+ console .print (Panel ("\n " .join (env_info ), title = "Debug Environment" , border_style = "magenta" ))
882903 if not here and project_path .exists ():
883904 shutil .rmtree (project_path )
884905 raise typer .Exit (1 )
0 commit comments