Add Codex CLI support with AGENTS.md and commands bootstrap
This commit is contained in:
@@ -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]")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user