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.*
|
*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"
|
"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.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"
|
".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]
|
[project.optional-dependencies]
|
||||||
test = [
|
test = [
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import json5
|
|||||||
import stat
|
import stat
|
||||||
import yaml
|
import yaml
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, Tuple
|
from typing import Any, List, Optional, Tuple
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
import httpx
|
import httpx
|
||||||
@@ -1715,6 +1715,7 @@ def _handle_agent_skills_migration(console: Console, agent_key: str) -> None:
|
|||||||
def init(
|
def init(
|
||||||
project_name: str = typer.Argument(None, help="Name for your new project directory (optional if using --here, or use '.' for current directory)"),
|
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),
|
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/)"),
|
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"),
|
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"),
|
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)"),
|
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"),
|
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)"),
|
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."),
|
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)"),
|
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)"),
|
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:
|
Examples:
|
||||||
specify init my-project
|
specify init my-project
|
||||||
specify init my-project --ai claude
|
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 my-project --ai copilot --no-git
|
||||||
specify init --ignore-agent-tools my-project
|
specify init --ignore-agent-tools my-project
|
||||||
specify init . --ai claude # Initialize in current directory
|
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 --here --force # Skip confirmation when current directory not empty
|
||||||
specify init my-project --ai claude --ai-skills # Install agent skills
|
specify init my-project --ai claude --ai-skills # Install agent skills
|
||||||
specify init --here --ai gemini --ai-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 --ai generic --ai-commands-dir .myagent/commands/ # Unsupported agent
|
||||||
specify init my-project --offline # Use bundled assets (no network access)
|
specify init my-project --offline # Use bundled assets (no network access)
|
||||||
specify init my-project --ai claude --preset healthcare-compliance # With preset
|
specify init my-project --ai claude --preset healthcare-compliance # With preset
|
||||||
@@ -1772,6 +1775,20 @@ def init(
|
|||||||
|
|
||||||
show_banner()
|
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)
|
# Detect when option values are likely misinterpreted flags (parameter ordering issue)
|
||||||
if ai_assistant and ai_assistant.startswith("--"):
|
if ai_assistant and ai_assistant.startswith("--"):
|
||||||
console.print(f"[red]Error:[/red] Invalid value for --ai: '{ai_assistant}'")
|
console.print(f"[red]Error:[/red] Invalid value for --ai: '{ai_assistant}'")
|
||||||
@@ -1802,7 +1819,7 @@ def init(
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
if ai_skills and not ai_assistant:
|
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")
|
console.print("[yellow]Usage:[/yellow] specify init <project> --ai <agent> --ai-skills")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
@@ -1854,6 +1871,19 @@ def init(
|
|||||||
"copilot"
|
"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.
|
# Agents that have moved from explicit commands/prompts to agent skills.
|
||||||
if selected_ai in AGENT_SKILLS_MIGRATIONS and not ai_skills:
|
if selected_ai in AGENT_SKILLS_MIGRATIONS and not ai_skills:
|
||||||
# If selected interactively (no --ai provided), automatically enable
|
# If selected interactively (no --ai provided), automatically enable
|
||||||
@@ -1957,7 +1987,10 @@ def init(
|
|||||||
"This will become the default in v0.6.0."
|
"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 [
|
for key, label in [
|
||||||
("fetch", "Fetch latest release"),
|
("fetch", "Fetch latest release"),
|
||||||
("download", "Download template"),
|
("download", "Download template"),
|
||||||
@@ -1992,7 +2025,26 @@ def init(
|
|||||||
verify = not skip_tls
|
verify = not skip_tls
|
||||||
local_ssl_context = ssl_context if verify else False
|
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:
|
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)
|
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:
|
else:
|
||||||
@@ -2090,6 +2142,7 @@ def init(
|
|||||||
"ai": selected_ai,
|
"ai": selected_ai,
|
||||||
"ai_skills": ai_skills,
|
"ai_skills": ai_skills,
|
||||||
"ai_commands_dir": ai_commands_dir,
|
"ai_commands_dir": ai_commands_dir,
|
||||||
|
"agent_pack": use_agent_pack,
|
||||||
"branch_numbering": branch_numbering or "sequential",
|
"branch_numbering": branch_numbering or "sequential",
|
||||||
"here": here,
|
"here": here,
|
||||||
"preset": preset,
|
"preset": preset,
|
||||||
@@ -2133,6 +2186,16 @@ def init(
|
|||||||
if not use_github:
|
if not use_github:
|
||||||
tracker.skip("cleanup", "not needed (no download)")
|
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")
|
tracker.complete("final", "project ready")
|
||||||
except (typer.Exit, SystemExit):
|
except (typer.Exit, SystemExit):
|
||||||
raise
|
raise
|
||||||
@@ -2366,6 +2429,616 @@ def version():
|
|||||||
console.print()
|
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 Commands =====
|
||||||
|
|
||||||
extension_app = typer.Typer(
|
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",
|
"args": "$ARGUMENTS",
|
||||||
"extension": ".md"
|
"extension": ".md"
|
||||||
},
|
},
|
||||||
|
"cursor-agent": {
|
||||||
|
"dir": ".cursor/commands",
|
||||||
|
"format": "markdown",
|
||||||
|
"args": "$ARGUMENTS",
|
||||||
|
"extension": ".md"
|
||||||
|
},
|
||||||
"qwen": {
|
"qwen": {
|
||||||
"dir": ".qwen/commands",
|
"dir": ".qwen/commands",
|
||||||
"format": "markdown",
|
"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