Add Codex CLI support with AGENTS.md and commands bootstrap

This commit is contained in:
honjo-hiroaki-gtt
2025-09-04 14:47:24 +09:00
parent 8bd155b9f5
commit 95fba17d20
3 changed files with 127 additions and 17 deletions

View File

@@ -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/), [Qwen CLI](https://github.com/QwenLM/qwen-code) or [opencode](https://opencode.ai/)
- 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), [opencode](https://opencode.ai/), or [Codex CLI](https://github.com/openai/codex)
- [uv](https://docs.astral.sh/uv/) for package management
- [Python 3.11+](https://www.python.org/downloads/)
- [Git](https://git-scm.com/downloads)
@@ -207,18 +207,27 @@ You will be prompted to select the AI agent you are using. You can also proactiv
specify init <project_name> --ai claude
specify init <project_name> --ai gemini
specify init <project_name> --ai copilot
specify init <project_name> --ai cursor
specify init <project_name> --ai qwen
specify init <project_name> --ai opencode
specify init <project_name> --ai codex
# Or in current directory:
specify init --here --ai claude
specify init --here --ai codex
```
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:
The CLI will check if you have Claude Code, Gemini CLI, Qwen CLI, opencode, or Codex 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:
```bash
specify init <project_name> --ai claude --ignore-agent-tools
```
> [!NOTE]
> Codex CLI specifics
> - When you run `specify init --ai codex`, the CLI ensures a `commands/` directory exists. If packaged templates are not available, it bootstraps minimal command files (`specify.md`, `plan.md`, `tasks.md`).
> - Codex reads project memory from `AGENTS.md`. If it does not exist yet, run `codex /init` inside the project.
> - To allow the bundled scripts under `scripts/` to run from Codex slash commands, open `codex /approvals` and enable “Run shell commands”.
### **STEP 1:** Bootstrap the project
Go to the project folder and run your AI agent. In our example, we're using `claude`.

View File

@@ -54,13 +54,14 @@ 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" ;;
codex) update_agent_file "$AGENTS_FILE" "Codex CLI" ;;
"") [ -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"; \
[ -f "$AGENTS_FILE" ] && update_agent_file "$AGENTS_FILE" "Codex/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 ;;
*) echo "ERROR: Unknown agent type '$AGENT_TYPE' (expected claude|gemini|copilot|cursor|qwen|opencode|codex)"; 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|codex]"

View File

@@ -59,7 +59,8 @@ AI_CHOICES = {
"gemini": "Gemini CLI",
"cursor": "Cursor",
"qwen": "Qwen Code",
"opencode": "opencode"
"opencode": "opencode",
"codex": "Codex CLI",
}
# Add script type choices
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
@@ -67,6 +68,50 @@ SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
# Claude CLI local installation path after migrate-installer
CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude"
# Embedded fallback command templates for Codex (used only if packaged templates are unavailable)
CODEX_CMD_SPECIFY = """---
name: specify
description: "Start a new feature by creating a specification and feature branch."
---
Start a new feature by creating a specification and feature branch.
Given the feature description provided as an argument, do this:
1. Run the script `scripts/create-new-feature.sh --json "{ARGS}"` from the repo root and parse its JSON for BRANCH_NAME and SPEC_FILE. All future paths must be absolute.
2. Load `templates/spec-template.md` and create the initial specification at SPEC_FILE, filling in the placeholders with concrete details derived from the arguments while preserving headings/order.
3. Report completion with the new branch name and spec file path.
"""
CODEX_CMD_PLAN = """---
name: plan
description: "Plan how to implement the specified feature."
---
Plan how to implement the specified feature.
Given the implementation details provided as an argument, do this:
1. Run `scripts/setup-plan.sh --json` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. Use absolute paths in all steps.
2. Read the feature specification (FEATURE_SPEC) and `memory/constitution.md`.
3. Copy `templates/plan-template.md` to IMPL_PLAN if not already present and fill in all sections using the specification and {ARGS} as Technical Context.
4. Ensure the plan includes phases and produces research.md, data-model.md (if needed), contracts/, quickstart.md as appropriate.
5. Report results with BRANCH and generated artifact paths.
"""
CODEX_CMD_TASKS = """---
name: tasks
description: "Break down the plan into executable tasks."
---
Break down the plan into executable tasks.
1. Run `scripts/check-task-prerequisites.sh --json` and parse FEATURE_DIR and AVAILABLE_DOCS.
2. Read plan.md and any available docs to derive concrete tasks.
3. Use `templates/tasks-template.md` as the base, generating numbered tasks (T001, T002, …) with clear file paths and dependency notes. Mark tasks that can run in parallel with [P].
4. Write the result to FEATURE_DIR/tasks.md.
"""
# ASCII Art Banner
BANNER = """
███████╗██████╗ ███████╗ ██████╗██╗███████╗██╗ ██╗
@@ -445,21 +490,34 @@ 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
# Find the template asset for the specified AI assistant (with fallback for Codex)
assets = release_data.get("assets", [])
pattern = f"spec-kit-template-{ai_assistant}-{script_type}"
matching_assets = [
asset for asset in release_data.get("assets", [])
asset for asset in assets
if pattern in asset["name"] and asset["name"].endswith(".zip")
]
if not matching_assets:
asset = matching_assets[0] if matching_assets else None
if asset is None and ai_assistant == "codex":
fallback_pattern = f"spec-kit-template-copilot-{script_type}"
fallback_assets = [
asset for asset in assets
if fallback_pattern in asset["name"] and asset["name"].endswith(".zip")
]
if fallback_assets:
asset = fallback_assets[0]
if verbose:
console.print("[yellow]No Codex-specific template found; falling back to GitHub Copilot template.[/yellow]")
if asset is None:
console.print(f"[red]No matching release asset found[/red] for pattern: [bold]{pattern}[/bold]")
asset_names = [a.get('name','?') for a in release_data.get('assets', [])]
asset_names = [a.get('name','?') for a in assets]
console.print(Panel("\n".join(asset_names) or "(no assets)", title="Available Assets", border_style="yellow"))
raise typer.Exit(1)
# Use the first matching asset
asset = matching_assets[0]
# Use the resolved asset
download_url = asset["browser_download_url"]
filename = asset["name"]
file_size = asset["size"]
@@ -724,7 +782,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 or opencode"),
ai_assistant: str = typer.Option(None, "--ai", help="AI assistant to use: claude, gemini, copilot, cursor, qwen, opencode, or codex"),
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"),
@@ -737,7 +795,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 or opencode)
2. Let you choose your AI assistant (Claude Code, Gemini CLI, GitHub Copilot, Cursor, Qwen Code, opencode, or Codex 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)
@@ -751,8 +809,10 @@ def init(
specify init my-project --ai cursor
specify init my-project --ai qwen
specify init my-project --ai opencode
specify init my-project --ai codex
specify init --ignore-agent-tools my-project
specify init --here --ai claude
specify init --here --ai codex
specify init --here
"""
# Show banner first
@@ -838,6 +898,10 @@ def init(
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
elif selected_ai == "codex":
if not check_tool("codex", "Install from: https://github.com/openai/codex"):
console.print("[red]Error:[/red] Codex CLI is required for Codex 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:
@@ -888,6 +952,9 @@ def init(
]:
tracker.add(key, label)
if selected_ai == "codex":
tracker.add("commands", "Ensure Codex commands")
# 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()))
@@ -902,6 +969,31 @@ def init(
# Ensure scripts are executable (POSIX)
ensure_executable_scripts(project_path, tracker=tracker)
if selected_ai == "codex":
tracker.start("commands")
try:
target_cmds = project_path / "commands"
if not target_cmds.exists():
commands_src = None
for ancestor in Path(__file__).resolve().parents:
candidate = ancestor / "templates" / "commands"
if candidate.exists() and candidate.is_dir():
commands_src = candidate
break
if commands_src is not None:
shutil.copytree(commands_src, target_cmds, dirs_exist_ok=True)
tracker.complete("commands", "added")
else:
target_cmds.mkdir(parents=True, exist_ok=True)
(target_cmds / "specify.md").write_text(CODEX_CMD_SPECIFY, encoding="utf-8")
(target_cmds / "plan.md").write_text(CODEX_CMD_PLAN, encoding="utf-8")
(target_cmds / "tasks.md").write_text(CODEX_CMD_TASKS, encoding="utf-8")
tracker.complete("commands", "bootstrapped")
else:
tracker.skip("commands", "already present")
except Exception as codex_error:
tracker.error("commands", str(codex_error))
# Git step
if not no_git:
tracker.start("git")
@@ -975,6 +1067,12 @@ def init(
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")
elif selected_ai == "codex":
steps_lines.append(f"{step_num}. Use / commands with Codex CLI")
steps_lines.append(" - Run codex /specify to create specifications")
steps_lines.append(" - Run codex /plan to create implementation plans")
steps_lines.append(" - Run codex /tasks to generate task lists")
steps_lines.append(" - See AGENTS.md for bundled commands")
# Removed script variant step (scripts are transparent to users)
step_num += 1
@@ -1004,6 +1102,7 @@ def check():
tracker.add("code", "VS Code (for GitHub Copilot)")
tracker.add("cursor-agent", "Cursor IDE agent (optional)")
tracker.add("opencode", "opencode")
tracker.add("codex", "Codex CLI")
# Check each tool
git_ok = check_tool_for_tracker("git", "https://git-scm.com/downloads", tracker)
@@ -1016,6 +1115,7 @@ def check():
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)
codex_ok = check_tool_for_tracker("codex", "https://github.com/openai/codex", tracker)
# Render the final tree
console.print(tracker.render())
@@ -1026,7 +1126,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 cursor_ok or qwen_ok or opencode_ok):
if not (claude_ok or gemini_ok or cursor_ok or qwen_ok or opencode_ok or codex_ok):
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")