mirror of
https://github.com/github/spec-kit.git
synced 2026-02-02 14:03:36 +00:00
Refactor agent configuration
This commit is contained in:
@@ -64,22 +64,92 @@ def _github_auth_headers(cli_token: str | None = None) -> dict:
|
|||||||
token = _github_token(cli_token)
|
token = _github_token(cli_token)
|
||||||
return {"Authorization": f"Bearer {token}"} if token else {}
|
return {"Authorization": f"Bearer {token}"} if token else {}
|
||||||
|
|
||||||
AI_CHOICES = {
|
# Agent configuration with name, folder, install URL, and CLI tool requirement
|
||||||
"copilot": "GitHub Copilot",
|
AGENT_CONFIG = {
|
||||||
"claude": "Claude Code",
|
"copilot": {
|
||||||
"gemini": "Gemini CLI",
|
"name": "GitHub Copilot",
|
||||||
"cursor": "Cursor",
|
"folder": ".github/",
|
||||||
"qwen": "Qwen Code",
|
"install_url": None, # IDE-based, no CLI check needed
|
||||||
"opencode": "opencode",
|
"requires_cli": False,
|
||||||
"codex": "Codex CLI",
|
},
|
||||||
"windsurf": "Windsurf",
|
"claude": {
|
||||||
"kilocode": "Kilo Code",
|
"name": "Claude Code",
|
||||||
"auggie": "Auggie CLI",
|
"folder": ".claude/",
|
||||||
"codebuddy": "CodeBuddy",
|
"install_url": "https://docs.anthropic.com/en/docs/claude-code/setup",
|
||||||
"roo": "Roo Code",
|
"requires_cli": True,
|
||||||
"q": "Amazon Q Developer CLI",
|
},
|
||||||
|
"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"}
|
SCRIPT_TYPE_CHOICES = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
|
||||||
|
|
||||||
CLAUDE_LOCAL_PATH = Path.home() / ".claude" / "local" / "claude"
|
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
|
raise
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def check_tool_for_tracker(tool: str, tracker: StepTracker) -> bool:
|
def check_tool(tool: str, install_hint: str = "", tracker: StepTracker = None) -> bool:
|
||||||
"""Check if a tool is installed and update tracker."""
|
"""Check if a tool is installed. Optionally 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:
|
Args:
|
||||||
"""Check if a tool is installed."""
|
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`
|
# Special handling for Claude CLI after `claude migrate-installer`
|
||||||
# See: https://github.com/github/spec-kit/issues/123
|
# See: https://github.com/github/spec-kit/issues/123
|
||||||
# The migrate-installer command REMOVES the original executable from PATH
|
# 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
|
# This path should be prioritized over other claude executables in PATH
|
||||||
if tool == "claude":
|
if tool == "claude":
|
||||||
if CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file():
|
if CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file():
|
||||||
|
if tracker:
|
||||||
|
tracker.complete(tool, "available")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if shutil.which(tool):
|
found = shutil.which(tool) is not None
|
||||||
return True
|
|
||||||
|
if tracker:
|
||||||
|
if found:
|
||||||
|
tracker.complete(tool, "available")
|
||||||
else:
|
else:
|
||||||
return False
|
tracker.error(tool, "not found")
|
||||||
|
|
||||||
|
return found
|
||||||
|
|
||||||
def is_git_repo(path: Path = None) -> bool:
|
def is_git_repo(path: Path = None) -> bool:
|
||||||
"""Check if the specified path is inside a git repository."""
|
"""Check if the specified path is inside a git repository."""
|
||||||
@@ -840,46 +916,17 @@ def init(
|
|||||||
|
|
||||||
# Check agent tools unless ignored
|
# Check agent tools unless ignored
|
||||||
if not ignore_agent_tools:
|
if not ignore_agent_tools:
|
||||||
agent_tool_missing = False
|
agent_config = AGENT_CONFIG.get(selected_ai)
|
||||||
install_url = ""
|
if agent_config and agent_config["requires_cli"]:
|
||||||
if selected_ai == "claude":
|
cli_tool = selected_ai
|
||||||
if not check_tool("claude", "https://docs.anthropic.com/en/docs/claude-code/setup"):
|
if selected_ai == "cursor":
|
||||||
install_url = "https://docs.anthropic.com/en/docs/claude-code/setup"
|
cli_tool = "cursor-agent"
|
||||||
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:
|
install_url = agent_config["install_url"]
|
||||||
|
if not check_tool(cli_tool, install_url):
|
||||||
error_panel = Panel(
|
error_panel = Panel(
|
||||||
f"[cyan]{selected_ai}[/cyan] not found\n"
|
f"[cyan]{selected_ai}[/cyan] not found\n"
|
||||||
f"Install with: [cyan]{install_url}[/cyan]\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"{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",
|
"Tip: Use [cyan]--ignore-agent-tools[/cyan] to skip this check",
|
||||||
title="[red]Agent Detection Error[/red]",
|
title="[red]Agent Detection Error[/red]",
|
||||||
@@ -987,24 +1034,8 @@ def init(
|
|||||||
console.print("\n[bold green]Project ready.[/bold green]")
|
console.print("\n[bold green]Project ready.[/bold green]")
|
||||||
|
|
||||||
# Agent folder security notice
|
# Agent folder security notice
|
||||||
agent_folder_map = {
|
if selected_ai in AGENT_FOLDER_MAP:
|
||||||
"claude": ".claude/",
|
agent_folder = AGENT_FOLDER_MAP[selected_ai]
|
||||||
"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]
|
|
||||||
security_notice = Panel(
|
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"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.",
|
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")
|
tracker = StepTracker("Check Available Tools")
|
||||||
|
|
||||||
|
# Add git check
|
||||||
tracker.add("git", "Git version control")
|
tracker.add("git", "Git version control")
|
||||||
tracker.add("claude", "Claude Code CLI")
|
git_ok = check_tool("git", tracker=tracker)
|
||||||
tracker.add("gemini", "Gemini CLI")
|
|
||||||
tracker.add("qwen", "Qwen Code CLI")
|
|
||||||
tracker.add("code", "Visual Studio Code")
|
|
||||||
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)
|
# Check AI agent tools
|
||||||
claude_ok = check_tool_for_tracker("claude", tracker)
|
agent_results = {}
|
||||||
gemini_ok = check_tool_for_tracker("gemini", tracker)
|
for agent_key, agent_config in AGENT_CONFIG.items():
|
||||||
qwen_ok = check_tool_for_tracker("qwen", tracker)
|
agent_name = agent_config["name"]
|
||||||
code_ok = check_tool_for_tracker("code", tracker)
|
# Determine the CLI tool name (usually same as agent key, with exceptions)
|
||||||
code_insiders_ok = check_tool_for_tracker("code-insiders", tracker)
|
cli_tool = agent_key
|
||||||
cursor_ok = check_tool_for_tracker("cursor-agent", tracker)
|
if agent_key == "cursor":
|
||||||
windsurf_ok = check_tool_for_tracker("windsurf", tracker)
|
cli_tool = "cursor-agent"
|
||||||
kilocode_ok = check_tool_for_tracker("kilocode", tracker)
|
|
||||||
opencode_ok = check_tool_for_tracker("opencode", tracker)
|
tracker.add(cli_tool, agent_name)
|
||||||
codex_ok = check_tool_for_tracker("codex", tracker)
|
agent_results[agent_key] = check_tool(cli_tool, tracker=tracker)
|
||||||
auggie_ok = check_tool_for_tracker("auggie", tracker)
|
|
||||||
roo_ok = check_tool_for_tracker("roo", tracker)
|
# Check VS Code variants (not in agent config)
|
||||||
codebuddy_ok = check_tool_for_tracker("codebuddy", tracker)
|
tracker.add("code", "Visual Studio Code")
|
||||||
q_ok = check_tool_for_tracker("q", tracker)
|
code_ok = check_tool("code", tracker=tracker)
|
||||||
|
|
||||||
|
tracker.add("code-insiders", "Visual Studio Code Insiders")
|
||||||
|
code_insiders_ok = check_tool("code-insiders", tracker=tracker)
|
||||||
|
|
||||||
console.print(tracker.render())
|
console.print(tracker.render())
|
||||||
|
|
||||||
@@ -1106,7 +1128,7 @@ def check():
|
|||||||
if not git_ok:
|
if not git_ok:
|
||||||
console.print("[dim]Tip: Install git for repository management[/dim]")
|
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]")
|
console.print("[dim]Tip: Install an AI assistant for the best experience[/dim]")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
Reference in New Issue
Block a user