Compare commits

...

4 Commits

Author SHA1 Message Date
Den Delimarsky 🌺
0889635e66 Update with Windsurf support 2025-09-19 18:01:47 -07:00
Den Delimarsky
2825bb1247 Merge pull request #243 from zryfish/dev/add_authentication_when_download_template
add github auth headers if there are GITHUB_TOKEN/GH_TOKEN set
2025-09-19 17:29:59 -07:00
zhangrenyu
b1688b9633 expose token as an argument through cli --github-token 2025-09-17 17:05:49 +08:00
zryfish
70413f5214 add github auth headers if there are GITHUB_TOKEN/GH_TOKEN set 2025-09-15 00:06:08 +08:00
6 changed files with 293 additions and 21 deletions

View File

@@ -81,7 +81,7 @@ jobs:
cat > release_notes.md << EOF
Template release ${{ steps.get_tag.outputs.new_version }}
Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, and opencode.
Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor, Qwen, opencode, and Windsurf.
Now includes per-script variants for POSIX shell (sh) and PowerShell (ps).
@@ -98,6 +98,8 @@ jobs:
- spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip
- spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip
- spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip
- spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip
- spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip
EOF
echo "Generated release notes:"
@@ -122,6 +124,8 @@ jobs:
spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-qwen-sh-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-qwen-ps-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-windsurf-sh-${{ steps.get_tag.outputs.new_version }}.zip \
spec-kit-template-windsurf-ps-${{ steps.get_tag.outputs.new_version }}.zip \
--title "Spec Kit Templates - $VERSION_NO_V" \
--notes-file release_notes.md
env:

View File

@@ -154,13 +154,16 @@ build_variant() {
opencode)
mkdir -p "$base_dir/.opencode/command"
generate_commands opencode md "\$ARGUMENTS" "$base_dir/.opencode/command" "$script" ;;
windsurf)
mkdir -p "$base_dir/.windsurf/workflows"
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
esac
( cd "$base_dir" && zip -r "../spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip" . )
echo "Created spec-kit-template-${agent}-${script}-${NEW_VERSION}.zip"
}
# Determine agent list
ALL_AGENTS=(claude gemini copilot cursor qwen opencode)
ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf)
ALL_SCRIPTS=(sh ps)

230
AGENTS.md Normal file
View File

