Skip to content

Commit 9c87fdd

Browse files
committed
Update __init__.py
1 parent 301a556 commit 9c87fdd

1 file changed

Lines changed: 19 additions & 55 deletions

File tree

src/specify_cli/__init__.py

Lines changed: 19 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,6 @@ def _github_auth_headers(cli_token: str | None = None) -> dict:
146146
},
147147
}
148148

149-
# Derived dictionaries for backward compatibility
150-
AI_CHOICES = {key: config["name"] for key, config in AGENT_CONFIG.items()}
151-
AGENT_FOLDER_MAP = {key: config["folder"] for key, config in AGENT_CONFIG.items()}
152-
153149
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
154150

155151
CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude"
@@ -202,7 +198,7 @@ def _update(self, key: str, status: str, detail: str):
202198
s["detail"] = detail
203199
self._maybe_refresh()
204200
return
205-
# If not present, add it
201+
206202
self.steps.append({"key": key, "label": key, "status": status, "detail": detail})
207203
self._maybe_refresh()
208204

@@ -219,7 +215,6 @@ def render(self):
219215
label = step["label"]
220216
detail_text = step["detail"].strip() if step["detail"] else ""
221217

222-
# Circles (unchanged styling)
223218
status = step["status"]
224219
if status == "done":
225220
symbol = "[green]●[/green]"
@@ -343,7 +338,6 @@ def run_selection_loop():
343338
console.print("\n[red]Selection failed.[/red]")
344339
raise typer.Exit(1)
345340

346-
# Suppress explicit selection print; tracker / later logic will report consolidated status
347341
return selected_key
348342

349343
console = Console()
@@ -367,7 +361,6 @@ def format_help(self, ctx, formatter):
367361

368362
def show_banner():
369363
"""Display the ASCII art banner."""
370-
# Create gradient effect with different colors
371364
banner_lines = BANNER.strip().split('\n')
372365
colors = ["bright_blue", "blue", "cyan", "bright_cyan", "white", "bright_white"]
373366

@@ -383,8 +376,6 @@ def show_banner():
383376
@app.callback()
384377
def callback(ctx: typer.Context):
385378
"""Show banner when no subcommand is provided."""
386-
# Show banner only when no subcommand and no help flag
387-
# (help is handled by BannerGroup)
388379
if ctx.invoked_subcommand is None and "--help" not in sys.argv and "-h" not in sys.argv:
389380
show_banner()
390381
console.print(Align.center("[dim]Run 'specify --help' for usage information[/dim]"))
@@ -515,7 +506,6 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
515506
console.print(Panel(str(e), title="Fetch Error", border_style="red"))
516507
raise typer.Exit(1)
517508

