From 91d708cb0c255642d5c8fca013517457c2fe1ca8 Mon Sep 17 00:00:00 2001 From: WilliamK112 <164879897+WilliamK112@users.noreply.github.com> Date: Tue, 23 Jun 2026 09:29:16 -0400 Subject: [PATCH] refactor(cli): share target help fragments --- src/apm_cli/commands/compile/cli.py | 4 ++-- src/apm_cli/commands/deps/cli.py | 8 ++++++-- src/apm_cli/commands/install.py | 16 ++++++++++++++-- src/apm_cli/commands/update.py | 4 ++-- src/apm_cli/core/target_detection.py | 21 ++++++++++++++++++--- tests/unit/test_cli_consistency.py | 27 +++++++++++++++++++++++++++ 6 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/apm_cli/commands/compile/cli.py b/src/apm_cli/commands/compile/cli.py index 6f69d65de..bd313b205 100644 --- a/src/apm_cli/commands/compile/cli.py +++ b/src/apm_cli/commands/compile/cli.py @@ -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 ( @@ -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", diff --git a/src/apm_cli/commands/deps/cli.py b/src/apm_cli/commands/deps/cli.py index b8922c5b4..5fee1e960 100644 --- a/src/apm_cli/commands/deps/cli.py +++ b/src/apm_cli/commands/deps/cli.py @@ -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, @@ -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", diff --git a/src/apm_cli/commands/install.py b/src/apm_cli/commands/install.py index 3e5aa8f9c..79adba0eb 100644 --- a/src/apm_cli/commands/install.py +++ b/src/apm_cli/commands/install.py @@ -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. @@ -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", diff --git a/src/apm_cli/commands/update.py b/src/apm_cli/commands/update.py index c91373272..4e4b490d3 100644 --- a/src/apm_cli/commands/update.py +++ b/src/apm_cli/commands/update.py @@ -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, @@ -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)." diff --git a/src/apm_cli/core/target_detection.py b/src/apm_cli/core/target_detection.py index 25f14029c..83c4bc322 100644 --- a/src/apm_cli/core/target_detection.py +++ b/src/apm_cli/core/target_detection.py @@ -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/", diff --git a/tests/unit/test_cli_consistency.py b/tests/unit/test_cli_consistency.py index da7d34d09..952fda044 100644 --- a/tests/unit/test_cli_consistency.py +++ b/tests/unit/test_cli_consistency.py @@ -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 @@ -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. @@ -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()