From 92621bca7d61565c301b1e0285855b2a36bde5b2 Mon Sep 17 00:00:00 2001 From: lispking Date: Mon, 22 Sep 2025 14:20:31 +0800 Subject: [PATCH 01/19] feat: support codebuddy ai --- README.md | 3 ++- src/specify_cli/__init__.py | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d5b371b7..328ca4e3 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c | [Windsurf](https://windsurf.com/) | ✅ | | | [Kilo Code](https://github.com/Kilo-Org/kilocode) | ✅ | | | [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | | +| [CodeBuddy Code](https://cnb.cool/codebuddy/codebuddy-code) | ✅ | | | [Codex CLI](https://github.com/openai/codex) | ⚠️ | Codex [does not support](https://github.com/openai/codex/issues/2890) custom arguments for slash commands. | ## 🔧 Specify CLI Reference @@ -124,7 +125,7 @@ The `specify` command supports the following options: | Argument/Option | Type | Description | |------------------------|----------|------------------------------------------------------------------------------| | `` | Argument | Name for your new project directory (optional if using `--here`) | -| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, or `auggie` | +| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `cursor`, `qwen`, `opencode`, `codex`, `windsurf`, `kilocode`, `auggie`, or `codebuddy` | | `--script` | Option | Script variant to use: `sh` (bash/zsh) or `ps` (PowerShell) | | `--ignore-agent-tools` | Flag | Skip checks for AI agent tools like Claude Code | | `--no-git` | Flag | Skip git repository initialization | diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 8025dae7..58016e24 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -74,6 +74,7 @@ AI_CHOICES = { "windsurf": "Windsurf", "kilocode": "Kilo Code", "auggie": "Auggie CLI", + "codebuddy": "CodeBuddy Code", } # Add script type choices SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} @@ -777,11 +778,13 @@ def init( specify init my-project --ai opencode specify init my-project --ai codex specify init my-project --ai windsurf + specify init my-project --ai kilocode specify init my-project --ai auggie + specify init my-project --ai codebuddy specify init --ignore-agent-tools my-project specify init --here --ai claude specify init --here --ai codex - specify init --here + specify init --here --ai codebuddy """ # Show banner first show_banner() @@ -892,6 +895,10 @@ def init( if not check_tool("auggie", "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"): install_url = "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli" agent_tool_missing = True + elif selected_ai == "codebuddy": + if not check_tool("codebuddy", "https://cnb.cool/codebuddy/codebuddy-code"): + install_url = "https://cnb.cool/codebuddy/codebuddy-code" + agent_tool_missing = True # GitHub Copilot and Cursor checks are not needed as they're typically available in supported IDEs if agent_tool_missing: @@ -1015,6 +1022,7 @@ def init( "windsurf": ".windsurf/", "kilocode": ".kilocode/", "auggie": ".augment/", + "codebuddy": ".codebuddy/", "copilot": ".github/" } @@ -1093,6 +1101,7 @@ def check(): tracker.add("opencode", "opencode") tracker.add("codex", "Codex CLI") tracker.add("auggie", "Auggie CLI") + tracker.add("codebuddy", "CodeBuddy Code") git_ok = check_tool_for_tracker("git", tracker) claude_ok = check_tool_for_tracker("claude", tracker) @@ -1106,6 +1115,7 @@ def check(): opencode_ok = check_tool_for_tracker("opencode", tracker) codex_ok = check_tool_for_tracker("codex", tracker) auggie_ok = check_tool_for_tracker("auggie", tracker) + codebuddy_ok = check_tool_for_tracker("codebuddy", tracker) console.print(tracker.render()) From 318b76de50a8bffe02973a3e4a6e8772ecc6a576 Mon Sep 17 00:00:00 2001 From: lispking Date: Mon, 22 Sep 2025 14:34:43 +0800 Subject: [PATCH 02/19] feat: support codebuddy ai --- .github/workflows/scripts/create-github-release.sh | 2 ++ .../workflows/scripts/create-release-packages.sh | 5 ++++- AGENTS.md | 10 +++++++--- README.md | 1 + scripts/bash/update-agent-context.sh | 13 +++++++++++-- scripts/powershell/update-agent-context.ps1 | 9 ++++++--- src/specify_cli/__init__.py | 12 +++++++----- 7 files changed, 38 insertions(+), 14 deletions(-) diff --git a/.github/workflows/scripts/create-github-release.sh b/.github/workflows/scripts/create-github-release.sh index 0257520f..63343d03 100644 --- a/.github/workflows/scripts/create-github-release.sh +++ b/.github/workflows/scripts/create-github-release.sh @@ -38,5 +38,7 @@ gh release create "$VERSION" \ .genreleases/spec-kit-template-auggie-ps-"$VERSION".zip \ .genreleases/spec-kit-template-roo-sh-"$VERSION".zip \ .genreleases/spec-kit-template-roo-ps-"$VERSION".zip \ + .genreleases/spec-kit-template-codebuddy-sh-"$VERSION".zip \ + .genreleases/spec-kit-template-codebuddy-ps-"$VERSION".zip \ --title "Spec Kit Templates - $VERSION_NO_V" \ --notes-file release_notes.md \ No newline at end of file diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index 1a12e558..0d551602 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -172,13 +172,16 @@ build_variant() { roo) mkdir -p "$base_dir/.roo/commands" generate_commands roo md "\$ARGUMENTS" "$base_dir/.roo/commands" "$script" ;; + codebuddy) + mkdir -p "$base_dir/.codebuddy/commands" + generate_commands codebuddy md "\$ARGUMENTS" "$base_dir/.codebuddy/commands" "$script" ;; esac ( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . ) echo "Created $GENRELEASES_DIR/spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" } # Determine agent list -ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf codex kilocode auggie roo) +ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf codex kilocode auggie roo codebuddy) ALL_SCRIPTS=(sh ps) diff --git a/AGENTS.md b/AGENTS.md index 59b99566..247744a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,6 +38,7 @@ Specify supports multiple AI agents by generating agent-specific command files a | **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI | | **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI | | **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows | +| **CodeBuddy** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI | ### Step-by-Step Integration Guide @@ -55,7 +56,8 @@ AI_CHOICES = { "cursor": "Cursor", "qwen": "Qwen Code", "opencode": "opencode", - "windsurf": "Windsurf" # Add new agent here + "windsurf": "Windsurf", + "codebuddy": "CodeBuddy" # Add new agent here } ``` @@ -69,10 +71,11 @@ agent_folder_map = { "qwen": ".qwen/", "opencode": ".opencode/", "codex": ".codex/", - "windsurf": ".windsurf/", # Add new agent folder here + "windsurf": ".windsurf/", "kilocode": ".kilocode/", "auggie": ".auggie/", - "copilot": ".github/" + "copilot": ".github/", + "codebuddy": ".codebuddy/" # Add new agent folder here } ``` @@ -197,6 +200,7 @@ Require a command-line tool to be installed: - **Cursor**: `cursor-agent` CLI - **Qwen Code**: `qwen` CLI - **opencode**: `opencode` CLI +- **CodeBuddy**: `codebuddy` CLI ### IDE-Based Agents Work within integrated development environments: diff --git a/README.md b/README.md index 079e6738..13ced74c 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,7 @@ specify init --ai qwen specify init --ai opencode specify init --ai codex specify init --ai windsurf +specify init --ai codebuddy # Or in current directory: specify init --here --ai claude specify init --here --ai codex diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index d3cc422e..ea467647 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -69,6 +69,7 @@ WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md" KILOCODE_FILE="$REPO_ROOT/.kilocode/rules/specify-rules.md" AUGGIE_FILE="$REPO_ROOT/.augment/rules/specify-rules.md" ROO_FILE="$REPO_ROOT/.roo/rules/specify-rules.md" +CODEBUDDY_FILE="$REPO_ROOT/.codebuddy/rules/specify-rules.md" # Template file TEMPLATE_FILE="$REPO_ROOT/.specify/templates/agent-file-template.md" @@ -580,9 +581,12 @@ update_specific_agent() { roo) update_agent_file "$ROO_FILE" "Roo Code" ;; + codebuddy) + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" + ;; *) log_error "Unknown agent type '$agent_type'" - log_error "Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo" + log_error "Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy" exit 1 ;; esac @@ -641,6 +645,11 @@ update_all_existing_agents() { update_agent_file "$ROO_FILE" "Roo Code" found_agent=true fi + + if [[ -f "$CODEBUDDY_FILE" ]]; then + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" + found_agent=true + fi # If no agent files exist, create a default Claude file if [[ "$found_agent" == false ]]; then @@ -665,7 +674,7 @@ print_summary() { fi echo - log_info "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo]" + log_info "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy]" } #============================================================================== diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 8f4830a9..17fa9b44 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -25,7 +25,7 @@ Relies on common helper functions in common.ps1 #> param( [Parameter(Position=0)] - [ValidateSet('claude','gemini','copilot','cursor','qwen','opencode','codex','windsurf','kilocode','auggie','roo')] + [ValidateSet('claude','gemini','copilot','cursor','qwen','opencode','codex','windsurf','kilocode','auggie','roo','codebuddy')] [string]$AgentType ) @@ -54,6 +54,7 @@ $WINDSURF_FILE = Join-Path $REPO_ROOT '.windsurf/rules/specify-rules.md' $KILOCODE_FILE = Join-Path $REPO_ROOT '.kilocode/rules/specify-rules.md' $AUGGIE_FILE = Join-Path $REPO_ROOT '.augment/rules/specify-rules.md' $ROO_FILE = Join-Path $REPO_ROOT '.roo/rules/specify-rules.md' +$CODEBUDDY_FILE = Join-Path $REPO_ROOT '.codebuddy/rules/specify-rules.md' $TEMPLATE_FILE = Join-Path $REPO_ROOT '.specify/templates/agent-file-template.md' @@ -376,7 +377,8 @@ function Update-SpecificAgent { 'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' } 'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' } 'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' } - default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo'; return $false } + 'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' } + default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy'; return $false } } } @@ -393,6 +395,7 @@ function Update-AllExistingAgents { if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true } if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true } if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true } + if (Test-Path $CODEBUDDY_FILE) { if (-not (Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI')) { $ok = $false }; $found = $true } if (-not $found) { Write-Info 'No existing agent files found, creating default Claude file...' if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false } @@ -407,7 +410,7 @@ function Print-Summary { if ($NEW_FRAMEWORK) { Write-Host " - Added framework: $NEW_FRAMEWORK" } if ($NEW_DB -and $NEW_DB -ne 'N/A') { Write-Host " - Added database: $NEW_DB" } Write-Host '' - Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo]' + Write-Info 'Usage: ./update-agent-context.ps1 [-AgentType claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy]' } function Main { diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 14b9b0d3..068e8563 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -74,7 +74,7 @@ AI_CHOICES = { "windsurf": "Windsurf", "kilocode": "Kilo Code", "auggie": "Auggie CLI", - "codebuddy": "CodeBuddy Code", + "codebuddy": "CodeBuddy CLI", "roo": "Roo Code", } # Add script type choices @@ -749,7 +749,7 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = @app.command() def init( project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here)"), - ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, codex, windsurf, kilocode, or auggie"), + ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, codex, windsurf, kilocode, auggie, roo, or codebuddy"), script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"), ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"), no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"), @@ -763,7 +763,7 @@ def init( This command will: 1. Check that required tools are installed (git is optional) - 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, or Auggie CLI) + 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, Roo Code, or CodeBuddy CLI) 3. Download the appropriate template from GitHub 4. Extract the template to a new project directory or current directory 5. Initialize a fresh git repository (if not --no-git and no existing repo) @@ -1103,7 +1103,8 @@ def check(): tracker.add("opencode", "opencode") tracker.add("codex", "Codex CLI") tracker.add("auggie", "Auggie CLI") - tracker.add("codebuddy", "CodeBuddy Code") + tracker.add("roo", "Roo Code") + tracker.add("codebuddy", "CodeBuddy CLI") git_ok = check_tool_for_tracker("git", tracker) claude_ok = check_tool_for_tracker("claude", tracker) @@ -1117,6 +1118,7 @@ def check(): opencode_ok = check_tool_for_tracker("opencode", tracker) codex_ok = check_tool_for_tracker("codex", tracker) auggie_ok = check_tool_for_tracker("auggie", tracker) + roo_ok = check_tool_for_tracker("roo", tracker) codebuddy_ok = check_tool_for_tracker("codebuddy", tracker) console.print(tracker.render()) @@ -1125,7 +1127,7 @@ def check(): if not git_ok: console.print("[dim]Tip: Install git for repository management[/dim]") - if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or kilocode_ok or opencode_ok or codex_ok or auggie_ok): + if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or kilocode_ok or opencode_ok or codex_ok or auggie_ok or roo_ok or codebuddy_ok): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") From 8b09559690166fae71e5da28f22d73ab736160ae Mon Sep 17 00:00:00 2001 From: lispking Date: Tue, 23 Sep 2025 05:22:35 +0800 Subject: [PATCH 03/19] update CodeBuddy to international site --- AGENTS.md | 2 +- README.md | 2 +- scripts/bash/update-agent-context.sh | 4 ++-- scripts/powershell/update-agent-context.ps1 | 4 ++-- src/specify_cli/__init__.py | 10 +++++----- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 247744a5..c0e6ceec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,7 +38,7 @@ Specify supports multiple AI agents by generating agent-specific command files a | **Qwen Code** | `.qwen/commands/` | TOML | `qwen` | Alibaba's Qwen Code CLI | | **opencode** | `.opencode/command/` | Markdown | `opencode` | opencode CLI | | **Windsurf** | `.windsurf/workflows/` | Markdown | N/A (IDE-based) | Windsurf IDE workflows | -| **CodeBuddy** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy CLI | +| **CodeBuddy** | `.codebuddy/commands/` | Markdown | `codebuddy` | CodeBuddy | ### Step-by-Step Integration Guide diff --git a/README.md b/README.md index 13ced74c..b6aed441 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Want to see Spec Kit in action? Watch our [video overview](https://www.youtube.c | [Windsurf](https://windsurf.com/) | ✅ | | | [Kilo Code](https://github.com/Kilo-Org/kilocode) | ✅ | | | [Auggie CLI](https://docs.augmentcode.com/cli/overview) | ✅ | | -| [CodeBuddy Code](https://cnb.cool/codebuddy/codebuddy-code) | ✅ | | +| [CodeBuddy](https://www.codebuddy.ai/) | ✅ | | | [Roo Code](https://roocode.com/) | ✅ | | | [Codex CLI](https://github.com/openai/codex) | ⚠️ | Codex [does not support](https://github.com/openai/codex/issues/2890) custom arguments for slash commands. | diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index ea467647..0b07f85c 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -582,7 +582,7 @@ update_specific_agent() { update_agent_file "$ROO_FILE" "Roo Code" ;; codebuddy) - update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy" ;; *) log_error "Unknown agent type '$agent_type'" @@ -647,7 +647,7 @@ update_all_existing_agents() { fi if [[ -f "$CODEBUDDY_FILE" ]]; then - update_agent_file "$CODEBUDDY_FILE" "CodeBuddy CLI" + update_agent_file "$CODEBUDDY_FILE" "CodeBuddy" found_agent=true fi diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 17fa9b44..7977927a 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -377,7 +377,7 @@ function Update-SpecificAgent { 'kilocode' { Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code' } 'auggie' { Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI' } 'roo' { Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code' } - 'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI' } + 'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy' } default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy'; return $false } } } @@ -395,7 +395,7 @@ function Update-AllExistingAgents { if (Test-Path $KILOCODE_FILE) { if (-not (Update-AgentFile -TargetFile $KILOCODE_FILE -AgentName 'Kilo Code')) { $ok = $false }; $found = $true } if (Test-Path $AUGGIE_FILE) { if (-not (Update-AgentFile -TargetFile $AUGGIE_FILE -AgentName 'Auggie CLI')) { $ok = $false }; $found = $true } if (Test-Path $ROO_FILE) { if (-not (Update-AgentFile -TargetFile $ROO_FILE -AgentName 'Roo Code')) { $ok = $false }; $found = $true } - if (Test-Path $CODEBUDDY_FILE) { if (-not (Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy CLI')) { $ok = $false }; $found = $true } + if (Test-Path $CODEBUDDY_FILE) { if (-not (Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy')) { $ok = $false }; $found = $true } if (-not $found) { Write-Info 'No existing agent files found, creating default Claude file...' if (-not (Update-AgentFile -TargetFile $CLAUDE_FILE -AgentName 'Claude Code')) { $ok = $false } diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 068e8563..e7f9032f 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -74,7 +74,7 @@ AI_CHOICES = { "windsurf": "Windsurf", "kilocode": "Kilo Code", "auggie": "Auggie CLI", - "codebuddy": "CodeBuddy CLI", + "codebuddy": "CodeBuddy", "roo": "Roo Code", } # Add script type choices @@ -763,7 +763,7 @@ def init( This command will: 1. Check that required tools are installed (git is optional) - 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, Roo Code, or CodeBuddy CLI) + 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, Codex CLI, Windsurf, Kilo Code, Auggie CLI, Roo Code, or CodeBuddy) 3. Download the appropriate template from GitHub 4. Extract the template to a new project directory or current directory 5. Initialize a fresh git repository (if not --no-git and no existing repo) @@ -897,8 +897,8 @@ def init( install_url = "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli" agent_tool_missing = True elif selected_ai == "codebuddy": - if not check_tool("codebuddy", "https://cnb.cool/codebuddy/codebuddy-code"): - install_url = "https://cnb.cool/codebuddy/codebuddy-code" + if not check_tool("codebuddy", "https://www.codebuddy.ai"): + install_url = "https://www.codebuddy.ai" agent_tool_missing = True # GitHub Copilot and Cursor checks are not needed as they're typically available in supported IDEs @@ -1104,7 +1104,7 @@ def check(): tracker.add("codex", "Codex CLI") tracker.add("auggie", "Auggie CLI") tracker.add("roo", "Roo Code") - tracker.add("codebuddy", "CodeBuddy CLI") + tracker.add("codebuddy", "CodeBuddy") git_ok = check_tool_for_tracker("git", tracker) claude_ok = check_tool_for_tracker("claude", tracker) From 7c4c1edd851b91162f1e8a4baf115236b15bae48 Mon Sep 17 00:00:00 2001 From: Eric Yeh Date: Fri, 10 Oct 2025 06:09:38 +0800 Subject: [PATCH 04/19] Escape backslashes in TOML outputs --- .github/workflows/scripts/create-release-packages.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index 19e49d39..1b1f0509 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -91,6 +91,7 @@ generate_commands() { case $ext in toml) + body=$(printf '%s\n' "$body" | sed 's/\\/\\\\/g') { echo "description = \"$description\""; echo; echo "prompt = \"\"\""; echo "$body"; echo "\"\"\""; } > "$output_dir/speckit.$name.$ext" ;; md) echo "$body" > "$output_dir/speckit.$name.$ext" ;; From 72ed39d8a1c46698e08a80e66a60cfd72fdd294b Mon Sep 17 00:00:00 2001 From: Simon Gent Date: Fri, 10 Oct 2025 10:11:06 +0100 Subject: [PATCH 05/19] Add ignore file verification step to /speckit.implement command - Added step 4 for project setup verification - Detects and creates/verifies ignore files based on project setup - Includes patterns for .gitignore, .dockerignore, .eslintignore, .prettierignore, .npmignore, .terraformignore, .helmignore - Provides technology-specific patterns (Node.js, Python, Java, C#/.NET, Go) - Includes tool-specific patterns (Docker, ESLint, Prettier, Terraform) - Renumbered subsequent steps 5-9 --- templates/commands/implement.md | 39 ++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/templates/commands/implement.md b/templates/commands/implement.md index da8021e4..2c9b65b0 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -54,27 +54,56 @@ You **MUST** consider the user input before proceeding (if not empty). - **IF EXISTS**: Read research.md for technical decisions and constraints - **IF EXISTS**: Read quickstart.md for integration scenarios -4. Parse tasks.md structure and extract: +4. **Project Setup Verification**: + - **REQUIRED**: Create/verify ignore files based on actual project setup: + + **Detection & Creation Logic**: + - Check `git rev-parse --git-dir 2>/dev/null` → create/verify .gitignore + - Check if Dockerfile* exists or Docker in plan.md → create/verify .dockerignore + - Check if .eslintrc* or eslint.config.* exists → create/verify .eslintignore + - Check if .prettierrc* exists → create/verify .prettierignore + - Check if .npmrc or package.json exists → create/verify .npmignore (if publishing) + - Check if terraform files (*.tf) exist → create/verify .terraformignore + - Check if .helmignore needed (helm charts present) → create/verify .helmignore + + **If ignore file already exists**: Verify it contains essential patterns, append missing critical patterns only + **If ignore file missing**: Create with full pattern set for detected technology + + **Common Patterns by Technology** (from plan.md tech stack): + - **Node.js/JavaScript**: `node_modules/`, `dist/`, `build/`, `*.log`, `.env*` + - **Python**: `__pycache__/`, `*.pyc`, `.venv/`, `venv/`, `dist/`, `*.egg-info/` + - **Java**: `target/`, `*.class`, `*.jar`, `.gradle/`, `build/` + - **C#/.NET**: `bin/`, `obj/`, `*.user`, `*.suo`, `packages/` + - **Go**: `*.exe`, `*.test`, `vendor/`, `*.out` + - **Universal**: `.DS_Store`, `Thumbs.db`, `*.tmp`, `*.swp`, `.vscode/`, `.idea/` + + **Tool-Specific Patterns**: + - **Docker**: `node_modules/`, `.git/`, `.gitignore`, `README.md`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/` + - **ESLint**: `node_modules/`, `dist/`, `build/`, `coverage/`, `*.min.js` + - **Prettier**: `node_modules/`, `dist/`, `build/`, `coverage/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` + - **Terraform**: `.terraform/`, `*.tfstate*`, `*.tfvars`, `.terraform.lock.hcl` + +5. Parse tasks.md structure and extract: - **Task phases**: Setup, Tests, Core, Integration, Polish - **Task dependencies**: Sequential vs parallel execution rules - **Task details**: ID, description, file paths, parallel markers [P] - **Execution flow**: Order and dependency requirements -5. Execute implementation following the task plan: +6. Execute implementation following the task plan: - **Phase-by-phase execution**: Complete each phase before moving to the next - **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together - **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks - **File-based coordination**: Tasks affecting the same files must run sequentially - **Validation checkpoints**: Verify each phase completion before proceeding -6. Implementation execution rules: +7. Implementation execution rules: - **Setup first**: Initialize project structure, dependencies, configuration - **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios - **Core development**: Implement models, services, CLI commands, endpoints - **Integration work**: Database connections, middleware, logging, external services - **Polish and validation**: Unit tests, performance optimization, documentation -7. Progress tracking and error handling: +8. Progress tracking and error handling: - Report progress after each completed task - Halt execution if any non-parallel task fails - For parallel tasks [P], continue with successful tasks, report failed ones @@ -82,7 +111,7 @@ You **MUST** consider the user input before proceeding (if not empty). - Suggest next steps if implementation cannot proceed - **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file. -8. Completion validation: +9. Completion validation: - Verify all required tasks are completed - Check that implemented features match the original specification - Validate that tests pass and coverage meets requirements From 583d55667749536079060a0e0afcde5e1c5b5895 Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:43:31 -0700 Subject: [PATCH 06/19] Update plan.md --- templates/commands/plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/commands/plan.md b/templates/commands/plan.md index f65ad38f..42bb6023 100644 --- a/templates/commands/plan.md +++ b/templates/commands/plan.md @@ -20,7 +20,7 @@ You **MUST** consider the user input before proceeding (if not empty). 1. **Setup**: Run `{SCRIPT}` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). -2. **Load context**: Read FEATURE_SPEC and `.specify/memory/constitution.md`. Load IMPL_PLAN template (already copied). +2. **Load context**: Read FEATURE_SPEC and `/memory/constitution.md`. Load IMPL_PLAN template (already copied). 3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to: - Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION") From 07d506feb5f3a5137ca8b5bd863f7911612a0de9 Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 10:53:21 -0700 Subject: [PATCH 07/19] Update changelog --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4706b826..20ce5895 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ All notable changes to the Specify CLI and templates are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.19] - 2025-10-10 + +### Changed + +- Fixed the path to the constitution in `plan.md` (thank you to [@lyzno1](https://github.com/lyzno1) for spotting). +- Fixed backslash escapes in generated TOML files for Gemini (thank you to [@hsin19](https://github.com/hsin19) for the contribution). +- Implementation command now ensures that the correct ignore files are added (thank you to [@sigent-amazon](https://github.com/sigent-amazon) for the contribution). + ## [0.0.18] - 2025-10-06 ### Added diff --git a/pyproject.toml b/pyproject.toml index 86f9abba..f1a79030 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "specify-cli" -version = "0.0.18" +version = "0.0.19" description = "Specify CLI, part of GitHub Spec Kit. A tool to bootstrap your projects for Spec-Driven Development (SDD)." requires-python = ">=3.11" dependencies = [ From 199c63901f53549f684dd8940c926b1f7efeebc1 Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:04:25 -0700 Subject: [PATCH 08/19] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20ce5895..a90841f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.0.19] - 2025-10-10 +### Added + +- Support for CodeBuddy (thank you to [@lispking](https://github.com/lispking) for the contribution). + ### Changed - Fixed the path to the constitution in `plan.md` (thank you to [@lyzno1](https://github.com/lyzno1) for spotting). From 426ac8ab2e2a3ddfa11107a419f9a93dc7c0d4e1 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:05:16 -0700 Subject: [PATCH 09/19] Update templates/commands/implement.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- templates/commands/implement.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/commands/implement.md b/templates/commands/implement.md index 2c9b65b0..c2f0b205 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -58,7 +58,11 @@ You **MUST** consider the user input before proceeding (if not empty). - **REQUIRED**: Create/verify ignore files based on actual project setup: **Detection & Creation Logic**: - - Check `git rev-parse --git-dir 2>/dev/null` → create/verify .gitignore + - Check if the following command succeeds to determine if the repository is a git repo (create/verify .gitignore if so): + + ```sh + git rev-parse --git-dir 2>/dev/null + ``` - Check if Dockerfile* exists or Docker in plan.md → create/verify .dockerignore - Check if .eslintrc* or eslint.config.* exists → create/verify .eslintignore - Check if .prettierrc* exists → create/verify .prettierignore From 9ef389baba784f0b7ef2328d3f021c0f0d0de373 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:05:38 -0700 Subject: [PATCH 10/19] Update templates/commands/implement.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- templates/commands/implement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/commands/implement.md b/templates/commands/implement.md index c2f0b205..60555975 100644 --- a/templates/commands/implement.md +++ b/templates/commands/implement.md @@ -82,7 +82,7 @@ You **MUST** consider the user input before proceeding (if not empty). - **Universal**: `.DS_Store`, `Thumbs.db`, `*.tmp`, `*.swp`, `.vscode/`, `.idea/` **Tool-Specific Patterns**: - - **Docker**: `node_modules/`, `.git/`, `.gitignore`, `README.md`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/` + - **Docker**: `node_modules/`, `.git/`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/` - **ESLint**: `node_modules/`, `dist/`, `build/`, `coverage/`, `*.min.js` - **Prettier**: `node_modules/`, `dist/`, `build/`, `coverage/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` - **Terraform**: `.terraform/`, `*.tfstate*`, `*.tfvars`, `.terraform.lock.hcl` From 3cc545243bd2a183204ff4e46a57bf9ff79b9490 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:07:12 -0700 Subject: [PATCH 11/19] Update AGENTS.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- AGENTS.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2f0ced2b..cd88dd34 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -77,10 +77,6 @@ agent_folder_map = { "kilocode": ".kilocode/", "auggie": ".auggie/", "copilot": ".github/", - "windsurf": ".windsurf/", - "kilocode": ".kilocode/", - "auggie": ".auggie/", - "copilot": ".github/", "q": ".amazonq/", "codebuddy": ".codebuddy/" } From 68809bdacb47c2f648143a1aea1a46d664dcf5b8 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:07:21 -0700 Subject: [PATCH 12/19] Update scripts/powershell/update-agent-context.ps1 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- scripts/powershell/update-agent-context.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 806ee090..baf586e4 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -381,7 +381,6 @@ function Update-SpecificAgent { 'codebuddy' { Update-AgentFile -TargetFile $CODEBUDDY_FILE -AgentName 'CodeBuddy' } 'q' { Update-AgentFile -TargetFile $Q_FILE -AgentName 'Amazon Q Developer CLI' } default { Write-Err "Unknown agent type '$Type'"; Write-Err 'Expected: claude|gemini|copilot|cursor|qwen|opencode|codex|windsurf|kilocode|auggie|roo|codebuddy|q'; return $false } - } } From e83d2c777d13cfc27e9fc845399dbaf2dc47c625 Mon Sep 17 00:00:00 2001 From: Den Delimarsky <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:07:45 -0700 Subject: [PATCH 13/19] Update src/specify_cli/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/specify_cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 49b2f37a..2ebfe753 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -1106,7 +1106,7 @@ def check(): if not git_ok: console.print("[dim]Tip: Install git for repository management[/dim]") - if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or kilocode_ok or opencode_ok or codex_ok or auggie_ok or codebuddy_ok or q_ok): + if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or kilocode_ok or opencode_ok or codex_ok or auggie_ok or roo_ok or codebuddy_ok or q_ok): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") def main(): From bb9ec8e6384e99e9b25e0aa672bb79b4cdef852e Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:12:54 -0700 Subject: [PATCH 14/19] Refactor agent configuration --- src/specify_cli/__init__.py | 272 +++++++++++++++++++----------------- 1 file changed, 147 insertions(+), 125 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 49b2f37a..1da5466f 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -64,22 +64,92 @@ def _github_auth_headers(cli_token: str | None = None) -> dict: token = _github_token(cli_token) return {"Authorization": f"Bearer {token}"} if token else {} -AI_CHOICES = { - "copilot": "GitHub Copilot", - "claude": "Claude Code", - "gemini": "Gemini CLI", - "cursor": "Cursor", - "qwen": "Qwen Code", - "opencode": "opencode", - "codex": "Codex CLI", - "windsurf": "Windsurf", - "kilocode": "Kilo Code", - "auggie": "Auggie CLI", - "codebuddy": "CodeBuddy", - "roo": "Roo Code", - "q": "Amazon Q Developer CLI", +# Agent configuration with name, folder, install URL, and CLI tool requirement +AGENT_CONFIG = { + "copilot": { + "name": "GitHub Copilot", + "folder": ".github/", + "install_url": None, # IDE-based, no CLI check needed + "requires_cli": False, + }, + "claude": { + "name": "Claude Code", + "folder": ".claude/", + "install_url": "https://docs.anthropic.com/en/docs/claude-code/setup", + "requires_cli": True, + }, + "gemini": { + "name": "Gemini CLI", + "folder": ".gemini/", + "install_url": "https://github.com/google-gemini/gemini-cli", + "requires_cli": True, + }, + "cursor": { + "name": "Cursor", + "folder": ".cursor/", + "install_url": None, # IDE-based + "requires_cli": False, + }, + "qwen": { + "name": "Qwen Code", + "folder": ".qwen/", + "install_url": "https://github.com/QwenLM/qwen-code", + "requires_cli": True, + }, + "opencode": { + "name": "opencode", + "folder": ".opencode/", + "install_url": "https://opencode.ai", + "requires_cli": True, + }, + "codex": { + "name": "Codex CLI", + "folder": ".codex/", + "install_url": "https://github.com/openai/codex", + "requires_cli": True, + }, + "windsurf": { + "name": "Windsurf", + "folder": ".windsurf/", + "install_url": None, # IDE-based + "requires_cli": False, + }, + "kilocode": { + "name": "Kilo Code", + "folder": ".kilocode/", + "install_url": None, # IDE-based + "requires_cli": False, + }, + "auggie": { + "name": "Auggie CLI", + "folder": ".augment/", + "install_url": "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli", + "requires_cli": True, + }, + "codebuddy": { + "name": "CodeBuddy", + "folder": ".codebuddy/", + "install_url": "https://www.codebuddy.ai", + "requires_cli": True, + }, + "roo": { + "name": "Roo Code", + "folder": ".roo/", + "install_url": None, # IDE-based + "requires_cli": False, + }, + "q": { + "name": "Amazon Q Developer CLI", + "folder": ".amazonq/", + "install_url": "https://aws.amazon.com/developer/learning/q-developer-cli/", + "requires_cli": True, + }, } +# Derived dictionaries for backward compatibility +AI_CHOICES = {key: config["name"] for key, config in AGENT_CONFIG.items()} +AGENT_FOLDER_MAP = {key: config["folder"] for key, config in AGENT_CONFIG.items()} + SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude" @@ -338,18 +408,17 @@ def run_command(cmd: list[str], check_return: bool = True, capture: bool = False raise return None -def check_tool_for_tracker(tool: str, tracker: StepTracker) -> bool: - """Check if a tool is installed and update tracker.""" - if shutil.which(tool): - tracker.complete(tool, "available") - return True - else: - tracker.error(tool, "not found") - return False - -def check_tool(tool: str, install_hint: str) -> bool: - """Check if a tool is installed.""" +def check_tool(tool: str, install_hint: str = "", tracker: StepTracker = None) -> bool: + """Check if a tool is installed. Optionally update tracker. + Args: + tool: Name of the tool to check + install_hint: URL or hint for installing the tool (for error messages) + tracker: Optional StepTracker to update with results + + Returns: + True if tool is found, False otherwise + """ # Special handling for Claude CLI after `claude migrate-installer` # See: https://github.com/github/spec-kit/issues/123 # The migrate-installer command REMOVES the original executable from PATH @@ -357,12 +426,19 @@ def check_tool(tool: str, install_hint: str) -> bool: # This path should be prioritized over other claude executables in PATH if tool == "claude": if CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file(): + if tracker: + tracker.complete(tool, "available") return True - if shutil.which(tool): - return True - else: - return False + found = shutil.which(tool) is not None + + if tracker: + if found: + tracker.complete(tool, "available") + else: + tracker.error(tool, "not found") + + return found def is_git_repo(path: Path = None) -> bool: """Check if the specified path is inside a git repository.""" @@ -840,55 +916,26 @@ def init( # Check agent tools unless ignored if not ignore_agent_tools: - agent_tool_missing = False - install_url = "" - if selected_ai == "claude": - if not check_tool("claude", "https://docs.anthropic.com/en/docs/claude-code/setup"): - install_url = "https://docs.anthropic.com/en/docs/claude-code/setup" - agent_tool_missing = True - elif selected_ai == "gemini": - if not check_tool("gemini", "https://github.com/google-gemini/gemini-cli"): - install_url = "https://github.com/google-gemini/gemini-cli" - agent_tool_missing = True - elif selected_ai == "qwen": - if not check_tool("qwen", "https://github.com/QwenLM/qwen-code"): - install_url = "https://github.com/QwenLM/qwen-code" - agent_tool_missing = True - elif selected_ai == "opencode": - if not check_tool("opencode", "https://opencode.ai"): - install_url = "https://opencode.ai" - agent_tool_missing = True - elif selected_ai == "codex": - if not check_tool("codex", "https://github.com/openai/codex"): - install_url = "https://github.com/openai/codex" - agent_tool_missing = True - elif selected_ai == "auggie": - if not check_tool("auggie", "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"): - install_url = "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli" - agent_tool_missing = True - elif selected_ai == "codebuddy": - if not check_tool("codebuddy", "https://www.codebuddy.ai"): - install_url = "https://www.codebuddy.ai" - agent_tool_missing = True - elif selected_ai == "q": - if not check_tool("q", "https://github.com/aws/amazon-q-developer-cli"): - install_url = "https://aws.amazon.com/developer/learning/q-developer-cli/" - agent_tool_missing = True - # GitHub Copilot and Cursor checks are not needed as they're typically available in supported IDEs - - if agent_tool_missing: - error_panel = Panel( - f"[cyan]{selected_ai}[/cyan] not found\n" - f"Install with: [cyan]{install_url}[/cyan]\n" - f"{AI_CHOICES[selected_ai]} is required to continue with this project type.\n\n" - "Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check", - title="[red]Agent Detection Error[/red]", - border_style="red", - padding=(1, 2) - ) - console.print() - console.print(error_panel) - raise typer.Exit(1) + agent_config = AGENT_CONFIG.get(selected_ai) + if agent_config and agent_config["requires_cli"]: + cli_tool = selected_ai + if selected_ai == "cursor": + cli_tool = "cursor-agent" + + install_url = agent_config["install_url"] + if not check_tool(cli_tool, install_url): + error_panel = Panel( + f"[cyan]{selected_ai}[/cyan] not found\n" + f"Install from: [cyan]{install_url}[/cyan]\n" + f"{AI_CHOICES[selected_ai]} is required to continue with this project type.\n\n" + "Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check", + title="[red]Agent Detection Error[/red]", + border_style="red", + padding=(1, 2) + ) + console.print() + console.print(error_panel) + raise typer.Exit(1) # Determine script type (explicit, interactive, or OS default) if script_type: @@ -987,24 +1034,8 @@ def init( console.print("\n[bold green]Project ready.[/bold green]") # Agent folder security notice - agent_folder_map = { - "claude": ".claude/", - "gemini": ".gemini/", - "cursor": ".cursor/", - "qwen": ".qwen/", - "opencode": ".opencode/", - "codex": ".codex/", - "windsurf": ".windsurf/", - "kilocode": ".kilocode/", - "auggie": ".augment/", - "codebuddy": ".codebuddy/", - "copilot": ".github/", - "roo": ".roo/", - "q": ".amazonq/" - } - - if selected_ai in agent_folder_map: - agent_folder = agent_folder_map[selected_ai] + if selected_ai in AGENT_FOLDER_MAP: + agent_folder = AGENT_FOLDER_MAP[selected_ai] security_notice = Panel( f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n" f"Consider adding [cyan]{agent_folder}[/cyan] (or parts of it) to [cyan].gitignore[/cyan] to prevent accidental credential leakage.", @@ -1067,37 +1098,28 @@ def check(): tracker = StepTracker("Check Available Tools") + # Add git check tracker.add("git", "Git version control") - tracker.add("claude", "Claude Code CLI") - tracker.add("gemini", "Gemini CLI") - tracker.add("qwen", "Qwen Code CLI") + git_ok = check_tool("git", tracker=tracker) + + # Check AI agent tools + agent_results = {} + for agent_key, agent_config in AGENT_CONFIG.items(): + agent_name = agent_config["name"] + # Determine the CLI tool name (usually same as agent key, with exceptions) + cli_tool = agent_key + if agent_key == "cursor": + cli_tool = "cursor-agent" + + tracker.add(cli_tool, agent_name) + agent_results[agent_key] = check_tool(cli_tool, tracker=tracker) + + # Check VS Code variants (not in agent config) tracker.add("code", "Visual Studio Code") + code_ok = check_tool("code", tracker=tracker) + tracker.add("code-insiders", "Visual Studio Code Insiders") - tracker.add("cursor-agent", "Cursor IDE agent") - tracker.add("windsurf", "Windsurf IDE") - tracker.add("kilocode", "Kilo Code IDE") - tracker.add("opencode", "opencode") - tracker.add("codex", "Codex CLI") - tracker.add("auggie", "Auggie CLI") - tracker.add("roo", "Roo Code") - tracker.add("codebuddy", "CodeBuddy") - tracker.add("q", "Amazon Q Developer CLI") - - git_ok = check_tool_for_tracker("git", tracker) - claude_ok = check_tool_for_tracker("claude", tracker) - gemini_ok = check_tool_for_tracker("gemini", tracker) - qwen_ok = check_tool_for_tracker("qwen", tracker) - code_ok = check_tool_for_tracker("code", tracker) - code_insiders_ok = check_tool_for_tracker("code-insiders", tracker) - cursor_ok = check_tool_for_tracker("cursor-agent", tracker) - windsurf_ok = check_tool_for_tracker("windsurf", tracker) - kilocode_ok = check_tool_for_tracker("kilocode", tracker) - opencode_ok = check_tool_for_tracker("opencode", tracker) - codex_ok = check_tool_for_tracker("codex", tracker) - auggie_ok = check_tool_for_tracker("auggie", tracker) - roo_ok = check_tool_for_tracker("roo", tracker) - codebuddy_ok = check_tool_for_tracker("codebuddy", tracker) - q_ok = check_tool_for_tracker("q", tracker) + code_insiders_ok = check_tool("code-insiders", tracker=tracker) console.print(tracker.render()) @@ -1106,7 +1128,7 @@ def check(): if not git_ok: console.print("[dim]Tip: Install git for repository management[/dim]") - if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or kilocode_ok or opencode_ok or codex_ok or auggie_ok or codebuddy_ok or q_ok): + if not any(agent_results.values()): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") def main(): From 9c87fdd5bbd2dd5231214674faf304058d855237 Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:22:57 -0700 Subject: [PATCH 15/19] Update __init__.py --- src/specify_cli/__init__.py | 74 ++++++++++--------------------------- 1 file changed, 19 insertions(+), 55 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 1da5466f..5d53109d 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -146,10 +146,6 @@ AGENT_CONFIG = { }, } -# Derived dictionaries for backward compatibility -AI_CHOICES = {key: config["name"] for key, config in AGENT_CONFIG.items()} -AGENT_FOLDER_MAP = {key: config["folder"] for key, config in AGENT_CONFIG.items()} - SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude" @@ -202,7 +198,7 @@ class StepTracker: s["detail"] = detail self._maybe_refresh() return - # If not present, add it + self.steps.append({"key": key, "label": key, "status": status, "detail": detail}) self._maybe_refresh() @@ -219,7 +215,6 @@ class StepTracker: label = step["label"] detail_text = step["detail"].strip() if step["detail"] else "" - # Circles (unchanged styling) status = step["status"] if status == "done": symbol = "[green]●[/green]" @@ -343,7 +338,6 @@ def select_with_arrows(options: dict, prompt_text: str = "Select an option", def console.print("\n[red]Selection failed.[/red]") raise typer.Exit(1) - # Suppress explicit selection print; tracker / later logic will report consolidated status return selected_key console = Console() @@ -367,7 +361,6 @@ app = typer.Typer( def show_banner(): """Display the ASCII art banner.""" - # Create gradient effect with different colors banner_lines = BANNER.strip().split('\n') colors = ["bright_blue", "blue", "cyan", "bright_cyan", "white", "bright_white"] @@ -383,8 +376,6 @@ def show_banner(): @app.callback() def callback(ctx: typer.Context): """Show banner when no subcommand is provided.""" - # Show banner only when no subcommand and no help flag - # (help is handled by BannerGroup) if ctx.invoked_subcommand is None and "--help" not in sys.argv and "-h" not in sys.argv: show_banner() 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 console.print(Panel(str(e), title="Fetch Error", border_style="red")) raise typer.Exit(1) - # Find the template asset for the specified AI assistant assets = release_data.get("assets", []) pattern = f"spec-kit-template-{ai_assistant}-{script_type}" matching_assets = [ @@ -600,7 +590,6 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ """ current_dir = Path.cwd() - # Step: fetch + download combined if tracker: tracker.start("fetch", "contacting GitHub API") try: @@ -633,12 +622,10 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ console.print("Extracting template...") try: - # Create project directory only if not using current directory if not is_current_dir: project_path.mkdir(parents=True) with zipfile.ZipFile(zip_path, 'r') as zip_ref: - # List all files in the ZIP for debugging zip_contents = zip_ref.namelist() if tracker: tracker.start("zip-list") @@ -646,13 +633,11 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ elif verbose: console.print(f"[cyan]ZIP contains {len(zip_contents)} items[/cyan]") - # For current directory, extract to a temp location first if is_current_dir: with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) zip_ref.extractall(temp_path) - # Check what was extracted extracted_items = list(temp_path.iterdir()) if tracker: tracker.start("extracted-summary") @@ -660,7 +645,6 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ elif verbose: console.print(f"[cyan]Extracted {len(extracted_items)} items to temp location[/cyan]") - # Handle GitHub-style ZIP with a single root directory source_dir = temp_path if len(extracted_items) == 1 and extracted_items[0].is_dir(): source_dir = extracted_items[0] @@ -670,14 +654,12 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ elif verbose: console.print(f"[cyan]Found nested directory structure[/cyan]") - # Copy contents to current directory for item in source_dir.iterdir(): dest_path = project_path / item.name if item.is_dir(): if dest_path.exists(): if verbose and not tracker: console.print(f"[yellow]Merging directory:[/yellow] {item.name}") - # Recursively copy directory contents for sub_item in item.rglob('*'): if sub_item.is_file(): rel_path = sub_item.relative_to(item) @@ -693,10 +675,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ if verbose and not tracker: console.print(f"[cyan]Template files merged into current directory[/cyan]") else: - # Extract directly to project directory (original behavior) zip_ref.extractall(project_path) - # Check what was extracted extracted_items = list(project_path.iterdir()) if tracker: tracker.start("extracted-summary") @@ -706,16 +686,14 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ for item in extracted_items: console.print(f" - {item.name} ({'dir' if item.is_dir() else 'file'})") - # Handle GitHub-style ZIP with a single root directory if len(extracted_items) == 1 and extracted_items[0].is_dir(): - # Move contents up one level nested_dir = extracted_items[0] temp_move_dir = project_path.parent / f"{project_path.name}_temp" - # Move the nested directory contents to temp location + shutil.move(str(nested_dir), str(temp_move_dir)) - # Remove the now-empty project directory + project_path.rmdir() - # Rename temp directory to project directory + shutil.move(str(temp_move_dir), str(project_path)) if tracker: tracker.add("flatten", "Flatten nested directory") @@ -731,7 +709,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ console.print(f"[red]Error extracting template:[/red] {e}") if debug: console.print(Panel(str(e), title="Extraction Error", border_style="red")) - # Clean up project directory if created and not current directory + if not is_current_dir and project_path.exists(): shutil.rmtree(project_path) raise typer.Exit(1) @@ -741,7 +719,7 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_ finally: if tracker: tracker.add("cleanup", "Remove temporary archive") - # Clean up downloaded ZIP file + if zip_path.exists(): zip_path.unlink() if tracker: @@ -836,7 +814,6 @@ def init( show_banner() - # Handle '.' as shorthand for current directory (equivalent to --here) if project_name == ".": here = True project_name = None # Clear project_name to use existing validation logic @@ -887,14 +864,11 @@ def init( f"{'Working Path':<15} [dim]{current_dir}[/dim]", ] - # Add target path only if different from working dir if not here: setup_lines.append(f"{'Target Path':<15} [dim]{project_path}[/dim]") console.print(Panel("\n".join(setup_lines), border_style="cyan", padding=(1, 2))) - # Check git only if we might need it (not --no-git) - # Only set to True if the user wants it and the tool is available should_init_git = False if not no_git: should_init_git = check_tool("git", "https://git-scm.com/downloads") @@ -902,32 +876,32 @@ def init( console.print("[yellow]Git not found - will skip repository initialization[/yellow]") if ai_assistant: - if ai_assistant not in AI_CHOICES: - console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AI_CHOICES.keys())}") + if ai_assistant not in AGENT_CONFIG: + console.print(f"[red]Error:[/red] Invalid AI assistant '{ai_assistant}'. Choose from: {', '.join(AGENT_CONFIG.keys())}") raise typer.Exit(1) selected_ai = ai_assistant else: - # Use arrow-key selection interface + # Create options dict for selection (agent_key: display_name) + ai_choices = {key: config["name"] for key, config in AGENT_CONFIG.items()} selected_ai = select_with_arrows( - AI_CHOICES, + ai_choices, "Choose your AI assistant:", "copilot" ) - # Check agent tools unless ignored if not ignore_agent_tools: agent_config = AGENT_CONFIG.get(selected_ai) if agent_config and agent_config["requires_cli"]: cli_tool = selected_ai if selected_ai == "cursor": cli_tool = "cursor-agent" - + install_url = agent_config["install_url"] if not check_tool(cli_tool, install_url): error_panel = Panel( f"[cyan]{selected_ai}[/cyan] not found\n" f"Install from: [cyan]{install_url}[/cyan]\n" - f"{AI_CHOICES[selected_ai]} is required to continue with this project type.\n\n" + f"{agent_config['name']} is required to continue with this project type.\n\n" "Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check", title="[red]Agent Detection Error[/red]", border_style="red", @@ -937,16 +911,14 @@ def init( console.print(error_panel) raise typer.Exit(1) - # Determine script type (explicit, interactive, or OS default) if script_type: if script_type not in SCRIPT_TYPE_CHOICES: console.print(f"[red]Error:[/red] Invalid script type '{script_type}'. Choose from: {', '.join(SCRIPT_TYPE_CHOICES.keys())}") raise typer.Exit(1) selected_script = script_type else: - # Auto-detect default default_script = "ps" if os.name == "nt" else "sh" - # Provide interactive selection similar to AI if stdin is a TTY + if sys.stdin.isatty(): selected_script = select_with_arrows(SCRIPT_TYPE_CHOICES, "Choose script type (or press Enter)", default_script) else: @@ -955,12 +927,10 @@ def init( console.print(f"[cyan]Selected AI assistant:[/cyan] {selected_ai}") console.print(f"[cyan]Selected script type:[/cyan] {selected_script}") - # Download and set up project - # New tree-based progress (no emojis); include earlier substeps tracker = StepTracker("Initialize Specify Project") - # Flag to allow suppressing legacy headings + sys._specify_tracker_active = True - # Pre steps recorded as completed before live rendering + tracker.add("precheck", "Check required tools") tracker.complete("precheck", "ok") tracker.add("ai-select", "Select AI assistant") @@ -980,21 +950,17 @@ def init( ]: tracker.add(key, label) - # Use transient so live tree is replaced by the final static render (avoids duplicate output) with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live: tracker.attach_refresh(lambda: live.update(tracker.render())) try: - # Create a httpx client with verify based on skip_tls verify = not skip_tls local_ssl_context = ssl_context if verify else False local_client = httpx.Client(verify=local_ssl_context) download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token) - # Ensure scripts are executable (POSIX) ensure_executable_scripts(project_path, tracker=tracker) - # Git step if not no_git: tracker.start("git") if is_git_repo(project_path): @@ -1026,16 +992,15 @@ def init( shutil.rmtree(project_path) raise typer.Exit(1) finally: - # Force final render pass - # Final static tree (ensures finished state visible after Live context ends) console.print(tracker.render()) console.print("\n[bold green]Project ready.[/bold green]") # Agent folder security notice - if selected_ai in AGENT_FOLDER_MAP: - agent_folder = AGENT_FOLDER_MAP[selected_ai] + agent_config = AGENT_CONFIG.get(selected_ai) + if agent_config: + agent_folder = agent_config["folder"] security_notice = Panel( f"Some agents may store credentials, auth tokens, or other identifying and private artifacts in the agent folder within your project.\n" 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( console.print() console.print(security_notice) - # Boxed "Next steps" section steps_lines = [] if not here: steps_lines.append(f"1. Go to the project folder: [cyan]cd {project_name}[/cyan]") From e91aca54ee1ccf9925d1080c6c89b708cafbc78c Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:35:05 -0700 Subject: [PATCH 16/19] Git errors are now highlighted. --- src/specify_cli/__init__.py | 50 +++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 5d53109d..caa6b036 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -451,26 +451,38 @@ def is_git_repo(path: Path = None) -> bool: except (subprocess.CalledProcessError, FileNotFoundError): return False -def init_git_repo(project_path: Path, quiet: bool = False) -> bool: +def init_git_repo(project_path: Path, quiet: bool = False) -> Tuple[bool, Optional[str]]: """Initialize a git repository in the specified path. - quiet: if True suppress console output (tracker handles status) + + Args: + project_path: Path to initialize git repository in + quiet: if True suppress console output (tracker handles status) + + Returns: + Tuple of (success: bool, error_message: Optional[str]) """ try: original_cwd = Path.cwd() os.chdir(project_path) if not quiet: console.print("[cyan]Initializing git repository...[/cyan]") - subprocess.run(["git", "init"], check=True, capture_output=True) - subprocess.run(["git", "add", "."], check=True, capture_output=True) - subprocess.run(["git", "commit", "-m", "Initial commit from Specify template"], check=True, capture_output=True) + subprocess.run(["git", "init"], check=True, capture_output=True, text=True) + subprocess.run(["git", "add", "."], check=True, capture_output=True, text=True) + subprocess.run(["git", "commit", "-m", "Initial commit from Specify template"], check=True, capture_output=True, text=True) if not quiet: console.print("[green]✓[/green] Git repository initialized") - return True + return True, None except subprocess.CalledProcessError as e: + error_msg = f"Command: {' '.join(e.cmd)}\nExit code: {e.returncode}" + if e.stderr: + error_msg += f"\nError: {e.stderr.strip()}" + elif e.stdout: + error_msg += f"\nOutput: {e.stdout.strip()}" + if not quiet: console.print(f"[red]Error initializing git repository:[/red] {e}") - return False + return False, error_msg finally: os.chdir(original_cwd) @@ -950,6 +962,9 @@ def init( ]: tracker.add(key, label) + # Track git error message outside Live context so it persists + git_error_message = None + with Live(tracker.render(), console=console, refresh_per_second=8, transient=True) as live: tracker.attach_refresh(lambda: live.update(tracker.render())) try: @@ -966,10 +981,12 @@ def init( if is_git_repo(project_path): tracker.complete("git", "existing repo detected") elif should_init_git: - if init_git_repo(project_path, quiet=True): + success, error_msg = init_git_repo(project_path, quiet=True) + if success: tracker.complete("git", "initialized") else: tracker.error("git", "init failed") + git_error_message = error_msg else: tracker.skip("git", "git not available") else: @@ -996,6 +1013,23 @@ def init( console.print(tracker.render()) console.print("\n[bold green]Project ready.[/bold green]") + + # Show git error details if initialization failed + if git_error_message: + console.print() + git_error_panel = Panel( + f"[yellow]Warning:[/yellow] Git repository initialization failed\n\n" + f"{git_error_message}\n\n" + f"[dim]You can initialize git manually later with:[/dim]\n" + f"[cyan]cd {project_path if not here else '.'}[/cyan]\n" + f"[cyan]git init[/cyan]\n" + f"[cyan]git add .[/cyan]\n" + f"[cyan]git commit -m \"Initial commit\"[/cyan]", + title="[red]Git Initialization Failed[/red]", + border_style="red", + padding=(1, 2) + ) + console.print(git_error_panel) # Agent folder security notice agent_config = AGENT_CONFIG.get(selected_ai) From 058ee510a7e322b11dc07f3b75bb69d15f619468 Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:35:48 -0700 Subject: [PATCH 17/19] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a90841f3..219a4cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support for CodeBuddy (thank you to [@lispking](https://github.com/lispking) for the contribution). +- You can now see Git-sourced errors in the Specify CLI. ### Changed From ed9044345b305882a1dda6427afbd97bf32e19a7 Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:45:51 -0700 Subject: [PATCH 18/19] Consolidate Cursor naming --- README.md | 2 +- src/specify_cli/__init__.py | 18 +++++------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 106e023b..9f51efa5 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ specify init my-project specify init my-project --ai claude # Initialize with Cursor support -specify init my-project --ai cursor +specify init my-project --ai cursor-agent # Initialize with Windsurf support specify init my-project --ai windsurf diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index caa6b036..01d0b90a 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -84,7 +84,7 @@ AGENT_CONFIG = { "install_url": "https://github.com/google-gemini/gemini-cli", "requires_cli": True, }, - "cursor": { + "cursor-agent": { "name": "Cursor", "folder": ".cursor/", "install_url": None, # IDE-based @@ -789,7 +789,7 @@ def ensure_executable_scripts(project_path: Path, tracker: StepTracker | None = @app.command() def init( project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"), - ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, or q"), + ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor-agent, qwen, opencode, codex, windsurf, kilocode, auggie, codebuddy, or q"), script_type: str = typer.Option(None, "--script", help="Script type to use: sh or ps"), ignore_agent_tools: bool = typer.Option(False, "--ignore-agent-tools", help="Skip checks for AI agent tools like Claude Code"), no_git: bool = typer.Option(False, "--no-git", help="Skip git repository initialization"), @@ -904,12 +904,8 @@ def init( if not ignore_agent_tools: agent_config = AGENT_CONFIG.get(selected_ai) if agent_config and agent_config["requires_cli"]: - cli_tool = selected_ai - if selected_ai == "cursor": - cli_tool = "cursor-agent" - install_url = agent_config["install_url"] - if not check_tool(cli_tool, install_url): + if not check_tool(selected_ai, install_url): error_panel = Panel( f"[cyan]{selected_ai}[/cyan] not found\n" f"Install from: [cyan]{install_url}[/cyan]\n" @@ -1104,13 +1100,9 @@ def check(): agent_results = {} for agent_key, agent_config in AGENT_CONFIG.items(): agent_name = agent_config["name"] - # Determine the CLI tool name (usually same as agent key, with exceptions) - cli_tool = agent_key - if agent_key == "cursor": - cli_tool = "cursor-agent" - tracker.add(cli_tool, agent_name) - agent_results[agent_key] = check_tool(cli_tool, tracker=tracker) + tracker.add(agent_key, agent_name) + agent_results[agent_key] = check_tool(agent_key, tracker=tracker) # Check VS Code variants (not in agent config) tracker.add("code", "Visual Studio Code") From aec568949c5654c38a2d127aaaac222752d41b24 Mon Sep 17 00:00:00 2001 From: "den (work)" <53200638+localden@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:49:51 -0700 Subject: [PATCH 19/19] Update __init__.py --- src/specify_cli/__init__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 01d0b90a..ed51f066 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -399,12 +399,11 @@ def run_command(cmd: list[str], check_return: bool = True, capture: bool = False raise return None -def check_tool(tool: str, install_hint: str = "", tracker: StepTracker = None) -> bool: +def check_tool(tool: str, tracker: StepTracker = None) -> bool: """Check if a tool is installed. Optionally update tracker. Args: tool: Name of the tool to check - install_hint: URL or hint for installing the tool (for error messages) tracker: Optional StepTracker to update with results Returns: @@ -1092,11 +1091,9 @@ def check(): tracker = StepTracker("Check Available Tools") - # Add git check tracker.add("git", "Git version control") git_ok = check_tool("git", tracker=tracker) - # Check AI agent tools agent_results = {} for agent_key, agent_config in AGENT_CONFIG.items(): agent_name = agent_config["name"]