mirror of
https://github.com/github/spec-kit.git
synced 2026-03-25 14:53:08 +00:00
Compare commits
24 Commits
v0.4.0
...
copilot/ag
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b59b82813c | ||
|
|
38ae759568 | ||
|
|
ca9c73da0f | ||
|
|
433502b72d | ||
|
|
720ac509d2 | ||
|
|
48392ea865 | ||
|
|
34fa61e1cc | ||
|
|
790448294e | ||
|
|
b94e541234 | ||
|
|
ab8c58ff23 | ||
|
|
00117c5074 | ||
|
|
795f1e7703 | ||
|
|
55bcbd3977 | ||
|
|
978addc390 | ||
|
|
9b580a536b | ||
|
|
d6016ab9db | ||
|
|
c2227a7ffd | ||
|
|
c3efd1fb71 | ||
|
|
e190116d13 | ||
|
|
a63c248c80 | ||
|
|
b5a5e3fc35 | ||
|
|
ec5471af61 | ||
|
|
3212309e7c | ||
|
|
8b20d0b336 |
52
AGENTS.md
52
AGENTS.md
@@ -427,4 +427,56 @@ When adding new agents:
|
||||
|
||||
---
|
||||
|
||||
## Agent Pack System (new)
|
||||
|
||||
The agent pack system is a declarative, self-contained replacement for the legacy `AGENT_CONFIG` + case/switch architecture. Each agent is defined by a `speckit-agent.yml` manifest and an optional `bootstrap.py` module. When `bootstrap.py` is absent, the built-in `DefaultBootstrap` class derives its directory layout from the manifest's `commands_dir` field.
|
||||
|
||||
### `--agent` flag on `specify init`
|
||||
|
||||
`specify init --agent <id>` uses the pack-based init flow instead of the legacy `--ai` flow. Both accept the same agent IDs, but `--agent` additionally enables installed-file tracking so that `specify agent switch` can cleanly tear down agent files later.
|
||||
|
||||
```bash
|
||||
specify init my-project --agent claude # Pack-based flow (with file tracking)
|
||||
specify init --here --agent gemini --ai-skills # With skills
|
||||
```
|
||||
|
||||
`--agent` and `--ai` are mutually exclusive. When `--agent` is used, `init-options.json` gains `"agent_pack": true`. The `generic` agent (which requires `--ai-commands-dir`) falls through to the legacy flow since it has no embedded pack.
|
||||
|
||||
### `specify agent` subcommands
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------------- | ----------- |
|
||||
| `specify agent list` | List all available agent packs |
|
||||
| `specify agent list --installed`| List only agents installed in the current project |
|
||||
| `specify agent info <id>` | Show detailed information about an agent pack |
|
||||
| `specify agent switch <id>` | Switch the active agent (tears down old, sets up new) |
|
||||
| `specify agent search [query]` | Search agents by name, ID, description, or tags |
|
||||
| `specify agent validate <path>` | Validate an agent pack directory |
|
||||
| `specify agent export <id>` | Export an agent pack for editing |
|
||||
| `specify agent add <id> --from <path>` | Install an agent pack from a local directory |
|
||||
| `specify agent remove <id>` | Remove a cached/override agent pack |
|
||||
|
||||
> **Note:** `specify agent add <id>` without `--from <path>` is reserved for future catalog-based installation, which is not yet implemented.
|
||||
|
||||
### Pack resolution order
|
||||
|
||||
Agent packs resolve by priority (highest first):
|
||||
1. **User-level** (`~/.specify/agents/<id>/`) — applies to all projects
|
||||
2. **Project-level** (`.specify/agents/<id>/`) — project-specific override
|
||||
3. **Catalog cache** (downloaded via `specify agent add`)
|
||||
4. **Embedded** (bundled in the specify-cli wheel)
|
||||
|
||||
### Trust boundary
|
||||
|
||||
Agent packs can include a `bootstrap.py` module that is dynamically imported and executed. Pack authors can run arbitrary code through this mechanism. Only install packs from trusted sources. The 4-level resolution stack means that placing a pack in any of the resolution directories causes its code to run when the agent is loaded.
|
||||
|
||||
### Installed-file tracking
|
||||
|
||||
When using `--agent`, all installed files are recorded in `.specify/agent-manifest-<id>.json` with SHA-256 hashes. During `specify agent switch`, the CLI:
|
||||
1. Checks for user-modified files before teardown
|
||||
2. Prompts for confirmation if files were changed
|
||||
3. Feeds tracked file lists into teardown for precise, file-level removal (directories are never deleted)
|
||||
|
||||
---
|
||||
|
||||
*This documentation should be updated whenever new agents are added to maintain accuracy and completeness.*
|
||||
|
||||
@@ -43,6 +43,8 @@ packages = ["src/specify_cli"]
|
||||
"scripts/powershell" = "specify_cli/core_pack/scripts/powershell"
|
||||
".github/workflows/scripts/create-release-packages.sh" = "specify_cli/core_pack/release_scripts/create-release-packages.sh"
|
||||
".github/workflows/scripts/create-release-packages.ps1" = "specify_cli/core_pack/release_scripts/create-release-packages.ps1"
|
||||
# Official agent packs (embedded in wheel for zero-config offline operation)
|
||||
"src/specify_cli/core_pack/agents" = "specify_cli/core_pack/agents"
|
||||
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
|
||||
@@ -36,7 +36,7 @@ import json5
|
||||
import stat
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Tuple
|
||||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
import typer
|
||||
import httpx
|
||||
@@ -1715,6 +1715,7 @@ def _handle_agent_skills_migration(console: Console, agent_key: str) -> None:
|
||||
def init(
|
||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
|
||||
ai_assistant: str = typer.Option(None, "--ai", help=AI_ASSISTANT_HELP),
|
||||
agent: str = typer.Option(None, "--agent", help="AI agent to use (enables file tracking for clean teardown when switching agents). Accepts the same agent IDs as --ai. Use --ai generic for custom agent directories."),
|
||||
ai_commands_dir: str = typer.Option(None, "--ai-commands-dir", help="Directory for agent command files (required with --ai generic, e.g. .myagent/commands/)"),
|
||||
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"),
|
||||
@@ -1724,7 +1725,7 @@ def init(
|
||||
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)"),
|
||||
ai_skills: bool = typer.Option(False, "--ai-skills", help="Install Prompt.MD templates as agent skills (requires --ai)"),
|
||||
ai_skills: bool = typer.Option(False, "--ai-skills", help="Install Prompt.MD templates as agent skills (requires --ai or --agent)"),
|
||||
offline: bool = typer.Option(False, "--offline", help="Use assets bundled in the specify-cli package instead of downloading from GitHub (no network access required). Bundled assets will become the default in v0.6.0 and this flag will be removed."),
|
||||
preset: str = typer.Option(None, "--preset", help="Install a preset during initialization (by preset ID)"),
|
||||
branch_numbering: str = typer.Option(None, "--branch-numbering", help="Branch numbering strategy: 'sequential' (001, 002, ...) or 'timestamp' (YYYYMMDD-HHMMSS)"),
|
||||
@@ -1753,6 +1754,7 @@ def init(
|
||||
Examples:
|
||||
specify init my-project
|
||||
specify init my-project --ai claude
|
||||
specify init my-project --agent claude # Pack-based flow (with file tracking)
|
||||
specify init my-project --ai copilot --no-git
|
||||
specify init --ignore-agent-tools my-project
|
||||
specify init . --ai claude # Initialize in current directory
|
||||
@@ -1765,6 +1767,7 @@ def init(
|
||||
specify init --here --force # Skip confirmation when current directory not empty
|
||||
specify init my-project --ai claude --ai-skills # Install agent skills
|
||||
specify init --here --ai gemini --ai-skills
|
||||
specify init my-project --agent claude --ai-skills # Pack-based flow with skills
|
||||
specify init my-project --ai generic --ai-commands-dir .myagent/commands/ # Unsupported agent
|
||||
specify init my-project --offline # Use bundled assets (no network access)
|
||||
specify init my-project --ai claude --preset healthcare-compliance # With preset
|
||||
@@ -1772,6 +1775,20 @@ def init(
|
||||
|
||||
show_banner()
|
||||
|
||||
# --agent and --ai are interchangeable for agent selection, but --agent
|
||||
# additionally opts into the pack-based flow (file tracking via
|
||||
# finalize_setup for tracked teardown/switch).
|
||||
use_agent_pack = False
|
||||
if agent:
|
||||
if ai_assistant:
|
||||
console.print("[red]Error:[/red] --agent and --ai cannot both be specified. Use one or the other.")
|
||||
raise typer.Exit(1)
|
||||
ai_assistant = agent
|
||||
# "generic" uses --ai-commands-dir and has no embedded pack,
|
||||
# so it falls through to the legacy flow.
|
||||
if agent != "generic":
|
||||
use_agent_pack = True
|
||||
|
||||
# Detect when option values are likely misinterpreted flags (parameter ordering issue)
|
||||
if ai_assistant and ai_assistant.startswith("--"):
|
||||
console.print(f"[red]Error:[/red] Invalid value for --ai: '{ai_assistant}'")
|
||||
@@ -1802,7 +1819,7 @@ def init(
|
||||
raise typer.Exit(1)
|
||||
|
||||
if ai_skills and not ai_assistant:
|
||||
console.print("[red]Error:[/red] --ai-skills requires --ai to be specified")
|
||||
console.print("[red]Error:[/red] --ai-skills requires --ai or --agent to be specified")
|
||||
console.print("[yellow]Usage:[/yellow] specify init <project> --ai <agent> --ai-skills")
|
||||
raise typer.Exit(1)
|
||||
|
||||
@@ -1854,6 +1871,19 @@ def init(
|
||||
"copilot"
|
||||
)
|
||||
|
||||
# When --agent is used, validate that the agent resolves through the pack
|
||||
# system and prepare the bootstrap for post-init file tracking.
|
||||
agent_bootstrap = None
|
||||
if use_agent_pack:
|
||||
from .agent_pack import resolve_agent_pack, load_bootstrap, PackResolutionError, AgentPackError
|
||||
try:
|
||||
resolved = resolve_agent_pack(selected_ai, project_path=project_path)
|
||||
agent_bootstrap = load_bootstrap(resolved.path, resolved.manifest)
|
||||
console.print(f"[dim]Pack-based flow: {resolved.manifest.name} ({resolved.source})[/dim]")
|
||||
except (PackResolutionError, AgentPackError) as exc:
|
||||
console.print(f"[red]Error resolving agent pack:[/red] {exc}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Agents that have moved from explicit commands/prompts to agent skills.
|
||||
if selected_ai in AGENT_SKILLS_MIGRATIONS and not ai_skills:
|
||||
# If selected interactively (no --ai provided), automatically enable
|
||||
@@ -1957,7 +1987,10 @@ def init(
|
||||
"This will become the default in v0.6.0."
|
||||
)
|
||||
|
||||
if use_github:
|
||||
if use_agent_pack:
|
||||
# Pack-based flow: setup() owns scaffolding, always uses bundled assets.
|
||||
tracker.add("scaffold", "Apply bundled assets")
|
||||
elif use_github:
|
||||
for key, label in [
|
||||
("fetch", "Fetch latest release"),
|
||||
("download", "Download template"),
|
||||
@@ -1992,7 +2025,26 @@ def init(
|
||||
verify = not skip_tls
|
||||
local_ssl_context = ssl_context if verify else False
|
||||
|
||||
if use_github:
|
||||
# -- scaffolding ------------------------------------------------
|
||||
# Pack-based flow (--agent): setup() owns scaffolding and
|
||||
# returns every file it created. Legacy flow (--ai): scaffold
|
||||
# directly or download from GitHub.
|
||||
agent_setup_files: list[Path] = []
|
||||
|
||||
if use_agent_pack and agent_bootstrap is not None:
|
||||
tracker.start("scaffold")
|
||||
try:
|
||||
agent_setup_files = agent_bootstrap.setup(
|
||||
project_path, selected_script, {"here": here})
|
||||
tracker.complete(
|
||||
"scaffold",
|
||||
f"{selected_ai} ({len(agent_setup_files)} files)")
|
||||
except Exception as exc:
|
||||
tracker.error("scaffold", str(exc))
|
||||
if not here and project_path.exists():
|
||||
shutil.rmtree(project_path)
|
||||
raise typer.Exit(1)
|
||||
elif use_github:
|
||||
with httpx.Client(verify=local_ssl_context) as local_client:
|
||||
download_and_extract_template(project_path, selected_ai, selected_script, here, verbose=False, tracker=tracker, client=local_client, debug=debug, github_token=github_token)
|
||||
else:
|
||||
@@ -2090,6 +2142,7 @@ def init(
|
||||
"ai": selected_ai,
|
||||
"ai_skills": ai_skills,
|
||||
"ai_commands_dir": ai_commands_dir,
|
||||
"agent_pack": use_agent_pack,
|
||||
"branch_numbering": branch_numbering or "sequential",
|
||||
"here": here,
|
||||
"preset": preset,
|
||||
@@ -2133,6 +2186,16 @@ def init(
|
||||
if not use_github:
|
||||
tracker.skip("cleanup", "not needed (no download)")
|
||||
|
||||
# When --agent is used, record all installed agent files for
|
||||
# tracked teardown. setup() already returned the files it
|
||||
# created; pass them to finalize_setup so the manifest is
|
||||
# accurate. finalize_setup also scans the agent directory
|
||||
# to catch any additional files created by later pipeline
|
||||
# steps (skills, extensions, presets).
|
||||
if use_agent_pack and agent_bootstrap is not None:
|
||||
agent_bootstrap.finalize_setup(
|
||||
project_path, agent_files=agent_setup_files)
|
||||
|
||||
tracker.complete("final", "project ready")
|
||||
except (typer.Exit, SystemExit):
|
||||
raise
|
||||
@@ -2366,6 +2429,616 @@ def version():
|
||||
console.print()
|
||||
|
||||
|
||||
# ===== Agent Commands =====
|
||||
|
||||
agent_app = typer.Typer(
|
||||
name="agent",
|
||||
help="Manage agent packs for AI assistants",
|
||||
add_completion=False,
|
||||
)
|
||||
app.add_typer(agent_app, name="agent")
|
||||
|
||||
|
||||
@agent_app.command("list")
|
||||
def agent_list(
|
||||
installed: bool = typer.Option(False, "--installed", help="Only show agents that have files present in the current project"),
|
||||
):
|
||||
"""List available agent packs."""
|
||||
from .agent_pack import list_all_agents, list_embedded_agents, _manifest_path
|
||||
|
||||
show_banner()
|
||||
|
||||
project_path = Path.cwd()
|
||||
agents = list_all_agents(project_path=project_path)
|
||||
|
||||
if installed:
|
||||
# Filter to only agents that have an install manifest in the
|
||||
# current project, i.e. agents whose files are actually present.
|
||||
agents = [
|
||||
a for a in agents
|
||||
if _manifest_path(project_path, a.manifest.id).is_file()
|
||||
]
|
||||
|
||||
if not agents and not installed:
|
||||
agents_from_embedded = list_embedded_agents()
|
||||
if not agents_from_embedded:
|
||||
console.print("[yellow]No agent packs found.[/yellow]")
|
||||
console.print("[dim]Agent packs are embedded in the specify-cli wheel.[/dim]")
|
||||
raise typer.Exit(0)
|
||||
|
||||
if not agents and installed:
|
||||
console.print("[yellow]No agents are installed in the current project.[/yellow]")
|
||||
console.print("[dim]Use 'specify init --agent <id>' or 'specify agent switch <id>' to install one.[/dim]")
|
||||
raise typer.Exit(0)
|
||||
|
||||
title = "Installed Agents" if installed else "Available Agent Packs"
|
||||
table = Table(title=title, show_lines=False)
|
||||
table.add_column("ID", style="cyan", no_wrap=True)
|
||||
table.add_column("Name", style="white")
|
||||
table.add_column("Version", style="dim")
|
||||
table.add_column("Source", style="green")
|
||||
table.add_column("CLI Required", style="yellow", justify="center")
|
||||
|
||||
for resolved in agents:
|
||||
m = resolved.manifest
|
||||
cli_marker = "✓" if m.requires_cli else "—"
|
||||
source_display = resolved.source
|
||||
if resolved.overrides:
|
||||
source_display += f" (overrides {resolved.overrides})"
|
||||
table.add_row(m.id, m.name, m.version, source_display, cli_marker)
|
||||
|
||||
console.print(table)
|
||||
console.print(f"\n[dim]{len(agents)} agent(s) {'installed' if installed else 'available'}[/dim]")
|
||||
|
||||
|
||||
@agent_app.command("info")
|
||||
def agent_info(
|
||||
agent_id: str = typer.Argument(..., help="Agent pack ID (e.g. 'claude', 'gemini')"),
|
||||
):
|
||||
"""Show detailed information about an agent pack."""
|
||||
from .agent_pack import resolve_agent_pack, PackResolutionError
|
||||
|
||||
show_banner()
|
||||
|
||||
try:
|
||||
resolved = resolve_agent_pack(agent_id, project_path=Path.cwd())
|
||||
except PackResolutionError as exc:
|
||||
console.print(f"[red]Error:[/red] {exc}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
m = resolved.manifest
|
||||
|
||||
info_table = Table(show_header=False, box=None, padding=(0, 2))
|
||||
info_table.add_column("Key", style="cyan", justify="right")
|
||||
info_table.add_column("Value", style="white")
|
||||
|
||||
info_table.add_row("Agent", f"{m.name} ({m.id})")
|
||||
info_table.add_row("Version", m.version)
|
||||
info_table.add_row("Description", m.description or "—")
|
||||
info_table.add_row("Author", m.author or "—")
|
||||
info_table.add_row("License", m.license or "—")
|
||||
info_table.add_row("", "")
|
||||
|
||||
source_display = resolved.source
|
||||
if resolved.source == "catalog":
|
||||
source_display = f"catalog — {resolved.path}"
|
||||
elif resolved.source == "embedded":
|
||||
source_display = "embedded (bundled in specify-cli wheel)"
|
||||
|
||||
info_table.add_row("Source", source_display)
|
||||
if resolved.overrides:
|
||||
info_table.add_row("Overrides", resolved.overrides)
|
||||
info_table.add_row("Pack Path", str(resolved.path))
|
||||
info_table.add_row("", "")
|
||||
|
||||
info_table.add_row("Requires CLI", "Yes" if m.requires_cli else "No")
|
||||
if m.install_url:
|
||||
info_table.add_row("Install URL", m.install_url)
|
||||
if m.cli_tool:
|
||||
info_table.add_row("CLI Tool", m.cli_tool)
|
||||
info_table.add_row("", "")
|
||||
|
||||
info_table.add_row("Commands Dir", m.commands_dir or "—")
|
||||
info_table.add_row("Format", m.command_format)
|
||||
info_table.add_row("Arg Placeholder", m.arg_placeholder)
|
||||
info_table.add_row("File Extension", m.file_extension)
|
||||
info_table.add_row("", "")
|
||||
|
||||
info_table.add_row("Tags", ", ".join(m.tags) if m.tags else "—")
|
||||
info_table.add_row("Speckit Version", m.speckit_version)
|
||||
|
||||
panel = Panel(
|
||||
info_table,
|
||||
title=f"[bold cyan]Agent: {m.name}[/bold cyan]",
|
||||
border_style="cyan",
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print(panel)
|
||||
|
||||
|
||||
@agent_app.command("validate")
|
||||
def agent_validate(
|
||||
pack_path: str = typer.Argument(..., help="Path to the agent pack directory to validate"),
|
||||
):
|
||||
"""Validate an agent pack's structure and manifest."""
|
||||
from .agent_pack import validate_pack, ManifestValidationError, AgentManifest, MANIFEST_FILENAME
|
||||
|
||||
show_banner()
|
||||
|
||||
path = Path(pack_path).resolve()
|
||||
if not path.is_dir():
|
||||
console.print(f"[red]Error:[/red] Not a directory: {path}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
try:
|
||||
warnings = validate_pack(path)
|
||||
except ManifestValidationError as exc:
|
||||
console.print(f"[red]Validation failed:[/red] {exc}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
manifest = AgentManifest.from_yaml(path / MANIFEST_FILENAME)
|
||||
console.print(f"[green]✓[/green] Pack '{manifest.id}' ({manifest.name}) is valid")
|
||||
|
||||
if warnings:
|
||||
console.print(f"\n[yellow]Warnings ({len(warnings)}):[/yellow]")
|
||||
for w in warnings:
|
||||
console.print(f" [yellow]⚠[/yellow] {w}")
|
||||
else:
|
||||
console.print("[green]No warnings.[/green]")
|
||||
|
||||
|
||||
@agent_app.command("export")
|
||||
def agent_export(
|
||||
agent_id: str = typer.Argument(..., help="Agent pack ID to export"),
|
||||
to: str = typer.Option(..., "--to", help="Destination directory for the exported pack"),
|
||||
):
|
||||
"""Export the active agent pack to a directory for editing."""
|
||||
from .agent_pack import export_pack, PackResolutionError
|
||||
|
||||
show_banner()
|
||||
|
||||
dest = Path(to).resolve()
|
||||
try:
|
||||
result = export_pack(agent_id, dest, project_path=Path.cwd())
|
||||
except PackResolutionError as exc:
|
||||
console.print(f"[red]Error:[/red] {exc}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"[green]✓[/green] Exported '{agent_id}' pack to {result}")
|
||||
console.print(f"[dim]Edit files in {result} and use as a project-level override.[/dim]")
|
||||
|
||||
|
||||
@agent_app.command("switch")
|
||||
def agent_switch(
|
||||
agent_id: str = typer.Argument(..., help="Agent pack ID to switch to"),
|
||||
force: bool = typer.Option(False, "--force", help="Remove agent files even if they were modified since installation"),
|
||||
):
|
||||
"""Switch the active AI agent in the current project.
|
||||
|
||||
Tears down the current agent and sets up the new one.
|
||||
Preserves specs, plans, tasks, constitution, memory, templates, and scripts.
|
||||
"""
|
||||
from .agent_pack import (
|
||||
resolve_agent_pack,
|
||||
load_bootstrap,
|
||||
check_modified_files,
|
||||
get_tracked_files,
|
||||
PackResolutionError,
|
||||
AgentPackError,
|
||||
)
|
||||
|
||||
show_banner()
|
||||
|
||||
project_path = Path.cwd()
|
||||
init_options_file = project_path / ".specify" / "init-options.json"
|
||||
|
||||
if not init_options_file.exists():
|
||||
console.print("[red]Error:[/red] Not a Specify project (missing .specify/init-options.json)")
|
||||
console.print("[yellow]Hint:[/yellow] Run 'specify init --here' first.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Load current project options
|
||||
try:
|
||||
options = json.loads(init_options_file.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError) as exc:
|
||||
console.print(f"[red]Error reading init options:[/red] {exc}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
current_agent = options.get("ai")
|
||||
script_type = options.get("script", "sh")
|
||||
|
||||
# Resolve the new agent pack
|
||||
try:
|
||||
resolved = resolve_agent_pack(agent_id, project_path=project_path)
|
||||
except PackResolutionError as exc:
|
||||
console.print(f"[red]Error:[/red] {exc}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
console.print(f"[bold]Switching agent: {current_agent or '(none)'} → {agent_id}[/bold]")
|
||||
|
||||
# Snapshot tracked files before teardown so we can attempt rollback
|
||||
# if the new agent's setup fails after teardown.
|
||||
old_tracked_agent: dict[str, str] = {}
|
||||
old_tracked_ext: dict[str, str] = {}
|
||||
|
||||
# Teardown current agent (best effort — may have been set up with old system)
|
||||
if current_agent:
|
||||
try:
|
||||
current_resolved = resolve_agent_pack(current_agent, project_path=project_path)
|
||||
current_bootstrap = load_bootstrap(current_resolved.path, current_resolved.manifest)
|
||||
|
||||
# Check for modified files BEFORE teardown and prompt for confirmation
|
||||
modified = check_modified_files(project_path, current_agent)
|
||||
if modified and not force:
|
||||
console.print("[yellow]The following files have been modified since installation:[/yellow]")
|
||||
for f in modified:
|
||||
console.print(f" {f}")
|
||||
if not typer.confirm("Remove these modified files?"):
|
||||
console.print("[dim]Aborted. Use --force to skip this check.[/dim]")
|
||||
raise typer.Exit(0)
|
||||
|
||||
# Retrieve tracked file lists and feed them into teardown
|
||||
old_tracked_agent, old_tracked_ext = get_tracked_files(project_path, current_agent)
|
||||
all_files = {**old_tracked_agent, **old_tracked_ext}
|
||||
|
||||
if all_files:
|
||||
console.print(f" [dim]Tearing down {current_agent}...[/dim]")
|
||||
current_bootstrap.teardown(
|
||||
project_path,
|
||||
force=force,
|
||||
files=all_files,
|
||||
)
|
||||
console.print(f" [green]✓[/green] {current_agent} removed")
|
||||
else:
|
||||
# No install manifest (legacy --ai project) — fall back
|
||||
# to removing the agent directory via AGENT_CONFIG.
|
||||
agent_config = AGENT_CONFIG.get(current_agent, {})
|
||||
agent_folder = agent_config.get("folder")
|
||||
if agent_folder:
|
||||
agent_dir = project_path / agent_folder.rstrip("/")
|
||||
if agent_dir.is_dir():
|
||||
if not force:
|
||||
console.print(f"[yellow]No install manifest found for '{current_agent}' (legacy project).[/yellow]")
|
||||
console.print(f" Directory to remove: {agent_dir}")
|
||||
if not typer.confirm("Remove this directory?"):
|
||||
console.print("[dim]Aborted. Use --force to skip this check.[/dim]")
|
||||
raise typer.Exit(0)
|
||||
shutil.rmtree(agent_dir)
|
||||
console.print(f" [green]✓[/green] {current_agent} directory removed (legacy)")
|
||||
else:
|
||||
console.print(f" [yellow]Warning:[/yellow] No tracked files or AGENT_CONFIG entry for '{current_agent}' — skipping teardown")
|
||||
except AgentPackError:
|
||||
# If pack-based resolution/load fails, try legacy cleanup via AGENT_CONFIG
|
||||
agent_config = AGENT_CONFIG.get(current_agent, {})
|
||||
agent_folder = agent_config.get("folder")
|
||||
if agent_folder:
|
||||
agent_dir = project_path / agent_folder.rstrip("/")
|
||||
if agent_dir.is_dir():
|
||||
if not force:
|
||||
console.print(f"[yellow]No agent pack found for '{current_agent}' (legacy project).[/yellow]")
|
||||
console.print(f" Directory to remove: {agent_dir}")
|
||||
if not typer.confirm("Remove this directory?"):
|
||||
console.print("[dim]Aborted. Use --force to skip this check.[/dim]")
|
||||
raise typer.Exit(0)
|
||||
shutil.rmtree(agent_dir)
|
||||
console.print(f" [green]✓[/green] {current_agent} directory removed (legacy)")
|
||||
|
||||
# Setup new agent — with rollback on failure
|
||||
try:
|
||||
new_bootstrap = load_bootstrap(resolved.path, resolved.manifest)
|
||||
console.print(f" [dim]Setting up {agent_id}...[/dim]")
|
||||
agent_files = new_bootstrap.setup(project_path, script_type, options)
|
||||
console.print(f" [green]✓[/green] {agent_id} installed")
|
||||
except (AgentPackError, Exception) as exc:
|
||||
console.print(f"[red]Error setting up {agent_id}:[/red] {exc}")
|
||||
|
||||
# Attempt to restore the old agent so the project is not left
|
||||
# in a broken state after teardown succeeded but setup failed.
|
||||
if current_agent:
|
||||
console.print(f"[yellow]Attempting to restore previous agent ({current_agent})...[/yellow]")
|
||||
try:
|
||||
rollback_resolved = resolve_agent_pack(current_agent, project_path=project_path)
|
||||
rollback_bs = load_bootstrap(rollback_resolved.path, rollback_resolved.manifest)
|
||||
rollback_files = rollback_bs.setup(project_path, script_type, options)
|
||||
# Re-register extension commands (same as the success path)
|
||||
rollback_ext_files = _reregister_extension_commands(project_path, current_agent)
|
||||
rollback_bs.finalize_setup(
|
||||
project_path,
|
||||
agent_files=rollback_files,
|
||||
extension_files=rollback_ext_files,
|
||||
)
|
||||
console.print(f" [green]✓[/green] {current_agent} restored")
|
||||
except Exception:
|
||||
# Rollback also failed — mark error state in init-options
|
||||
console.print(
|
||||
f"[red]Rollback failed.[/red] "
|
||||
f"The project may be in a broken state — "
|
||||
f"run 'specify init --here --agent {current_agent}' to repair."
|
||||
)
|
||||
options["agent_switch_error"] = (
|
||||
f"Switch to '{agent_id}' failed after teardown of "
|
||||
f"'{current_agent}'. Run 'specify init --here --agent "
|
||||
f"{current_agent}' to restore."
|
||||
)
|
||||
init_options_file.write_text(
|
||||
json.dumps(options, indent=2), encoding="utf-8"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
# Update init options
|
||||
options["ai"] = agent_id
|
||||
options["agent_pack"] = True
|
||||
options.pop("agent_switch_error", None) # clear any previous error
|
||||
init_options_file.write_text(json.dumps(options, indent=2), encoding="utf-8")
|
||||
|
||||
# Re-register extension commands for the new agent
|
||||
extension_files = _reregister_extension_commands(project_path, agent_id)
|
||||
|
||||
# Record all installed files (agent + extensions) for tracked teardown
|
||||
new_bootstrap.finalize_setup(
|
||||
project_path,
|
||||
agent_files=agent_files,
|
||||
extension_files=extension_files,
|
||||
)
|
||||
|
||||
console.print(f"\n[bold green]Successfully switched to {resolved.manifest.name}[/bold green]")
|
||||
|
||||
|
||||
def _reregister_extension_commands(project_path: Path, agent_id: str) -> List[Path]:
|
||||
"""Re-register all installed extension commands for a new agent after switching.
|
||||
|
||||
Returns:
|
||||
List of absolute file paths created by extension registration.
|
||||
"""
|
||||
created_files: List[Path] = []
|
||||
registry_file = project_path / ".specify" / "extensions" / ".registry"
|
||||
if not registry_file.is_file():
|
||||
return created_files
|
||||
|
||||
try:
|
||||
registry_data = json.loads(registry_file.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return created_files
|
||||
|
||||
extensions = registry_data.get("extensions", {})
|
||||
if not extensions:
|
||||
return created_files
|
||||
|
||||
try:
|
||||
from .agents import CommandRegistrar
|
||||
registrar = CommandRegistrar()
|
||||
except ImportError:
|
||||
return created_files
|
||||
|
||||
# Snapshot the commands directory before registration so we can
|
||||
# detect which files were created by extension commands.
|
||||
agent_config = registrar.AGENT_CONFIGS.get(agent_id)
|
||||
if agent_config:
|
||||
commands_dir = project_path / agent_config["dir"]
|
||||
pre_existing = set(commands_dir.rglob("*")) if commands_dir.is_dir() else set()
|
||||
else:
|
||||
pre_existing = set()
|
||||
|
||||
reregistered = 0
|
||||
for ext_id, ext_data in extensions.items():
|
||||
commands = ext_data.get("registered_commands", {})
|
||||
if not commands:
|
||||
continue
|
||||
|
||||
ext_dir = project_path / ".specify" / "extensions" / ext_id
|
||||
if not ext_dir.is_dir():
|
||||
continue
|
||||
|
||||
# Get the command list from the manifest
|
||||
manifest_file = ext_dir / "extension.yml"
|
||||
if not manifest_file.is_file():
|
||||
continue
|
||||
|
||||
try:
|
||||
from .extensions import ExtensionManifest
|
||||
manifest = ExtensionManifest(manifest_file)
|
||||
if manifest.commands:
|
||||
registered = registrar.register_commands(
|
||||
agent_id, manifest.commands, ext_id, ext_dir / "commands", project_path
|
||||
)
|
||||
if registered:
|
||||
reregistered += len(registered)
|
||||
except Exception as exc:
|
||||
import logging as _logging
|
||||
_logging.getLogger(__name__).debug(
|
||||
"Failed to re-register extension '%s' for agent '%s': %s",
|
||||
ext_id, agent_id, exc,
|
||||
)
|
||||
continue
|
||||
|
||||
# Collect files created by extension registration
|
||||
if agent_config:
|
||||
commands_dir = project_path / agent_config["dir"]
|
||||
if commands_dir.is_dir():
|
||||
for p in commands_dir.rglob("*"):
|
||||
if p.is_file() and p not in pre_existing:
|
||||
created_files.append(p)
|
||||
|
||||
if reregistered:
|
||||
console.print(
|
||||
f" [green]✓[/green] Re-registered {reregistered} extension command(s) ({len(created_files)} file(s))"
|
||||
)
|
||||
|
||||
return created_files
|
||||
|
||||
|
||||
@agent_app.command("search")
|
||||
def agent_search(
|
||||
query: str = typer.Argument(None, help="Search query (matches agent ID, name, or tags)"),
|
||||
tag: str = typer.Option(None, "--tag", help="Filter by tag"),
|
||||
):
|
||||
"""Search for agent packs across embedded and catalog sources."""
|
||||
from .agent_pack import list_all_agents
|
||||
|
||||
show_banner()
|
||||
|
||||
all_agents = list_all_agents(project_path=Path.cwd())
|
||||
|
||||
if query:
|
||||
query_lower = query.lower()
|
||||
all_agents = [
|
||||
a for a in all_agents
|
||||
if query_lower in a.manifest.id.lower()
|
||||
or query_lower in a.manifest.name.lower()
|
||||
or query_lower in a.manifest.description.lower()
|
||||
or any(query_lower in t.lower() for t in a.manifest.tags)
|
||||
]
|
||||
|
||||
if tag:
|
||||
tag_lower = tag.lower()
|
||||
all_agents = [
|
||||
a for a in all_agents
|
||||
if any(tag_lower == t.lower() for t in a.manifest.tags)
|
||||
]
|
||||
|
||||
if not all_agents:
|
||||
console.print("[yellow]No agents found matching your search.[/yellow]")
|
||||
raise typer.Exit(0)
|
||||
|
||||
table = Table(title="Search Results", show_lines=False)
|
||||
table.add_column("ID", style="cyan", no_wrap=True)
|
||||
table.add_column("Name", style="white")
|
||||
table.add_column("Description", style="dim")
|
||||
table.add_column("Tags", style="green")
|
||||
table.add_column("Source", style="yellow")
|
||||
|
||||
for resolved in all_agents:
|
||||
m = resolved.manifest
|
||||
table.add_row(
|
||||
m.id, m.name,
|
||||
(m.description[:50] + "...") if len(m.description) > 53 else m.description,
|
||||
", ".join(m.tags),
|
||||
resolved.source,
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
console.print(f"\n[dim]{len(all_agents)} result(s)[/dim]")
|
||||
|
||||
|
||||
@agent_app.command("add")
|
||||
def agent_add(
|
||||
agent_id: str = typer.Argument(..., help="Agent pack ID to install"),
|
||||
from_path: str = typer.Option(None, "--from", help="Install from a local path instead of a catalog"),
|
||||
):
|
||||
"""Install an agent pack from a catalog or local path."""
|
||||
from .agent_pack import (
|
||||
_catalog_agents_dir,
|
||||
AgentManifest,
|
||||
ManifestValidationError,
|
||||
MANIFEST_FILENAME,
|
||||
)
|
||||
|
||||
show_banner()
|
||||
|
||||
if from_path:
|
||||
source = Path(from_path).resolve()
|
||||
if not source.is_dir():
|
||||
console.print(f"[red]Error:[/red] Not a directory: {source}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
manifest_file = source / MANIFEST_FILENAME
|
||||
if not manifest_file.is_file():
|
||||
console.print(f"[red]Error:[/red] No {MANIFEST_FILENAME} found in {source}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
try:
|
||||
manifest = AgentManifest.from_yaml(manifest_file)
|
||||
except ManifestValidationError as exc:
|
||||
console.print(f"[red]Validation failed:[/red] {exc}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
if manifest.id != agent_id:
|
||||
console.print(
|
||||
f"[red]Error:[/red] Manifest ID '{manifest.id}' does not match "
|
||||
f"the specified agent ID '{agent_id}'."
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
dest = _catalog_agents_dir() / manifest.id
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(source, dest, dirs_exist_ok=True)
|
||||
console.print(f"[green]✓[/green] Installed '{manifest.id}' ({manifest.name}) from {source}")
|
||||
else:
|
||||
# Catalog fetch — placeholder for future catalog integration
|
||||
console.print("[yellow]Catalog fetch not yet implemented.[/yellow]")
|
||||
console.print("[dim]Use --from <path> to install from a local directory.[/dim]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
|
||||
@agent_app.command("remove")
|
||||
def agent_remove(
|
||||
agent_id: str = typer.Argument(..., help="Agent pack ID to remove"),
|
||||
force: bool = typer.Option(False, "--force", help="Skip confirmation prompts"),
|
||||
):
|
||||
"""Remove a cached/override agent pack.
|
||||
|
||||
If the agent is an official embedded agent, removing the override
|
||||
falls back to the embedded version.
|
||||
"""
|
||||
from .agent_pack import (
|
||||
_catalog_agents_dir,
|
||||
_user_agents_dir,
|
||||
_embedded_agents_dir,
|
||||
MANIFEST_FILENAME,
|
||||
)
|
||||
|
||||
show_banner()
|
||||
|
||||
removed = False
|
||||
|
||||
# Check user-level — prompt because this affects all projects globally
|
||||
user_pack = _user_agents_dir() / agent_id
|
||||
if user_pack.is_dir():
|
||||
if not force:
|
||||
console.print(
|
||||
f"[yellow]User-level override for '{agent_id}' affects all projects globally.[/yellow]"
|
||||
)
|
||||
if not typer.confirm("Remove this user-level override?"):
|
||||
console.print("[dim]Skipped user-level override removal.[/dim]")
|
||||
else:
|
||||
shutil.rmtree(user_pack)
|
||||
console.print(f"[green]✓[/green] Removed user-level override for '{agent_id}'")
|
||||
removed = True
|
||||
else:
|
||||
shutil.rmtree(user_pack)
|
||||
console.print(f"[green]✓[/green] Removed user-level override for '{agent_id}'")
|
||||
removed = True
|
||||
|
||||
# Check project-level
|
||||
project_pack = Path.cwd() / ".specify" / "agents" / agent_id
|
||||
if project_pack.is_dir():
|
||||
shutil.rmtree(project_pack)
|
||||
console.print(f"[green]✓[/green] Removed project-level override for '{agent_id}'")
|
||||
removed = True
|
||||
|
||||
# Check catalog cache
|
||||
catalog_pack = _catalog_agents_dir() / agent_id
|
||||
if catalog_pack.is_dir():
|
||||
shutil.rmtree(catalog_pack)
|
||||
console.print(f"[green]✓[/green] Removed catalog-cached version of '{agent_id}'")
|
||||
removed = True
|
||||
|
||||
if not removed:
|
||||
# Check if it's an embedded agent
|
||||
embedded_pack = _embedded_agents_dir() / agent_id / MANIFEST_FILENAME
|
||||
if embedded_pack.is_file():
|
||||
console.print(f"[yellow]'{agent_id}' is an embedded official agent and cannot be removed.[/yellow]")
|
||||
console.print("[dim]It has no overrides to remove.[/dim]")
|
||||
else:
|
||||
console.print(f"[red]Error:[/red] Agent '{agent_id}' not found.")
|
||||
raise typer.Exit(1)
|
||||
else:
|
||||
# Check for embedded fallback
|
||||
embedded_pack = _embedded_agents_dir() / agent_id / MANIFEST_FILENAME
|
||||
if embedded_pack.is_file():
|
||||
console.print(f"[dim]Embedded version of '{agent_id}' is now active.[/dim]")
|
||||
|
||||
|
||||
# ===== Extension Commands =====
|
||||
|
||||
extension_app = typer.Typer(
|
||||
|
||||
1005
src/specify_cli/agent_pack.py
Normal file
1005
src/specify_cli/agent_pack.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -47,6 +47,12 @@ class CommandRegistrar:
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md"
|
||||
},
|
||||
"cursor-agent": {
|
||||
"dir": ".cursor/commands",
|
||||
"format": "markdown",
|
||||
"args": "$ARGUMENTS",
|
||||
"extension": ".md"
|
||||
},
|
||||
"qwen": {
|
||||
"dir": ".qwen/commands",
|
||||
"format": "markdown",
|
||||
|
||||
23
src/specify_cli/core_pack/agents/agy/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/agy/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "agy"
|
||||
name: "Antigravity"
|
||||
version: "1.0.0"
|
||||
description: "Antigravity IDE for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: false
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['ide', 'antigravity']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".agent/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/amp/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/amp/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "amp"
|
||||
name: "Amp"
|
||||
version: "1.0.0"
|
||||
description: "Amp CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://ampcode.com/manual#install"
|
||||
cli_tool: "amp"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'amp']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".agents/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/auggie/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/auggie/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "auggie"
|
||||
name: "Auggie CLI"
|
||||
version: "1.0.0"
|
||||
description: "Auggie CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli"
|
||||
cli_tool: "auggie"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'augment', 'auggie']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".augment/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
23
src/specify_cli/core_pack/agents/bob/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/bob/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "bob"
|
||||
name: "IBM Bob"
|
||||
version: "1.0.0"
|
||||
description: "IBM Bob IDE for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: false
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['ide', 'ibm', 'bob']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".bob/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/claude/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/claude/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "claude"
|
||||
name: "Claude Code"
|
||||
version: "1.0.0"
|
||||
description: "Anthropic's Claude Code CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://docs.anthropic.com/en/docs/claude-code/setup"
|
||||
cli_tool: "claude"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'anthropic', 'claude']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".claude/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/codebuddy/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/codebuddy/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "codebuddy"
|
||||
name: "CodeBuddy"
|
||||
version: "1.0.0"
|
||||
description: "CodeBuddy CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://www.codebuddy.ai/cli"
|
||||
cli_tool: "codebuddy"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'codebuddy']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".codebuddy/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/codex/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/codex/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "codex"
|
||||
name: "Codex CLI"
|
||||
version: "1.0.0"
|
||||
description: "OpenAI Codex CLI with project skills support"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://github.com/openai/codex"
|
||||
cli_tool: "codex"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'openai', 'codex', 'skills']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".agents/skills"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: "/SKILL.md"
|
||||
23
src/specify_cli/core_pack/agents/copilot/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/copilot/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "copilot"
|
||||
name: "GitHub Copilot"
|
||||
version: "1.0.0"
|
||||
description: "GitHub Copilot for AI-assisted development in VS Code"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: false
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['ide', 'github', 'copilot']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".github/agents"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".agent.md"
|
||||
@@ -0,0 +1,23 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "cursor-agent"
|
||||
name: "Cursor"
|
||||
version: "1.0.0"
|
||||
description: "Cursor IDE for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: false
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['ide', 'cursor']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".cursor/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/gemini/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/gemini/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "gemini"
|
||||
name: "Gemini CLI"
|
||||
version: "1.0.0"
|
||||
description: "Google's Gemini CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://github.com/google-gemini/gemini-cli"
|
||||
cli_tool: "gemini"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'google', 'gemini']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".gemini/commands"
|
||||
format: "toml"
|
||||
arg_placeholder: "{{args}}"
|
||||
file_extension: ".toml"
|
||||
25
src/specify_cli/core_pack/agents/iflow/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/iflow/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "iflow"
|
||||
name: "iFlow CLI"
|
||||
version: "1.0.0"
|
||||
description: "iFlow CLI by iflow-ai for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://docs.iflow.cn/en/cli/quickstart"
|
||||
cli_tool: "iflow"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'iflow']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".iflow/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/junie/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/junie/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "junie"
|
||||
name: "Junie"
|
||||
version: "1.0.0"
|
||||
description: "Junie by JetBrains for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://junie.jetbrains.com/"
|
||||
cli_tool: "junie"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'jetbrains', 'junie']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".junie/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
23
src/specify_cli/core_pack/agents/kilocode/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/kilocode/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "kilocode"
|
||||
name: "Kilo Code"
|
||||
version: "1.0.0"
|
||||
description: "Kilo Code IDE for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: false
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['ide', 'kilocode']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".kilocode/workflows"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/kimi/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/kimi/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "kimi"
|
||||
name: "Kimi Code"
|
||||
version: "1.0.0"
|
||||
description: "Kimi Code CLI by Moonshot AI with skills support"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://code.kimi.com/"
|
||||
cli_tool: "kimi"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'moonshot', 'kimi', 'skills']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".kimi/skills"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: "/SKILL.md"
|
||||
25
src/specify_cli/core_pack/agents/kiro-cli/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/kiro-cli/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "kiro-cli"
|
||||
name: "Kiro CLI"
|
||||
version: "1.0.0"
|
||||
description: "Kiro CLI by Amazon for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://kiro.dev/docs/cli/"
|
||||
cli_tool: "kiro-cli"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'amazon', 'kiro']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".kiro/prompts"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/opencode/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/opencode/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "opencode"
|
||||
name: "opencode"
|
||||
version: "1.0.0"
|
||||
description: "opencode CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://opencode.ai"
|
||||
cli_tool: "opencode"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'opencode']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".opencode/command"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/pi/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/pi/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "pi"
|
||||
name: "Pi Coding Agent"
|
||||
version: "1.0.0"
|
||||
description: "Pi terminal coding agent for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://www.npmjs.com/package/@mariozechner/pi-coding-agent"
|
||||
cli_tool: "pi"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'pi']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".pi/prompts"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/qodercli/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/qodercli/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "qodercli"
|
||||
name: "Qoder CLI"
|
||||
version: "1.0.0"
|
||||
description: "Qoder CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://qoder.com/cli"
|
||||
cli_tool: "qodercli"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'qoder']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".qoder/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/qwen/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/qwen/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "qwen"
|
||||
name: "Qwen Code"
|
||||
version: "1.0.0"
|
||||
description: "Alibaba's Qwen Code CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://github.com/QwenLM/qwen-code"
|
||||
cli_tool: "qwen"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'alibaba', 'qwen']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".qwen/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
23
src/specify_cli/core_pack/agents/roo/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/roo/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "roo"
|
||||
name: "Roo Code"
|
||||
version: "1.0.0"
|
||||
description: "Roo Code IDE for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: false
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['ide', 'roo']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".roo/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/shai/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/shai/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "shai"
|
||||
name: "SHAI"
|
||||
version: "1.0.0"
|
||||
description: "SHAI CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://github.com/ovh/shai"
|
||||
cli_tool: "shai"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'ovh', 'shai']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".shai/commands"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/tabnine/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/tabnine/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "tabnine"
|
||||
name: "Tabnine CLI"
|
||||
version: "1.0.0"
|
||||
description: "Tabnine CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://docs.tabnine.com/main/getting-started/tabnine-cli"
|
||||
cli_tool: "tabnine"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'tabnine']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".tabnine/agent/commands"
|
||||
format: "toml"
|
||||
arg_placeholder: "{{args}}"
|
||||
file_extension: ".toml"
|
||||
23
src/specify_cli/core_pack/agents/trae/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/trae/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "trae"
|
||||
name: "Trae"
|
||||
version: "1.0.0"
|
||||
description: "Trae IDE for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: false
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['ide', 'trae']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".trae/rules"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
25
src/specify_cli/core_pack/agents/vibe/speckit-agent.yml
Normal file
25
src/specify_cli/core_pack/agents/vibe/speckit-agent.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "vibe"
|
||||
name: "Mistral Vibe"
|
||||
version: "1.0.0"
|
||||
description: "Mistral Vibe CLI for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: true
|
||||
install_url: "https://github.com/mistralai/mistral-vibe"
|
||||
cli_tool: "vibe"
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['cli', 'mistral', 'vibe']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".vibe/prompts"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
23
src/specify_cli/core_pack/agents/windsurf/speckit-agent.yml
Normal file
23
src/specify_cli/core_pack/agents/windsurf/speckit-agent.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
schema_version: "1.0"
|
||||
|
||||
agent:
|
||||
id: "windsurf"
|
||||
name: "Windsurf"
|
||||
version: "1.0.0"
|
||||
description: "Windsurf IDE for AI-assisted development"
|
||||
author: "github"
|
||||
license: "MIT"
|
||||
|
||||
runtime:
|
||||
requires_cli: false
|
||||
|
||||
requires:
|
||||
speckit_version: ">=0.1.0"
|
||||
|
||||
tags: ['ide', 'windsurf']
|
||||
|
||||
command_registration:
|
||||
commands_dir: ".windsurf/workflows"
|
||||
format: "markdown"
|
||||
arg_placeholder: "$ARGUMENTS"
|
||||
file_extension: ".md"
|
||||
1353
tests/test_agent_pack.py
Normal file
1353
tests/test_agent_pack.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user