518-
# Find the template asset for the specified AI assistant
519509
assets = release_data.get("assets", [])
520510
pattern = f"spec-kit-template-{ai_assistant}-{script_type}"
521511
matching_assets = [
@@ -600,7 +590,6 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
600590
"""
601591
current_dir = Path.cwd()
602592

603-
# Step: fetch + download combined
604593
if tracker:
605594
tracker.start("fetch", "contacting GitHub API")
606595
try:
@@ -633,34 +622,29 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
633622
console.print("Extracting template...")
634623

635624
try:
636-
# Create project directory only if not using current directory
637625
if not is_current_dir:
638626
project_path.mkdir(parents=True)
639627

640628
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
641-
# List all files in the ZIP for debugging
642629
zip_contents = zip_ref.namelist()
643630
if tracker:
644631
tracker.start("zip-list")
645632
tracker.complete("zip-list", f"{len(zip_contents)} entries")
646633
elif verbose:
647634
console.print(f"[cyan]ZIP contains {len(zip_contents)} items[/cyan]")
648635

649-
# For current directory, extract to a temp location first
650636
if is_current_dir:
651637
with tempfile.TemporaryDirectory() as temp_dir:
652638
temp_path = Path(temp_dir)
653639
zip_ref.extractall(temp_path)
654640

655-
# Check what was extracted
656641
extracted_items = list(temp_path.iterdir())
657642
if tracker:
658643
tracker.start("extracted-summary")
659644
tracker.complete("extracted-summary", f"temp {len(extracted_items)} items")
660645
elif verbose:
661646
console.print(f"[cyan]Extracted {len(extracted_items)} items to temp location[/cyan]")
662647

663-
# Handle GitHub-style ZIP with a single root directory
664648
source_dir = temp_path
665649
if len(extracted_items) == 1 and extracted_items[0].is_dir():
666650
source_dir = extracted_items[0]
@@ -670,14 +654,12 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
670654
elif verbose:
671655
console.print(f"[cyan]Found nested directory structure[/cyan]")
672656

673-
# Copy contents to current directory
674657
for item in source_dir.iterdir():
675658
dest_path = project_path / item.name
676659
if item.is_dir():
677660
if dest_path.exists():
678661
if verbose and not tracker:
679662
console.print(f"[yellow]Merging directory:[/yellow] {item.name}")
680-
# Recursively copy directory contents
681663
for sub_item in item.rglob('*'):
682664
if sub_item.is_file():
683665
rel_path = sub_item.relative_to(item)
@@ -693,10 +675,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
693675
if verbose and not tracker:
694676
console.print(f"[cyan]Template files merged into current directory[/cyan]")
695677
else:
696-
# Extract directly to project directory (original behavior)
697678
zip_ref.extractall(project_path)
698679

699-
# Check what was extracted
700680
extracted_items = list(project_path.iterdir())
701681
if tracker:
702682
tracker.start("extracted-summary")
@@ -706,16 +686,14 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
706686
for item in extracted_items:
707687
console.print(f" - {item.name} ({'dir' if item.is_dir() else 'file'})")
708688

709-
# Handle GitHub-style ZIP with a single root directory
710689
if len(extracted_items) == 1 and extracted_items[0].is_dir():
711-
# Move contents up one level
712690
nested_dir = extracted_items[0]
713691
temp_move_dir = project_path.parent / f"{project_path.name}_temp"
714-
# Move the nested directory contents to temp location
692+
715693
shutil.move(str(nested_dir), str(temp_move_dir))
716-
# Remove the now-empty project directory
694+
717695
project_path.rmdir()
718-
# Rename temp directory to project directory
696+
719697
shutil.move(str(temp_move_dir), str(project_path))
720698
if tracker:
721699
tracker.add("flatten", "Flatten nested directory")
@@ -731,7 +709,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
731709
console.print(f"[red]Error extracting template:[/red] {e}")
732710
if debug:
733711
console.print(Panel(str(e), title="Extraction Error", border_style="red"))
734-
# Clean up project directory if created and not current directory
712+
735713
if not is_current_dir and project_path.exists():
736714
shutil.rmtree(project_path)
737715
raise typer.Exit(1)
@@ -741,7 +719,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
741719
finally:
742720
if tracker:
743721
tracker.add("cleanup", "Remove temporary archive")
744-
# Clean up downloaded ZIP file
722+
745723
if zip_path.exists():
746724
zip_path.unlink()
747725
if tracker:
@@ -836,7 +814,6 @@ def init(
836814

837815
show_banner()
838816

839-
# Handle '.' as shorthand for current directory (equivalent to --here)
840817
if project_name == ".":
841818
here = True
842819
project_name = None # Clear project_name to use existing validation logic
@@ -887,47 +864,44 @@ def init(
887864
f"{'Working Path':<15} [dim]{current_dir}[/dim]",
888865
]
889866

890-
# Add target path only if different from working dir
891867
if not here:
892868
setup_lines.append(f"{'Target Path':<15} [dim]{project_path}[/dim]")
893869

894870
console.print(Panel("\n".join(setup_lines), border_style="cyan", padding=(1, 2)))
895871

896-
# Check git only if we might need it (not --no-git)
897-
# Only set to True if the user wants it and the tool is available
898872
should_init_git = False
899873
if not no_git:
900874
should_init_git = check_tool("git", "https://git-scm.com/downloads")
901875
if not should_init_git:
902876
console.print("[yellow]Git not found - will skip repository initialization[/yellow]")
903877

904878
if ai_assistant:
905-
if ai_assistant not in AI_CHOICES:
906-
console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AI_CHOICES.keys())}")
879+
if ai_assistant not in AGENT_CONFIG:
880+
console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AGENT_CONFIG.keys())}")
907881
raise typer.Exit(1)
908882
selected_ai = ai_assistant
909883
else:
910-
# Use arrow-key selection interface
884+
# Create options dict for selection (agent_key: display_name)
885+
ai_choices = {key: config["name"] for key, config in AGENT_CONFIG.items()}
911886
selected_ai = select_with_arrows(
912-
AI_CHOICES,
887+
ai_choices,
913888
"Choose your AI assistant:",
914889
"copilot"
915890
)
916891

917-
# Check agent tools unless ignored
918892
if not ignore_agent_tools:
919893
agent_config = AGENT_CONFIG.get(selected_ai)
920894
if agent_config and agent_config["requires_cli"]:
921895
cli_tool = selected_ai
922896
if selected_ai == "cursor":
923897
cli_tool = "cursor-agent"
924-
898+
925899
install_url = agent_config["install_url"]
926900
if not check_tool(cli_tool, install_url):
927901
error_panel = Panel(
928902
f"[cyan]{selected_ai}[/cyan] not found\n"
929903
f"Install from: [cyan]{install_url}[/cyan]\n"
930-
f"{AI_CHOICES[selected_ai]} is required to continue with this project type.\n\n"
904+
f"{agent_config['name']} is required to continue with this project type.\n\n"
931905
"Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check",
932906
title="[red]Agent Detection Error[/red]",
933907
border_style="red",
@@ -937,16 +911,14 @@ def init(
937911
console.print(error_panel)
938912
raise typer.Exit(1)
939913

940-
# Determine script type (explicit, interactive, or OS default)
941914
if script_type:
942915
if script_type not in SCRIPT_TYPE_CHOICES:
943916
console.print(f"[red]Error:[/red] Invalid script type '{script_type}'. Choose from: {', '.join(SCRIPT_TYPE_CHOICES.keys())}")
944917
raise typer.Exit(1)
945918
selected_script = script_type
946919
else:
947-
# Auto-detect default
948920
default_script = "ps" if os.name == "nt" else "sh"
949-
# Provide interactive selection similar to AI if stdin is a TTY
921+
950922
if sys.stdin.isatty():
951923
selected_script = select_with_arrows(SCRIPT_TYPE_CHOICES, "Choose script type (or press Enter)", default_script)
952924
else:
@@ -955,12 +927,10 @@ def init(
955927
console.print(f"[cyan]Selected AI assistant:[/cyan] {selected_ai}")
956928
console.print(f"[cyan]Selected script type:[/cyan] {selected_script}")
957929

958-
# Download and set up project
959-
# New tree-based progress (no emojis); include earlier substeps
960930
tracker = StepTracker("Initialize Specify Project")
961-
# Flag to allow suppressing legacy headings
931+
962932
sys._specify_tracker_active = True
963-
# Pre steps recorded as completed before live rendering
933+
964934
tracker.add("precheck", "Check required tools")
965935
tracker.complete("precheck", "ok")
966936
tracker.add("ai-select", "Select AI assistant")
@@ -980,21 +950,17 @@ def init(
980950
]:
981951
tracker.add(key, label)
982952

983-
# Use transient so live tree is replaced by the final static render (avoids duplicate output)
984953
with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live:
985954
tracker.attach_refresh(lambda: live.update(tracker.render()))
986955
try:
987-
# Create a httpx client with verify based on skip_tls
988956
verify = not skip_tls
989957
local_ssl_context = ssl_context if verify else False
990958
local_client = httpx.Client(verify=local_ssl_context)
991959

992960
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token)
993961

994-
# Ensure scripts are executable (POSIX)
995962
ensure_executable_scripts(project_path, tracker=tracker)
996963

997-
# Git step
998964
if not no_git:
999965
tracker.start("git")
1000966
if is_git_repo(project_path):
@@ -1026,16 +992,15 @@ def init(
1026992
shutil.rmtree(project_path)
1027993
raise typer.Exit(1)
1028994
finally:
1029-
# Force final render
1030995
pass
1031996

1032-
# Final static tree (ensures finished state visible after Live context ends)
1033997
console.print(tracker.render())
1034998
console.print("\n[bold green]Project ready.[/bold green]")
1035999

10361000
# Agent folder security notice
1037-
if selected_ai in AGENT_FOLDER_MAP:
1038-
agent_folder = AGENT_FOLDER_MAP[selected_ai]
1001+
agent_config = AGENT_CONFIG.get(selected_ai)
1002+
if agent_config:
1003+
agent_folder = agent_config["folder"]
10391004
security_notice = Panel(
10401005
f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n"
10411006
f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.",
@@ -1046,7 +1011,6 @@ def init(
10461011
console.print()
10471012
console.print(security_notice)
10481013

1049-
# Boxed "Next steps" section
10501014
steps_lines = []
10511015
if not here:
10521016
steps_lines.append(f"1. Go to the project folder: [cyan]cd {project_name}[/cyan]")

0 commit comments

Comments
 (0)