Skip to content
Draft
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
4 changes: 2 additions & 2 deletions src/apm_cli/commands/compile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ...compilation import AgentsCompiler, CompilationConfig
from ...constants import AGENTS_MD_FILENAME, APM_DIR, APM_MODULES_DIR, APM_YML_FILENAME
from ...core.command_logger import CommandLogger
from ...core.target_detection import TargetParamType
from ...core.target_detection import TARGET_HELP_DETAILS, TargetParamType
from ...primitives.discovery import clear_discovery_cache, discover_primitives
from ...utils import perf_stats
from ...utils.console import (
Expand Down Expand Up @@ -799,7 +799,7 @@ def _coerce_provenance_targets(value):
"-t",
type=TargetParamType(),
default=None,
help="Target platform (comma-separated). Values: copilot, claude, cursor, opencode, codex, gemini, antigravity, windsurf, kiro, agent-skills, all. 'agent-skills' deploys to .agents/skills/ (cross-client). 'antigravity' (alias 'agy') deploys to .agents/ and is explicit-only -- not part of 'all'. 'all' = copilot+claude+cursor+opencode+codex+gemini+windsurf+kiro (excludes agent-skills and antigravity); combine with 'agent-skills' or 'antigravity' to add them.",
help=f"Target platform (comma-separated). {TARGET_HELP_DETAILS}",
)
@click.option(
"--dry-run",
Expand Down
8 changes: 6 additions & 2 deletions src/apm_cli/commands/deps/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# Import existing APM components
from ...constants import APM_MODULES_DIR, APM_YML_FILENAME, SKILL_MD_FILENAME
from ...core.command_logger import CommandLogger
from ...core.target_detection import TargetParamType
from ...core.target_detection import TARGET_HELP_DETAILS, TargetParamType
from ...models.apm_package import APMPackage
from .._helpers import (
UnknownPackageError,
Expand Down Expand Up @@ -727,7 +727,11 @@ def clean(dry_run: bool, yes: bool):
"-t",
type=TargetParamType(),
default=None,
help="Target platform (comma-separated). Values: copilot, claude, cursor, opencode, codex, gemini, antigravity, windsurf, kiro, agent-skills, all. 'agent-skills' deploys to .agents/skills/ (cross-client). 'antigravity' (alias 'agy') deploys to .agents/ and is explicit-only -- not part of 'all'. 'all' = copilot+claude+cursor+opencode+codex+gemini+windsurf+kiro (excludes agent-skills and antigravity); combine with 'agent-skills' or 'antigravity' to add them. 'copilot-cowork' is also accepted when the copilot-cowork experimental flag is enabled (run 'apm experimental enable copilot-cowork').",
help=(
f"Target platform (comma-separated). {TARGET_HELP_DETAILS} "
"'copilot-cowork' is also accepted when the copilot-cowork experimental "
"flag is enabled (run 'apm experimental enable copilot-cowork')."
),
)
@click.option(
"--parallel-downloads",
Expand Down
16 changes: 14 additions & 2 deletions src/apm_cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
)
from ..core.auth import AuthResolver
from ..core.command_logger import InstallLogger, _ValidationOutcome
from ..core.target_detection import TargetParamType
from ..core.target_detection import TARGET_HELP_DETAILS, TargetParamType

# MCP --mcp helpers (module-level re-exports for test patches); must stay at
# import time per comments in the original mid-file block.
Expand Down Expand Up @@ -944,7 +944,19 @@ def _handle_mcp_install(
"target",
type=TargetParamType(),
default=None,
help="Target harness(es) to deploy to. Comma-separated for multiple: --target claude,cursor. Repeating the flag (e.g. '-t a -t b') is NOT supported -- only the last value wins; use commas. Highest-priority entry in the resolution chain (--target > apm.yml targets: > auto-detect). Values: copilot, claude, cursor, opencode, codex, gemini, antigravity, windsurf, kiro, agent-skills, all. 'agent-skills' deploys to .agents/skills/ (cross-client). 'antigravity' (alias 'agy') deploys to .agents/ (AGENTS.md + rules + skills + hooks.json + mcp_config.json) and is explicit-only -- not part of 'all' or auto-detection. 'all' = copilot+claude+cursor+opencode+codex+gemini+windsurf+kiro (excludes agent-skills and antigravity); combine with 'agent-skills' or 'antigravity' to add them. 'copilot-cowork' is also accepted when the copilot-cowork experimental flag is enabled (run 'apm experimental enable copilot-cowork'). 'copilot-app' is also accepted when the copilot-app experimental flag is enabled (run 'apm experimental enable copilot-app'). Note: '--target all' on 'apm compile' is deprecated; use 'apm compile --all' instead.",
help=(
"Target harness(es) to deploy to. Comma-separated for multiple: "
"--target claude,cursor. Repeating the flag (e.g. '-t a -t b') is "
"NOT supported -- only the last value wins; use commas. Highest-priority "
"entry in the resolution chain (--target > apm.yml targets: > auto-detect). "
f"{TARGET_HELP_DETAILS} "
"'copilot-cowork' is also accepted when the copilot-cowork experimental "
"flag is enabled (run 'apm experimental enable copilot-cowork'). "
"'copilot-app' is also accepted when the copilot-app experimental flag "
"is enabled (run 'apm experimental enable copilot-app'). Note: "
"'--target all' on 'apm compile' is deprecated; use 'apm compile --all' "
"instead."
),
)
@click.option(
"--allow-insecure",
Expand Down
4 changes: 2 additions & 2 deletions src/apm_cli/commands/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@

from ..core.auth import AuthResolver
from ..core.command_logger import InstallLogger
from ..core.target_detection import TargetParamType
from ..core.target_detection import TARGET_HELP_EXAMPLE_CSV, TargetParamType
from ..deps.github_downloader import GitHubPackageDownloader
from ..deps.revision_pins import (
RemoteRefDownloader,
Expand Down Expand Up @@ -251,7 +251,7 @@ def _annotate_lockfile_revision_tags(project_root: Path, updates: list[RevisionP
default=None,
help=(
"Agent target(s) to update for "
"(e.g. claude, copilot, cursor, windsurf, kiro, codex, opencode, gemini). "
f"(e.g. {TARGET_HELP_EXAMPLE_CSV}). "
"Comma-separated for multiple: --target claude,cursor. "
"Highest-priority entry in the resolution chain "
"(--target > apm.yml targets: > auto-detect)."
Expand Down
21 changes: 18 additions & 3 deletions src/apm_cli/core/target_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,13 +706,28 @@ class ResolvedTargets:
"claude",
"copilot",
"cursor",
"codex",
"gemini",
"opencode",
"windsurf",
"kiro",
"codex",
"opencode",
"gemini",
]

TARGET_HELP_EXAMPLE_CSV = ", ".join(CANONICAL_TARGETS_ORDERED)
TARGET_HELP_VALUES_CSV = ", ".join(
[*CANONICAL_TARGETS_ORDERED, "antigravity", "agent-skills", "all"]
)
TARGET_ALL_HELP_EXPANSION = "+".join(CANONICAL_TARGETS_ORDERED)
TARGET_HELP_DETAILS = (
f"Values: {TARGET_HELP_VALUES_CSV}. "
"'agent-skills' deploys to .agents/skills/ (cross-client). "
"'antigravity' (alias 'agy') deploys to .agents/ and is explicit-only -- "
"not part of 'all'. "
f"'all' = {TARGET_ALL_HELP_EXPANSION} "
"(excludes agent-skills and antigravity); combine with 'agent-skills' "
"or 'antigravity' to add them."
)

# Canonical deploy directories for each target.
CANONICAL_DEPLOY_DIRS: dict[str, str] = {
"claude": ".claude/",
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/test_cli_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
from click.testing import CliRunner

from apm_cli.cli import cli
from apm_cli.core.target_detection import (
TARGET_ALL_HELP_EXPANSION,
TARGET_HELP_EXAMPLE_CSV,
TARGET_HELP_VALUES_CSV,
)
from apm_cli.output.script_formatters import ScriptExecutionFormatter


Expand All @@ -18,6 +23,17 @@ def _walk_commands(group: click.Group, prefix: tuple[str, ...] = ()):
yield from _walk_commands(cmd, path)


def _option_help(path: tuple[str, ...], option_name: str) -> str:
command: click.Command = cli
for name in path:
assert isinstance(command, click.Group)
command = command.commands[name]
for param in command.params:
if isinstance(param, click.Option) and option_name in param.opts:
return param.help or ""
raise AssertionError(f"{' '.join(path)} is missing {option_name}")


def test_every_registered_command_has_explicit_help():
"""Silent-drift guard: no command may rely on the docstring fallback.

Expand Down Expand Up @@ -119,6 +135,17 @@ def test_mcp_install_forwards_unknown_options_before_double_dash():
assert "would add MCP server 'myserver'" in result.output


def test_target_help_uses_shared_display_fragments():
"""Keep target help examples aligned with target_detection.py."""
for path in (("install",), ("compile",), ("deps", "update")):
help_text = _option_help(path, "--target")
assert TARGET_HELP_VALUES_CSV in help_text
assert TARGET_ALL_HELP_EXPANSION in help_text

update_help = _option_help(("update",), "--target")
assert TARGET_HELP_EXAMPLE_CSV in update_help


def test_pack_unpack_dry_run_help_has_no_trailing_period():
runner = CliRunner()

Expand Down