@@ -0,0 +1,230 @@
# AGENTS.md
## About Spec Kit and Specify
**GitHub Spec Kit** is a comprehensive toolkit for implementing Spec-Driven Development (SDD) - a methodology that emphasizes creating clear specifications before implementation. The toolkit includes templates, scripts, and workflows that guide development teams through a structured approach to building software.
**Specify CLI** is the command-line interface that bootstraps projects with the Spec Kit framework. It sets up the necessary directory structures, templates, and AI agent integrations to support the Spec-Driven Development workflow.
The toolkit supports multiple AI coding assistants, allowing teams to use their preferred tools while maintaining consistent project structure and development practices.
---
## Adding New Agent Support
This section explains how to add support for new AI agents/assistants to the Specify CLI. Use this guide as a reference when integrating new AI tools into the Spec-Driven Development workflow.
### Overview
Specify supports multiple AI agents by generating agent-specific command files and directory structures when initializing projects. Each agent has its own conventions for:
- **Command file formats** (Markdown, TOML, etc.)
- **Directory structures** (`.claude/commands/`, `.windsurf/workflows/`, etc.)
- **Command invocation patterns** (slash commands, CLI tools, etc.)
- **Argument passing conventions** (`$ARGUMENTS`, `{{args}}`, etc.)
### Current Supported Agents
| Agent | Directory | Format | CLI Tool | Description |
|-------|-----------|---------|----------|-------------|
| **Claude Code** | `.claude/commands/` | Markdown | `claude` | Anthropic's Claude Code CLI |
| **Gemini CLI** | `.gemini/commands/` | TOML | `gemini` | Google's Gemini CLI |
| **GitHub Copilot** | `.github/prompts/` | Markdown | N/A (IDE-based) | GitHub Copilot in VS Code |
| **Cursor** | `.cursor/commands/` | Markdown | `cursor-agent` | Cursor CLI |
| **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 |
### Step-by-Step Integration Guide
Follow these steps to add a new agent (using Windsurf as an example):
#### 1. Update AI_CHOICES Constant
Add the new agent to the `AI_CHOICES` dictionary in `src/specify_cli/__init__.py`:
```python
AI_CHOICES = {
"copilot": "GitHub Copilot",
"claude": "Claude Code",
"gemini": "Gemini CLI",
"cursor": "Cursor",
"qwen": "Qwen Code",
"opencode": "opencode",
"windsurf": "Windsurf" # Add new agent here
}
```
#### 2. Update CLI Help Text
Update all help text and examples to include the new agent:
- Command option help: `--ai` parameter description
- Function docstrings and examples
- Error messages with agent lists
#### 3. Update Release Package Script
Modify `.github/workflows/scripts/create-release-packages.sh`:
##### Add to ALL_AGENTS array:
```bash
ALL_AGENTS=(claude gemini copilot cursor qwen opencode windsurf)
```
##### Add case statement for directory structure:
```bash
case $agent in
# ... existing cases ...
windsurf)
mkdir -p "$base_dir/.windsurf/workflows"
generate_commands windsurf md "\$ARGUMENTS" "$base_dir/.windsurf/workflows" "$script" ;;
esac
```
#### 4. Update Agent Context Scripts
##### Bash script (`scripts/bash/update-agent-context.sh`):
Add file variable:
```bash
WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
```
Add to case statement:
```bash
case "$AGENT_TYPE" in
# ... existing cases ...
windsurf) update_agent_file "$WINDSURF_FILE" "Windsurf" ;;
"")
# ... existing checks ...
[ -f "$WINDSURF_FILE" ] && update_agent_file "$WINDSURF_FILE" "Windsurf";
# Update default creation condition
;;
esac
```
##### PowerShell script (`scripts/powershell/update-agent-context.ps1`):
Add file variable:
```powershell
$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md'
```
Add to switch statement:
```powershell
switch ($AgentType) {
# ... existing cases ...
'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' }
'' {
foreach ($pair in @(
# ... existing pairs ...
@{file=$windsurfFile; name='Windsurf'}
)) {
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
}
# Update default creation condition
}
}
```
#### 5. Update CLI Tool Checks (Optional)
For agents that require CLI tools, add checks in the `check()` command and agent validation:
```python
# In check() command
tracker.add("windsurf", "Windsurf IDE (optional)")
windsurf_ok = check_tool_for_tracker("windsurf", "https://windsurf.com/", tracker)
# In init validation (only if CLI tool required)
elif selected_ai == "windsurf":
if not check_tool("windsurf", "Install from: https://windsurf.com/"):
console.print("[red]Error:[/red] Windsurf CLI is required for Windsurf projects")
agent_tool_missing = True
```
**Note**: Skip CLI checks for IDE-based agents (Copilot, Windsurf).
## Agent Categories
### CLI-Based Agents
Require a command-line tool to be installed:
- **Claude Code**: `claude` CLI
- **Gemini CLI**: `gemini` CLI
- **Cursor**: `cursor-agent` CLI
- **Qwen Code**: `qwen` CLI
- **opencode**: `opencode` CLI
### IDE-Based Agents
Work within integrated development environments:
- **GitHub Copilot**: Built into VS Code/compatible editors
- **Windsurf**: Built into Windsurf IDE
## Command File Formats
### Markdown Format
Used by: Claude, Cursor, opencode, Windsurf
```markdown
---
description: "Command description"
---
Command content with {SCRIPT} and $ARGUMENTS placeholders.
```
### TOML Format
Used by: Gemini, Qwen
```toml
description = "Command description"
prompt = """
Command content with {SCRIPT} and {{args}} placeholders.
"""
```
## Directory Conventions
- **CLI agents**: Usually `.<agent-name>/commands/`
- **IDE agents**: Follow IDE-specific patterns:
- Copilot: `.github/prompts/`
- Cursor: `.cursor/commands/`
- Windsurf: `.windsurf/workflows/`
## Argument Patterns
Different agents use different argument placeholders:
- **Markdown/prompt-based**: `$ARGUMENTS`
- **TOML-based**: `{{args}}`
- **Script placeholders**: `{SCRIPT}` (replaced with actual script path)
- **Agent placeholders**: `__AGENT__` (replaced with agent name)
## Testing New Agent Integration
1. **Build test**: Run package creation script locally
2. **CLI test**: Test `specify init --ai <agent>` command
3. **File generation**: Verify correct directory structure and files
4. **Command validation**: Ensure generated commands work with the agent
5. **Context update**: Test agent context update scripts
## Common Pitfalls
1. **Forgetting update scripts**: Both bash and PowerShell scripts must be updated
2. **Missing CLI checks**: Only add for agents that actually have CLI tools
3. **Wrong argument format**: Use correct placeholder format for each agent type
4. **Directory naming**: Follow agent-specific conventions exactly
5. **Help text inconsistency**: Update all user-facing text consistently
## Future Considerations
When adding new agents:
- Consider the agent's native command/workflow patterns
- Ensure compatibility with the Spec-Driven Development process
- Document any special requirements or limitations
- Update this guide with lessons learned
---
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*

