Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
801 changes: 13 additions & 788 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"}
2 changes: 2 additions & 0 deletions src/specify_cli/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""CLI command groups — each module exposes a register(app) function."""
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
738 changes: 738 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