From c5f758247099c2c1c5a7cd5c6dc5492eba927732 Mon Sep 17 00:00:00 2001 From: Emre Date: Wed, 17 Sep 2025 13:12:50 -0700 Subject: [PATCH] feat: Add opencode ai agent --- .github/workflows/release.yml | 6 +++++- .../scripts/create-release-packages.sh | 7 +++++-- CHANGELOG.md | 9 +++++++- README.md | 9 ++++---- pyproject.toml | 2 +- scripts/bash/update-agent-context.sh | 11 +++++----- scripts/powershell/update-agent-context.ps1 | 9 +++++--- src/specify_cli/__init__.py | 21 +++++++++++++++---- templates/plan-template.md | 4 ++-- 9 files changed, 55 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb29563..e04a758 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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, and Cursor. + Updated specification-driven development templates for GitHub Copilot, Claude Code, Gemini CLI, Cursor and opencode. Now includes per-script variants for POSIX shell (sh) and PowerShell (ps). @@ -94,6 +94,8 @@ jobs: - spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip - spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip - spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip + - spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip + - spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip EOF echo "Generated release notes:" @@ -114,6 +116,8 @@ jobs: spec-kit-template-gemini-ps-${{ steps.get_tag.outputs.new_version }}.zip \ spec-kit-template-cursor-sh-${{ steps.get_tag.outputs.new_version }}.zip \ spec-kit-template-cursor-ps-${{ steps.get_tag.outputs.new_version }}.zip \ + spec-kit-template-opencode-sh-${{ steps.get_tag.outputs.new_version }}.zip \ + spec-kit-template-opencode-ps-${{ steps.get_tag.outputs.new_version }}.zip \ --title "Spec Kit Templates - $VERSION_NO_V" \ --notes-file release_notes.md env: diff --git a/.github/workflows/scripts/create-release-packages.sh b/.github/workflows/scripts/create-release-packages.sh index 0cb1fdb..90c7f41 100644 --- a/.github/workflows/scripts/create-release-packages.sh +++ b/.github/workflows/scripts/create-release-packages.sh @@ -6,7 +6,7 @@ set -euo pipefail # Usage: .github/workflows/scripts/create-release-packages.sh # Version argument should include leading 'v'. # Optionally set AGENTS and/or SCRIPTS env vars to limit what gets built. -# AGENTS : space or comma separated subset of: claude gemini copilot (default: all) +# AGENTS : space or comma separated subset of: claude gemini copilot opencode (default: all) # SCRIPTS : space or comma separated subset of: sh ps (default: both) # Examples: # AGENTS=claude SCRIPTS=sh $0 v0.2.0 @@ -147,13 +147,16 @@ build_variant() { mkdir -p "$base_dir/.qwen/commands" generate_commands qwen md "\$ARGUMENTS" "$base_dir/.qwen/commands" "$script" [[ -f agent_templates/qwen/QWEN.md ]] && cp agent_templates/qwen/QWEN.md "$base_dir/QWEN.md" ;; + opencode) + mkdir -p "$base_dir/.opencode/command" + generate_commands opencode md "\$ARGUMENTS" "$base_dir/.opencode/command" "$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) +ALL_AGENTS=(claude gemini copilot cursor qwen opencode) ALL_SCRIPTS=(sh ps) norm_list() { diff --git a/CHANGELOG.md b/CHANGELOG.md index 0722e52..5c763a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.6] - 2025-09-17 + +### Added + +- opencode support as additional AI assistant option + ## [0.0.5] - 2025-09-17 ### Added @@ -25,4 +31,5 @@ N/A ### Changed -N/A \ No newline at end of file +N/A + diff --git a/README.md b/README.md index eaf85c2..3c65440 100644 --- a/README.md +++ b/README.md @@ -81,14 +81,14 @@ The `specify` command supports the following options: | Command | Description | |-------------|----------------------------------------------------------------| | `init` | Initialize a new Specify project from the latest template | -| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `cursor-agent`) | +| `check` | Check for installed tools (`git`, `claude`, `gemini`, `code`/`code-insiders`, `opencode`, `cursor-agent`) | ### `specify init` Arguments & 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`, or `cursor` | +| `--ai` | Option | AI assistant to use: `claude`, `gemini`, `copilot`, `opencode`, or `cursor` | | `--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 | @@ -170,7 +170,7 @@ Our research and experimentation focus on: ## 🔧 Prerequisites - **Linux/macOS** (or WSL2 on Windows) -- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Cursor](https://cursor.sh/), or [Qwen CLI](https://github.com/QwenLM/qwen-code) +- AI coding agent: [Claude Code](https://www.anthropic.com/claude-code), [GitHub Copilot](https://code.visualstudio.com/), [Gemini CLI](https://github.com/google-gemini/gemini-cli), [Cursor](https://cursor.sh/), [Qwen CLI](https://github.com/QwenLM/qwen-code) or [opencode](https://opencode.ai/) - [uv](https://docs.astral.sh/uv/) for package management - [Python 3.11+](https://www.python.org/downloads/) - [Git](https://git-scm.com/downloads) @@ -208,11 +208,12 @@ specify init --ai claude specify init --ai gemini specify init --ai copilot specify init --ai qwen +specify init --ai opencode # Or in current directory: specify init --here --ai claude ``` -The CLI will check if you have Claude Code, Gemini CLI, or Qwen CLI installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command: +The CLI will check if you have Claude Code, Gemini CLI, Qwen CLI or opencode installed. If you do not, or you prefer to get the templates without checking for the right tools, use `--ignore-agent-tools` with your command: ```bash specify init --ai claude --ignore-agent-tools diff --git a/pyproject.toml b/pyproject.toml index 412c04a..3daa03d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "specify-cli" -version = "0.0.5" +version = "0.0.6" description = "Setup tool for Specify spec-driven development projects" requires-python = ">=3.11" dependencies = [ diff --git a/scripts/bash/update-agent-context.sh b/scripts/bash/update-agent-context.sh index 2ad22cb..7e46ca4 100644 --- a/scripts/bash/update-agent-context.sh +++ b/scripts/bash/update-agent-context.sh @@ -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" +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"; AGENTS_FILE="$REPO_ROOT/AGENTS.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 ===" @@ -52,11 +52,12 @@ case "$AGENT_TYPE" in gemini) update_agent_file "$GEMINI_FILE" "Gemini CLI" ;; copilot) update_agent_file "$COPILOT_FILE" "GitHub Copilot" ;; cursor) update_agent_file "$CURSOR_FILE" "Cursor IDE" ;; + opencode) update_agent_file "$AGENTS_FILE" "opencode" ;; "") [ -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"; \ - if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_FILE" ]; then update_agent_file "$CLAUDE_FILE" "Claude Code"; fi ;; - *) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor)"; 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]" + [ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "opencode"; \ + if [ ! -f "$CLAUDE_FILE" ] && [ ! -f "$GEMINI_FILE" ] && [ ! -f "$COPILOT_FILE" ] && [ ! -f "$CURSOR_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|opencode)"; exit 1 ;; +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|opencode]" diff --git a/scripts/powershell/update-agent-context.ps1 b/scripts/powershell/update-agent-context.ps1 index 4578ed3..ee40204 100644 --- a/scripts/powershell/update-agent-context.ps1 +++ b/scripts/powershell/update-agent-context.ps1 @@ -13,6 +13,7 @@ $claudeFile = Join-Path $repoRoot 'CLAUDE.md' $geminiFile = Join-Path $repoRoot 'GEMINI.md' $copilotFile = Join-Path $repoRoot '.github/copilot-instructions.md' $cursorFile = Join-Path $repoRoot '.cursor/rules/specify-rules.mdc' +$agentsFile = Join-Path $repoRoot 'AGENTS.md' Write-Output "=== Updating agent context files for feature $currentBranch ===" @@ -71,21 +72,23 @@ switch ($AgentType) { 'gemini' { Update-AgentFile $geminiFile 'Gemini CLI' } 'copilot' { Update-AgentFile $copilotFile 'GitHub Copilot' } 'cursor' { Update-AgentFile $cursorFile 'Cursor IDE' } + 'opencode' { Update-AgentFile $agentsFile 'opencode' } '' { foreach ($pair in @( @{file=$claudeFile; name='Claude Code'}, @{file=$geminiFile; name='Gemini CLI'}, @{file=$copilotFile; name='GitHub Copilot'}, @{file=$cursorFile; name='Cursor IDE'} + @{file=$agentsFile; name='opencode'} )) { 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)) { + if (-not (Test-Path $claudeFile) -and -not (Test-Path $geminiFile) -and -not (Test-Path $copilotFile) -and -not (Test-Path $cursorFile) -and -not (Test-Path $agentsFile)) { 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 or leave empty for all."; exit 1 } + Default { Write-Error "ERROR: Unknown agent type '$AgentType'. Use: claude, gemini, copilot, cursor, opencode or leave empty for all."; exit 1 } } Write-Output '' @@ -95,4 +98,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]' +Write-Output 'Usage: ./update-agent-context.ps1 [claude|gemini|copilot|cursor|opencode]' diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index c413e34..5d5fdd4 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -58,7 +58,8 @@ AI_CHOICES = { "claude": "Claude Code", "gemini": "Gemini CLI", "cursor": "Cursor", - "qwen": "Qwen Code" + "qwen": "Qwen Code", + "opencode": "opencode" } # Add script type choices SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"} @@ -723,7 +724,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, or qwen"), + ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen or opencode"), 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"), @@ -736,7 +737,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, or Qwen Code) + 2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code or opencode) 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) @@ -749,6 +750,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 --ignore-agent-tools my-project specify init --here --ai claude specify init --here @@ -832,6 +834,10 @@ def init( if not check_tool("qwen", "Install from: https://github.com/QwenLM/qwen-code"): console.print("[red]Error:[/red] Qwen CLI is required for Qwen Code projects") agent_tool_missing = True + elif selected_ai == "opencode": + if not check_tool("opencode", "Install from: https://opencode.ai"): + console.print("[red]Error:[/red] opencode CLI is required for opencode projects") + agent_tool_missing = True # GitHub Copilot and Cursor checks are not needed as they're typically available in supported IDEs if agent_tool_missing: @@ -964,6 +970,11 @@ def init( steps_lines.append(" - Run qwen /plan to create implementation plans") steps_lines.append(" - Run qwen /tasks to generate tasks") steps_lines.append(" - See QWEN.md for all available commands") + elif selected_ai == "opencode": + steps_lines.append(f"{step_num}. Use / commands with opencode") + steps_lines.append(" - Use /specify to create specifications") + steps_lines.append(" - Use /plan to create implementation plans") + steps_lines.append(" - Use /tasks to generate tasks") # Removed script variant step (scripts are transparent to users) step_num += 1 @@ -992,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("opencode", "opencode") # Check each tool git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker) @@ -1003,6 +1015,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) + opencode_ok = check_tool_for_tracker("opencode", "https://opencode.ai/", tracker) # Render the final tree console.print(tracker.render()) @@ -1013,7 +1026,7 @@ def check(): # Recommendations if not git_ok: console.print("[dim]Tip: Install git for repository management[/dim]") - if not (claude_ok or gemini_ok or qwen_ok): + if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or opencode_ok): console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]") diff --git a/templates/plan-template.md b/templates/plan-template.md index b48ebb4..cb3eca1 100644 --- a/templates/plan-template.md +++ b/templates/plan-template.md @@ -24,7 +24,7 @@ scripts: → Update Progress Tracking: Initial Constitution Check 5. Execute Phase 0 → research.md → If NEEDS CLARIFICATION remain: ERROR "Resolve unknowns" -6. Execute Phase 1 → contracts, data-model.md, quickstart.md, agent-specific template file (e.g., `CLAUDE.md` for Claude Code, `.github/copilot-instructions.md` for GitHub Copilot, `GEMINI.md` for Gemini CLI, or `QWEN.md` for Qwen Code). +6. Execute Phase 1 → contracts, data-model.md, quickstart.md, agent-specific template file (e.g., `CLAUDE.md` for Claude Code, `.github/copilot-instructions.md` for GitHub Copilot, `GEMINI.md` for Gemini CLI, `QWEN.md` for Qwen Code or `AGENTS.md` for opencode). 7. Re-evaluate Constitution Check section → If new violations: Refactor design, return to Phase 1 → Update Progress Tracking: Post-Design Constitution Check @@ -214,4 +214,4 @@ ios/ or android/ - [ ] Complexity deviations documented --- -*Based on Constitution v2.1.1 - See `/memory/constitution.md`* \ No newline at end of file +*Based on Constitution v2.1.1 - See `/memory/constitution.md`*