View File

@@ -4,7 +4,7 @@ REPO_ROOT=$(git rev-parse --show-toplevel)
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
FEATURE_DIR="$REPO_ROOT/specs/$CURRENT_BRANCH"
NEW_PLAN="$FEATURE_DIR/plan.md"
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"; GEMINI_FILE="$REPO_ROOT/GEMINI.md"; COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"; CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"; QWEN_FILE="$REPO_ROOT/QWEN.md"; AGENTS_FILE="$REPO_ROOT/AGENTS.md"
CLAUDE_FILE="$REPO_ROOT/CLAUDE.md"; GEMINI_FILE="$REPO_ROOT/GEMINI.md"; COPILOT_FILE="$REPO_ROOT/.github/copilot-instructions.md"; CURSOR_FILE="$REPO_ROOT/.cursor/rules/specify-rules.mdc"; QWEN_FILE="$REPO_ROOT/QWEN.md"; AGENTS_FILE="$REPO_ROOT/AGENTS.md"; WINDSURF_FILE="$REPO_ROOT/.windsurf/rules/specify-rules.md"
AGENT_TYPE="$1"
[ -f "$NEW_PLAN" ] || { echo "ERROR: No plan.md found at $NEW_PLAN"; exit 1; }
echo "=== Updating agent context files for feature $CURRENT_BRANCH ==="
@@ -54,13 +54,15 @@ case "$AGENT_TYPE" in
cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;;
qwen) update_agent_file "$QWEN_FILE" "Qwen Code" ;;
opencode) update_agent_file "$AGENTS_FILE" "opencode" ;;
windsurf) update_agent_file "$WINDSURF_FILE" "Windsurf" ;;
"") [ -f "$CLAUDE_FILE" ] && update_agent_file "$CLAUDE_FILE" "Claude Code"; \
[ -f "$GEMINI_FILE" ] && update_agent_file "$GEMINI_FILE" "Gemini CLI"; \
[ -f "$COPILOT_FILE" ] && update_agent_file "$COPILOT_FILE" "GitHub Copilot"; \
[ -f "$CURSOR_FILE" ] && update_agent_file "$CURSOR_FILE" "Cursor IDE"; \
[ -f "$QWEN_FILE" ] && update_agent_file "$QWEN_FILE" "Qwen Code"; \
[ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "opencode"; \
if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ] && [ ! -f "$QWEN_FILE" ] && [ ! -f "$AGENTS_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;;
*) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode)"; exit 1 ;;
[ -f "$WINDSURF_FILE" ] && update_agent_file "$WINDSURF_FILE" "Windsurf"; \
if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ] && [ ! -f "$QWEN_FILE" ] && [ ! -f "$AGENTS_FILE" ] && [ ! -f "$WINDSURF_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;;
*) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode|windsurf)"; exit 1 ;;
esac
echo; echo "Summary of changes:"; [ -n "$NEW_LANG" ] && echo "- Added language: $NEW_LANG"; [ -n "$NEW_FRAMEWORK" ] && echo "- Added framework: $NEW_FRAMEWORK"; [ -n "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ] && echo "- Added database: $NEW_DB"; echo; echo "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode]"
echo; echo "Summary of changes:"; [ -n "$NEW_LANG" ] && echo "- Added language: $NEW_LANG"; [ -n "$NEW_FRAMEWORK" ] && echo "- Added framework: $NEW_FRAMEWORK"; [ -n "$NEW_DB" ] && [ "$NEW_DB" != "N/A" ] && echo "- Added database: $NEW_DB"; echo; echo "Usage: $0 [claude|gemini|copilot|cursor|qwen|opencode|windsurf]"

