Skip to content
815 changes: 13 additions & 802 deletions src/specify_cli/__init__.py

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions src/specify_cli/_agent_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Agent configuration constants derived from the integration registry."""
from __future__ import annotations

from typing import Any


def _build_agent_config() -> dict[str, dict[str, Any]]:
from .integrations import INTEGRATION_REGISTRY
config: dict[str, dict[str, Any]] = {}
for key, integration in INTEGRATION_REGISTRY.items():
if integration.config:
config[key] = dict(integration.config)
return config


AGENT_CONFIG: dict[str, dict[str, Any]] = _build_agent_config()

DEFAULT_INIT_INTEGRATION = "copilot"

AI_ASSISTANT_ALIASES: dict[str, str] = {
"kiro": "kiro-cli",
}


def _build_ai_assistant_help() -> str:
non_generic_agents = sorted(agent for agent in AGENT_CONFIG if agent != "generic")
base_help = (
f"AI assistant to use: {', '.join(non_generic_agents)}, "
"or generic (requires --ai-commands-dir)."
)
if not AI_ASSISTANT_ALIASES:
return base_help
alias_phrases = []
for alias, target in sorted(AI_ASSISTANT_ALIASES.items()):
alias_phrases.append(f"'{alias}' as an alias for '{target}'")
if len(alias_phrases) == 1:
aliases_text = alias_phrases[0]
else:
aliases_text = ", ".join(alias_phrases[:-1]) + " and " + alias_phrases[-1]
return base_help + " Use " + aliases_text + "."


AI_ASSISTANT_HELP: str = _build_ai_assistant_help()

SCRIPT_TYPE_CHOICES: dict[str, str] = {"sh": "POSIX Shell (bash/zsh)", "ps": "PowerShell"}
7 changes: 7 additions & 0 deletions src/specify_cli/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""CLI command groups extracted from the main application.

Implemented command modules expose a ``register(app)`` function. Placeholder
modules are import-only anchors for command groups that still live in the main
application module.
"""
from __future__ import annotations
2 changes: 2 additions & 0 deletions src/specify_cli/commands/extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""specify extension * commands — placeholder for future extraction."""
from __future__ import annotations
743 changes: 743 additions & 0 deletions src/specify_cli/commands/init.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/specify_cli/commands/integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""specify integration * commands — placeholder for future extraction."""
from __future__ import annotations
2 changes: 2 additions & 0 deletions src/specify_cli/commands/preset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""specify preset * commands — placeholder for future extraction."""
from __future__ import annotations
2 changes: 2 additions & 0 deletions src/specify_cli/commands/workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""specify workflow * commands — placeholder for future extraction."""
from __future__ import annotations
4 changes: 2 additions & 2 deletions tests/integrations/test_integration_claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ def test_interactive_claude_selection_uses_integration_path(self, tmp_path):
os.chdir(project)
runner = CliRunner()
with (
patch("specify_cli._stdin_is_interactive", return_value=True),
patch("specify_cli.select_with_arrows", return_value="claude"),
patch("specify_cli.commands.init._stdin_is_interactive", return_value=True),
patch("specify_cli.commands.init.select_with_arrows", return_value="claude"),
):
result = runner.invoke(
app,
Expand Down
48 changes: 48 additions & 0 deletions tests/test_commands_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Tests for the commands/ package structure."""
import importlib


def test_commands_package_importable():
mod = importlib.import_module("specify_cli.commands")
assert mod is not None


def test_commands_init_importable():
mod = importlib.import_module("specify_cli.commands.init")
assert hasattr(mod, "register")
assert callable(mod.register)


def test_commands_stubs_importable():
for name in ("integration", "preset", "extension", "workflow"):
mod = importlib.import_module(f"specify_cli.commands.{name}")
assert mod is not None


def test_agent_config_importable():
from specify_cli._agent_config import (
AGENT_CONFIG,
AI_ASSISTANT_ALIASES,
AI_ASSISTANT_HELP,
DEFAULT_INIT_INTEGRATION,
SCRIPT_TYPE_CHOICES,
)
assert isinstance(AGENT_CONFIG, dict)
assert isinstance(AI_ASSISTANT_ALIASES, dict)
assert isinstance(AI_ASSISTANT_HELP, str)
assert DEFAULT_INIT_INTEGRATION == "copilot"
assert "sh" in SCRIPT_TYPE_CHOICES


def test_agent_config_re_exported_from_init():
from specify_cli import AGENT_CONFIG, AI_ASSISTANT_ALIASES, AI_ASSISTANT_HELP, SCRIPT_TYPE_CHOICES
assert isinstance(AGENT_CONFIG, dict)
assert "sh" in SCRIPT_TYPE_CHOICES


def test_init_command_registered():
from specify_cli import app
callback_names = [
cmd.callback.__name__ for cmd in app.registered_commands if cmd.callback
]
assert "init" in callback_names