View File

@@ -15,6 +15,7 @@ $copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md'
$cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc'
$qwenFile = Join-Path $repoRoot 'QWEN.md'
$agentsFile = Join-Path $repoRoot 'AGENTS.md'
$windsurfFile = Join-Path $repoRoot '.windsurf/rules/specify-rules.md'
Write-Output "=== Updating agent context files for feature $currentBranch ==="
@@ -75,6 +76,7 @@ switch ($AgentType) {
'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' }
'qwen' { Update-AgentFile $qwenFile 'Qwen Code' }
'opencode' { Update-AgentFile $agentsFile 'opencode' }
'windsurf' { Update-AgentFile $windsurfFile 'Windsurf' }
'' {
foreach ($pair in @(
@{file=$claudeFile; name='Claude Code'},
@@ -82,16 +84,17 @@ switch ($AgentType) {
@{file=$copilotFile; name='GitHub Copilot'},
@{file=$cursorFile; name='Cursor IDE'},
@{file=$qwenFile; name='Qwen Code'},
@{file=$agentsFile; name='opencode'}
@{file=$agentsFile; name='opencode'},
@{file=$windsurfFile; name='Windsurf'}
)) {
if (Test-Path $pair.file) { Update-AgentFile $pair.file $pair.name }
}
if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile)) {
if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $qwenFile) -and -not (Test-Path $agentsFile) -and -not (Test-Path $windsurfFile)) {
Write-Output 'No agent context files found. Creating Claude Code context file by default.'
Update-AgentFile $claudeFile 'Claude Code'
}
}
Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode or leave empty for all."; exit 1 }
Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, qwen, opencode, windsurf or leave empty for all."; exit 1 }
}
Write-Output ''
@@ -101,4 +104,4 @@ if ($newFramework) { Write-Output "- Added framework: $newFramework" }
if ($newDb -and $newDb -ne 'N/A') { Write-Output "- Added database: $newDb" }
Write-Output ''
Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode]'
Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|qwen|opencode|windsurf]'

View File

@@ -52,6 +52,19 @@ import truststore
ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client = httpx.Client(verify=ssl_context)
def _github_token(cli_token: str | None = None) -> str | None:
return cli_token or os.getenv("GH_TOKEN") or os.getenv("GITHUB_TOKEN")
def _github_auth_headers(cli_token: str | None = None) -> dict:
"""Headers for GitHub REST API requests.
- Uses Bearer auth if token present
"""
headers = {}
token = _github_token(cli_token)
if token:
headers["Authorization"] = f"Bearer {token}"
return headers
# Constants
AI_CHOICES = {
"copilot": "GitHub Copilot",
@@ -59,7 +72,8 @@ AI_CHOICES = {
"gemini": "Gemini CLI",
"cursor": "Cursor",
"qwen": "Qwen Code",
"opencode": "opencode"
"opencode": "opencode",
"windsurf": "Windsurf"
}
# Add script type choices
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
@@ -418,7 +432,7 @@ def init_git_repo(project_path: Path, quiet: bool = False) -> bool:
os.chdir(original_cwd)
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]:
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, github_token: str = None) -> Tuple[Path, dict]:
repo_owner = "github"
repo_name = "spec-kit"
if client is None:
@@ -429,7 +443,12 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest"
try:
response = client.get(api_url, timeout=30, follow_redirects=True)
response = client.get(
api_url,
timeout=30,
follow_redirects=True,
headers=_github_auth_headers(github_token) or None,
)
status = response.status_code
if status != 200:
msg = f"GitHub API returned {status} for {api_url}"
@@ -475,7 +494,14 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
console.print(f"[cyan]Downloading template...[/cyan]")
try:
with client.stream("GET", download_url, timeout=60, follow_redirects=True) as response:
# Include auth header for initial GitHub request; it won't leak across cross-origin redirects
with client.stream(
"GET",
download_url,
timeout=60,
follow_redirects=True,
headers=_github_auth_headers(github_token) or None,
) as response:
if response.status_code != 200:
body_sample = response.text[:400]
raise RuntimeError(f"Download failed with {response.status_code}\nHeaders: {response.headers}\nBody (truncated): {body_sample}")
@@ -519,7 +545,7 @@ def download_template_from_github(ai_assistant: str, download_dir: Path, *, scri
return zip_path, metadata
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:
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, github_token: str = None) -> Path:
"""Download the latest release and extract it to create a new project.
Returns project_path. Uses tracker if provided (with keys: fetch, download, extract, cleanup)
"""
@@ -536,7 +562,8 @@ def download_and_extract_template(project_path: Path, ai_assistant: str, script_
verbose=verbose and tracker is None,
show_progress=(tracker is None),
client=client,
debug=debug
debug=debug,
github_token=github_token
)
if tracker:
tracker.complete("fetch", f"release {meta['release']} ({meta['size']:,} bytes)")
@@ -724,20 +751,21 @@ 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 or opencode"),
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode or windsurf"),
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"),
here: bool = typer.Option(False, "--here", help="Initialize project in the current directory instead of creating a new one"),
skip_tls: bool = typer.Option(False, "--skip-tls", help="Skip SSL/TLS verification (not recommended)"),
debug: bool = typer.Option(False, "--debug", help="Show verbose diagnostic output for network and extraction failures"),
github_token: str = typer.Option(None, "--github-token", help="GitHub token to use for API requests (or set GH_TOKEN or GITHUB_TOKEN environment variable)"),
):
"""
Initialize a new Specify project from the latest template.
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 or opencode)
2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode or Windsurf)
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)
@@ -750,7 +778,7 @@ def init(
specify init my-project --ai copilot --no-git
specify init my-project --ai cursor
specify init my-project --ai qwen
specify init my-project --ai opencode
specify init my-project --ai windsurf
specify init --ignore-agent-tools my-project
specify init --here --ai claude
specify init --here
@@ -897,7 +925,7 @@ def init(
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)
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)
@@ -975,6 +1003,7 @@ def check():
tracker.add("qwen", "Qwen Code CLI")
tracker.add("code", "VS Code (for GitHub Copilot)")
tracker.add("cursor-agent", "Cursor IDE agent (optional)")
tracker.add("windsurf", "Windsurf IDE (optional)")
tracker.add("opencode", "opencode")
git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker)
@@ -985,6 +1014,7 @@ def check():
if not code_ok:
code_ok = check_tool_for_tracker("code-insiders", "https://code.visualstudio.com/insiders/", tracker)
cursor_ok = check_tool_for_tracker("cursor-agent", "https://cursor.sh/", tracker)
windsurf_ok = check_tool_for_tracker("windsurf", "https://windsurf.com/", tracker)
opencode_ok = check_tool_for_tracker("opencode", "https://opencode.ai/", tracker)
console.print(tracker.render())
@@ -993,7 +1023,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 opencode_ok):
if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or windsurf_ok or opencode_ok):
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")