From 777e81a6113e14d58cf26bb0b0fcdb681c396887 Mon Sep 17 00:00:00 2001 From: NUNO MIGUEL DA SILVA SALVACAO Date: Mon, 16 Feb 2026 12:28:06 +0000 Subject: [PATCH 1/5] feat(crawler): harden parsing, discovery and plugin generation --- src/crawler/cli_crawler.py | 41 ++ src/crawler/config.py | 4 +- src/crawler/detector.py | 134 ++++- src/crawler/discovery.py | 68 ++- src/crawler/executor.py | 31 ++ src/crawler/formatter.py | 21 +- src/crawler/parser.py | 170 +++++- src/crawler/parsers/commands.py | 80 ++- src/crawler/parsers/flags.py | 106 ++++ src/crawler/parsers/manpage.py | 73 ++- src/crawler/parsers/sections.py | 25 +- src/crawler/parsers/usage.py | 309 +++++++++++ src/crawler/pipeline.py | 113 +++- src/crawler/version.py | 288 +++++++++- src/generator/plugin_generator.py | 503 +++++++++++++----- src/lib/cli_identity.py | 42 ++ tests/conftest.py | 5 + tests/fixtures/pnpm/help.txt | 47 ++ .../integration/test_gcc_climap_generation.py | 53 ++ .../test_pnpm_climap_generation.py | 44 ++ tests/performance/test_smoke_perf.py | 61 +++ tests/test_discovery_thread_safety.py | 32 ++ tests/test_generate_plugin.py | 46 +- tests/test_parser_description_cleaning.py | 63 +++ tests/test_parser_manpage.py | 46 ++ tests/test_parser_sections.py | 11 + tests/test_pipeline_integration.py | 20 +- tests/unit/test_author_config.py | 48 ++ tests/unit/test_cli_crawler_basic.py | 19 + tests/unit/test_cli_name_canonicalization.py | 75 +++ tests/unit/test_command_parsing_basic.py | 23 + tests/unit/test_edge_case_auth_help.py | 128 +++++ tests/unit/test_edge_case_long_help.py | 63 +++ tests/unit/test_edge_case_no_help.py | 46 ++ tests/unit/test_embedded_help_boundary.py | 28 + tests/unit/test_flag_dedup_embedded_help.py | 31 ++ tests/unit/test_flag_parsing_basic.py | 30 ++ .../unit/test_flag_parsing_gnu_single_dash.py | 66 +++ tests/unit/test_keyword_generation.py | 64 +++ tests/unit/test_output_layout.py | 52 ++ tests/unit/test_parser_pnpm_grouped_help.py | 37 ++ tests/unit/test_parsing_rich_man.py | 57 ++ tests/unit/test_progressive_disclosure.py | 57 ++ tests/unit/test_subcommand_help_safety.py | 144 +++++ .../unit/test_usage_line_option_extraction.py | 145 +++++ tests/unit/test_version_detection_fallback.py | 264 +++++++++ 46 files changed, 3581 insertions(+), 232 deletions(-) create mode 100644 src/crawler/cli_crawler.py create mode 100644 src/lib/cli_identity.py create mode 100644 tests/fixtures/pnpm/help.txt create mode 100644 tests/integration/test_gcc_climap_generation.py create mode 100644 tests/integration/test_pnpm_climap_generation.py create mode 100644 tests/performance/test_smoke_perf.py create mode 100644 tests/test_discovery_thread_safety.py create mode 100644 tests/test_parser_description_cleaning.py create mode 100644 tests/unit/test_author_config.py create mode 100644 tests/unit/test_cli_crawler_basic.py create mode 100644 tests/unit/test_cli_name_canonicalization.py create mode 100644 tests/unit/test_command_parsing_basic.py create mode 100644 tests/unit/test_edge_case_auth_help.py create mode 100644 tests/unit/test_edge_case_long_help.py create mode 100644 tests/unit/test_edge_case_no_help.py create mode 100644 tests/unit/test_embedded_help_boundary.py create mode 100644 tests/unit/test_flag_dedup_embedded_help.py create mode 100644 tests/unit/test_flag_parsing_basic.py create mode 100644 tests/unit/test_flag_parsing_gnu_single_dash.py create mode 100644 tests/unit/test_keyword_generation.py create mode 100644 tests/unit/test_output_layout.py create mode 100644 tests/unit/test_parser_pnpm_grouped_help.py create mode 100644 tests/unit/test_parsing_rich_man.py create mode 100644 tests/unit/test_progressive_disclosure.py create mode 100644 tests/unit/test_subcommand_help_safety.py create mode 100644 tests/unit/test_usage_line_option_extraction.py create mode 100644 tests/unit/test_version_detection_fallback.py diff --git a/src/crawler/cli_crawler.py b/src/crawler/cli_crawler.py new file mode 100644 index 0000000..1c72e35 --- /dev/null +++ b/src/crawler/cli_crawler.py @@ -0,0 +1,41 @@ +"""Compatibility crawler entrypoint and basic help execution helpers (T013).""" + +from __future__ import annotations + +from .config import CLIConfig, CrawlerConfig +from .executor import Executor +from .models import CLIMap, ExecutionResult +from .pipeline import crawl_all, crawl_cli, main + + +def run_root_help(cli_name: str, config: CLIConfig | None = None) -> ExecutionResult: + """Execute ` --help` with crawler defaults.""" + cfg = config or CLIConfig(name=cli_name) + executor = Executor(cfg) + return executor.run_with_retry([cli_name, "--help"]) + + +def crawl_single( + cli_name: str, + config: CLIConfig | None = None, +) -> CLIMap: + """Compatibility helper for crawling one CLI.""" + cfg = config or CLIConfig(name=cli_name) + return crawl_cli(cli_name, cfg) + + +def crawl_configured(config: CrawlerConfig, output_dir: str = "output") -> list[CLIMap]: + """Compatibility helper for crawling all configured CLIs.""" + from pathlib import Path + + return crawl_all(config, Path(output_dir)) + + +__all__ = [ + "crawl_all", + "crawl_cli", + "crawl_configured", + "crawl_single", + "main", + "run_root_help", +] diff --git a/src/crawler/config.py b/src/crawler/config.py index ed0b201..44dbf36 100644 --- a/src/crawler/config.py +++ b/src/crawler/config.py @@ -20,7 +20,7 @@ class CLIConfig: max_concurrent: int = 5 retry: int = 1 environment: str = "wsl" - raw_threshold: int = 10240 + raw_threshold: int = 10000 group: str | None = None help_pattern: str | None = None plugins: PluginConfig | None = None @@ -49,7 +49,7 @@ def _build_config(raw: dict) -> CrawlerConfig: max_concurrent=int(defaults_raw.get("max_concurrent", 5)), retry=int(defaults_raw.get("retry", 1)), environment=str(defaults_raw.get("environment", "wsl")), - raw_threshold=int(defaults_raw.get("raw_threshold", 10240)), + raw_threshold=int(defaults_raw.get("raw_threshold", 10000)), ) clis = {} diff --git a/src/crawler/detector.py b/src/crawler/detector.py index ff2954a..a4da0de 100644 --- a/src/crawler/detector.py +++ b/src/crawler/detector.py @@ -6,7 +6,7 @@ import re from .config import CLIConfig -from .executor import Executor +from .executor import Executor, format_auth_required_error, is_auth_required_failure from .models import ExecutionResult, HelpDetectionResult from .parsers.manpage import is_manpage @@ -26,6 +26,36 @@ re.IGNORECASE, ) +SAFE_BARE_FALLBACK_SUBCOMMANDS = { + "help", + "version", + "--version", + "-v", +} + +MUTATING_SUBCOMMAND_TOKENS = { + "add", + "apply", + "branch", + "checkout", + "cherry-pick", + "clean", + "clone", + "commit", + "init", + "merge", + "mv", + "pull", + "push", + "rebase", + "reset", + "restore", + "rm", + "stash", + "switch", + "tag", +} + def detect_help_pattern( cli_name: str, @@ -33,6 +63,7 @@ def detect_help_pattern( config: CLIConfig, ) -> HelpDetectionResult: """Try help patterns in order, return first that produces usable output.""" + auth_result: HelpDetectionResult | None = None # If config has a help_pattern override, try it first if config.help_pattern: @@ -44,6 +75,8 @@ def detect_help_pattern( result=result, is_manpage=is_manpage(result.stdout), ) + if is_auth_required_failure(result) and auth_result is None: + auth_result = _auth_required_result(cli_name, result) best_result: HelpDetectionResult | None = None @@ -65,6 +98,8 @@ def detect_help_pattern( result=result, is_manpage=is_manpage(result.stdout), ) + if is_auth_required_failure(result) and auth_result is None: + auth_result = _auth_required_result(cli_name, result) # Keep track of best non-empty result as fallback if result.stdout.strip() and not best_result: @@ -79,6 +114,8 @@ def detect_help_pattern( if best_result: logger.warning("No clear help output for %s, using best guess", cli_name) return best_result + if auth_result: + return auth_result return HelpDetectionResult( pattern="unknown", @@ -100,54 +137,119 @@ def detect_subcommand_help( """Get help for a specific subcommand.""" # Build command: cli_name subcmd1 subcmd2 --help base_cmd = [cli_name] + subcommand_path + auth_result: HelpDetectionResult | None = None + last_result = ExecutionResult( + stdout="", + stderr="", + exit_code=1, + command=base_cmd, + ) # Primary: append help pattern if help_pattern not in ("bare", "unknown"): cmd = base_cmd + [help_pattern] result = executor.run_with_retry(cmd) + last_result = result if _is_help_output(result.stdout): return HelpDetectionResult( pattern=help_pattern, result=result, is_manpage=is_manpage(result.stdout), ) + if is_auth_required_failure(result) and auth_result is None: + auth_result = _auth_required_result(" ".join(base_cmd), result) # Fallback: try -h (git subcommands prefer this for compact output) if help_pattern != "-h": cmd = base_cmd + ["-h"] result = executor.run_with_retry(cmd) + last_result = result if _is_help_output(result.stdout): return HelpDetectionResult( pattern="-h", result=result, is_manpage=is_manpage(result.stdout), ) + if is_auth_required_failure(result) and auth_result is None: + auth_result = _auth_required_result(" ".join(base_cmd), result) # Fallback: help subcmd pattern cmd = [cli_name, "help"] + subcommand_path result = executor.run_with_retry(cmd) + last_result = result if _is_help_output(result.stdout): return HelpDetectionResult( pattern="help", result=result, is_manpage=is_manpage(result.stdout), ) + if is_auth_required_failure(result) and auth_result is None: + auth_result = _auth_required_result(" ".join(base_cmd), result) - # Last resort: bare subcommand - result = executor.run(base_cmd, timeout=2) - if _is_help_output(result.stdout): + # Last resort: bare subcommand (only for explicitly safe paths). + if _should_try_bare_subcommand_fallback(subcommand_path): + result = executor.run(base_cmd, timeout=2) + last_result = result + if _is_help_output(result.stdout): + return HelpDetectionResult( + pattern="bare", + result=result, + is_manpage=is_manpage(result.stdout), + ) + if is_auth_required_failure(result): + auth_result = auth_result or _auth_required_result(" ".join(base_cmd), result) + else: + safety_warning = ( + "SAFETY_GUARD: Bare subcommand fallback skipped for potentially " + f"mutating command '{' '.join(base_cmd)}'." + ) + logger.warning("%s", safety_warning) + if auth_result: + return auth_result + stderr_parts = [last_result.stderr.strip(), safety_warning] + stderr = " | ".join(part for part in stderr_parts if part) return HelpDetectionResult( - pattern="bare", - result=result, - is_manpage=is_manpage(result.stdout), + pattern="unknown", + result=ExecutionResult( + stdout="", + stderr=stderr, + exit_code=last_result.exit_code, + command=last_result.command, + timed_out=last_result.timed_out, + duration=last_result.duration, + ), + is_manpage=False, ) + if auth_result: + return auth_result + return HelpDetectionResult( pattern="unknown", - result=result, + result=last_result, ) +def _should_try_bare_subcommand_fallback(subcommand_path: list[str]) -> bool: + """Gate bare subcommand fallback to explicitly safe, non-mutating paths.""" + tokens = [_normalize_subcommand_token(token) for token in subcommand_path if token.strip()] + if not tokens: + return False + + if tokens[0] == "help": + return True + + if any(token in MUTATING_SUBCOMMAND_TOKENS for token in tokens): + return False + + return all(token in SAFE_BARE_FALLBACK_SUBCOMMANDS for token in tokens) + + +def _normalize_subcommand_token(token: str) -> str: + """Normalize a subcommand token for safety checks.""" + return token.strip().lower() + + def _is_help_output(text: str) -> bool: """Heuristic: does this text look like help output?""" if not text or len(text.strip()) < 20: @@ -160,3 +262,19 @@ def _is_help_output(text: str) -> bool: # Count help-related keywords keyword_count = len(HELP_KEYWORDS.findall(text[:2000])) return keyword_count >= 2 + + +def _auth_required_result(command_hint: str, result: ExecutionResult) -> HelpDetectionResult: + """Build a structured auth-required detection result.""" + return HelpDetectionResult( + pattern="auth_required", + result=ExecutionResult( + stdout="", + stderr=format_auth_required_error(command_hint), + exit_code=result.exit_code or 1, + command=result.command, + timed_out=result.timed_out, + duration=result.duration, + ), + is_manpage=False, + ) diff --git a/src/crawler/discovery.py b/src/crawler/discovery.py index 8da0690..21f5734 100644 --- a/src/crawler/discovery.py +++ b/src/crawler/discovery.py @@ -4,6 +4,7 @@ import concurrent.futures import logging +import threading from dataclasses import dataclass, field from .config import CLIConfig @@ -22,6 +23,45 @@ class CrawlState: depth: int = 0 errors: int = 0 warnings: list[str] = field(default_factory=list) + lock: threading.Lock = field(default_factory=threading.Lock, repr=False) + + def mark_visited(self, command_path: str) -> bool: + """Atomically mark a command as visited. + + Returns True when the command was newly added, False if it was already visited. + """ + with self.lock: + if command_path in self.visited: + return False + self.visited.add(command_path) + return True + + def set_raw_output(self, command_path: str, output: str) -> None: + """Atomically store raw help output for a command path.""" + with self.lock: + self.raw_outputs[command_path] = output + + def get_raw_output(self, command_path: str) -> str: + """Atomically fetch raw help output for a command path.""" + with self.lock: + return self.raw_outputs.get(command_path, "") + + def increment_errors(self) -> None: + """Atomically increment parse/crawl error counter.""" + with self.lock: + self.errors += 1 + + def add_warning(self, warning: str) -> None: + """Atomically append a warning.""" + with self.lock: + self.warnings.append(warning) + + def extend_warnings(self, warnings: list[str]) -> None: + """Atomically append many warnings.""" + if not warnings: + return + with self.lock: + self.warnings.extend(warnings) def discover_and_crawl( @@ -55,10 +95,9 @@ def discover_and_crawl( futures = {} for subcmd_name in subcommand_names: full_path = f"{parent_path} {subcmd_name}" - if full_path in state.visited: + if not state.mark_visited(full_path): logger.debug("Skipping visited: %s", full_path) continue - state.visited.add(full_path) future = pool.submit( _crawl_single_subcommand, @@ -82,8 +121,8 @@ def discover_and_crawl( name, cmd = result results[name] = cmd except Exception as e: - state.errors += 1 - state.warnings.append(f"Error crawling {parent_path} {subcmd_name}: {e}") + state.increment_errors() + state.add_warning(f"Error crawling {parent_path} {subcmd_name}: {e}") logger.warning("Error crawling %s %s: %s", parent_path, subcmd_name, e) return results @@ -115,11 +154,18 @@ def _crawl_single_subcommand( ) if not detection.result.stdout.strip(): - state.warnings.append(f"No help output for: {full_path}") + stderr_msg = detection.result.stderr.strip() + if detection.pattern == "auth_required" and stderr_msg: + state.add_warning(stderr_msg) + logger.warning("Auth required for %s help: %s", full_path, stderr_msg) + elif stderr_msg: + state.add_warning(stderr_msg) + logger.warning("No usable help for %s: %s", full_path, stderr_msg) + state.add_warning(f"No help output for: {full_path}") return None # Detect when subcommand returns identical help as parent (echoed parent) - parent_raw = state.raw_outputs.get(parent_path, "") + parent_raw = state.get_raw_output(parent_path) is_echoed = parent_raw and detection.result.stdout.strip() == parent_raw.strip() if is_echoed: @@ -132,7 +178,7 @@ def _crawl_single_subcommand( description=desc, confidence=0.6, ) - state.raw_outputs[full_path] = detection.result.stdout + state.set_raw_output(full_path, detection.result.stdout) return subcmd_name, cmd # Parse the help output @@ -144,17 +190,19 @@ def _crawl_single_subcommand( ) # Store raw output - state.raw_outputs[full_path] = detection.result.stdout + state.set_raw_output(full_path, detection.result.stdout) # Track warnings - state.warnings.extend(parse_result.warnings) + state.extend_warnings(parse_result.warnings) cmd = parse_result.command # If cmd description is identical to parent's, prefer the parent listing one-liner if subcmd_name in parent_descriptions: parent_desc_oneliner = parent_descriptions[subcmd_name] - if cmd.description == state.raw_outputs.get(parent_path, "").splitlines()[0].strip(): + parent_lines = state.get_raw_output(parent_path).splitlines() + parent_first_line = parent_lines[0].strip() if parent_lines else "" + if parent_first_line and cmd.description == parent_first_line: cmd.description = parent_desc_oneliner # Recursively crawl sub-subcommands diff --git a/src/crawler/executor.py b/src/crawler/executor.py index b81b04c..f657334 100644 --- a/src/crawler/executor.py +++ b/src/crawler/executor.py @@ -14,6 +14,17 @@ logger = logging.getLogger("cli_crawler.executor") ANSI_RE = re.compile(r"\[[0-9;]*[a-zA-Z]|\][^]*|\[.*?[@-~]") +AUTH_REQUIRED_RE = re.compile( + r"\b(" + r"not\s+logged\s+in|please\s+log\s*in|login\s+required|" + r"authentication\s+required|authorization\s+required|" + r"requires?\s+(?:authentication|authorization|login)|" + r"access\s+denied|permission\s+denied|unauthorized|forbidden|" + r"invalid\s+(?:credential|credentials|token)|" + r"expired\s+token|token\s+(?:required|missing)" + r")\b", + re.IGNORECASE, +) def _quote_ps_arg(arg: str) -> str: @@ -152,3 +163,23 @@ def _strip_ansi(self, text: str) -> str: """Remove ANSI escape codes and BOM.""" text = text.lstrip("\ufeff") # strip BOM return ANSI_RE.sub("", text) + + +def is_auth_required_failure(result: ExecutionResult) -> bool: + """Detect auth-required failures from command output.""" + if result.exit_code == 0 and not result.timed_out: + return False + # Prefer stderr-only matching to avoid false positives from normal help text. + signal_text = (result.stderr or "").strip() + if not signal_text: + return False + return bool(AUTH_REQUIRED_RE.search(signal_text)) + + +def format_auth_required_error(command_hint: str) -> str: + """Build a structured auth-required error message.""" + root_cli = command_hint.split()[0] if command_hint.strip() else command_hint + return ( + "AUTH_REQUIRED: Help command requires authentication for " + f"'{command_hint}'. Run '{root_cli} auth login' and retry." + ) diff --git a/src/crawler/formatter.py b/src/crawler/formatter.py index d337f11..eaee172 100644 --- a/src/crawler/formatter.py +++ b/src/crawler/formatter.py @@ -17,7 +17,7 @@ def write_output( cli_map: CLIMap, raw_outputs: dict[str, str], output_path: Path, - config: CLIConfig, + _config: CLIConfig, include_raw: bool = False, ) -> None: """Write main JSON and optionally separate raw JSON.""" @@ -32,16 +32,15 @@ def write_output( _embed_raw(data.get("tree", {}), raw_outputs, cli_map.cli_name) elif raw_outputs: total_raw = sum(len(v) for v in raw_outputs.values()) - if total_raw > config.raw_threshold: - # Write separate raw file - raw_path = output_path.with_suffix(".raw.json") - raw_data = {path: text for path, text in sorted(raw_outputs.items())} - raw_path.write_text( - json.dumps(raw_data, indent=2, ensure_ascii=False), - encoding="utf-8", - ) - data["raw_file"] = raw_path.name - logger.info("Raw text written to %s (%d bytes)", raw_path, total_raw) + # Always write separate raw sidecar for deterministic output layout. + raw_path = output_path.with_suffix(".raw.json") + raw_data = {path: text for path, text in sorted(raw_outputs.items())} + raw_path.write_text( + json.dumps(raw_data, indent=2, ensure_ascii=False), + encoding="utf-8", + ) + data["raw_file"] = raw_path.name + logger.info("Raw text written to %s (%d bytes)", raw_path, total_raw) # Write main JSON output_path.write_text( diff --git a/src/crawler/parser.py b/src/crawler/parser.py index 9e05a82..89d568e 100644 --- a/src/crawler/parser.py +++ b/src/crawler/parser.py @@ -3,6 +3,8 @@ from __future__ import annotations import logging +import re +from dataclasses import replace from .models import Command, EnvVar, Flag, ParseResult, PositionalArg from .parsers.commands import parse_command_section @@ -11,9 +13,13 @@ from .parsers.flags import parse_flags_section from .parsers.manpage import is_manpage, parse_manpage from .parsers.sections import SectionType, segment_help_text -from .parsers.usage import extract_usage_from_text, parse_usage +from .parsers.usage import extract_usage_from_text, extract_usage_line_options, parse_usage logger = logging.getLogger("cli_crawler.parser") +EMBEDDED_HELP_HEADER_RE = re.compile( + r"^\s*([a-zA-Z][\w.+-]*)\s+-\s+.*\bcommand(?:-|\s)?line\b", + re.IGNORECASE, +) def parse_help_output( @@ -23,6 +29,8 @@ def parse_help_output( force_manpage: bool = False, ) -> ParseResult: """Main parse entry point. Dispatches to manpage or section-based parser.""" + text, embedded_warning = _truncate_embedded_help(text, cli_name) + if not text.strip(): return ParseResult( command=Command(path=command_path, name=command_path.split()[-1]), @@ -30,9 +38,13 @@ def parse_help_output( ) if force_manpage or is_manpage(text): - return _parse_manpage(text, cli_name, command_path) + result = _parse_manpage(text, cli_name, command_path) + else: + result = _parse_sectioned(text, cli_name, command_path) - return _parse_sectioned(text, cli_name, command_path) + if embedded_warning: + result.warnings.append(embedded_warning) + return result def _parse_manpage(text: str, cli_name: str, command_path: str) -> ParseResult: @@ -70,8 +82,10 @@ def _parse_sectioned(text: str, cli_name: str, command_path: str) -> ParseResult for line in section.content.splitlines(): stripped = line.strip() if stripped and not stripped.lower().startswith("usage:"): - cmd.description = stripped - break + cleaned = _clean_description(stripped, command_path) + if cleaned: + cmd.description = cleaned + break elif section.type == SectionType.USAGE: usage = parse_usage(section.content) @@ -86,7 +100,13 @@ def _parse_sectioned(text: str, cli_name: str, command_path: str) -> ParseResult else: subcommand_names.append(pc.name) if pc.description: - subcommand_descriptions[pc.name] = pc.description + cleaned = _clean_description( + pc.description, + f"{command_path} {pc.name}", + drop_circular_name=False, + ) + if cleaned: + subcommand_descriptions[pc.name] = cleaned elif section.type == SectionType.FLAGS: flags = parse_flags_section(section.content) @@ -124,10 +144,20 @@ def _parse_sectioned(text: str, cli_name: str, command_path: str) -> ParseResult # If this is root level, global_flags go to CLIMap level; # local flags stay with command + if not all_flags and not global_flags: + inline_flags = extract_usage_line_options(text) + if inline_flags: + all_flags.extend(inline_flags) + warnings.append( + f"Recovered {len(inline_flags)} flags from sectionless usage/help lines." + ) + + all_flags = _deduplicate_flags(all_flags) + global_flags = _deduplicate_flags(global_flags) if global_flags: # At root level, we return global flags as the command's flags # The pipeline will separate them into CLIMap.global_flags - cmd.flags = global_flags + all_flags + cmd.flags = _deduplicate_flags(global_flags + all_flags) else: cmd.flags = all_flags @@ -135,6 +165,7 @@ def _parse_sectioned(text: str, cli_name: str, command_path: str) -> ParseResult inline_envvars = extract_envvars_from_text(text) env_vars.extend(inline_envvars) cmd.env_vars = _deduplicate_envvars(env_vars) + cmd.description = _clean_description(cmd.description, command_path) # Compute confidence cmd.confidence = _compute_confidence(cmd, text) @@ -211,3 +242,128 @@ def _deduplicate_envvars(env_vars: list[EnvVar]) -> list[EnvVar]: seen.add(ev.name) result.append(ev) return result + + +def _truncate_embedded_help(text: str, cli_name: str) -> tuple[str, str | None]: + """Trim foreign embedded tool help blocks from wrapper CLIs (e.g., yq embedding jq).""" + lines = text.splitlines() + if not lines: + return text, None + + primary_names: set[str] = {cli_name.lower()} + usage_cmd = _extract_usage_command(lines) + if usage_cmd: + primary_names.add(usage_cmd) + + for idx, line in enumerate(lines): + m = EMBEDDED_HELP_HEADER_RE.match(line) + if not m: + continue + foreign_name = m.group(1).lower() + if foreign_name in primary_names: + continue + + kept = "\n".join(lines[:idx]).rstrip() + if kept: + warning = ( + "Embedded help boundary detected for " + f"'{foreign_name}' at line {idx + 1}; foreign block ignored." + ) + return kept, warning + + return text, None + + +def _extract_usage_command(lines: list[str]) -> str | None: + """Extract root command token from first usage line.""" + usage_re = re.compile(r"^\s*(?:usage)\s*:?\s*(\S+)", re.IGNORECASE) + for line in lines: + m = usage_re.match(line) + if not m: + continue + token = m.group(1).strip().lower() + if token: + return token + return None + + +def _deduplicate_flags(flags: list[Flag]) -> list[Flag]: + """Deduplicate flags by long/name with deterministic precedence.""" + deduped: list[Flag] = [] + index_by_key: dict[str, int] = {} + + for flag in flags: + key = (flag.long_name or flag.name or "").strip() + if not key: + continue + + if key not in index_by_key: + index_by_key[key] = len(deduped) + deduped.append(flag) + continue + + idx = index_by_key[key] + merged = _merge_flag_metadata(deduped[idx], flag) + if _flag_rank(flag) > _flag_rank(deduped[idx]): + merged = _merge_flag_metadata(flag, deduped[idx]) + deduped[idx] = merged + + return deduped + + +def _merge_flag_metadata(primary: Flag, secondary: Flag) -> Flag: + """Merge flag metadata, preferring primary while filling missing fields.""" + return replace( + primary, + long_name=primary.long_name or secondary.long_name, + short_name=primary.short_name or secondary.short_name, + default=primary.default if primary.default is not None else secondary.default, + choices=primary.choices or secondary.choices, + description=primary.description or secondary.description, + confidence=max(primary.confidence, secondary.confidence), + ) + + +def _flag_rank(flag: Flag) -> tuple[int, int, int, float]: + """Rank flag richness for deterministic replacement preference.""" + return ( + 1 if flag.short_name else 0, + 1 if flag.default is not None else 0, + len(flag.description or ""), + flag.confidence, + ) + + +_DESC_NOISE_PATTERNS = [ + re.compile(r"^\s*(?:fatal|error)\s*:", re.IGNORECASE), + re.compile(r"\baccepts?\s+\d+\s+arg(?:ument)?s?\b", re.IGNORECASE), + re.compile(r"\balready initialized\b", re.IGNORECASE), + re.compile(r"^\s*\[(?:warn|warning|info|error)\]\s*", re.IGNORECASE), +] + + +def _clean_description( + description: str, + command_path: str, + *, + drop_circular_name: bool = True, +) -> str: + """Filter noisy runtime/status messages from descriptions.""" + desc = (description or "").strip() + if not desc: + return "" + + normalized = re.sub(r"\s+", " ", desc).strip().lower() + leaf = command_path.split()[-1].strip().lower() + full = command_path.strip().lower() + + if normalized.startswith("usage:"): + return "" + if drop_circular_name and normalized in {leaf, full}: + return "" + + for pattern in _DESC_NOISE_PATTERNS: + if pattern.search(desc): + return "" + + return desc diff --git a/src/crawler/parsers/commands.py b/src/crawler/parsers/commands.py index e76a1c1..5e3bda5 100644 --- a/src/crawler/parsers/commands.py +++ b/src/crawler/parsers/commands.py @@ -23,6 +23,17 @@ r"(.+)$" # description ) +# Tabular with alias pair: " i, install Description" +CMD_TABULAR_ALIAS_RE = re.compile( + r"^\s+" + r"([\w][\w.-]*)" # alias or short name + r"\s*,\s*" + r"([\w][\w.-]*)" # canonical or alternate name + r"(\*)?" # optional plugin marker + r"\s{2,}" + r"(.+)$" +) + # Colon-delimited: " auth: Authenticate..." CMD_COLON_RE = re.compile( r"^\s+" @@ -77,18 +88,62 @@ def parse_command_section(content: str, group: str | None = None) -> list[Parsed def _try_tabular(lines: list[str], group: str | None) -> list[ParsedCommand]: """Try tabular format parsing.""" results: list[ParsedCommand] = [] - for line in lines: + i = 0 + while i < len(lines): + line = lines[i] stripped = line.strip() if not stripped: + i += 1 continue - m = CMD_TABULAR_RE.match(line) - if m: - name = m.group(1) - is_plugin = bool(m.group(2)) - desc = m.group(3).strip() + + alias_match = CMD_TABULAR_ALIAS_RE.match(line) + m = CMD_TABULAR_RE.match(line) if not alias_match else None + + if alias_match or m: + alias_name = None + if alias_match: + first = alias_match.group(1) + second = alias_match.group(2) + is_plugin = bool(alias_match.group(3)) + desc = alias_match.group(4).strip() + if len(second) > len(first): + name = second + alias_name = first + elif len(first) > len(second): + name = first + alias_name = second + else: + name = second + alias_name = first + else: + name = m.group(1) + is_plugin = bool(m.group(2)) + desc = m.group(3).strip() + + # Merge wrapped continuation lines used by some CLIs (e.g., pnpm). + j = i + 1 + while j < len(lines): + next_line = lines[j] + next_stripped = next_line.strip() + if not next_stripped: + break + if ( + CMD_TABULAR_ALIAS_RE.match(next_line) + or CMD_TABULAR_RE.match(next_line) + or CMD_COLON_RE.match(next_line) + or CMD_CSV_RE.match(next_line) + ): + break + indent = len(next_line) - len(next_line.lstrip()) + if indent >= 10 and not next_stripped.endswith(":"): + desc = f"{desc} {next_stripped}" + j += 1 + continue + break # Skip common non-command lines if _is_noise(name): + i = j continue is_alias = bool(ALIAS_RE.search(desc)) @@ -108,6 +163,19 @@ def _try_tabular(lines: list[str], group: str | None) -> list[ParsedCommand]: alias_target=alias_target, ) ) + if alias_name and not _is_noise(alias_name): + results.append( + ParsedCommand( + name=alias_name, + description=f"Alias for {name}", + group=group, + is_alias=True, + alias_target=name, + ) + ) + i = j + continue + i += 1 return results diff --git a/src/crawler/parsers/flags.py b/src/crawler/parsers/flags.py index ac0c525..8e0fc3b 100644 --- a/src/crawler/parsers/flags.py +++ b/src/crawler/parsers/flags.py @@ -65,6 +65,18 @@ r"\s*$" ) +# GNU single-dash long option token: +# -print-file-name= +# -Wa, +# -Xassembler +# -dumpmachine +FLAG_GNU_SINGLE_DASH_TOKEN_RE = re.compile( + r"^-" + r"(?P[A-Za-z][A-Za-z0-9-]*)" + r"(?:,(?P[^\s]+))?" + r"(?:=(?P[^\s]*))?$" +) + # --- Extraction patterns for metadata within descriptions --- CHOICES_BRACKET_RE = re.compile(r"\[possible values?:\s*([^\]]+)\]", re.IGNORECASE) @@ -224,6 +236,10 @@ def _try_parse_flag(line: str) -> Flag | None: confidence=0.90 if m.group(1) else 0.85, ) + gnu_flag = _try_parse_gnu_single_dash(line) + if gnu_flag: + return gnu_flag + # Try short-only m = FLAG_SHORT_ONLY_RE.match(line) if m: @@ -249,6 +265,96 @@ def _try_parse_flag(line: str) -> Flag | None: return None +def _try_parse_gnu_single_dash(line: str) -> Flag | None: + """Parse GNU-style single-dash long options.""" + stripped = line.strip() + if not stripped or stripped.startswith("--") or not stripped.startswith("-"): + return None + + parts = re.split(r"\s{2,}", stripped, maxsplit=1) + if len(parts) != 2: + return None + + spec, description = parts[0].strip(), parts[1].strip() + if not spec or not description: + return None + + tokens = spec.split() + option_token = tokens[0] + m = FLAG_GNU_SINGLE_DASH_TOKEN_RE.match(option_token) + if not m: + return None + + name_core = m.group("name") + comma_value = m.group("comma") + equals_value = m.group("equals") + value_hint: str | None = None + + if equals_value is not None: + value_hint = equals_value or "value" + elif comma_value: + value_hint = comma_value + elif len(tokens) > 1 and _looks_like_gnu_value_token(tokens[1]): + value_hint = tokens[1] + elif len(tokens) > 1: + # Some GNU help pages use lowercase metavars (e.g., `-L dir`, `-l library`). + candidate = tokens[1].strip().strip(",;:") + if candidate and not candidate.startswith("-"): + value_hint = candidate + + # Keep one-letter bare flags in the dedicated short-only parser path. + if len(name_core) == 1 and value_hint is None: + return None + + canonical = f"-{name_core}" + short_name = canonical if len(name_core) == 1 else None + + return _build_flag( + short_name=short_name, + long=canonical, + value=value_hint, + description=description, + confidence=0.82 if value_hint else 0.78, + force_bool=value_hint is None, + ) + + +def _looks_like_gnu_value_token(token: str) -> bool: + """Return True when token is a likely metavar/value token.""" + value = token.strip().strip(",;:") + if not value: + return False + + if value.startswith("<") or value.startswith("["): + return True + + if "|" in value: + return True + + lowered = value.lower() + value_keywords = { + "arg", + "args", + "byte-size", + "class", + "directory", + "file", + "language", + "lib", + "number", + "path", + "prog", + "standard", + "string", + "targets", + "value", + } + if lowered in value_keywords: + return True + + return value.isupper() and len(value) > 1 + + def _build_flag( short_name: str | None, long: str, diff --git a/src/crawler/parsers/manpage.py b/src/crawler/parsers/manpage.py index 1fd7833..0e37978 100644 --- a/src/crawler/parsers/manpage.py +++ b/src/crawler/parsers/manpage.py @@ -31,6 +31,25 @@ # Synopsis continuation SYNOPSIS_CMD_RE = re.compile(r"^\s+(\S.+)$") +EXAMPLE_COMMAND_RE = re.compile( + r"^(?:[$#]\s*)?" + r"(?:[a-z0-9][\w.-]*|\.\/\S+|/\S+)" + r"(?:\s+.+)?$" +) +EXAMPLE_BULLET_PREFIX_RE = re.compile(r"^(?:[-*]|\d+[.)])\s+") +EXAMPLE_PROSE_STARTERS = { + "for", + "see", + "note", + "notes", + "tip", + "tips", + "example", + "examples", + "this", + "that", + "the", +} def is_manpage(text: str) -> bool: @@ -303,13 +322,51 @@ def _parse_manpage_envvars(content: str) -> list[EnvVar]: def _parse_manpage_examples(content: str) -> list[str]: """Parse EXAMPLES section of man page.""" examples: list[str] = [] - for line in content.splitlines(): + seen: set[str] = set() + lines = content.splitlines() + + candidate_indents = [ + len(line) - len(line.lstrip()) + for line in lines + if line.strip() and not line.lstrip().startswith((".", "'")) + ] + min_indent = min(candidate_indents) if candidate_indents else 0 + + for line in lines: stripped = line.strip() - if stripped and not stripped.startswith(".") and not stripped.startswith("'"): - indent = len(line) - len(line.lstrip()) - if indent >= 8 and re.match(r"^[\w$./]", stripped): - # Remove leading $ if present - if stripped.startswith("$ "): - stripped = stripped[2:] - examples.append(stripped) + if not stripped or stripped.startswith(".") or stripped.startswith("'"): + continue + + indent = len(line) - len(line.lstrip()) + # In man pages, command examples are typically at the shallowest indent in EXAMPLES. + # If min indent is 0 (left-aligned prose present), do not apply this filter. + if min_indent > 0 and indent > min_indent + 1: + continue + + candidate = EXAMPLE_BULLET_PREFIX_RE.sub("", stripped).strip() + if candidate.startswith("$ ") or candidate.startswith("# "): + candidate = candidate[2:].strip() + + if not candidate: + continue + + first_word = candidate.split()[0].lower() + if first_word in EXAMPLE_PROSE_STARTERS: + continue + + # Description/prose lines usually end with punctuation and are more deeply indented. + if ( + indent > min_indent + and candidate.endswith(".") + and not stripped.startswith(("$", "#", "./", "/")) + ): + continue + + if not EXAMPLE_COMMAND_RE.match(stripped) and not EXAMPLE_COMMAND_RE.match(candidate): + continue + + if candidate not in seen: + seen.add(candidate) + examples.append(candidate) + return examples diff --git a/src/crawler/parsers/sections.py b/src/crawler/parsers/sections.py index a3c7f5b..9d48055 100644 --- a/src/crawler/parsers/sections.py +++ b/src/crawler/parsers/sections.py @@ -75,6 +75,17 @@ def _p(pattern: str, stype: SectionType) -> None: SectionType.COMMANDS, ) +# Sentence-case grouped command headings (pnpm-style) +_p( + r"^\s*(?:" + r"Manage\s+your\s+dependencies|" + r"Review\s+your\s+dependencies|" + r"Run\s+your\s+scripts|" + r"Other" + r")\s*:\s*$", + SectionType.COMMANDS, +) + # Flags / Options (generic, after global/inherited) _p( r"^\s*(?:" @@ -110,8 +121,9 @@ def _p(pattern: str, stype: SectionType) -> None: ] # Box-drawing format (rich-click) -_BOX_HEADER_RE = re.compile(r"^\s*\+-\s*(.+?)\s*-+\+\s*$") -_BOX_BORDER_RE = re.compile(r"^\s*\+[-─]+\+\s*$") +# Supports both ASCII (+-|) and Unicode (╭─╮│╰) variants. +_BOX_HEADER_RE = re.compile(r"^\s*(?:\+-|╭[─-])\s*(.+?)\s*(?:-+\+|[─-]*╮)\s*$") +_BOX_BORDER_RE = re.compile(r"^\s*(?:\+[-─]+\+|╭[─-]+╮|╰[─-]+╯)\s*$") def detect_section_type(line: str) -> SectionType | None: @@ -251,7 +263,7 @@ def _strip_rich_boxes(text: str) -> str: Also merges multi-line continuations within boxes (lines with heavy indentation that continue the previous line's description). """ - if "+-" not in text: + if all(token not in text for token in ("+-", "╭", "│", "╰")): return text lines = text.splitlines() @@ -274,8 +286,11 @@ def _strip_rich_boxes(text: str) -> str: has_boxes = True continue - # Box content: | content | - if stripped.startswith("|") and stripped.endswith("|") and len(stripped) > 2: + # Box content: | content | or │ content │ + if ( + (stripped.startswith("|") and stripped.endswith("|")) + or (stripped.startswith("│") and stripped.endswith("│")) + ) and len(stripped) > 2: inner = stripped[1:-1] inner_stripped = inner.strip() if inner_stripped: diff --git a/src/crawler/parsers/usage.py b/src/crawler/parsers/usage.py index 7cfff8e..9e41e35 100644 --- a/src/crawler/parsers/usage.py +++ b/src/crawler/parsers/usage.py @@ -4,6 +4,8 @@ import re +from ..models import Flag + # Usage line patterns USAGE_PREFIX_RE = re.compile( r"^\s*(?:usage|Usage|USAGE)\s*:?\s*(.+)$", @@ -11,6 +13,11 @@ ) USAGE_CONTINUATION_RE = re.compile(r"^\s{4,}\S") +OPTION_LINE_START_RE = re.compile(r"^\s*-(?:-|[A-Za-z0-9])") +LONG_OPTION_RE = re.compile(r"--[\w][\w-]*") +DESC_SPLIT_RE = re.compile(r"\s{2,}|\s:\s|\t+") +DESCRIPTION_CONTINUATION_RE = re.compile(r"^\s+\S") +PLACEHOLDER_DESC_SPLIT_RE = re.compile(r"^(?P.+?[>\]])\s+(?P.+)$") def parse_usage(content: str) -> str: @@ -46,3 +53,305 @@ def extract_usage_from_text(text: str, cli_name: str) -> str: if usage: return usage return "" + + +def extract_usage_line_options(text: str) -> list[Flag]: + """Extract options from sectionless help formats (e.g., python3-style).""" + flags: list[Flag] = [] + lines = text.splitlines() + + for idx, line in enumerate(lines): + if not OPTION_LINE_START_RE.match(line): + continue + + stripped = line.strip() + if not stripped: + continue + + spec, desc = _split_option_spec_and_description(stripped) + if not desc: + desc = _extract_continuation_description(lines, idx) + + tokens = _extract_option_tokens(spec) + if not tokens: + continue + + long_tokens = [t for t in tokens if t.startswith("--")] + short_tokens = [t for t in tokens if t.startswith("-") and not t.startswith("--")] + primary_long = long_tokens[0] if long_tokens else None + primary_short = short_tokens[0] if short_tokens else None + primary_name = primary_long or primary_short + if not primary_name: + continue + + value_hint = _extract_value_hint(spec, primary_name) + choices = _extract_choices_from_hint(value_hint) + flag_type = "string" if value_hint else "bool" + + flags.append( + Flag( + name=primary_name, + long_name=primary_long, + short_name=primary_short, + type=flag_type, + choices=choices, + description=desc, + confidence=0.55, + ) + ) + + return flags + + +def _extract_value_hint(spec: str, primary_name: str) -> str | None: + """Extract value hint from option spec fragment.""" + # Attached value encoded in primary token itself (e.g., -Xlint:all, -DNAME=VALUE, -O2). + compact = primary_name[1:] if primary_name.startswith("-") else primary_name + if ":" in compact: + suffix = compact.split(":", 1)[1].strip() + if suffix: + return suffix + if "=" in compact: + suffix = compact.split("=", 1)[1].strip() + if suffix: + return suffix + if len(compact) > 1 and compact[0].isalnum() and compact[1:].isdigit(): + return compact[1:] + + # Explicit placeholders. + placeholder_match = re.search(r"<([^>]+)>|\[([^\]]+)\]", spec) + if placeholder_match: + return (placeholder_match.group(1) or placeholder_match.group(2) or "").strip() + + # Pattern: `-c cmd`, `--flag value` + remainder = re.sub( + rf"^.*?{re.escape(primary_name)}\s*", + "", + spec, + count=1, + ).strip() + if remainder.startswith(":"): + remainder = remainder[1:].strip() + if not remainder: + return None + + token = remainder.split()[0].strip(":;,") + if not token or token.startswith("-"): + return None + if not re.search(r"[A-Za-z0-9_<\[]", token): + return None + return token + + +def _extract_choices_from_hint(value_hint: str | None) -> list[str] | None: + """Extract choice list from value hint token.""" + if not value_hint or "|" not in value_hint: + return None + values = [p.strip("[]<>(),:") for p in value_hint.split("|")] + values = [v for v in values if v] + return values or None + + +def _normalize_option_description(raw: str) -> str: + """Normalize fallback option descriptions from sectionless help lines.""" + desc = (raw or "").strip() + if not desc: + return "" + + # Remove noisy leading separators often seen in python-style help. + desc = re.sub(r"^[\s:;\-–—]+", "", desc).strip() + desc = re.sub(r"\s+", " ", desc) + return desc + + +def _split_option_spec_and_description(stripped: str) -> tuple[str, str]: + """Split a sectionless option line into spec and description.""" + parts = DESC_SPLIT_RE.split(stripped, maxsplit=1) + if len(parts) > 1: + spec, desc = _rebalance_alias_split(parts[0].strip(), parts[1].strip()) + return spec, _normalize_option_description(desc) + + spec, desc, matched = _split_colon_description_outside_placeholders(stripped) + if matched: + return spec, _normalize_option_description(desc) + + m = PLACEHOLDER_DESC_SPLIT_RE.match(stripped) + if m: + spec = m.group("spec").strip() + if _extract_option_tokens(spec): + return spec, _normalize_option_description(m.group("desc")) + + return stripped, "" + + +def _extract_continuation_description(lines: list[str], option_index: int) -> str: + """Extract description from continuation lines below an option row.""" + descriptions: list[str] = [] + + for line in lines[option_index + 1 :]: + stripped = line.strip() + if not stripped: + break + if OPTION_LINE_START_RE.match(line): + break + if not DESCRIPTION_CONTINUATION_RE.match(line): + break + + normalized = _normalize_option_description(stripped) + if normalized: + descriptions.append(normalized) + + return " ".join(descriptions).strip() + + +def _split_colon_description_outside_placeholders( + stripped: str, +) -> tuple[str, str, bool]: + """Split `spec: description` while ignoring colons inside `<...>` or `[...]`.""" + angle_depth = 0 + square_depth = 0 + + for idx, ch in enumerate(stripped): + if ch == "<": + angle_depth += 1 + elif ch == ">" and angle_depth > 0: + angle_depth -= 1 + elif ch == "[": + square_depth += 1 + elif ch == "]" and square_depth > 0: + square_depth -= 1 + elif ch == ":" and angle_depth == 0 and square_depth == 0: + next_char = stripped[idx + 1 : idx + 2] + if next_char and not next_char.isspace(): + continue + spec = stripped[:idx].strip() + if not spec or not _extract_option_tokens(spec): + continue + desc = stripped[idx + 1 :].strip() + return spec, desc, True + + return "", "", False + + +def _rebalance_alias_split(spec: str, desc: str) -> tuple[str, str]: + """Rebalance early column split for `-x, --long ...` sectionless lines.""" + existing_tokens = _extract_option_tokens(spec) + if any(token.startswith("--") for token in existing_tokens): + return spec, desc + + short_tokens = [t for t in existing_tokens if t.startswith("-") and not t.startswith("--")] + if not short_tokens: + return spec, desc + + candidate = desc.lstrip() + if not candidate.startswith("--"): + return spec, desc + + tokens = candidate.split() + if not tokens or not LONG_OPTION_RE.match(tokens[0]): + return spec, desc + + consumed = 1 + if len(tokens) > 1 and _looks_like_option_value_token(tokens[1]): + consumed += 1 + + merged_spec = " ".join([spec, *tokens[:consumed]]).strip() + merged_desc = " ".join(tokens[consumed:]).strip() + if not merged_desc: + merged_desc = desc + + return merged_spec, merged_desc + + +def _extract_option_tokens(spec: str) -> list[str]: + """Extract deterministic option tokens from sectionless option specs.""" + tokens: list[str] = [] + + for match in LONG_OPTION_RE.finditer(spec): + tokens.append(match.group(0)) + + for fragment in re.split(r"[,\s]+", spec): + short_token = _normalize_short_option_fragment(fragment) + if short_token: + tokens.append(short_token) + + # Stable de-duplication while preserving first-seen order. + seen: set[str] = set() + result: list[str] = [] + for token in tokens: + if token in seen: + continue + seen.add(token) + result.append(token) + return result + + +def _normalize_short_option_fragment(fragment: str) -> str | None: + """Normalize short-option fragments, including attached and combined forms.""" + token = (fragment or "").strip().strip(",;") + if not token or token.startswith("--") or not token.startswith("-"): + return None + + body = token[1:] + if not body or not body[0].isalnum(): + return None + + if len(body) == 1: + return f"-{body}" + + # Lowercase alpha clusters are treated as combined short shorthand (e.g., -abc). + if body.isalpha() and body.islower() and len(body) <= 3: + return f"-{body}" + + # Keep multi-letter option names intact to avoid collisions (-help, -classpath). + if body.isalpha() and len(body) > 1: + return f"-{body}" + + # Attached value forms map to canonical short (e.g., -O2 -> -O, -DNAME=VALUE -> -D). + if "=" in body: + return f"-{body[0]}" + if len(body) > 1 and body[1:].isdigit(): + return f"-{body[0]}" + + # Extended forms with non-alpha payload keep full token for deterministic behavior. + if ":" in body: + return f"-{body}" + + # Fallback to canonical short. + return f"-{body[0]}" + + +def _looks_like_option_value_token(token: str) -> bool: + """Detect value-hint tokens that are part of an option spec.""" + if not token: + return False + + if token.startswith("<") or token.startswith("["): + return True + + if "|" in token: + return True + + value_keywords = { + "arg", + "args", + "bool", + "boolean", + "category", + "count", + "file", + "float", + "int", + "integer", + "name", + "number", + "path", + "string", + "url", + "value", + } + lowered = token.lower().strip(":,;") + if lowered in value_keywords: + return True + + return token.isupper() and len(token) > 1 diff --git a/src/crawler/pipeline.py b/src/crawler/pipeline.py index 2a9b8aa..1b35863 100644 --- a/src/crawler/pipeline.py +++ b/src/crawler/pipeline.py @@ -19,6 +19,21 @@ logger = logging.getLogger("cli_crawler") +def _resolve_output_path(output: str | Path, cli_name: str) -> Path: + """Resolve CLI output target. + + Accepts either: + - a directory path (legacy behavior) -> /.json + - a JSON file path -> exact file path + """ + output_path = Path(output) + if output_path.exists() and output_path.is_dir(): + return output_path / f"{cli_name}.json" + if output_path.suffix.lower() == ".json": + return output_path + return output_path / f"{cli_name}.json" + + def crawl_cli( cli_name: str, config: CLIConfig, @@ -39,16 +54,38 @@ def crawl_cli( # 3. Detect help pattern detection = detect_help_pattern(cli_name, executor, config) + help_error = "" + if detection.pattern == "auth_required": + help_error = detection.result.stderr.strip() or ( + "AUTH_REQUIRED: Help command requires authentication." + ) + logger.error(help_error) + if strict: + raise RuntimeError(help_error) + if detection.pattern == "unknown": - logger.error("No help output found for %s", cli_name) + logger.warning("No standard help output for %s; running in degraded mode.", cli_name) if strict: raise RuntimeError(f"No help output found for {cli_name}") logger.info("Help pattern: %s (manpage=%s)", detection.pattern, detection.is_manpage) + root_help = "" if detection.pattern == "auth_required" else detection.result.stdout + parse_input, progressive_loading, raw_line_count, parsed_line_count = _apply_progressive_loading( + root_help, + config.raw_threshold, + ) + progressive_warning = "" + if progressive_loading: + progressive_warning = ( + "Progressive loading enabled for " + f"{cli_name}: parsed {parsed_line_count}/{raw_line_count} help lines." + ) + logger.warning(progressive_warning) + # 4. Parse root help parse_result = parse_help_output( - detection.result.stdout, + parse_input, cli_name, cli_name, force_manpage=detection.is_manpage, @@ -56,13 +93,19 @@ def crawl_cli( # 5. Build initial state state = CrawlState(visited={cli_name}) - state.raw_outputs[cli_name] = detection.result.stdout - state.warnings.extend(parse_result.warnings) + state.set_raw_output(cli_name, root_help) + state.extend_warnings(parse_result.warnings) + if detection.pattern == "unknown": + state.add_warning(f"No standard help output for {cli_name}; using partial CLIMap.") + if help_error: + state.add_warning(help_error) + if progressive_warning: + state.add_warning(progressive_warning) # 6. Separate global flags from local flags global_flags, local_flags = _separate_global_flags( parse_result.command.flags, - detection.result.stdout, + parse_input, ) # 7. Discover plugins if configured @@ -97,6 +140,14 @@ def crawl_cli( # 9. Compute meta duration = time.monotonic() - start meta = _compute_meta(subtree, duration, state, global_flags) + meta["confidence_score"] = ( + f"{_compute_confidence_score(parse_result.command.confidence, detection.pattern, state, progressive_loading):.2f}" + ) + meta["progressive_loading"] = "true" if progressive_loading else "false" + meta["raw_line_count"] = str(raw_line_count) + meta["parsed_line_count"] = str(parsed_line_count) + if help_error: + meta["help_error"] = help_error # 10. Assemble CLIMap # 10. Assemble CLIMap @@ -157,6 +208,48 @@ def crawl_all( return results +def _apply_progressive_loading( + raw_text: str, + threshold_lines: int, +) -> tuple[str, bool, int, int]: + """Return parse input with optional line-based truncation for very long help.""" + if not raw_text: + return "", False, 0, 0 + + lines = raw_text.splitlines() + raw_line_count = len(lines) + if threshold_lines <= 0 or raw_line_count <= threshold_lines: + return raw_text, False, raw_line_count, raw_line_count + + truncated = "\n".join(lines[:threshold_lines]).rstrip() + truncated = ( + f"{truncated}\n\n[... truncated by cli-crawler after {threshold_lines} lines " + f"(original: {raw_line_count}) ...]" + ) + return truncated, True, raw_line_count, threshold_lines + + +def _compute_confidence_score( + base_confidence: float, + help_pattern: str, + state: CrawlState, + progressive_loading: bool, +) -> float: + """Compute a crawl-level confidence score with degradation penalties.""" + score = max(0.0, min(base_confidence, 1.0)) + + if help_pattern == "auth_required": + score = min(score, 0.25) + elif help_pattern == "unknown": + score = min(score, 0.35) + + score -= min(0.25, len(state.warnings) * 0.05) + if progressive_loading: + score -= 0.05 + + return max(0.0, min(score, 1.0)) + + def _separate_global_flags( all_flags: list[Flag], raw_text: str, @@ -243,7 +336,10 @@ def main() -> None: "-o", "--output", default="output", - help="Output directory for CLIMap JSON (default: output/)", + help=( + "Output path for CLIMap JSON. Accepts either a directory " + "(default: output/) or a .json file path." + ), ) parser.add_argument( "--raw", @@ -278,9 +374,8 @@ def main() -> None: config = CrawlerConfig() cli_config = config.clis.get(args.cli_name, CLIConfig(name=args.cli_name)) - output_dir = Path(args.output) - output_dir.mkdir(parents=True, exist_ok=True) - output_path = output_dir / f"{args.cli_name}.json" + output_path = _resolve_output_path(args.output, args.cli_name) + output_path.parent.mkdir(parents=True, exist_ok=True) cli_map = crawl_cli( args.cli_name, diff --git a/src/crawler/version.py b/src/crawler/version.py index 082bfae..b2ce47f 100644 --- a/src/crawler/version.py +++ b/src/crawler/version.py @@ -5,58 +5,290 @@ import logging import re +from lib.cli_identity import canonical_cli_name + from .executor import Executor logger = logging.getLogger("cli_crawler.version") -VERSION_PATTERNS = [ - # "git version 2.43.0" - re.compile(r"version\s+v?(\d+\.\d+(?:\.\d+)?(?:[-.\w]*))"), - # "pip 24.0 from ..." or "langchain-cli 0.0.37" - re.compile(r"^\w[\w-]*\s+(\d+\.\d+(?:\.\d+)?)(?:\s|$)"), - # "npm@11.8.0" or "@11.8.0" - re.compile(r"@(\d+\.\d+\.\d+)"), - # "v3.1.0-alpha.3" (standalone) - re.compile(r"\bv(\d+\.\d+\.\d+[-.\w]*)"), - # "2.43.0" on its own line - re.compile(r"^(\d+\.\d+(?:\.\d+)?[-.\w]*)\s*$"), -] +VERSION_TOKEN = r"\d+\.\d+(?:\.\d+)?(?:[-.\w]*)" +VERSION_KEYWORD_RE = re.compile(rf"version\s+v?({VERSION_TOKEN})", re.IGNORECASE) +LEADING_NAME_RE = re.compile(rf"^([A-Za-z][\w.+-]*)\s+({VERSION_TOKEN})(?:\s|$)") +AT_VERSION_RE = re.compile(rf"@({VERSION_TOKEN})") +HYPHENATED_NAME_RE = re.compile(rf"\b([A-Za-z][\w.+-]*)-({VERSION_TOKEN})\b") +STANDALONE_V_RE = re.compile(rf"\bv({VERSION_TOKEN})\b") +BARE_VERSION_RE = re.compile(rf"^({VERSION_TOKEN})\s*$") VERSION_COMMANDS = ["--version", "-V", "version", "-v"] def detect_version(cli_name: str, executor: Executor) -> str: """Try version commands, parse version string.""" + placeholder_version = "" for vcmd in VERSION_COMMANDS: cmd = [cli_name, vcmd] if vcmd != "version" else [cli_name, vcmd] result = executor.run(cmd, timeout=3) if result.exit_code == 0 or result.stdout.strip(): text = result.stdout.strip() - version = _parse_version(text) + version = _parse_version(text, cli_name=cli_name) if version: - logger.debug("Version for %s: %s (via %s)", cli_name, version, vcmd) - return version + if _is_placeholder_version(version): + placeholder_version = placeholder_version or version + else: + logger.debug("Version for %s: %s (via %s)", cli_name, version, vcmd) + return version # Also check stderr (some CLIs output version there) if result.stderr.strip() and not result.stderr.startswith("TIMEOUT"): - version = _parse_version(result.stderr.strip()) + version = _parse_version(result.stderr.strip(), cli_name=cli_name) if version: - return version + if _is_placeholder_version(version): + placeholder_version = placeholder_version or version + else: + return version + + if placeholder_version: + logger.warning( + "Detected placeholder version %s for %s; returning unknown version instead", + placeholder_version, + cli_name, + ) + return "" logger.warning("Could not detect version for %s", cli_name) return "" -def _parse_version(text: str) -> str: - """Extract version string from text.""" - # Try each line - for line in text.splitlines(): - stripped = line.strip() - if not stripped: +def _is_placeholder_version(version: str) -> bool: + """Detect non-informative placeholder versions.""" + normalized = version.strip().lower() + if not normalized: + return True + + # "0", "0.0", "0.0.0", "0.0.0-dev", "0.0.0+unknown" + if re.fullmatch(r"0(?:\.0+)*(?:[-+][\w.-]+)?", normalized): + return True + + if normalized in {"unknown", "dev", "development", "snapshot", "nightly"}: + return True + + return False + + +def _parse_version(text: str, cli_name: str | None = None) -> str: + """Extract best version candidate from text. + + Preference order: + 1. Candidates strongly attributable to the target CLI. + 2. Generic version candidates. + 3. Dependency-style `-` fallback. + """ + candidates = _collect_version_candidates(text, cli_name) + if not candidates: + return "" + + placeholder_candidate = "" + for _score, version in sorted(candidates, key=lambda item: item[0], reverse=True): + if _is_placeholder_version(version): + placeholder_candidate = placeholder_candidate or version continue - for pattern in VERSION_PATTERNS: - m = pattern.search(stripped) - if m: - return m.group(1) - return "" + return version + + return placeholder_candidate + + +def _collect_version_candidates(text: str, cli_name: str | None) -> list[tuple[int, str]]: + """Collect scored version candidates from text lines.""" + scored: dict[str, int] = {} + mention_re = _build_cli_mention_re(cli_name) + + for raw_line in text.splitlines(): + line = raw_line.strip() + if not line: + continue + + line_mentions_cli = bool(mention_re and mention_re.search(line)) + has_structural_cli_version = _line_has_structural_cli_version(line, cli_name) + has_foreign_version_subject = _line_has_foreign_version_subject(line, cli_name) + has_foreign_subject = _line_has_foreign_subject(line, cli_name) + + if not has_foreign_version_subject: + keyword_score = 220 + if line_mentions_cli: + keyword_score = 300 + if has_structural_cli_version: + keyword_score = 380 + _add_scored_match( + scored, + VERSION_KEYWORD_RE.search(line), + score_if_match=keyword_score, + group_index=1, + ) + + m = LEADING_NAME_RE.search(line) + if m: + source_name = m.group(1) + if cli_name and not _is_cli_name_match(source_name, cli_name): + continue + score = 360 if _is_cli_name_match(source_name, cli_name) else 200 + _add_scored_value(scored, m.group(2), score) + + if not (has_foreign_subject and not line_mentions_cli and not has_structural_cli_version): + for m in AT_VERSION_RE.finditer(line): + score = 180 + if line_mentions_cli: + score = 280 + if has_structural_cli_version: + score = 340 + _add_scored_value(scored, m.group(1), score) + + if not (has_foreign_subject and not line_mentions_cli and not has_structural_cli_version): + for m in STANDALONE_V_RE.finditer(line): + score = 170 + if line_mentions_cli: + score = 250 + if has_structural_cli_version: + score = 360 + _add_scored_value(scored, m.group(1), score) + + _add_scored_match( + scored, + BARE_VERSION_RE.search(line), + score_if_match=160, + group_index=1, + ) + + for m in HYPHENATED_NAME_RE.finditer(line): + source_name = m.group(1) + score = 220 if _is_cli_name_match(source_name, cli_name) else 20 + _add_scored_value(scored, m.group(2), score) + + return [(score, version) for version, score in scored.items()] + + +def _add_scored_match( + scored: dict[str, int], + match: re.Match[str] | None, + score_if_match: int, + group_index: int, +) -> None: + """Add regex match group to scored candidates map.""" + if not match: + return + _add_scored_value(scored, match.group(group_index), score_if_match) + + +def _add_scored_value(scored: dict[str, int], version: str, score: int) -> None: + """Keep max score for each candidate version.""" + if not version: + return + current = scored.get(version) + if current is None or score > current: + scored[version] = score + + +def _build_cli_mention_re(cli_name: str | None) -> re.Pattern[str] | None: + """Build regex that detects explicit CLI mentions in a line.""" + aliases = _cli_name_aliases(cli_name) + if not aliases: + return None + escaped_aliases = sorted((re.escape(alias) for alias in aliases), key=len, reverse=True) + joined = "|".join(escaped_aliases) + return re.compile(rf"(? bool: + """Check if a line structurally looks like ` version ...`.""" + for alias in _cli_name_aliases(cli_name): + escaped = re.escape(alias) + pattern = re.compile( + rf"^\s*{escaped}(?![A-Za-z0-9_.+-])(?:\s+version\b|\s+v?\d|[@:/-]v?\d)", + re.IGNORECASE, + ) + if pattern.search(line): + return True + return False + + +def _cli_name_aliases(cli_name: str | None) -> tuple[str, ...]: + """Return candidate aliases for a CLI identity (`git.exe`, `git`, path leaf).""" + if not cli_name: + return () + + raw = (cli_name or "").strip().strip('"').strip("'") + if not raw: + return () + + leaf = re.split(r"[\\/]", raw)[-1] or raw + canonical = canonical_cli_name(raw) + + aliases: list[str] = [] + seen: set[str] = set() + for candidate in (raw, leaf, canonical): + normalized = candidate.strip().lower() + if not normalized or normalized in seen: + continue + aliases.append(candidate.strip()) + seen.add(normalized) + return tuple(aliases) + + +def _line_has_foreign_version_subject(line: str, cli_name: str | None) -> bool: + """Detect ` version ...` subjects that should not boost confidence.""" + if not cli_name: + return False + m = re.search(r"^\s*([A-Za-z][\w.+-]*)\s+version\b", line, re.IGNORECASE) + if not m: + return False + return not _is_cli_name_match(m.group(1), cli_name) + + +def _line_has_foreign_subject(line: str, cli_name: str | None) -> bool: + """Detect a foreign leading subject token in a line.""" + if not cli_name: + return False + m = re.search(r"^\s*([A-Za-z][\w.+-]*)", line) + if not m: + return False + subject = m.group(1) + if subject.lower() in {"version", "v"}: + return False + return not _is_cli_name_match(subject, cli_name) + + +def _is_cli_name_match(source_name: str, cli_name: str | None) -> bool: + """Check whether parsed source name likely refers to the target CLI.""" + if not cli_name: + return False + + source_norm = _normalize_cli_token(source_name) + cli_norm = _normalize_cli_token(cli_name) + if not source_norm or not cli_norm: + return False + + if source_norm == cli_norm: + return True + + # Numeric variants (python/python3, python3/python312). + if source_norm.startswith(cli_norm) and source_norm[len(cli_norm) :].isdigit(): + return True + if cli_norm.startswith(source_norm) and cli_norm[len(source_norm) :].isdigit(): + return True + + source_lower = source_name.lower() + cli_lower = canonical_cli_name(cli_name) or cli_name.lower() + cli_alias_suffixes = {"cli", "command", "tool"} + for sep in ("-", "_", "."): + if source_lower.startswith(f"{cli_lower}{sep}"): + suffix = source_lower[len(cli_lower) + 1 :] + if suffix in cli_alias_suffixes: + return True + + return False + + +def _normalize_cli_token(value: str) -> str: + """Normalize CLI token for loose comparisons.""" + canonical = canonical_cli_name(value) + return re.sub(r"[^a-z0-9]+", "", canonical) diff --git a/src/generator/plugin_generator.py b/src/generator/plugin_generator.py index c1c795d..19763eb 100644 --- a/src/generator/plugin_generator.py +++ b/src/generator/plugin_generator.py @@ -12,12 +12,16 @@ import argparse import json import logging +import os import re import textwrap +from collections import Counter from collections.abc import Iterator from dataclasses import dataclass, field from pathlib import Path +from lib.cli_identity import plugin_slug + log = logging.getLogger(__name__) # --------------------------------------------------------------------------- @@ -37,6 +41,7 @@ class Stats: top_commands: list[str] = field(default_factory=list) version: str = "" cli_name: str = "" + cli_slug: str = "" # --------------------------------------------------------------------------- @@ -120,6 +125,7 @@ def compute_stats(cli_map: dict) -> Stats: top_commands=top_cmds, version=cli_map.get("cli_version", "unknown"), cli_name=cli_map["cli_name"], + cli_slug=plugin_slug(cli_map["cli_name"]), ) @@ -209,140 +215,344 @@ def _clean_description(desc: str) -> str: return desc.strip() or desc +KEYWORD_STOPWORDS = { + "the", + "and", + "for", + "with", + "from", + "that", + "this", + "these", + "those", + "into", + "over", + "under", + "through", + "using", + "used", + "use", + "tool", + "tools", + "command", + "commands", + "subcommand", + "subcommands", + "option", + "options", + "flag", + "flags", + "manage", + "manages", + "management", + "execute", + "executing", + "run", + "runs", + "running", + "show", + "shows", + "list", + "lists", + "help", + "cli", +} + +COMPACT_SKILL_TOKEN_BUDGET = 800 +COMPACT_SKILL_MAX_EXAMPLES = 5 + + +def generate_semantic_keywords(cli_map: dict, max_keywords: int = 8) -> list[str]: + """Generate semantic keywords from CLI name, command groups, and domain terms.""" + cli_name = str(cli_map.get("cli_name", "")).strip().lower() + if not cli_name: + return [] + + keywords: list[str] = [cli_name] + seen: set[str] = {cli_name} + cli_parts = {part for part in re.split(r"[-_]", cli_name) if part} + + commands = cli_map.get("commands", {}) + + # Prioritize command groups (nodes with subcommands) by breadth. + command_groups: list[tuple[int, str]] = [] + for name, data in commands.items(): + subcommands = data.get("subcommands", {}) + if subcommands: + command_groups.append((len(subcommands), name.lower())) + for _size, group_name in sorted(command_groups, key=lambda item: (-item[0], item[1])): + normalized = group_name.strip() + if ( + not normalized + or normalized in seen + or normalized in KEYWORD_STOPWORDS + or normalized in cli_parts + ): + continue + keywords.append(normalized) + seen.add(normalized) + if len(keywords) >= max_keywords: + return keywords[:max_keywords] + + # Then mine domain terms from full descriptions (not only first words). + frequencies: Counter[str] = Counter() + for _name, data, depth in walk_tree(commands): + description = _clean_description(data.get("description", "")).lower() + if not description: + continue + weight = 2 if depth == 0 else 1 + for token in re.findall(r"[a-z][a-z0-9-]{2,}", description): + normalized = token.strip("-") + if ( + not normalized + or normalized in seen + or normalized in KEYWORD_STOPWORDS + or normalized in cli_parts + ): + continue + frequencies[normalized] += weight + + for token, _count in frequencies.most_common(): + keywords.append(token) + seen.add(token) + if len(keywords) >= max_keywords: + break + + return keywords[:max_keywords] + + +def _resolve_author(author: str | None) -> str | None: + """Resolve author from explicit arg or environment variable.""" + if author is not None and author.strip(): + return author.strip() + env_author = os.getenv("CLI_PLUGINS_AUTHOR", "").strip() + return env_author or None + + +def _build_author_metadata(author: str) -> dict[str, str]: + """Create plugin author metadata from a string.""" + match = re.match(r"^\s*([^<]+?)\s*<([^>]+)>\s*$", author) + if match: + name, email = match.groups() + return {"name": name.strip(), "email": email.strip()} + return {"name": author} + + +def _approx_token_count(text: str) -> int: + """Approximate token count for compactness checks.""" + return len(re.findall(r"\w+|[^\w\s]", text)) + + +def _escape_triple_backticks(text: str) -> str: + """Prevent accidental markdown fence breaks in generated docs.""" + return text.replace("```", r"\`\`\`") + + +def _example_command_from_usage(path: str, usage: str, cli_name: str) -> str: + """Build a safe command example from usage/path without hallucinating.""" + normalized_usage = re.sub(r"\s+", " ", usage.strip()) + if normalized_usage: + normalized_usage = re.sub(r"(?i)^usage:\s*", "", normalized_usage).strip() + if normalized_usage.startswith(cli_name) and len(normalized_usage) <= 160: + return normalized_usage + return path.strip() + + +def _collect_document_examples( + cli_map: dict, + *, + limit: int | None = None, +) -> tuple[list[tuple[str, int, str, str]], bool]: + """Collect examples for docs. + + Returns: + (entries, has_explicit_examples) + entries = list of (path, depth, command_example, description) + """ + cli = cli_map["cli_name"] + entries: list[tuple[str, int, str, str]] = [] + seen: set[str] = set() + + # Prefer explicit examples extracted from help. + for name, data, depth in walk_tree(cli_map["commands"]): + path = data.get("path", f"{cli} {name}") + raw = data.get("examples", []) + for i in range(0, len(raw) - 1, 2): + cmd, desc = raw[i], raw[i + 1] + key = cmd.strip() + if not key or key in seen: + continue + seen.add(key) + entries.append((path, depth, key, _clean_description(desc))) + if limit is not None and len(entries) >= limit: + return entries, True + + if entries: + return entries, True + + # Fallback: synthesize examples from usage/path when explicit examples don't exist. + fallback_note = "Generated from command usage (CLI help has no explicit EXAMPLES section)." + for name, data, depth in walk_tree(cli_map["commands"]): + path = data.get("path", f"{cli} {name}") + usage = data.get("usage_pattern", "") + cmd = _example_command_from_usage(path, usage, cli) + key = cmd.strip() + if not key or key in seen: + continue + seen.add(key) + desc = _clean_description(data.get("description", "")) or fallback_note + entries.append((path, depth, key, desc)) + if limit is not None and len(entries) >= limit: + break + + return entries, False + + # --------------------------------------------------------------------------- # Template generators # --------------------------------------------------------------------------- -def generate_plugin_json(cli_map: dict, stats: Stats) -> str: +def generate_plugin_json(cli_map: dict, stats: Stats, author: str | None = None) -> str: """Generate .claude-plugin/plugin.json content.""" cli = stats.cli_name - # Extract keywords from top commands (first 5) + cli name - keywords = [cli] - # Add unique meaningful words from descriptions - seen: set[str] = {cli} - for data in cli_map["commands"].values(): - for word in data.get("description", "").lower().split(): - word = re.sub(r"[^a-z0-9-]", "", word) - if len(word) > 3 and word not in seen: - keywords.append(word) - seen.add(word) - if len(keywords) >= 6: - break - if len(keywords) >= 6: - break + cli_slug = stats.cli_slug + keywords = generate_semantic_keywords(cli_map) obj = { - "name": f"cli-{cli}", + "name": f"cli-{cli_slug}", "version": stats.version, "description": f"Command reference plugin for {cli} CLI", "keywords": keywords, - "author": { - "name": "Nuno Salvacao", - "email": "nuno.salvacao@gmail.com", - "url": "https://github.com/nsalvacao", - }, "repository": "https://github.com/nsalvacao/cli-plugins", "license": "MIT", } + resolved_author = _resolve_author(author) + if resolved_author: + obj["author"] = _build_author_metadata(resolved_author) return json.dumps(obj, indent=2) + "\n" def generate_skill_md(cli_map: dict, stats: Stats) -> str: - """Generate the main SKILL.md file.""" + """Generate the main SKILL.md file in compact progressive-disclosure form.""" cli = stats.cli_name - trigger = build_trigger_phrases(cli, cli_map) + cli_slug = stats.cli_slug + trigger = ( + f"This skill should be used when the user needs help with {cli} CLI commands, " + "flags, and troubleshooting." + ) + + # Compact top-level command summary (no long descriptions). + command_groups = sorted( + name for name, data in cli_map["commands"].items() if data.get("subcommands") + ) + leaf_commands = sorted( + name for name, data in cli_map["commands"].items() if not data.get("subcommands") + ) - # Build quick reference table -- all top-level commands - quick_ref_rows = ["| Command | Description |", "| --- | --- |"] - for name in stats.top_commands: - data = cli_map["commands"].get(name, {}) - desc = _clean_description(data.get("description", "")) - quick_ref_rows.append(f"| `{cli} {name}` | {desc} |") - quick_ref = "\n".join(quick_ref_rows) + command_examples: list[str] = [] + for preferred in ("agent", "swarm"): + if preferred in cli_map["commands"]: + command_examples.append(preferred) + for candidate in sorted(cli_map["commands"].keys()): + if candidate not in command_examples: + command_examples.append(candidate) + if len(command_examples) >= 3: + break # Global flags table gf_table = format_flags_table(cli_map.get("global_flags", [])) + if not gf_table: + gf_table = "_No global flags detected._" - # Command groups summary - grps = group_commands(cli_map["commands"]) - groups_section = "" - for label, cmds in grps.items(): - groups_section += f"\n### {label}\n\n" - groups_section += ", ".join(f"`{c}`" for c in cmds) + "\n" - - # Common usage examples (pick first 8 unique examples from tree) + # Compact usage examples (first 5 unique examples). examples_lines: list[str] = [] - seen_ex: set[str] = set() - for _, data, _ in walk_tree(cli_map["commands"]): - raw = data.get("examples", []) - for i in range(0, len(raw) - 1, 2): - cmd, desc = raw[i], raw[i + 1] - if cmd not in seen_ex: - seen_ex.add(cmd) - examples_lines.append(f"```bash\n{cmd}\n```\n{desc}\n") - if len(examples_lines) >= 8: - break - if len(examples_lines) >= 8: + doc_examples, _has_explicit_examples = _collect_document_examples( + cli_map, limit=COMPACT_SKILL_MAX_EXAMPLES + ) + for _path, _depth, cmd, desc in doc_examples: + safe_cmd = _escape_triple_backticks(cmd) + safe_desc = _escape_triple_backticks(desc) + examples_lines.append(f"```bash\n{safe_cmd}\n```\n{safe_desc}\n") + examples_block = "\n".join(examples_lines) if examples_lines else "_No examples extracted._" + + def _render_top_level_section(group_limit: int, leaf_limit: int) -> str: + lines: list[str] = [] + if command_groups: + shown_groups = command_groups[:group_limit] + lines.append("Command Groups:") + lines.append(", ".join(f"`{name}`" for name in shown_groups)) + if len(shown_groups) < len(command_groups): + lines.append( + f"... +{len(command_groups) - len(shown_groups)} more in `references/commands.md`" + ) + if leaf_commands: + shown_leaf = leaf_commands[:leaf_limit] + lines.append("Standalone Commands:") + lines.append(", ".join(f"`{name}`" for name in shown_leaf)) + if len(shown_leaf) < len(leaf_commands): + lines.append( + f"... +{len(leaf_commands) - len(shown_leaf)} more in `references/commands.md`" + ) + lines.append( + "Command format examples: " + + ", ".join(f"`{cli} {name}`" for name in command_examples) + ) + return "\n".join(lines) if lines else "_No commands._" + + def _render_skill_md(group_limit: int, leaf_limit: int) -> str: + top_commands_section = _render_top_level_section(group_limit, leaf_limit) + parts: list[str] = [] + parts.append(f"---\nname: cli-{cli_slug}\ndescription: >-\n {trigger}\n---\n") + parts.append(f"# {cli} CLI Reference\n") + parts.append(f"Compact command reference for **{cli}** v{stats.version}.\n") + parts.append( + f"- **{stats.total_commands}** total commands\n" + f"- **{stats.total_flags}** command flags + **{stats.global_flags}** global flags\n" + f"- **{stats.total_examples}** extracted usage examples\n" + f"- Max nesting depth: {stats.max_depth}\n" + ) + parts.append("## When to Use\n") + parts.append( + f"- Constructing or validating `{cli}` commands\n" + f"- Looking up flags/options fast\n" + f"- Troubleshooting failed invocations\n" + ) + parts.append("## Top-Level Commands\n") + parts.append(top_commands_section + "\n") + parts.append("### Global Flags\n") + parts.append(gf_table + "\n") + parts.append("## Common Usage Patterns (Compact)\n") + parts.append(examples_block) + parts.append( + "## Detailed References\n\n" + "- Full command tree: `references/commands.md`\n" + "- Full examples catalog: `references/examples.md`\n" + ) + parts.append("## Re-Scanning\n") + parts.append( + "After a CLI update, run `/scan-cli` or execute crawler + generator again.\n" + ) + return "\n".join(parts) + + group_limit = max(1, len(command_groups)) + leaf_limit = max(1, len(leaf_commands)) + skill_md = _render_skill_md(group_limit, leaf_limit) + + # Keep shrinking command lists until compact budget is met. + while _approx_token_count(skill_md) > COMPACT_SKILL_TOKEN_BUDGET: + changed = False + if leaf_limit > 8: + leaf_limit = max(8, leaf_limit - 8) + changed = True + elif group_limit > 4: + group_limit = max(4, group_limit - 2) + changed = True + if not changed: break - examples_block = "\n".join(examples_lines) + skill_md = _render_skill_md(group_limit, leaf_limit) - parts: list[str] = [] - parts.append(f"---\nname: cli-{cli}\ndescription: >-\n {trigger}\n---\n") - parts.append(f"# {cli} CLI Reference\n") - parts.append(f"Expert command reference for **{cli}** v{stats.version}.\n") - parts.append( - f"- **{stats.total_commands}** commands " - f"({stats.commands_with_subcommands} with subcommands)\n" - f"- **{stats.total_flags}** command flags + " - f"**{stats.global_flags}** global flags\n" - f"- **{stats.total_examples}** usage examples\n" - f"- Max nesting depth: {stats.max_depth}\n" - ) - parts.append("## When to Use\n") - parts.append( - f"This skill applies when:\n" - f"- Constructing or validating `{cli}` commands\n" - f"- Looking up flags, options, or subcommands\n" - f"- Troubleshooting `{cli}` invocations or errors\n" - f"- Needing correct syntax for `{cli}` operations\n" - ) - parts.append("## Prerequisites\n") - # Only suggest npm install for node-based CLIs; otherwise generic - npm_clis = {"claude-flow", "langchain", "ag-kit"} - if cli in npm_clis: - parts.append(f"Install {cli}:\n```bash\nnpm install -g {cli}@latest\n```\n") - else: - parts.append(f"Ensure `{cli}` is installed and available on PATH.\n") - parts.append("## Quick Reference\n") - parts.append(quick_ref + "\n") - parts.append("### Global Flags\n") - parts.append(gf_table + "\n") - parts.append("## Command Overview\n") - parts.append(groups_section) - parts.append("## Common Usage Patterns\n") - parts.append(examples_block) - parts.append( - "## Detailed References\n\n" - "For complete command documentation including all flags and subcommands:\n" - "- **Full command tree:** see `references/commands.md`\n" - "- **All usage examples:** see `references/examples.md`\n" - ) - troubleshooting = "## Troubleshooting\n\n" - has_doctor = "doctor" in cli_map["commands"] - if has_doctor: - troubleshooting += f"- Run `{cli} doctor` to diagnose issues\n" - troubleshooting += ( - f"- Use `{cli} --help` or `{cli} --help` for inline help\n" - "- Add `--verbose` for detailed output during debugging\n" - ) - parts.append(troubleshooting) - parts.append( - "## Re-scanning\n\n" - "To update this plugin after a CLI version change, run the `/scan-cli` command\n" - "or manually execute the crawler and generator.\n" - ) - return "\n".join(parts) + return skill_md def generate_commands_md(cli_map: dict) -> str: @@ -410,34 +620,38 @@ def generate_examples_md(cli_map: dict) -> str: """Generate references/examples.md -- all usage examples.""" cli = cli_map["cli_name"] lines: list[str] = [f"# {cli} -- Usage Examples\n"] - seen: set[str] = set() - - for name, data, depth in walk_tree(cli_map["commands"]): - raw = data.get("examples", []) - if not raw: - continue - - path = data.get("path", f"{cli} {name}") + doc_examples, has_explicit_examples = _collect_document_examples(cli_map) + if not has_explicit_examples and doc_examples: + lines.append("_No explicit examples found in CLI help; generated from usage patterns._\n") + + grouped: dict[tuple[str, int], list[tuple[str, str]]] = {} + group_order: list[tuple[str, int]] = [] + for path, depth, cmd, desc in doc_examples: + key = (path, depth) + if key not in grouped: + grouped[key] = [] + group_order.append(key) + grouped[key].append((cmd, desc)) + + for path, depth in group_order: header_level = "##" if depth == 0 else "###" - section_lines: list[str] = [] - - for i in range(0, len(raw) - 1, 2): - cmd, desc = raw[i], raw[i + 1] - key = cmd.strip() - if key in seen: - continue - seen.add(key) - section_lines.append(f"```bash\n{cmd}\n```\n{desc}\n") - - if section_lines: - lines.append(f"{header_level} `{path}`\n") - lines.extend(section_lines) + lines.append(f"{header_level} `{path}`\n") + for cmd, desc in grouped[(path, depth)]: + safe_cmd = _escape_triple_backticks(cmd) + safe_desc = _escape_triple_backticks(desc) + lines.append(f"```bash\n{safe_cmd}\n```\n{safe_desc}\n") return "\n".join(lines) + "\n" -def generate_rescan_sh(cli_name: str, crawler_path: str) -> str: +def generate_rescan_sh( + cli_name: str, + crawler_path: str, + *, + plugin_slug_name: str | None = None, +) -> str: """Generate scripts/rescan.sh -- executable rescan wrapper.""" + resolved_slug = plugin_slug_name or plugin_slug(cli_name) return textwrap.dedent(f"""\ #!/usr/bin/env bash # Re-scan {cli_name} CLI and regenerate this plugin. @@ -449,7 +663,7 @@ def generate_rescan_sh(cli_name: str, crawler_path: str) -> str: CLI_NAME="{cli_name}" JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" - PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-{resolved_slug}" # Check CLI is available if ! command -v "$CLI_NAME" &>/dev/null; then @@ -508,26 +722,36 @@ def generate_plugin( output_dir: str | Path, crawler_path: str, *, + author: str | None = None, dry_run: bool = False, ) -> Path: """Create the full plugin directory structure.""" stats = compute_stats(cli_map) cli = stats.cli_name - plugin_root = Path(output_dir) / f"cli-{cli}" + cli_slug = stats.cli_slug + plugin_root = Path(output_dir) / f"cli-{cli_slug}" # Define all output paths files: dict[Path, str] = {} - files[plugin_root / ".claude-plugin" / "plugin.json"] = generate_plugin_json(cli_map, stats) - files[plugin_root / "skills" / f"cli-{cli}" / "SKILL.md"] = generate_skill_md(cli_map, stats) - files[plugin_root / "skills" / f"cli-{cli}" / "references" / "commands.md"] = ( + files[plugin_root / ".claude-plugin" / "plugin.json"] = generate_plugin_json( + cli_map, stats, author=author + ) + files[plugin_root / "skills" / f"cli-{cli_slug}" / "SKILL.md"] = generate_skill_md( + cli_map, stats + ) + files[plugin_root / "skills" / f"cli-{cli_slug}" / "references" / "commands.md"] = ( generate_commands_md(cli_map) ) - files[plugin_root / "skills" / f"cli-{cli}" / "references" / "examples.md"] = ( + files[plugin_root / "skills" / f"cli-{cli_slug}" / "references" / "examples.md"] = ( generate_examples_md(cli_map) ) files[plugin_root / "commands" / "scan-cli.md"] = generate_scan_cli_md(cli, crawler_path) - files[plugin_root / "scripts" / "rescan.sh"] = generate_rescan_sh(cli, crawler_path) + files[plugin_root / "scripts" / "rescan.sh"] = generate_rescan_sh( + cli, + crawler_path, + plugin_slug_name=cli_slug, + ) if dry_run: log.info("Dry run -- would create:") @@ -588,6 +812,14 @@ def main() -> None: action="store_true", help="Enable verbose logging", ) + parser.add_argument( + "--author", + help=( + "Plugin author name (or 'Name '). " + "If omitted, falls back to CLI_PLUGINS_AUTHOR env var. " + "If neither is set, author field is omitted." + ), + ) args = parser.parse_args() logging.basicConfig( @@ -600,6 +832,7 @@ def main() -> None: cli_map, args.output, args.json_path, + author=args.author, dry_run=args.dry_run, ) print(f"{'[DRY RUN] Would create' if args.dry_run else 'Generated'} plugin at: {plugin_root}") diff --git a/src/lib/cli_identity.py b/src/lib/cli_identity.py new file mode 100644 index 0000000..7af772a --- /dev/null +++ b/src/lib/cli_identity.py @@ -0,0 +1,42 @@ +"""Helpers for canonical CLI identity across OS-specific executable names.""" + +from __future__ import annotations + +import re + +WINDOWS_EXECUTABLE_SUFFIXES = ( + ".exe", + ".cmd", + ".bat", + ".com", + ".ps1", + ".psm1", +) + + +def canonical_cli_name(cli_name: str) -> str: + """Return canonical CLI identity (e.g. ``git.exe`` -> ``git``).""" + value = (cli_name or "").strip().strip('"').strip("'") + if not value: + return "" + + # Keep only the executable leaf if a full path is provided. + leaf = re.split(r"[\\/]", value)[-1] or value + lower_leaf = leaf.lower() + + for suffix in WINDOWS_EXECUTABLE_SUFFIXES: + if lower_leaf.endswith(suffix) and len(leaf) > len(suffix): + leaf = leaf[: -len(suffix)] + break + + return leaf.lower().strip() + + +def plugin_slug(cli_name: str) -> str: + """Return deterministic plugin slug from canonical CLI identity.""" + canonical = canonical_cli_name(cli_name) + base = canonical or (cli_name or "").strip().lower() + slug = re.sub(r"\s+", "-", base) + slug = re.sub(r"[^a-z0-9+._-]+", "-", slug) + slug = re.sub(r"-{2,}", "-", slug).strip("-") + return slug or "unknown-cli" diff --git a/tests/conftest.py b/tests/conftest.py index 7886772..1223ee8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -92,3 +92,8 @@ def langchain_app_new_help(): @pytest.fixture def langchain_serve_help(): return load_fixture("langchain", "serve_help.txt") + + +@pytest.fixture +def pnpm_help(): + return load_fixture("pnpm", "help.txt") diff --git a/tests/fixtures/pnpm/help.txt b/tests/fixtures/pnpm/help.txt new file mode 100644 index 0000000..fb7236b --- /dev/null +++ b/tests/fixtures/pnpm/help.txt @@ -0,0 +1,47 @@ +Version 10.28.0 (compiled to binary; bundled Node.js v20.11.1) +Usage: pnpm [command] [flags] + pnpm [ -h | --help | -v | --version ] + +These are common pnpm commands used in various situations, use 'pnpm help -a' to list all commands + +Manage your dependencies: + add Installs a package and any packages that it depends + on. By default, any new package is installed as a + prod dependency + i, install Install all dependencies for a project + ln, link Connect the local project to another one + rm, remove Removes packages from node_modules and from the + project's package.json + unlink Unlinks a package. Like yarn unlink but pnpm + re-installs the dependency after removing the + external link + up, update Updates packages to their latest version based on the + specified range + +Review your dependencies: + audit Checks for known security issues with the installed + packages + ls, list Print all the versions of packages that are + installed, as well as their dependencies, in a + tree-structure + outdated Check for outdated packages + why Shows all packages that depend on the specified + package + +Run your scripts: + create Create a project from a "create-*" or "@foo/create-*" + starter kit + dlx Fetches a package from the registry without + installing it as a dependency, hot loads it, and runs + whatever default command binary it exposes + exec Executes a shell command in scope of a project + run Runs a defined package script + +Other: + c, config Manage the pnpm configuration files + init Create a package.json file + publish Publishes a package to the registry + self-update Updates pnpm to the latest version + +Options: + -r, --recursive Run the command for each project in the workspace. diff --git a/tests/integration/test_gcc_climap_generation.py b/tests/integration/test_gcc_climap_generation.py new file mode 100644 index 0000000..9188d5c --- /dev/null +++ b/tests/integration/test_gcc_climap_generation.py @@ -0,0 +1,53 @@ +"""Integration regression test for gcc CLIMap + plugin generation (T120).""" + +from __future__ import annotations + +import shutil + +import pytest + +from crawler.config import CLIConfig +from crawler.pipeline import crawl_cli +from generator.plugin_generator import generate_plugin, load_cli_map + + +def test_gcc_climap_and_plugin_quality_regression(tmp_path) -> None: + if shutil.which("gcc") is None: + pytest.skip("gcc binary is not available on PATH") + + output_path = tmp_path / "gcc.json" + cli_map = crawl_cli( + "gcc", + CLIConfig(name="gcc", timeout=8, max_depth=1, max_concurrent=2), + output_path=output_path, + include_raw=False, + strict=False, + ) + + assert output_path.exists() + assert len(cli_map.global_flags) >= 20 + + names = {flag.name for flag in cli_map.global_flags} + assert "-print-file-name" in names + assert "-dumpmachine" in names + assert "-Wa" in names + assert "-Xassembler" in names + assert "-std" in names + + by_name = {flag.name: flag for flag in cli_map.global_flags} + assert by_name["-dumpmachine"].type == "bool" + assert by_name["-pipe"].type == "bool" + assert by_name["-Wa"].type == "string" + assert by_name["-Xassembler"].type == "string" + assert by_name["-std"].type == "string" + + loaded = load_cli_map(output_path) + plugin_root = generate_plugin(loaded, tmp_path / "plugins", str(output_path)) + commands_md = ( + plugin_root / "skills" / "cli-gcc" / "references" / "commands.md" + ).read_text(encoding="utf-8") + + assert "-print-file-name" in commands_md + assert "-Wa" in commands_md + assert "-Xassembler" in commands_md + assert "-std" in commands_md diff --git a/tests/integration/test_pnpm_climap_generation.py b/tests/integration/test_pnpm_climap_generation.py new file mode 100644 index 0000000..e4fbfda --- /dev/null +++ b/tests/integration/test_pnpm_climap_generation.py @@ -0,0 +1,44 @@ +"""Integration regression test for pnpm CLIMap + plugin generation (T081).""" + +from __future__ import annotations + +import shutil + +import pytest + +from crawler.config import CLIConfig +from crawler.pipeline import crawl_cli +from generator.plugin_generator import generate_plugin, load_cli_map + + +def test_pnpm_climap_and_plugin_quality_regression(tmp_path) -> None: + if shutil.which("pnpm") is None: + pytest.skip("pnpm binary is not available on PATH") + + output_path = tmp_path / "pnpm.json" + cli_map = crawl_cli( + "pnpm", + CLIConfig(name="pnpm", timeout=5, max_depth=2, max_concurrent=4), + output_path=output_path, + include_raw=False, + strict=False, + ) + + assert output_path.exists() + assert int(cli_map.metadata.get("total_commands", "0")) >= 12 + + loaded = load_cli_map(output_path) + plugin_root = generate_plugin(loaded, tmp_path / "plugins", str(output_path)) + commands_md = ( + plugin_root / "skills" / "cli-pnpm" / "references" / "commands.md" + ).read_text(encoding="utf-8") + examples_md = ( + plugin_root / "skills" / "cli-pnpm" / "references" / "examples.md" + ).read_text(encoding="utf-8") + skill_md = (plugin_root / "skills" / "cli-pnpm" / "SKILL.md").read_text(encoding="utf-8") + + assert "pnpm add" in commands_md + assert "pnpm install" in commands_md + assert "pnpm run" in commands_md + assert "```bash" in examples_md + assert "_No examples extracted._" not in skill_md diff --git a/tests/performance/test_smoke_perf.py b/tests/performance/test_smoke_perf.py new file mode 100644 index 0000000..592ff61 --- /dev/null +++ b/tests/performance/test_smoke_perf.py @@ -0,0 +1,61 @@ +"""Performance smoke tests (T053).""" + +from __future__ import annotations + +import shutil +import time +from pathlib import Path + +import pytest + +from crawler.config import CLIConfig +from crawler.parser import parse_help_output +from crawler.pipeline import crawl_cli +from generator.plugin_generator import generate_plugin, load_cli_map + + +@pytest.mark.performance +def test_parse_1000_line_fixture_under_5_seconds() -> None: + help_text = "\n".join( + ["Usage: perf-cli [OPTIONS] COMMAND"] + [f"--flag-{i} Description {i}" for i in range(1000)] + ) + + start = time.monotonic() + result = parse_help_output(help_text, "perf-cli", "perf-cli") + duration = time.monotonic() - start + + assert duration < 5.0 + assert result.command.usage_pattern + + +@pytest.mark.performance +def test_docker_crawl_and_generate_under_30_seconds(tmp_path: Path) -> None: + if shutil.which("docker") is None: + pytest.skip("docker CLI is not installed in this environment") + + output_path = tmp_path / "docker.json" + plugin_root_dir = tmp_path / "plugins" + config = CLIConfig( + name="docker", + timeout=5, + max_depth=2, + max_concurrent=4, + raw_threshold=10_000, + ) + + start = time.monotonic() + cli_map = crawl_cli( + "docker", + config, + output_path=output_path, + include_raw=False, + strict=False, + ) + plugin_root = generate_plugin(load_cli_map(output_path), plugin_root_dir, str(output_path)) + duration = time.monotonic() - start + + assert duration < 30.0 + assert output_path.exists() + assert output_path.with_suffix(".raw.json").exists() + assert plugin_root.exists() + assert int(cli_map.metadata.get("total_commands", "0")) >= 20 diff --git a/tests/test_discovery_thread_safety.py b/tests/test_discovery_thread_safety.py new file mode 100644 index 0000000..1db4d0b --- /dev/null +++ b/tests/test_discovery_thread_safety.py @@ -0,0 +1,32 @@ +"""Thread-safety tests for discovery CrawlState.""" + +from __future__ import annotations + +import concurrent.futures + +from crawler.discovery import CrawlState + + +def test_mark_visited_is_atomic() -> None: + state = CrawlState() + paths = ["demo run"] * 100 + [f"demo cmd-{i}" for i in range(100)] + + with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool: + results = list(pool.map(state.mark_visited, paths)) + + assert results.count(True) == 101 + assert len(state.visited) == 101 + + +def test_error_and_warning_updates_are_thread_safe() -> None: + state = CrawlState() + + def worker(i: int) -> None: + state.increment_errors() + state.add_warning(f"warn-{i}") + + with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool: + list(pool.map(worker, range(200))) + + assert state.errors == 200 + assert len(state.warnings) == 200 diff --git a/tests/test_generate_plugin.py b/tests/test_generate_plugin.py index 354ff67..d9cd5b0 100644 --- a/tests/test_generate_plugin.py +++ b/tests/test_generate_plugin.py @@ -75,7 +75,7 @@ def test_counts(self, cf_stats): assert cf_stats.total_examples >= 10 # unique examples assert cf_stats.global_flags == 8 assert cf_stats.cli_name == "claude-flow" - assert cf_stats.version == "3.1.0-alpha.3" + assert cf_stats.version.startswith("3.1.0-alpha.") def test_max_depth(self, cf_stats): assert cf_stats.max_depth >= 1 @@ -154,7 +154,7 @@ def test_valid_json(self, cf_map, cf_stats): content = generate_plugin_json(cf_map, cf_stats) obj = json.loads(content) assert obj["name"] == "cli-claude-flow" - assert obj["version"] == "3.1.0-alpha.3" + assert obj["version"] == cf_map["cli_version"] assert "keywords" in obj assert "claude-flow" in obj["keywords"] @@ -252,6 +252,48 @@ def test_deduplicates(self, cf_map): count = md.count("claude-flow doctor\n") assert count <= 1, "Examples should be deduplicated" + def test_escapes_backticks_in_example_descriptions(self): + cli_map = { + "cli_name": "demo", + "commands": { + "run": { + "path": "demo run", + "examples": ["demo run --fast", "Handle fenced text ```inline``` safely"], + "subcommands": {}, + } + }, + "metadata": {}, + } + md = generate_examples_md(cli_map) + assert md.count("```") == 2 + assert "```inline```" not in md + + def test_fallback_examples_from_usage_when_none_explicit(self): + cli_map = { + "cli_name": "pnpm", + "commands": { + "add": { + "path": "pnpm add", + "usage_pattern": "Usage: pnpm add ", + "description": "Install package dependencies", + "examples": [], + "subcommands": {}, + }, + "install": { + "path": "pnpm install", + "usage_pattern": "Usage: pnpm install [options]", + "description": "Install project dependencies", + "examples": [], + "subcommands": {}, + }, + }, + "metadata": {}, + } + md = generate_examples_md(cli_map) + assert "pnpm add " in md + assert "pnpm install [options]" in md + assert "_No explicit examples found in CLI help" in md + # --------------------------------------------------------------------------- # generate_scan_cli_md diff --git a/tests/test_parser_description_cleaning.py b/tests/test_parser_description_cleaning.py new file mode 100644 index 0000000..be24e54 --- /dev/null +++ b/tests/test_parser_description_cleaning.py @@ -0,0 +1,63 @@ +"""Tests for parser description cleaning of runtime/status noise.""" + +from __future__ import annotations + +from crawler.parser import parse_help_output + + +def test_runtime_error_description_is_discarded() -> None: + text = """ +error: unrecognized subcommand '--help' + +Usage: ruff [OPTIONS] COMMAND [ARGS]... + +Commands: + check Run checks on code +""" + result = parse_help_output(text, "ruff", "ruff") + assert result.command.description == "" + + +def test_state_message_description_is_discarded() -> None: + text = """ +[WARN] Swarm already initialized + +Usage: claude-flow swarm [OPTIONS] COMMAND [ARGS]... + +Commands: + status Get swarm status +""" + result = parse_help_output(text, "claude-flow", "claude-flow swarm") + assert result.command.description == "" + + +def test_circular_description_is_discarded() -> None: + text = """ +demo + +Usage: demo [OPTIONS] +""" + result = parse_help_output(text, "demo", "demo") + assert result.command.description == "" + + +def test_regular_description_is_kept() -> None: + text = """ +Fast and reliable package manager. + +Usage: uv [OPTIONS] COMMAND [ARGS]... +""" + result = parse_help_output(text, "uv", "uv") + assert result.command.description == "Fast and reliable package manager." + + +def test_subcommand_oneliner_equal_to_name_is_preserved() -> None: + text = """ +Usage: demo [OPTIONS] COMMAND [ARGS]... + +Commands: + deploy deploy +""" + result = parse_help_output(text, "demo", "demo") + assert "deploy" in result.subcommand_descriptions + assert result.subcommand_descriptions["deploy"] == "deploy" diff --git a/tests/test_parser_manpage.py b/tests/test_parser_manpage.py index 1487156..73caf4d 100644 --- a/tests/test_parser_manpage.py +++ b/tests/test_parser_manpage.py @@ -33,3 +33,49 @@ def test_git_commit_usage(self, git_commit_help): def test_confidence(self, git_commit_help): cmd, _ = parse_manpage(git_commit_help, "git", "git commit") assert cmd.confidence >= 0.60 + + def test_npm_style_examples_are_extracted(self): + manpage_text = """NPM-INSTALL(1) npm manual NPM-INSTALL(1) + +NAME + npm-install - Install a package + +SYNOPSIS + npm install [ ...] + +EXAMPLES + npm install + Install dependencies from package.json. + + $ npm install sax + Install the sax package. + + For more information, see npm help install. +""" + + cmd, _ = parse_manpage(manpage_text, "npm", "npm install") + assert "npm install" in cmd.examples + assert "npm install sax" in cmd.examples + assert all(not ex.lower().startswith("for more information") for ex in cmd.examples) + assert all("install dependencies from package.json." not in ex.lower() for ex in cmd.examples) + + def test_examples_with_left_aligned_prose_still_extract_commands(self): + manpage_text = """TOOL(1) user manual TOOL(1) + +NAME + tool - Demo tool + +SYNOPSIS + tool [OPTIONS] + +EXAMPLES +This section shows practical usage. + tool run --fast + Run quickly. + $ tool check + Validate current project. +""" + + cmd, _ = parse_manpage(manpage_text, "tool", "tool") + assert "tool run --fast" in cmd.examples + assert "tool check" in cmd.examples diff --git a/tests/test_parser_sections.py b/tests/test_parser_sections.py index 754b18e..d985d78 100644 --- a/tests/test_parser_sections.py +++ b/tests/test_parser_sections.py @@ -101,3 +101,14 @@ def test_richclick_arguments_section(self, langchain_app_new_help): types = [s.type for s in sections] assert SectionType.ARGUMENTS in types assert SectionType.FLAGS in types + + def test_pipe_table_without_box_markers_is_not_stripped(self): + text = """ +| Command | Description | +| --- | --- | +| run | Run the service | +""" + sections = segment_help_text(text) + assert sections + assert "| Command | Description |" in sections[0].content + assert "| run | Run the service |" in sections[0].content diff --git a/tests/test_pipeline_integration.py b/tests/test_pipeline_integration.py index 0e95904..965ea2f 100644 --- a/tests/test_pipeline_integration.py +++ b/tests/test_pipeline_integration.py @@ -82,6 +82,22 @@ def test_parse_langchain_serve(self, langchain_serve_help): assert "--port" in names assert "--host" in names + def test_parse_unicode_richclick_flags(self): + help_text = """ +Usage: demo [OPTIONS] + +╭─ Options ───────────────────────────────╮ +│ --port INTEGER Port to listen on │ +│ --host TEXT Host interface │ +│ --debug Enable debug mode │ +╰─────────────────────────────────────────╯ +""" + result = parse_help_output(help_text, "demo", "demo") + names = [f.name for f in result.command.flags] + assert "--port" in names + assert "--host" in names + assert "--debug" in names + class TestOutputSchema: def test_json_output_exists(self): @@ -93,8 +109,10 @@ def test_json_output_exists(self): if f.name.endswith(".raw.json"): continue data = json.loads(f.read_text(encoding="utf-8")) + if "cli_name" not in data: + # Allow auxiliary reports (e.g., config-audit.json) in output/. + continue # Verify required top-level keys - assert "cli_name" in data assert "cli_version" in data assert "metadata" in data assert "commands" in data diff --git a/tests/unit/test_author_config.py b/tests/unit/test_author_config.py new file mode 100644 index 0000000..28a3549 --- /dev/null +++ b/tests/unit/test_author_config.py @@ -0,0 +1,48 @@ +"""Unit tests for configurable plugin author metadata (T029).""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from generator.plugin_generator import compute_stats, generate_plugin_json, load_cli_map + +FIXTURE_PATH = Path(__file__).resolve().parents[2] / "output" / "claude-flow.json" + + +@pytest.fixture +def cf_map() -> dict: + if not FIXTURE_PATH.exists(): + pytest.skip("output/claude-flow.json not available") + return load_cli_map(FIXTURE_PATH) + + +def test_author_omitted_when_not_configured(cf_map: dict, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("CLI_PLUGINS_AUTHOR", raising=False) + stats = compute_stats(cf_map) + + plugin_obj = json.loads(generate_plugin_json(cf_map, stats)) + + assert "author" not in plugin_obj + + +def test_author_loaded_from_environment(cf_map: dict, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("CLI_PLUGINS_AUTHOR", "Community Maintainers") + stats = compute_stats(cf_map) + + plugin_obj = json.loads(generate_plugin_json(cf_map, stats)) + + assert plugin_obj["author"] == {"name": "Community Maintainers"} + + +def test_explicit_author_overrides_environment( + cf_map: dict, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setenv("CLI_PLUGINS_AUTHOR", "Env Author") + stats = compute_stats(cf_map) + + plugin_obj = json.loads(generate_plugin_json(cf_map, stats, author="Flag Author")) + + assert plugin_obj["author"] == {"name": "Flag Author"} diff --git a/tests/unit/test_cli_crawler_basic.py b/tests/unit/test_cli_crawler_basic.py new file mode 100644 index 0000000..795bcaa --- /dev/null +++ b/tests/unit/test_cli_crawler_basic.py @@ -0,0 +1,19 @@ +"""Basic crawler smoke tests (T009).""" + +from __future__ import annotations + +from crawler.config import CLIConfig +from crawler.pipeline import crawl_cli + + +def test_git_initial_crawl_root_help(tmp_path) -> None: + """Initial crawl should parse git root help without crashing.""" + output_path = tmp_path / "git.json" + config = CLIConfig(name="git", timeout=5, max_depth=1, max_concurrent=2) + + cli_map = crawl_cli("git", config, output_path=output_path, include_raw=False, strict=False) + + assert cli_map.cli_name == "git" + assert output_path.exists() + assert int(cli_map.metadata.get("total_commands", "0")) >= 10 + assert cli_map.metadata.get("help_pattern") in {"--help", "-h", "help", "bare"} diff --git a/tests/unit/test_cli_name_canonicalization.py b/tests/unit/test_cli_name_canonicalization.py new file mode 100644 index 0000000..c01376c --- /dev/null +++ b/tests/unit/test_cli_name_canonicalization.py @@ -0,0 +1,75 @@ +"""Unit tests for executable-suffix CLI canonicalization (T133).""" + +from __future__ import annotations + +import json + +from crawler.models import ExecutionResult +from crawler.version import detect_version +from generator.plugin_generator import generate_plugin + + +class _VersionExecutor: + def __init__(self, results: dict[str, ExecutionResult]) -> None: + self.results = results + self.calls: list[list[str]] = [] + + def run(self, command: list[str], timeout: int | None = None) -> ExecutionResult: + del timeout + self.calls.append(command) + key = " ".join(command) + return self.results.get( + key, + ExecutionResult(stdout="", stderr="", exit_code=1, command=command), + ) + + +def test_detect_version_canonicalizes_executable_suffix_but_keeps_invocation_command() -> None: + executor = _VersionExecutor( + { + "git.exe --version": ExecutionResult( + stdout="git version 2.53.0.windows.1", + stderr="", + exit_code=0, + command=["git.exe", "--version"], + ), + } + ) + + version = detect_version("git.exe", executor) + + assert version == "2.53.0.windows.1" + assert executor.calls + assert executor.calls[0][0] == "git.exe" + + +def test_generate_plugin_uses_canonical_slug_for_executable_cli_names(tmp_path) -> None: + cli_map = { + "cli_name": "git.exe", + "cli_version": "2.53.0.windows.1", + "metadata": {"help_pattern": "--help"}, + "global_flags": [], + "commands": { + "status": { + "path": "git.exe status", + "description": "Show status", + "flags": [], + "subcommands": {}, + } + }, + } + + root = generate_plugin(cli_map, tmp_path, "output/git-exe-win.json") + + assert root.name == "cli-git" + plugin_json = json.loads((root / ".claude-plugin" / "plugin.json").read_text(encoding="utf-8")) + assert plugin_json["name"] == "cli-git" + + skill_path = root / "skills" / "cli-git" / "SKILL.md" + assert skill_path.exists() + skill_text = skill_path.read_text(encoding="utf-8") + assert "name: cli-git" in skill_text + assert "**git.exe**" in skill_text + + rescan_text = (root / "scripts" / "rescan.sh").read_text(encoding="utf-8") + assert 'CLI_NAME="git.exe"' in rescan_text diff --git a/tests/unit/test_command_parsing_basic.py b/tests/unit/test_command_parsing_basic.py new file mode 100644 index 0000000..ed7c353 --- /dev/null +++ b/tests/unit/test_command_parsing_basic.py @@ -0,0 +1,23 @@ +"""Basic command hierarchy parsing tests (T011).""" + +from __future__ import annotations + +from crawler.parser import parse_help_output + + +def test_parse_docker_root_command_hierarchy(docker_help) -> None: + result = parse_help_output(docker_help, "docker", "docker") + subcommands = set(result.subcommand_names) + + assert "run" in subcommands + assert "container" in subcommands + assert len(subcommands) >= 20 + + +def test_parse_claude_flow_root_commands(claude_flow_help) -> None: + result = parse_help_output(claude_flow_help, "claude-flow", "claude-flow") + subcommands = set(result.subcommand_names) + + assert "agent" in subcommands + assert "swarm" in subcommands + assert "memory" in subcommands diff --git a/tests/unit/test_edge_case_auth_help.py b/tests/unit/test_edge_case_auth_help.py new file mode 100644 index 0000000..9c65c51 --- /dev/null +++ b/tests/unit/test_edge_case_auth_help.py @@ -0,0 +1,128 @@ +"""Unit tests for EC-02: help requiring authentication should fail clearly.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from crawler.config import CLIConfig +from crawler.detector import detect_help_pattern +from crawler.executor import is_auth_required_failure +from crawler.models import ExecutionResult, HelpDetectionResult +from crawler.pipeline import crawl_cli + + +def _auth_detection(cli_name: str) -> HelpDetectionResult: + return HelpDetectionResult( + pattern="auth_required", + result=ExecutionResult( + stdout="", + stderr=( + "AUTH_REQUIRED: Help command requires authentication for " + f"'{cli_name}'. Run '{cli_name} auth login' and retry." + ), + exit_code=1, + command=[cli_name, "--help"], + ), + ) + + +def test_auth_required_help_returns_clear_warning_without_hanging( + monkeypatch, tmp_path: Path +) -> None: + cli_name = "private-cli" + config = CLIConfig(name=cli_name, raw_threshold=10_000) + + monkeypatch.setattr("crawler.pipeline.detect_version", lambda *_args, **_kwargs: "") + monkeypatch.setattr( + "crawler.pipeline.detect_help_pattern", + lambda *_args, **_kwargs: _auth_detection(cli_name), + ) + monkeypatch.setattr( + "crawler.pipeline.discover_and_crawl", + lambda *_args, **_kwargs: {}, + ) + + cli_map = crawl_cli( + cli_name, + config, + output_path=tmp_path / f"{cli_name}.json", + strict=False, + ) + + assert cli_map.cli_name == cli_name + assert cli_map.commands == {} + assert int(cli_map.metadata.get("parse_warnings", "0")) >= 1 + assert float(cli_map.metadata.get("confidence_score", "1.0")) <= 0.30 + assert ( + cli_map.metadata.get("help_error", "").startswith("AUTH_REQUIRED:") + or "AUTH_REQUIRED:" in cli_map.metadata.get("help_error", "") + ) + + +def test_auth_required_help_raises_in_strict_mode(monkeypatch, tmp_path: Path) -> None: + cli_name = "private-cli" + config = CLIConfig(name=cli_name, raw_threshold=10_000) + + monkeypatch.setattr("crawler.pipeline.detect_version", lambda *_args, **_kwargs: "") + monkeypatch.setattr( + "crawler.pipeline.detect_help_pattern", + lambda *_args, **_kwargs: _auth_detection(cli_name), + ) + + with pytest.raises(RuntimeError, match="AUTH_REQUIRED:"): + crawl_cli( + cli_name, + config, + output_path=tmp_path / f"{cli_name}.json", + strict=True, + ) + + +def test_auth_detection_uses_stderr_only_to_avoid_help_false_positives() -> None: + result = ExecutionResult( + stdout="Usage: demo [OPTIONS]\n--token optional token flag\n--help show help", + stderr="", + exit_code=1, + command=["demo", "--help"], + ) + assert not is_auth_required_failure(result) + + +def test_detect_help_pattern_continues_after_auth_failure_on_first_pattern() -> None: + cli_name = "demo" + valid_help = "\n".join( + [ + "Usage: demo [OPTIONS]", + "Options:", + " -h, --help Show help", + "Commands:", + " run Execute command", + ] + ) + + class _FakeExecutor: + def run_with_retry(self, command: list[str], timeout: int | None = None) -> ExecutionResult: + del timeout + if command == [cli_name, "--help"]: + return ExecutionResult( + stdout="", + stderr="authentication required", + exit_code=1, + command=command, + ) + if command == [cli_name, "-h"]: + return ExecutionResult( + stdout=valid_help, + stderr="", + exit_code=0, + command=command, + ) + return ExecutionResult(stdout="", stderr="", exit_code=1, command=command) + + def run(self, command: list[str], timeout: int | None = None) -> ExecutionResult: + return self.run_with_retry(command, timeout) + + detection = detect_help_pattern(cli_name, _FakeExecutor(), CLIConfig(name=cli_name)) + assert detection.pattern == "-h" diff --git a/tests/unit/test_edge_case_long_help.py b/tests/unit/test_edge_case_long_help.py new file mode 100644 index 0000000..9159701 --- /dev/null +++ b/tests/unit/test_edge_case_long_help.py @@ -0,0 +1,63 @@ +"""Unit test for EC-05: huge help output triggers progressive loading.""" + +from __future__ import annotations + +from pathlib import Path + +from crawler.config import CLIConfig +from crawler.models import Command, ExecutionResult, HelpDetectionResult, ParseResult +from crawler.pipeline import crawl_cli + + +def test_long_help_activates_progressive_loading(monkeypatch, tmp_path: Path) -> None: + cli_name = "huge-cli" + threshold = 10_000 + long_help = "\n".join( + ["Usage: huge-cli [OPTIONS] COMMAND"] + [f"line {i}" for i in range(12_050)] + ) + + detection = HelpDetectionResult( + pattern="--help", + result=ExecutionResult( + stdout=long_help, + stderr="", + exit_code=0, + command=[cli_name, "--help"], + ), + ) + + seen: dict[str, int] = {} + + def _fake_parse(text: str, _cli: str, command_path: str, force_manpage: bool = False) -> ParseResult: + del force_manpage + seen["parsed_lines"] = len(text.splitlines()) + return ParseResult( + command=Command( + path=command_path, + name=command_path.split()[-1], + description="parsed from progressive chunk", + confidence=0.8, + ), + warnings=[], + ) + + monkeypatch.setattr("crawler.pipeline.detect_version", lambda *_args, **_kwargs: "1.0.0") + monkeypatch.setattr("crawler.pipeline.detect_help_pattern", lambda *_args, **_kwargs: detection) + monkeypatch.setattr("crawler.pipeline.parse_help_output", _fake_parse) + monkeypatch.setattr( + "crawler.pipeline.discover_and_crawl", + lambda *_args, **_kwargs: {}, + ) + + cli_map = crawl_cli( + cli_name, + CLIConfig(name=cli_name, raw_threshold=threshold), + output_path=tmp_path / f"{cli_name}.json", + strict=False, + ) + + assert cli_map.metadata.get("progressive_loading") == "true" + assert cli_map.metadata.get("raw_line_count") == str(len(long_help.splitlines())) + assert cli_map.metadata.get("parsed_line_count") == str(threshold) + # +2 allows the truncation note/footer lines added by the pipeline wrapper. + assert seen["parsed_lines"] <= threshold + 2 diff --git a/tests/unit/test_edge_case_no_help.py b/tests/unit/test_edge_case_no_help.py new file mode 100644 index 0000000..245dbc4 --- /dev/null +++ b/tests/unit/test_edge_case_no_help.py @@ -0,0 +1,46 @@ +"""Unit test for EC-01: CLI without standard --help should degrade gracefully.""" + +from __future__ import annotations + +import logging +from pathlib import Path + +from crawler.config import CLIConfig +from crawler.models import ExecutionResult, HelpDetectionResult +from crawler.pipeline import crawl_cli + + +def test_no_standard_help_returns_partial_climap_and_warning( + monkeypatch, tmp_path: Path, caplog +) -> None: + cli_name = "mystery-cli" + config = CLIConfig(name=cli_name, raw_threshold=10_000) + + detection = HelpDetectionResult( + pattern="unknown", + result=ExecutionResult( + stdout="", + stderr="unknown option: --help", + exit_code=2, + command=[cli_name, "--help"], + ), + ) + + monkeypatch.setattr("crawler.pipeline.detect_version", lambda *_args, **_kwargs: "") + monkeypatch.setattr("crawler.pipeline.detect_help_pattern", lambda *_args, **_kwargs: detection) + monkeypatch.setattr( + "crawler.pipeline.discover_and_crawl", + lambda *_args, **_kwargs: {}, + ) + + caplog.set_level(logging.WARNING) + + output_path = tmp_path / f"{cli_name}.json" + cli_map = crawl_cli(cli_name, config, output_path=output_path, strict=False) + + assert output_path.exists() + assert cli_map.cli_name == cli_name + assert cli_map.commands == {} + assert int(cli_map.metadata.get("parse_warnings", "0")) >= 1 + assert float(cli_map.metadata.get("confidence_score", "1.0")) <= 0.40 + assert any("degraded mode" in msg.lower() for msg in caplog.messages) diff --git a/tests/unit/test_embedded_help_boundary.py b/tests/unit/test_embedded_help_boundary.py new file mode 100644 index 0000000..771489a --- /dev/null +++ b/tests/unit/test_embedded_help_boundary.py @@ -0,0 +1,28 @@ +"""Unit tests for embedded-help boundary filtering (T089).""" + +from __future__ import annotations + +from crawler.parser import parse_help_output + + +def test_parser_ignores_foreign_embedded_help_block() -> None: + help_text = """ +usage: yq [options] [input file...] + +options: + -h, --help show this help message and exit + --yaml-output emit YAML + --yq-native-flag yq-only option + +jq - commandline JSON processor [version 1.7] +Usage: jq [options] [file...] + --jq-only-flag embedded jq flag that must be ignored + --version jq version +""" + + result = parse_help_output(help_text, "yq", "yq") + names = [flag.name for flag in result.command.flags] + + assert "--yq-native-flag" in names + assert "--jq-only-flag" not in names + assert any("Embedded help boundary" in w for w in result.warnings) diff --git a/tests/unit/test_flag_dedup_embedded_help.py b/tests/unit/test_flag_dedup_embedded_help.py new file mode 100644 index 0000000..5684718 --- /dev/null +++ b/tests/unit/test_flag_dedup_embedded_help.py @@ -0,0 +1,31 @@ +"""Unit tests for embedded-help flag deduplication (T082).""" + +from __future__ import annotations + +from crawler.parser import parse_help_output + + +def test_embedded_help_deduplicates_help_and_version_flags() -> None: + help_text = """ +Usage: yq [options] [files ...] + +Options: + -h, --help show this help message and exit + --version show program's version number and exit + --yaml-output emit YAML output + +jq - commandline JSON processor [version 1.7] +Usage: jq [options] [file...] + --help show jq help + --version show jq version +""" + + result = parse_help_output(help_text, "yq", "yq") + names = [flag.name for flag in result.command.flags] + + assert names.count("--help") == 1 + assert names.count("--version") == 1 + assert "--yaml-output" in names + + help_flag = next(flag for flag in result.command.flags if flag.name == "--help") + assert help_flag.short_name == "-h" diff --git a/tests/unit/test_flag_parsing_basic.py b/tests/unit/test_flag_parsing_basic.py new file mode 100644 index 0000000..4a385ce --- /dev/null +++ b/tests/unit/test_flag_parsing_basic.py @@ -0,0 +1,30 @@ +"""Basic flag parsing tests (T010).""" + +from __future__ import annotations + +from crawler.parsers.flags import parse_flags_section + + +def test_parse_standard_and_short_flags() -> None: + content = """ + -m, --message Commit message + --amend Amend previous commit +""" + flags = parse_flags_section(content) + names = {f.name for f in flags} + + assert "--message" in names + assert "--amend" in names + msg = next(f for f in flags if f.name == "--message") + assert msg.short_name == "-m" + assert msg.type == "string" + + +def test_parse_bool_flag_type_inference() -> None: + content = """ + -v, --verbose Enable verbose output +""" + flags = parse_flags_section(content) + assert len(flags) == 1 + assert flags[0].name == "--verbose" + assert flags[0].type == "bool" diff --git a/tests/unit/test_flag_parsing_gnu_single_dash.py b/tests/unit/test_flag_parsing_gnu_single_dash.py new file mode 100644 index 0000000..5526aea --- /dev/null +++ b/tests/unit/test_flag_parsing_gnu_single_dash.py @@ -0,0 +1,66 @@ +"""Unit tests for GNU single-dash long-option parsing (T118).""" + +from __future__ import annotations + +from crawler.parsers.flags import parse_flags_section + + +def test_parse_gnu_single_dash_long_options_and_pass_through_families() -> None: + content = """ + -print-file-name= Display the full path to library . + -dumpmachine Display the compiler's target processor. + -Wa, Pass comma-separated on to the assembler. + -Wp, Pass comma-separated on to the preprocessor. + -Wl, Pass comma-separated on to the linker. + -Xassembler Pass on to the assembler. + -Xpreprocessor Pass on to the preprocessor. + -Xlinker Pass on to the linker. + -std= Assume that the input sources are for . +""" + flags = parse_flags_section(content) + by_name = {flag.name: flag for flag in flags} + + assert "-print-file-name" in by_name + assert by_name["-print-file-name"].type == "string" + assert by_name["-print-file-name"].description.startswith("Display the full path") + + assert "-dumpmachine" in by_name + assert by_name["-dumpmachine"].type == "bool" + + assert "-Wa" in by_name + assert by_name["-Wa"].type == "string" + assert by_name["-Wa"].description.startswith("Pass comma-separated") + + assert "-Wp" in by_name + assert by_name["-Wp"].type == "string" + + assert "-Wl" in by_name + assert by_name["-Wl"].type == "string" + + assert "-Xassembler" in by_name + assert by_name["-Xassembler"].type == "string" + + assert "-Xpreprocessor" in by_name + assert by_name["-Xpreprocessor"].type == "string" + + assert "-Xlinker" in by_name + assert by_name["-Xlinker"].type == "string" + + assert "-std" in by_name + assert by_name["-std"].type == "string" + + +def test_parse_gnu_single_dash_preserves_bool_and_one_letter_value_forms() -> None: + content = """ + -pipe Use pipes rather than intermediate files. + -save-temps Do not delete intermediate files. + -L dir Add dir to include search path. + -l library Link against library. +""" + flags = parse_flags_section(content) + by_name = {flag.name: flag for flag in flags} + + assert by_name["-pipe"].type == "bool" + assert by_name["-save-temps"].type == "bool" + assert by_name["-L"].type == "string" + assert by_name["-l"].type == "string" diff --git a/tests/unit/test_keyword_generation.py b/tests/unit/test_keyword_generation.py new file mode 100644 index 0000000..32286d1 --- /dev/null +++ b/tests/unit/test_keyword_generation.py @@ -0,0 +1,64 @@ +"""Unit tests for semantic keyword generation (T028).""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from generator.plugin_generator import generate_semantic_keywords + + +def test_keywords_are_semantic_not_first_words() -> None: + """Keywords should include domain terms from descriptions, not generic first words.""" + cli_map = { + "cli_name": "acme", + "metadata": {}, + "commands": { + "scan": { + "description": ( + "Manage command lifecycle for security analysis and compliance reporting" + ), + "subcommands": { + "deep": { + "description": "Run forensic analysis for incident response workflows", + "subcommands": {}, + "flags": [], + } + }, + "flags": [], + }, + "deploy": { + "description": "Manage releases with canary rollout and rollback automation", + "subcommands": {}, + "flags": [], + }, + }, + } + + keywords = generate_semantic_keywords(cli_map, max_keywords=7) + + assert keywords[0] == "acme" + assert "manage" not in keywords + assert "command" not in keywords + assert any( + token in keywords + for token in ("security", "compliance", "forensic", "rollout", "automation") + ) + + +def test_keywords_include_top_command_groups_from_fixture() -> None: + fixture = Path(__file__).resolve().parents[2] / "output" / "claude-flow.json" + if not fixture.exists(): + pytest.skip("output/claude-flow.json not available") + + cli_map = json.loads(fixture.read_text(encoding="utf-8")) + keywords = generate_semantic_keywords(cli_map, max_keywords=8) + top_groups = [ + name.lower() for name, data in cli_map["commands"].items() if data.get("subcommands") + ] + + assert "claude-flow" in keywords + assert top_groups, "fixture should expose at least one command group with subcommands" + assert any(group in keywords for group in top_groups) diff --git a/tests/unit/test_output_layout.py b/tests/unit/test_output_layout.py new file mode 100644 index 0000000..bb2b608 --- /dev/null +++ b/tests/unit/test_output_layout.py @@ -0,0 +1,52 @@ +"""Unit tests for deterministic output layout and path resolution.""" + +from __future__ import annotations + +import json +from pathlib import Path + +from crawler.config import CLIConfig +from crawler.formatter import write_output +from crawler.models import CLIMap +from crawler.pipeline import _resolve_output_path + + +def test_resolve_output_path_accepts_directory(tmp_path: Path) -> None: + resolved = _resolve_output_path(tmp_path, "docker") + assert resolved == tmp_path / "docker.json" + + +def test_resolve_output_path_accepts_json_file(tmp_path: Path) -> None: + explicit_file = tmp_path / "custom-output.json" + resolved = _resolve_output_path(explicit_file, "docker") + assert resolved == explicit_file + + +def test_resolve_output_path_treats_plain_value_as_directory(tmp_path: Path) -> None: + plain = tmp_path / "artifacts" + resolved = _resolve_output_path(plain, "docker") + assert resolved == plain / "docker.json" + + +def test_write_output_writes_main_and_raw_sidecar(tmp_path: Path) -> None: + output_path = tmp_path / "docker.json" + cli_map = CLIMap(cli_name="docker", cli_version="1.0", metadata={}, commands={}) + raw_outputs = {"docker": "Usage: docker [OPTIONS] COMMAND"} + + write_output( + cli_map, + raw_outputs, + output_path, + CLIConfig(name="docker", raw_threshold=99999999), + include_raw=False, + ) + + main_file = tmp_path / "docker.json" + raw_file = tmp_path / "docker.raw.json" + assert main_file.exists() + assert raw_file.exists() + + main_data = json.loads(main_file.read_text(encoding="utf-8")) + raw_data = json.loads(raw_file.read_text(encoding="utf-8")) + assert main_data["raw_file"] == "docker.raw.json" + assert raw_data["docker"].startswith("Usage: docker") diff --git a/tests/unit/test_parser_pnpm_grouped_help.py b/tests/unit/test_parser_pnpm_grouped_help.py new file mode 100644 index 0000000..2e0ebd7 --- /dev/null +++ b/tests/unit/test_parser_pnpm_grouped_help.py @@ -0,0 +1,37 @@ +"""pnpm grouped-help parsing tests (T079).""" + +from __future__ import annotations + +from crawler.parser import parse_help_output +from crawler.parsers.sections import SectionType, segment_help_text + + +def test_pnpm_group_headers_are_detected_as_command_sections(pnpm_help) -> None: + sections = segment_help_text(pnpm_help) + command_sections = [s for s in sections if s.type == SectionType.COMMANDS] + + assert len(command_sections) >= 3 + headers = {s.header for s in command_sections} + assert "Manage your dependencies:" in headers + assert "Review your dependencies:" in headers + assert "Run your scripts:" in headers + + +def test_pnpm_root_extracts_grouped_commands_and_alias_rows(pnpm_help) -> None: + result = parse_help_output(pnpm_help, "pnpm", "pnpm") + subcommands = set(result.subcommand_names) + + assert len(subcommands) >= 12 + assert "add" in subcommands + assert "install" in subcommands + assert "run" in subcommands + assert "config" in subcommands + assert "remove" in subcommands + + +def test_pnpm_wrapped_description_is_recovered(pnpm_help) -> None: + result = parse_help_output(pnpm_help, "pnpm", "pnpm") + desc = result.subcommand_descriptions.get("add", "") + + assert "By default" in desc + assert "prod dependency" in desc diff --git a/tests/unit/test_parsing_rich_man.py b/tests/unit/test_parsing_rich_man.py new file mode 100644 index 0000000..b1442b8 --- /dev/null +++ b/tests/unit/test_parsing_rich_man.py @@ -0,0 +1,57 @@ +"""Rich-Click and manpage parsing quality tests (T012).""" + +from __future__ import annotations + +import re + +from crawler.parser import parse_help_output +from crawler.parsers.manpage import parse_manpage + + +def _richclick_expected_flag_lines(text: str) -> int: + """Approximate count of rich-click option lines containing long flags.""" + return sum(1 for line in text.splitlines() if re.search(r"\|\s*--[\w-]+", line)) + + +def test_richclick_extraction_rate_above_half( + langchain_help, + langchain_app_new_help, + langchain_serve_help, +) -> None: + fixtures = [ + ("langchain", "langchain", langchain_help), + ("langchain", "langchain app new", langchain_app_new_help), + ("langchain", "langchain serve", langchain_serve_help), + ] + + extracted_total = 0 + expected_total = 0 + + for cli_name, command_path, text in fixtures: + result = parse_help_output(text, cli_name, command_path) + extracted_total += len(result.command.flags) + expected_total += _richclick_expected_flag_lines(text) + + assert expected_total > 0 + assert extracted_total / expected_total >= 0.5 + + +def test_manpage_examples_extracted_for_npm_style_content() -> None: + text = """NPM-INSTALL(1) npm manual NPM-INSTALL(1) + +NAME + npm-install - Install a package + +SYNOPSIS + npm install [ ...] + +EXAMPLES + npm install + Install dependencies from package.json. + + $ npm install sax + Install the sax package. +""" + cmd, _ = parse_manpage(text, "npm", "npm install") + assert "npm install" in cmd.examples + assert "npm install sax" in cmd.examples diff --git a/tests/unit/test_progressive_disclosure.py b/tests/unit/test_progressive_disclosure.py new file mode 100644 index 0000000..3ec7d71 --- /dev/null +++ b/tests/unit/test_progressive_disclosure.py @@ -0,0 +1,57 @@ +"""Unit tests for progressive disclosure output (T040).""" + +from __future__ import annotations + +import re +from pathlib import Path + +import pytest + +from generator.plugin_generator import ( + compute_stats, + generate_plugin, + generate_skill_md, + load_cli_map, +) + +DOCKER_JSON = Path(__file__).resolve().parents[2] / "output" / "docker.json" + + +def _approx_token_count(text: str) -> int: + """Approximate tokenizer-neutral token count.""" + return len(re.findall(r"\w+|[^\w\s]", text)) + + +@pytest.fixture +def docker_map() -> dict: + if not DOCKER_JSON.exists(): + pytest.skip("output/docker.json not available") + return load_cli_map(DOCKER_JSON) + + +def test_skill_md_compact_view_budget_and_examples(docker_map: dict) -> None: + stats = compute_stats(docker_map) + skill_md = generate_skill_md(docker_map, stats) + + assert _approx_token_count(skill_md) <= 800 + assert skill_md.count("```bash") <= 5 + assert "references/commands.md" in skill_md + assert "references/examples.md" in skill_md + + +def test_references_keep_full_details_while_skill_is_compact( + docker_map: dict, tmp_path: Path +) -> None: + plugin_root = generate_plugin(docker_map, tmp_path, str(DOCKER_JSON)) + skill_md = (plugin_root / "skills" / "cli-docker" / "SKILL.md").read_text(encoding="utf-8") + commands_md = ( + plugin_root / "skills" / "cli-docker" / "references" / "commands.md" + ).read_text(encoding="utf-8") + examples_md = ( + plugin_root / "skills" / "cli-docker" / "references" / "examples.md" + ).read_text(encoding="utf-8") + + assert _approx_token_count(skill_md) <= 800 + assert "docker run" in commands_md + assert commands_md.count("| Flag |") >= 1 + assert examples_md.count("```bash") > skill_md.count("```bash") diff --git a/tests/unit/test_subcommand_help_safety.py b/tests/unit/test_subcommand_help_safety.py new file mode 100644 index 0000000..190bac4 --- /dev/null +++ b/tests/unit/test_subcommand_help_safety.py @@ -0,0 +1,144 @@ +"""Unit tests for non-mutating subcommand help fallback safety (T131).""" + +from __future__ import annotations + +from crawler.detector import detect_subcommand_help +from crawler.models import ExecutionResult + + +class _RecordingExecutor: + def __init__(self, cli_name: str, bare_help_output: str = "") -> None: + self.cli_name = cli_name + self.bare_help_output = bare_help_output + self.calls: list[list[str]] = [] + + def run_with_retry(self, command: list[str], timeout: int | None = None) -> ExecutionResult: + del timeout + self.calls.append(command) + if command == [self.cli_name, "help"]: + return ExecutionResult( + stdout="Usage: tool help\nOptions:\n -h, --help show help", + stderr="", + exit_code=0, + command=command, + ) + return ExecutionResult(stdout="", stderr="", exit_code=1, command=command) + + def run(self, command: list[str], timeout: int | None = None) -> ExecutionResult: + del timeout + self.calls.append(command) + stdout = self.bare_help_output if command == [self.cli_name, "help"] else "" + return ExecutionResult(stdout=stdout, stderr="", exit_code=0 if stdout else 1, command=command) + + +class _MappingExecutor: + def __init__(self, mapping: dict[tuple[str, ...], ExecutionResult]) -> None: + self.mapping = mapping + self.calls: list[list[str]] = [] + + def run_with_retry(self, command: list[str], timeout: int | None = None) -> ExecutionResult: + del timeout + self.calls.append(command) + return self.mapping.get( + tuple(command), + ExecutionResult(stdout="", stderr="", exit_code=1, command=command), + ) + + def run(self, command: list[str], timeout: int | None = None) -> ExecutionResult: + del timeout + self.calls.append(command) + return self.mapping.get( + tuple(command), + ExecutionResult(stdout="", stderr="", exit_code=1, command=command), + ) + + +def test_mutating_subcommand_does_not_use_bare_fallback() -> None: + cli_name = "git.exe" + executor = _RecordingExecutor(cli_name=cli_name) + + detection = detect_subcommand_help( + cli_name=cli_name, + subcommand_path=["init"], + executor=executor, + help_pattern="--help", + ) + + assert detection.pattern == "unknown" + assert "SAFETY_GUARD" in detection.result.stderr + assert [cli_name, "init"] not in executor.calls + assert [cli_name, "init", "--help"] in executor.calls + assert [cli_name, "init", "-h"] in executor.calls + assert [cli_name, "help", "init"] in executor.calls + + +def test_explicit_help_subcommand_can_use_bare_fallback() -> None: + cli_name = "tool" + executor = _RecordingExecutor( + cli_name=cli_name, + bare_help_output="Usage: tool help\nOptions:\n --verbose be verbose", + ) + + detection = detect_subcommand_help( + cli_name=cli_name, + subcommand_path=["help"], + executor=executor, + help_pattern="--help", + ) + + assert detection.pattern == "bare" + assert detection.result.stdout.startswith("Usage: tool help") + assert [cli_name, "help"] in executor.calls + + +def test_version_subcommand_can_use_bare_fallback() -> None: + cli_name = "tool" + executor = _MappingExecutor( + { + (cli_name, "version"): ExecutionResult( + stdout="Usage: tool version\nOptions:\n --short short output", + stderr="", + exit_code=0, + command=[cli_name, "version"], + ) + } + ) + + detection = detect_subcommand_help( + cli_name=cli_name, + subcommand_path=["version"], + executor=executor, + help_pattern="--help", + ) + + assert detection.pattern == "bare" + assert detection.result.stdout.startswith("Usage: tool version") + assert [cli_name, "version"] in executor.calls + + +def test_mutating_subcommand_keeps_auth_required_when_bare_is_blocked() -> None: + cli_name = "git.exe" + auth_result = ExecutionResult( + stdout="", + stderr="authentication required", + exit_code=1, + command=[cli_name, "init", "--help"], + ) + executor = _MappingExecutor( + { + (cli_name, "init", "--help"): auth_result, + (cli_name, "init", "-h"): auth_result, + (cli_name, "help", "init"): auth_result, + } + ) + + detection = detect_subcommand_help( + cli_name=cli_name, + subcommand_path=["init"], + executor=executor, + help_pattern="--help", + ) + + assert detection.pattern == "auth_required" + assert detection.result.stderr.startswith("AUTH_REQUIRED:") + assert [cli_name, "init"] not in executor.calls diff --git a/tests/unit/test_usage_line_option_extraction.py b/tests/unit/test_usage_line_option_extraction.py new file mode 100644 index 0000000..103ddc6 --- /dev/null +++ b/tests/unit/test_usage_line_option_extraction.py @@ -0,0 +1,145 @@ +"""Unit tests for sectionless usage-line option extraction (T091).""" + +from __future__ import annotations + +from crawler.parser import parse_help_output + + +def test_sectionless_python_style_help_recovers_representative_flags() -> None: + help_text = """ +usage: python3 [option] ... [-c cmd | -m mod | file | -] [arg] ... + +Options and arguments (and corresponding environment variables): +-b issue warnings about str(bytes_instance), str(bytearray_instance) +-B don't write .pyc files on import +-c cmd : program passed in as string +-m mod : run library module as script +--check-hash-based-pycs always|default|never : control how Python invalidates hash-based .pyc files +--version : print the Python version number and exit +""" + + result = parse_help_output(help_text, "python3", "python3") + names = [flag.name for flag in result.command.flags] + + assert "-b" in names + assert "-c" in names + assert "-m" in names + assert "--check-hash-based-pycs" in names + assert "--version" in names + assert len(result.command.flags) >= 5 + + +def test_sectionless_fallback_normalizes_leading_colon_in_descriptions() -> None: + help_text = """ +usage: python [option] ... + +Options and arguments (and corresponding environment variables): +-B : don't write .pyc files on import +-E : ignore PYTHON* environment variables +--version : print the Python version number and exit +""" + + result = parse_help_output(help_text, "python", "python") + by_name = {flag.name: flag for flag in result.command.flags} + + assert by_name["-B"].description.startswith("don't write") + assert not by_name["-B"].description.startswith(":") + assert by_name["-E"].description.startswith("ignore PYTHON") + assert not by_name["-E"].description.startswith(":") + + +def test_sectionless_fallback_enriches_long_options_from_colon_style_lines() -> None: + help_text = """ +usage: python3 [option] ... [--help-env] [--help-all] + +Options and arguments (and corresponding environment variables): +--check-hash-based-pycs always|default|never: + control how Python invalidates hash-based .pyc files +--help-env: print help about Python environment variables and exit +--help-all: print complete help information and exit +""" + + result = parse_help_output(help_text, "python3", "python3") + by_name = {flag.name: flag for flag in result.command.flags} + + assert by_name["--help-env"].description.startswith("print help about Python") + assert by_name["--help-all"].description.startswith("print complete help") + assert by_name["--help-env"].type == "bool" + assert by_name["--help-all"].type == "bool" + + check_hash = by_name["--check-hash-based-pycs"] + assert check_hash.description.startswith("control how Python invalidates") + assert check_hash.choices == ["always", "default", "never"] + + +def test_sectionless_fallback_does_not_split_placeholder_colon_as_description() -> None: + help_text = """ +Usage: curl [options...] + -u, --user Server user and password +""" + + result = parse_help_output(help_text, "curl", "curl") + by_name = {flag.name: flag for flag in result.command.flags} + + assert by_name["--user"].description == "Server user and password" + assert by_name["--user"].type == "string" + + +def test_sectionless_fallback_handles_aligned_short_long_alias_columns() -> None: + help_text = """ +Usage: curl [options...] + -u, --user Server user and password +""" + + result = parse_help_output(help_text, "curl", "curl") + by_name = {flag.name: flag for flag in result.command.flags} + + assert "--user" in by_name + assert by_name["--user"].short_name == "-u" + assert by_name["--user"].description == "Server user and password" + + +def test_sectionless_fallback_parses_attached_and_combined_short_forms() -> None: + help_text = """ +Usage: tool [options] + -DNAME=VALUE define a key-value pair + -Xlint:all enable all lint warnings + -O2 set optimization level 2 + -abc enable combined shorthand flags +""" + + result = parse_help_output(help_text, "tool", "tool") + by_name = {flag.name: flag for flag in result.command.flags} + + assert "-D" in by_name + assert by_name["-D"].type == "string" + assert by_name["-D"].description.startswith("define a key-value pair") + + x_flag = by_name.get("-Xlint:all") or by_name.get("-X") + assert x_flag is not None + assert x_flag.type == "string" + assert x_flag.description.startswith("enable all lint warnings") + + assert "-O" in by_name + assert by_name["-O"].type == "string" + assert by_name["-O"].description.startswith("set optimization level 2") + + assert "-abc" in by_name + assert by_name["-abc"].type == "bool" + assert by_name["-abc"].description.startswith("enable combined shorthand flags") + + +def test_sectionless_fallback_keeps_word_like_short_tokens_without_collapsing() -> None: + help_text = """ +Usage: java [options] + -help print help + -classpath set class search path +""" + + result = parse_help_output(help_text, "java", "java") + by_name = {flag.name: flag for flag in result.command.flags} + + assert "-help" in by_name + assert "-classpath" in by_name + assert "-h" not in by_name + assert "-c" not in by_name diff --git a/tests/unit/test_version_detection_fallback.py b/tests/unit/test_version_detection_fallback.py new file mode 100644 index 0000000..58c7090 --- /dev/null +++ b/tests/unit/test_version_detection_fallback.py @@ -0,0 +1,264 @@ +"""Unit tests for robust version detection fallback (T084).""" + +from __future__ import annotations + +from crawler.models import ExecutionResult +from crawler.version import _parse_version, detect_version + + +class _FakeExecutor: + def __init__(self, results: dict[str, ExecutionResult]) -> None: + self._results = results + + def run(self, command: list[str], timeout: int | None = None) -> ExecutionResult: + del timeout + key = " ".join(command) + return self._results.get( + key, + ExecutionResult(stdout="", stderr="", exit_code=1, command=command), + ) + + +def test_parse_version_for_yq_style_line() -> None: + text = "yq (https://github.com/mikefarah/yq/) version v4.44.3" + assert _parse_version(text) == "4.44.3" + + +def test_detect_version_skips_zero_placeholder_and_uses_next_candidate() -> None: + cli_name = "yq" + executor = _FakeExecutor( + { + "yq --version": ExecutionResult( + stdout="yq 0.0.0", + stderr="", + exit_code=0, + command=["yq", "--version"], + ), + "yq -V": ExecutionResult( + stdout="jq-1.7", + stderr="", + exit_code=0, + command=["yq", "-V"], + ), + } + ) + + assert detect_version(cli_name, executor) == "1.7" + + +def test_detect_version_returns_empty_when_only_zero_placeholders() -> None: + cli_name = "yq" + executor = _FakeExecutor( + { + "yq --version": ExecutionResult( + stdout="yq 0.0.0", + stderr="", + exit_code=0, + command=["yq", "--version"], + ), + "yq -V": ExecutionResult( + stdout="yq 0.0.0", + stderr="", + exit_code=0, + command=["yq", "-V"], + ), + "yq version": ExecutionResult( + stdout="yq 0.0.0", + stderr="", + exit_code=0, + command=["yq", "version"], + ), + "yq -v": ExecutionResult( + stdout="yq 0.0.0", + stderr="", + exit_code=0, + command=["yq", "-v"], + ), + } + ) + + assert detect_version(cli_name, executor) == "" + + +def test_detect_version_skips_suffix_placeholder_and_uses_real_version() -> None: + cli_name = "tool" + executor = _FakeExecutor( + { + "tool --version": ExecutionResult( + stdout="tool version 0.0.0-dev", + stderr="", + exit_code=0, + command=["tool", "--version"], + ), + "tool -V": ExecutionResult( + stdout="tool version v2.4.1", + stderr="", + exit_code=0, + command=["tool", "-V"], + ), + } + ) + + assert detect_version(cli_name, executor) == "2.4.1" + + +def test_detect_version_treats_unknown_placeholder_as_missing() -> None: + cli_name = "tool" + executor = _FakeExecutor( + { + "tool --version": ExecutionResult( + stdout="tool unknown", + stderr="", + exit_code=0, + command=["tool", "--version"], + ), + "tool -V": ExecutionResult( + stdout="", + stderr="", + exit_code=1, + command=["tool", "-V"], + ), + "tool version": ExecutionResult( + stdout="", + stderr="", + exit_code=1, + command=["tool", "version"], + ), + "tool -v": ExecutionResult( + stdout="", + stderr="", + exit_code=1, + command=["tool", "-v"], + ), + } + ) + + assert detect_version(cli_name, executor) == "" + + +def test_detect_version_prefers_cli_version_over_dependency_version_in_same_output() -> None: + cli_name = "yq" + executor = _FakeExecutor( + { + "yq --version": ExecutionResult( + stdout="jq-1.7\nyq version v4.44.3", + stderr="", + exit_code=0, + command=["yq", "--version"], + ), + } + ) + + assert detect_version(cli_name, executor) == "4.44.3" + + +def test_detect_version_prefers_cli_leading_name_version_over_dependency_pattern() -> None: + cli_name = "yq" + executor = _FakeExecutor( + { + "yq --version": ExecutionResult( + stdout="jq-1.7\nyq 4.44.3", + stderr="", + exit_code=0, + command=["yq", "--version"], + ), + } + ) + + assert detect_version(cli_name, executor) == "4.44.3" + + +def test_detect_version_prefers_structural_cli_version_over_contextual_dependency_version() -> None: + cli_name = "tool" + executor = _FakeExecutor( + { + "tool --version": ExecutionResult( + stdout="tool uses libfoo version 1.2.3\ntool version 2.0.0", + stderr="", + exit_code=0, + command=["tool", "--version"], + ), + } + ) + + assert detect_version(cli_name, executor) == "2.0.0" + + +def test_detect_version_does_not_treat_prefixed_name_as_same_cli() -> None: + cli_name = "uv" + executor = _FakeExecutor( + { + "uv --version": ExecutionResult( + stdout="uvx version 0.1.0", + stderr="", + exit_code=0, + command=["uv", "--version"], + ), + } + ) + + assert detect_version(cli_name, executor) == "" + + +def test_detect_version_ignores_cli_name_inside_hyphenated_foreign_token() -> None: + cli_name = "jq" + executor = _FakeExecutor( + { + "jq --version": ExecutionResult( + stdout="yq-jq-helper version 1.2.3", + stderr="", + exit_code=0, + command=["jq", "--version"], + ), + } + ) + + assert detect_version(cli_name, executor) == "" + + +def test_detect_version_does_not_treat_prefixed_name_with_at_version_as_same_cli() -> None: + cli_name = "uv" + executor = _FakeExecutor( + { + "uv --version": ExecutionResult( + stdout="uvx@0.1.0", + stderr="", + exit_code=0, + command=["uv", "--version"], + ), + } + ) + + assert detect_version(cli_name, executor) == "" + + +def test_detect_version_ignores_foreign_version_v_prefix_line() -> None: + cli_name = "jq" + executor = _FakeExecutor( + { + "jq --version": ExecutionResult( + stdout="yq-jq-helper version v1.2.3", + stderr="", + exit_code=0, + command=["jq", "--version"], + ), + } + ) + + assert detect_version(cli_name, executor) == "" + + +def test_detect_version_prefers_structural_cli_v_line_over_contextual_at_version() -> None: + cli_name = "tool" + executor = _FakeExecutor( + { + "tool --version": ExecutionResult( + stdout="tool uses libfoo@1.2.3\ntool v2.0.0", + stderr="", + exit_code=0, + command=["tool", "--version"], + ), + } + ) + + assert detect_version(cli_name, executor) == "2.0.0" From 4e3d01f00885c3f5e5069ba159e88efa6fc45f62 Mon Sep 17 00:00:00 2001 From: NUNO MIGUEL DA SILVA SALVACAO Date: Mon, 16 Feb 2026 12:28:28 +0000 Subject: [PATCH 2/5] feat(config): add inventory audit command and config policy docs --- README.md | 26 +++++ config.yaml | 2 +- pyproject.toml | 1 + src/config/audit.py | 163 ++++++++++++++++++++++++++++++ tests/unit/test_config_audit.py | 174 ++++++++++++++++++++++++++++++++ 5 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 src/config/audit.py create mode 100644 tests/unit/test_config_audit.py diff --git a/README.md b/README.md index 1b4ddd2..2f3b3e7 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,11 @@ clis: discovery_command: my-tool plugin list ``` +**Minimal override policy (recommended)**: +- Keep only operational overrides that differ from defaults: `environment`, `help_pattern`, `max_depth`, `max_concurrent`, `plugins.discovery_command`. +- Do **not** mirror inventory in `config.yaml` (avoid listing every crawled/generated CLI just to keep a catalog). +- Use `output/*.json` and `plugins/cli-*` as inventory sources of truth. + ### Step 2: Crawl ```bash @@ -229,6 +234,27 @@ uv run python scripts/generate_plugin.py output/docker.json uv run python cli_crawler.py --all --config config.yaml ``` +### Config Audit (Inventory Drift) + +Audit drift between `config.yaml`, `output/*.json`, and `plugins/cli-*`: + +```bash +config-audit --config config.yaml --output-dir output --plugins-dir plugins --report output/config-audit.json +``` + +Alternative without installing console scripts: + +```bash +uv run python -m config.audit --config config.yaml --output-dir output --plugins-dir plugins --report output/config-audit.json +``` + +This report includes: +- `missing_in_config` +- `stale_in_config` +- `missing_output` +- `missing_plugin` +- `suggested_minimal_overrides` + ## Design Decisions - **Python stdlib only** -- No external dependencies for crawler or generator diff --git a/config.yaml b/config.yaml index 1d1cccf..125cee7 100644 --- a/config.yaml +++ b/config.yaml @@ -4,7 +4,7 @@ defaults: max_concurrent: 5 retry: 1 environment: wsl - raw_threshold: 10240 + raw_threshold: 10000 clis: git: diff --git a/pyproject.toml b/pyproject.toml index ff7f32c..a3c7cf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ Issues = "https://github.com/nsalvacao/cli-plugins/issues" [project.scripts] cli-crawler = "crawler.pipeline:main" generate-plugin = "generator.plugin_generator:main" +config-audit = "config.audit:main" [tool.hatch.build.targets.wheel] packages = ["src/crawler", "src/generator", "src/config", "src/lib"] diff --git a/src/config/audit.py b/src/config/audit.py new file mode 100644 index 0000000..f092879 --- /dev/null +++ b/src/config/audit.py @@ -0,0 +1,163 @@ +"""Config inventory auditing for config/output/plugins drift detection.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + +from crawler.config import CrawlerConfig, load_config + + +def _load_crawler_config(config_path: Path) -> CrawlerConfig: + if config_path.exists(): + return load_config(str(config_path)) + return CrawlerConfig() + + +def _collect_crawled_clis(output_dir: Path) -> set[str]: + if not output_dir.exists(): + return set() + clis: set[str] = set() + for path in output_dir.glob("*.json"): + if path.name.endswith(".raw.json"): + continue + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + continue + + cli_name = payload.get("cli_name") + if isinstance(cli_name, str) and cli_name.strip(): + clis.add(cli_name.strip()) + return clis + + +def _collect_plugin_clis(plugins_dir: Path) -> set[str]: + if not plugins_dir.exists(): + return set() + clis: set[str] = set() + for child in plugins_dir.iterdir(): + if not child.is_dir() or not child.name.startswith("cli-"): + continue + cli_name = child.name[len("cli-") :] + if cli_name: + clis.add(cli_name) + return clis + + +def _suggest_minimal_overrides(config: CrawlerConfig) -> tuple[dict[str, dict[str, Any]], list[str]]: + """Keep only operational overrides that differ from defaults.""" + defaults = config.defaults + suggestions: dict[str, dict[str, Any]] = {} + no_overrides: list[str] = [] + + for cli_name, cfg in sorted(config.clis.items()): + overrides: dict[str, Any] = {} + + if cfg.environment != defaults.environment: + overrides["environment"] = cfg.environment + if cfg.help_pattern: + overrides["help_pattern"] = cfg.help_pattern + if cfg.max_depth != defaults.max_depth: + overrides["max_depth"] = cfg.max_depth + if cfg.max_concurrent != defaults.max_concurrent: + overrides["max_concurrent"] = cfg.max_concurrent + if cfg.plugins and cfg.plugins.discovery_command: + overrides["plugins"] = {"discovery_command": cfg.plugins.discovery_command} + + if overrides: + suggestions[cli_name] = overrides + else: + no_overrides.append(cli_name) + + return suggestions, no_overrides + + +def build_config_audit_report( + config_path: Path, + output_dir: Path, + plugins_dir: Path, +) -> dict[str, Any]: + """Build a config drift report across config, output, and plugins inventory.""" + config = _load_crawler_config(config_path) + + configured = set(config.clis.keys()) + crawled = _collect_crawled_clis(output_dir) + generated_plugins = _collect_plugin_clis(plugins_dir) + discovered = crawled | generated_plugins + + suggested_overrides, entries_without_overrides = _suggest_minimal_overrides(config) + + report: dict[str, Any] = { + "configured_clis": sorted(configured), + "crawled_clis": sorted(crawled), + "plugin_clis": sorted(generated_plugins), + "missing_in_config": sorted(discovered - configured), + "stale_in_config": sorted(configured - discovered), + "missing_output": sorted(configured - crawled), + "missing_plugin": sorted(configured - generated_plugins), + "suggested_minimal_overrides": suggested_overrides, + "config_entries_without_overrides": sorted(entries_without_overrides), + } + + return report + + +def write_config_audit_report(report: dict[str, Any], report_path: Path) -> None: + report_path.parent.mkdir(parents=True, exist_ok=True) + report_path.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + +def _build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="config-audit", + description="Audit drift between config.yaml, output/*.json, and plugins/cli-* inventory.", + ) + parser.add_argument( + "--config", + type=Path, + default=Path("config.yaml"), + help="Path to config YAML (default: config.yaml)", + ) + parser.add_argument( + "--output-dir", + type=Path, + default=Path("output"), + help="Directory with crawl JSON files (default: output/)", + ) + parser.add_argument( + "--plugins-dir", + type=Path, + default=Path("plugins"), + help="Directory with generated plugins (default: plugins/)", + ) + parser.add_argument( + "--report", + type=Path, + default=Path("output/config-audit.json"), + help="JSON report output path (default: output/config-audit.json)", + ) + return parser + + +def main() -> None: + parser = _build_parser() + args = parser.parse_args() + + report = build_config_audit_report(args.config, args.output_dir, args.plugins_dir) + write_config_audit_report(report, args.report) + + print(f"Config audit report written to: {args.report}") + print(f" configured_clis: {len(report['configured_clis'])}") + print(f" crawled_clis: {len(report['crawled_clis'])}") + print(f" plugin_clis: {len(report['plugin_clis'])}") + print(f" missing_in_config: {len(report['missing_in_config'])}") + print(f" stale_in_config: {len(report['stale_in_config'])}") + print(f" missing_output: {len(report['missing_output'])}") + print(f" missing_plugin: {len(report['missing_plugin'])}") + + +if __name__ == "__main__": + main() diff --git a/tests/unit/test_config_audit.py b/tests/unit/test_config_audit.py new file mode 100644 index 0000000..7c0ef70 --- /dev/null +++ b/tests/unit/test_config_audit.py @@ -0,0 +1,174 @@ +"""Tests for config inventory auditing and minimal override suggestions.""" + +from __future__ import annotations + +import json +from pathlib import Path + +from config.audit import build_config_audit_report, write_config_audit_report + + +def _write(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + + +def test_build_config_audit_report_handles_empty_dirs(tmp_path: Path) -> None: + config_path = tmp_path / "config.yaml" + output_dir = tmp_path / "output" + plugins_dir = tmp_path / "plugins" + + _write( + config_path, + """defaults: + timeout: 5 + max_depth: 5 + max_concurrent: 5 + environment: wsl +clis: +""", + ) + output_dir.mkdir(parents=True, exist_ok=True) + plugins_dir.mkdir(parents=True, exist_ok=True) + + report = build_config_audit_report(config_path, output_dir, plugins_dir) + + assert report["missing_in_config"] == [] + assert report["stale_in_config"] == [] + assert report["missing_output"] == [] + assert report["missing_plugin"] == [] + assert report["suggested_minimal_overrides"] == {} + assert report["config_entries_without_overrides"] == [] + + +def test_build_config_audit_report_detects_inventory_drift(tmp_path: Path) -> None: + config_path = tmp_path / "config.yaml" + output_dir = tmp_path / "output" + plugins_dir = tmp_path / "plugins" + + _write( + config_path, + """defaults: + timeout: 5 + max_depth: 5 + max_concurrent: 5 + environment: wsl +clis: + git: + max_depth: 7 + docker: + group: containers +""", + ) + + _write( + output_dir / "git.json", + json.dumps({"cli_name": "git", "commands": {}, "metadata": {}, "global_flags": []}), + ) + _write( + output_dir / "gh.json", + json.dumps({"cli_name": "gh", "commands": {}, "metadata": {}, "global_flags": []}), + ) + _write(output_dir / "gh.raw.json", "{}") + + (plugins_dir / "cli-git").mkdir(parents=True, exist_ok=True) + (plugins_dir / "cli-npm").mkdir(parents=True, exist_ok=True) + (plugins_dir / "not-a-plugin").mkdir(parents=True, exist_ok=True) + + report = build_config_audit_report(config_path, output_dir, plugins_dir) + + assert report["configured_clis"] == ["docker", "git"] + assert report["crawled_clis"] == ["gh", "git"] + assert report["plugin_clis"] == ["git", "npm"] + + assert report["missing_in_config"] == ["gh", "npm"] + assert report["stale_in_config"] == ["docker"] + assert report["missing_output"] == ["docker"] + assert report["missing_plugin"] == ["docker"] + + assert report["suggested_minimal_overrides"] == {"git": {"max_depth": 7}} + assert report["config_entries_without_overrides"] == ["docker"] + + +def test_build_config_audit_report_tracks_environment_and_plugin_discovery_overrides( + tmp_path: Path, +) -> None: + config_path = tmp_path / "config.yaml" + output_dir = tmp_path / "output" + plugins_dir = tmp_path / "plugins" + + _write( + config_path, + """defaults: + timeout: 5 + max_depth: 5 + max_concurrent: 5 + environment: wsl +clis: + gh: + plugins: + discovery_command: gh extension list + gemini: + environment: windows +""", + ) + output_dir.mkdir(parents=True, exist_ok=True) + plugins_dir.mkdir(parents=True, exist_ok=True) + + report = build_config_audit_report(config_path, output_dir, plugins_dir) + overrides = report["suggested_minimal_overrides"] + + assert overrides["gh"]["plugins"]["discovery_command"] == "gh extension list" + assert overrides["gemini"]["environment"] == "windows" + + +def test_write_config_audit_report_writes_json(tmp_path: Path) -> None: + report_path = tmp_path / "output" / "config-audit.json" + report = { + "configured_clis": ["git"], + "crawled_clis": ["git"], + "plugin_clis": ["git"], + "missing_in_config": [], + "stale_in_config": [], + "missing_output": [], + "missing_plugin": [], + "suggested_minimal_overrides": {}, + "config_entries_without_overrides": [], + } + + write_config_audit_report(report, report_path) + loaded = json.loads(report_path.read_text(encoding="utf-8")) + assert loaded["configured_clis"] == ["git"] + + +def test_build_config_audit_report_ignores_non_climap_json_files(tmp_path: Path) -> None: + config_path = tmp_path / "config.yaml" + output_dir = tmp_path / "output" + plugins_dir = tmp_path / "plugins" + + _write( + config_path, + """defaults: + timeout: 5 + max_depth: 5 + max_concurrent: 5 + environment: wsl +clis: + git: +""", + ) + + _write( + output_dir / "git.json", + json.dumps({"cli_name": "git", "commands": {}, "metadata": {}, "global_flags": []}), + ) + _write( + output_dir / "config-audit.json", + json.dumps({"missing_in_config": [], "stale_in_config": []}), + ) + (plugins_dir / "cli-git").mkdir(parents=True, exist_ok=True) + + report = build_config_audit_report(config_path, output_dir, plugins_dir) + + assert report["crawled_clis"] == ["git"] + assert report["missing_in_config"] == [] From 6f1d0b02a86c5d0d099b5dfccd54e1f8b9acd4f2 Mon Sep 17 00:00:00 2001 From: NUNO MIGUEL DA SILVA SALVACAO Date: Mon, 16 Feb 2026 12:28:46 +0000 Subject: [PATCH 3/5] docs(workflow): update agent loop and backlog tracking --- .gitignore | 1 + AGENTS.md | 3 +- specs/001-cli-plugins-base/tasks.md | 262 ++++++++++++++++++++-------- 3 files changed, 197 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index 434770a..253e837 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ Requisitos.md crystalline-imagining-forest.md .ideas/ .dev/ +.productivity/ /.venv-wsl diff --git a/AGENTS.md b/AGENTS.md index 09d4724..aad98a8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,7 +54,8 @@ Every agent must follow this **10-step cycle** for every task: * Pick a CLI tool *not yet tested* (check D:\GitHub\cli-plugins\output) and available in WIN or WSL environment. * Run `cli-crawler ` -> `generate-plugin `. * Verify the output manually in D:\GitHub\cli-plugins\output and D:\GitHub\cli-plugins\plugins to prove the solution works in the wild. -8. **MARK TASK**: Check off `[x]` in `specs/001-cli-plugins-base/tasks.md` (or relevant task file). + * Verify the quality of the output and the generated plugin against the main goal of the project. If the quality is not good, go back to step 5 ou defer creating new improvement tasks in `specs/001-cli-plugins-base/tasks.md` and `CLAUDE.local.md`. +8. **MARK TASK**: Check off `[x]` in `specs/001-cli-plugins-base/tasks.md` and `CLAUDE.local.md` (or relevant task files). 9. **ATOMIC COMMIT**: Commit *only* related changes with a conventional message. 10. **PUSH + PR**: Push to branch, open PR (if applicable), and **WAIT FOR REVIEW**. diff --git a/specs/001-cli-plugins-base/tasks.md b/specs/001-cli-plugins-base/tasks.md index c4bf551..dff3310 100644 --- a/specs/001-cli-plugins-base/tasks.md +++ b/specs/001-cli-plugins-base/tasks.md @@ -27,15 +27,15 @@ **Purpose**: Project initialization, known structure bugs, and CI/CD — MUST complete before any user story work. CI/CD moved here from Phase 6 per constitution §CI/CD Automatizado and execution-plan A7 ("BLOCKS EVERYTHING"). -- [x] T001 Create project directories: `src/crawler`, `src/generator`, `src/config`, `src/lib`, `tests/unit`, `tests/integration`, `tests/end_to_end` — **decision**: migrate existing `crawler/` → `src/crawler/` (preserve git history with `git mv`) -- [x] T002 Initialize Python project with `pyproject.toml` in repository root *(exists — bugs fixed in T038)* -- [x] T038 [NEW] Fix known `pyproject.toml` bugs: move `classifiers` from `[project.urls]` to `[project]`; fix `readme` path from `docs/README.md` to `README.md`; add `[build-system]` section with `hatchling` — verify with `uv build && twine check dist/*` *(Blocker B1 — evaluation-results_v2)* -- [x] T039 [NEW] Register `generate-plugin` as CLI entry point in `pyproject.toml` `[project.scripts]`: `generate-plugin = "generator.plugin_generator:main"` — verify `pip install -e . && generate-plugin --help` works *(Blocker B2)* -- [x] T003 Configure basic `config.yaml` in `src/config/config.yaml` -- [x] T004 [P] Configure linting and formatting (Black, Ruff) in `pyproject.toml` — add `[tool.black]` and `[tool.ruff]` sections; verify `ruff check src/ tests/` runs clean -- [x] T005 [P] Setup Pytest for testing in `pyproject.toml` — add `[tool.pytest.ini_options]` with testpaths, markers for unit/integration/e2e -- [x] T032 [MOVED from Phase 6] Create CI/CD pipeline: `.github/workflows/ci.yml` — pytest matrix on Python 3.11+3.12, ruff lint, black check; runs on push and PR; add CI badge to `README.md` *(constitution §CI/CD Automatizado; execution-plan A7)* -- [x] T066 [P] [NEW] Add `__version__` to package via `src/__init__.py` sourced from `pyproject.toml`; add `--version` flag to `cli-crawler` and `generate-plugin` entry points *(evaluation-results A5, L3)* +- [x] **T001 Createeeee project directories** - `src/crawler`, `src/generator`, `src/config`, `src/lib`, `tests/unit`, `tests/integration`, `tests/end_to_end` — decision: migrate existing `crawler/` → `src/crawler/` (preserve git history with `git mv`) +- [x] **T002 Initialize pyproject.toml** - Python project in repository root (exists — bugs fixed in T038) +- [x] **T038 Fix known `pyproject.toml` bugs** - move `classifiers` from `[project.urls]` to `[project]`; fix `readme` path from `docs/README.md` to `README.md`; add `[build-system]` section with `hatchling` — verify with `uv build && twine check dist/*` *(Blocker B1 — evaluation-results_v2)* ([NEW]) +- [x] **T039 Register `generate-plugin` as CLI entry point** - in `pyproject.toml` `[project.scripts]`: `generate-plugin = "generator.plugin_generator:main"` — verify `pip install -e . && generate-plugin --help` works *(Blocker B2)* ([NEW]) +- [x] **T003 Configure basic `config.yaml`** - in `src/config/config.yaml` +- [x] **T004 Configure linting and formatting (Black, Ruff)** - in `pyproject.toml` — add `[tool.black]` and `[tool.ruff]` sections; verify `ruff check src/ tests/` runs clean ([P]) +- [x] **T005 Setup Pytest for testing** - in `pyproject.toml` — add `[tool.pytest.ini_options]` with testpaths, markers for unit/integration/e2e ([P]) +- [x] **T032 Create CI/CD pipeline** - `.github/workflows/ci.yml` — pytest matrix on Python 3.11+3.12, ruff lint, black check; runs on push and PR; add CI badge to `README.md` *(constitution §CI/CD Automatizado; execution-plan A7)* ([MOVED from Phase 6]) +- [x] **T066 Add __version__ and --version flags** - via `src/__init__.py` sourced from `pyproject.toml`; add `--version` flag to `cli-crawler` and `generate-plugin` entry points *(evaluation-results A5, L3)* ([P] [NEW]) **Checkpoint — Phase 1 AC**: `pip install -e '.[dev]'` succeeds; `pytest` discovers tests; `ruff check src/` exits 0; `npx ci` green; `cli-crawler --version` outputs version string. @@ -47,19 +47,19 @@ **⚠️ CRITICAL**: No user story work can begin until this phase is complete. -- [x] T006 Create CLIMap schema (`CLI`, `CLIMap`, `Command`, `Flag`, `Plugin` entities) in `src/crawler/models.py` — reconcile with data-model.md (see T044) -- [x] T044 [P] [NEW] Reconcile schema inconsistency: `data-model.md` defines `Flag.long_name` + `Flag.short_name` separately; `crawler/models.py` uses `name` + `short`. Decide canonical naming, update both `models.py` and `data-model.md` to match. Add migration note. *(M3)* -- [x] T007 Implement safe subprocess execution utility in `src/lib/subprocess_utils.py` — `subprocess.run` with tokenised args only, no `shell=True`, timeout enforcement, SAFE_ENV (disable colour/pager) -- [x] T067 [P] [NEW] Security review: audit `crawler/executor._build_command` Windows PowerShell path — confirm no shell-injection risk when joining command array for PowerShell; add unit test covering edge case *(evaluation-results §5 WARN, L4)* -- [x] T008 Configure basic logging infrastructure in `src/lib/logger.py` — **use Python stdlib `logging` module only; NO external packages** (FR-008, constitution §Zero Dependencies); structured levels (DEBUG/INFO/WARNING/ERROR); configurable via env var `CLI_PLUGINS_LOG_LEVEL` -- [ ] T028 [MOVED from Phase 6] [P] Implement semantic keyword generation for plugins in `src/generator/plugin_generator.py` — keywords derived from CLI name + top command group names + domain terms extracted from description (NOT first words of command descriptions) *(FR-012; Blocker B5)* -- [ ] T029 [MOVED from Phase 6] [P] Implement author configuration for plugins in `src/generator/plugin_generator.py` — `--author` CLI flag or `CLI_PLUGINS_AUTHOR` env var; omit `author` field entirely when not specified; community generators must not carry hardcoded attribution *(FR-011; Blocker B6)* -- [ ] T040 [NEW] Implement progressive disclosure in `src/generator/plugin_generator.py` — SKILL.md compact view (≤800 tokens: top-level commands + global flags + 5 examples); `references/commands.md` (full flag tables, loaded on demand); `references/examples.md` (all examples, loaded on demand) *(FR-004; constitution §Auto-Geração e Otimização)* -- [ ] T041 [NEW] Improve Rich-Click parser in `src/crawler/parsers/` to correctly extract flags from box-drawing (`╭─╮│`) formatted output — currently extracts 7 flags from 51 commands (cli-claude-flow 2.55/5); target: >50% extraction rate for Rich-Click CLIs *(SC-003; evaluation-results I4; CRITICAL C2)* -- [ ] T042 [P] [NEW] Improve man page example extraction in `src/crawler/parsers/manpage.py` — fix zero-examples issue for npm-style man page CLIs; target: extract documented examples from `EXAMPLES` section *(SC-003; evaluation-results §1.4)* -- [ ] T043 [P] [NEW] Add thread-safety locks to `CrawlState` in `src/crawler/discovery.py` — `threading.Lock()` on `visited`, `errors`, `warnings` sets to prevent race conditions under `ThreadPoolExecutor` *(SC-008; evaluation-results R8)* -- [ ] T045 [P] [NEW] Fix error messages captured as descriptions — extend `_clean_description` in `src/crawler/parser.py` with patterns: runtime errors (`fatal:`, `error:`, `accepts N arg(s)`), circular names (description == command name), state messages (`already initialized`) *(Blocker B3; evaluation-results §8 B3)* -- [ ] T046 [P] [NEW] Fix git plugin code fence formatting in `src/generator/plugin_generator.py` — ensure description text does not leak outside fenced ` ``` ` blocks in `examples.md` output *(Blocker B4; evaluation-results §8 B4)* +- [x] **T006 Create CLIMap schema** - CLI, CLIMap, Command, Flag, Plugin entities in `src/crawler/models.py` — reconcile with data-model.md (see T044) +- [x] **T044 Reconcile schema inconsistency** - `data-model.md` defines `Flag.long_name` + `Flag.short_name` separately; `crawler/models.py` uses `name` + `short`. Decide canonical naming, update both `models.py` and `data-model.md` to match. Add migration note. *(M3)* ([P] [NEW]) +- [x] **T007 Implement safe subprocess execution utility** - in `src/lib/subprocess_utils.py` — `subprocess.run` with tokenised args only, no `shell=True`, timeout enforcement, SAFE_ENV (disable colour/pager) +- [x] **T067 Security review** - audit `crawler/executor._build_command` Windows PowerShell path — confirm no shell-injection risk when joining command array for PowerShell; add unit test covering edge case *(evaluation-results §5 WARN, L4)* ([P] [NEW]) +- [x] **T008 Configure basic logging infrastructure** - in `src/lib/logger.py` — use Python stdlib `logging` module only; NO external packages (FR-008, constitution §Zero Dependencies); structured levels (DEBUG/INFO/WARNING/ERROR); configurable via env var `CLI_PLUGINS_LOG_LEVEL` +- [x] **T028 Implement semantic keyword generation for plugins** - in `src/generator/plugin_generator.py` — keywords derived from CLI name + top command group names + domain terms extracted from description (NOT first words of command descriptions) *(FR-012; Blocker B5)* ([MOVED from Phase 6] [P]) +- [x] **T029 Implement author configuration for plugins** - in `src/generator/plugin_generator.py` — `--author` CLI flag or `CLI_PLUGINS_AUTHOR` env var; omit `author` field entirely when not specified; community generators must not carry hardcoded attribution *(FR-011; Blocker B6)* ([MOVED from Phase 6] [P]) +- [x] **T040 Implement progressive disclosure** - in `src/generator/plugin_generator.py` — SKILL.md compact view (≤800 tokens: top-level commands + global flags + 5 examples); `references/commands.md` (full flag tables, loaded on demand); `references/examples.md` (all examples, loaded on demand) *(FR-004; constitution §Auto-Geração e Otimização)* ([NEW]) +- [x] **T041 Improve Rich-Click parser** - in `src/crawler/parsers/` to correctly extract flags from box-drawing (`╭─╮│`) formatted output — currently extracts 7 flags from 51 commands (cli-claude-flow 2.55/5); target: >50% extraction rate for Rich-Click CLIs *(SC-003; evaluation-results I4; CRITICAL C2)* ([NEW]) +- [x] **T042 Improve man page example extraction** - in `src/crawler/parsers/manpage.py` — fix zero-examples issue for npm-style man page CLIs; target: extract documented examples from `EXAMPLES` section *(SC-003; evaluation-results §1.4)* ([P] [NEW]) +- [x] **T043 Add thread-safety locks to `CrawlState`** - in `src/crawler/discovery.py` — `threading.Lock()` on `visited`, `errors`, `warnings` sets to prevent race conditions under `ThreadPoolExecutor` *(SC-008; evaluation-results R8)* ([P] [NEW]) +- [x] **T045 Fix error messages captured as descriptions** - extend `_clean_description` in `src/crawler/parser.py` with patterns: runtime errors (`fatal:`, `error:`, `accepts N arg(s)`), circular names (description == command name), state messages (`already initialized`) *(Blocker B3; evaluation-results §8 B3)* ([P] [NEW]) +- [x] **T046 Fix git plugin code fence formatting** - in `src/generator/plugin_generator.py` — ensure description text does not leak outside fenced ` ``` ` blocks in `examples.md` output *(Blocker B4; evaluation-results §8 B4)* ([P] [NEW]) **Checkpoint — Phase 2 AC**: All schema entities importable; subprocess utils pass security tests; Rich-Click flag extraction rate >50% for `claude-flow`; `plugin.json` has semantic keywords and no hardcoded author; SKILL.md + `references/` structure generated correctly for docker; zero error-message descriptions in regenerated git/gh plugins. @@ -73,26 +73,73 @@ ### Tests for User Story 1 (write FIRST — ensure they FAIL before implementation) -- [ ] T009 [P] [US1] Unit test for initial CLI crawling (e.g., `git --help`) in `tests/unit/test_cli_crawler_basic.py` -- [ ] T010 [P] [US1] Unit test for basic flag extraction in `tests/unit/test_flag_parsing_basic.py` -- [ ] T011 [P] [US1] Unit test for basic command hierarchy parsing in `tests/unit/test_command_parsing_basic.py` -- [ ] T012 [P] [US1] Unit test for Rich-Click/man page parsing quality in `tests/unit/test_parsing_rich_man.py` — AC: Rich-Click extraction rate >50%; man page examples extracted for npm-equivalent fixture -- [ ] T047 [P] [NEW] [US1] Unit test for EC-01: CLI without standard `--help` → parser emits warning, returns partial CLIMap, does not crash; in `tests/unit/test_edge_case_no_help.py` -- [ ] T048 [P] [NEW] [US1] Unit test for EC-02: CLI requiring auth for `--help` (simulated via timeout/exit-code) → clear error message, no hang; in `tests/unit/test_edge_case_auth_help.py` -- [ ] T049 [P] [NEW] [US1] Unit test for EC-05: `--help` output >10 000 lines → progressive loading triggered, SKILL.md compact, references/ on-demand; in `tests/unit/test_edge_case_long_help.py` +- [x] **T009 Unit test for initial CLI crawling (e.g., `git --help`)** - in `tests/unit/test_cli_crawler_basic.py` ([P] [US1]) +- [x] **T010 Unit test for basic flag extraction** - in `tests/unit/test_flag_parsing_basic.py` ([P] [US1]) +- [x] **T011 Unit test for basic command hierarchy parsing** - in `tests/unit/test_command_parsing_basic.py` ([P] [US1]) +- [x] **T012 Unit test for Rich-Click/man page parsing quality** - in `tests/unit/test_parsing_rich_man.py` — AC: Rich-Click extraction rate >50%; man page examples extracted for npm-equivalent fixture ([P] [US1]) +- [x] **T047 Unit test for EC-01** - CLI without standard `--help` → parser emits warning, returns partial CLIMap, does not crash; in `tests/unit/test_edge_case_no_help.py` ([P] [NEW] [US1]) +- [x] **T048 Unit test for EC-02** - CLI requiring auth for `--help` (simulated via timeout/exit-code) → clear error message, no hang; in `tests/unit/test_edge_case_auth_help.py` ([P] [NEW] [US1]) +- [x] **T049 Unit test for EC-05** - `--help` output >10 000 lines → progressive loading triggered, SKILL.md compact, references/ on-demand; in `tests/unit/test_edge_case_long_help.py` ([P] [NEW] [US1]) +- [x] **T079 Unit test for pnpm grouped-help format** - in `tests/unit/test_parser_pnpm_grouped_help.py` — AC: parse sentence-case section headers (e.g., `Manage your dependencies:`), extract comma-alias command lines (`i, install`), and recover wrapped descriptions ([P] [NEW] [US1]) +- [x] **T082 Unit test for embedded-help flag deduplication (yq+jq style)** - in `tests/unit/test_flag_dedup_embedded_help.py` — AC: duplicate long flags (`--help`, `--version`) are deduplicated with deterministic precedence ([P] [NEW] [US1]) +- [x] **T084 Unit test for robust version detection fallback** - in `tests/unit/test_version_detection_fallback.py` — AC: yq-style version outputs parse correctly (avoid `0.0.0` default when version text is present) ([P] [NEW] [US1]) +- [ ] **T086 Unit test for recursion loop guard in discovery** - (`tests/unit/test_discovery_loop_guard.py`) — AC: self-referential trees (bun-style) stay bounded; crawl terminates without combinatorial explosion ([P] [NEW] [US1]) +- [ ] **T088 Unit test for examples fallback on flag-only CLIs** - in `tests/unit/test_examples_fallback_flag_only.py` — AC: generated `examples.md`/SKILL compact section are non-empty for CLIs with zero subcommands (node-style) ([P] [NEW] [US1]) +- [x] **T089 Unit test for embedded-help boundary filtering** - (`tests/unit/test_embedded_help_boundary.py`) — AC: foreign embedded tool flags/env vars (e.g., jq block inside yq help) are not merged into parent CLIMap ([P] [NEW] [US1]) +- [x] **T091 Unit test for usage-line option extraction** - (`tests/unit/test_usage_line_option_extraction.py`) — AC: option-heavy CLIs without classic sections (python3-style) still extract representative root flags ([P] [NEW] [US1]) +- [x] **T093 Extend version fallback tests for placeholder suffixes (`0.0.0-dev`, `unknown`)** - in `tests/unit/test_version_detection_fallback.py` — AC: placeholders do not short-circuit detection of real versions ([P] [NEW] [US1]) +- [x] **T095 Unit test for sectionless fallback description normalization** - in `tests/unit/test_usage_line_option_extraction.py` — AC: recovered flag descriptions do not keep leading punctuation (`:`) and preserve meaningful text ([P] [NEW] [US1]) +- [x] **T097 Unit test for sectionless long-option enrichment** - in `tests/unit/test_usage_line_option_extraction.py` — AC: bracket-only long options from usage preamble (e.g., `--help-env`, `--help-all`) are retained with non-empty descriptions when discoverable in body ([P] [NEW] [US1]) +- [x] **T099 Unit test for sectionless attached-value/combined-short option parsing** - in `tests/unit/test_usage_line_option_extraction.py` — AC: flags like `-DNAME=VALUE`, `-Xlint:all`, `-O2`, `-abc` are recognized deterministically without dropping valid options ([P] [NEW] [US1]) +- [x] **T101 Unit test for version source preference** - in `tests/unit/test_version_detection_fallback.py` — AC: when output contains dependency versions (`jq-1.7`) and CLI self-version, parser prefers CLI version; dependency pattern acts as last-resort fallback ([P] [NEW] [US1]) +- [x] **T118 Unit test for GNU single-dash long-option parsing** - in `tests/unit/test_flag_parsing_gnu_single_dash.py` — AC: parse `-print-file-name=`, `-dumpmachine`, `-Wa,`, `-Xassembler `, `-std=` from gcc-style help without collapsing/removing valid options ([P] [NEW] [US1]) +- [ ] **T121 Unit test for sectionless single-space descriptions + numeric pseudo-flag filtering** - in `tests/unit/test_usage_line_option_extraction.py` — AC: lines like `-v verbose output` keep description text; non-option bullets like `-2024 release` are not parsed as flags ([P] [NEW] [US1]) ### Implementation for User Story 1 -- [ ] T013 [US1] Implement basic CLI crawling logic to execute `CLI --help` in `src/crawler/cli_crawler.py` -- [ ] T014 [US1] Implement core parsing logic for basic CLI formats (Go/Cobra, Python/Click) in `src/crawler/parser.py` -- [ ] T015 [US1] Implement pipeline orchestrator to manage crawl and parse steps in `src/crawler/pipeline.py` -- [ ] T016 [US1] Implement CLIMap-to-AI-Plugin conversion in `src/generator/plugin_generator.py` — delegates progressive disclosure to T040 implementation -- [ ] T050 [P] [NEW] [US1] Implement EC-01 handling: graceful degradation for non-standard `--help` (warnings emitted to logger, partial CLIMap returned, confidence_score penalised); in `src/crawler/cli_crawler.py` -- [ ] T051 [P] [NEW] [US1] Implement EC-02 handling: detect auth-required `--help` (exit-code, timeout, stderr patterns) → fail with structured error, no process hang; in `src/crawler/executor.py` / `subprocess_utils.py` -- [ ] T052 [P] [NEW] [US1] Implement EC-05 handling: when raw `--help` > configurable line threshold (default 10 000), activate chunked progressive loading in pipeline; store full detail in `references/` only; in `src/crawler/pipeline.py` -- [ ] T017 [P] [US1] Integration test for `CLI --help → CLIMap JSON` (docker) in `tests/integration/test_docker_climap_generation.py` -- [ ] T018 [P] [US1] E2E test for `CLI → CLIMap → Plugin` (mocking AI Assistant interaction) in `tests/end_to_end/test_docker_plugin_e2e.py` -- [ ] T053 [P] [NEW] Performance smoke test: `docker` crawl + generate completes in <30 s; parsing a 1 000-line help fixture in <5 s; in `tests/performance/test_smoke_perf.py` *(SC-004 early validation — prevents late discovery)* +- [x] **T013 Implement basic CLI crawling logic to execute `CLI --help`** - in `src/crawler/cli_crawler.py` ([US1]) +- [x] **T014 Implement core parsing logic for basic CLI formats (Go/Cobra, Python/Click)** - in `src/crawler/parser.py` ([US1]) +- [x] **T015 Implement pipeline orchestrator to manage crawl and parse steps** - in `src/crawler/pipeline.py` ([US1]) +- [x] **T016 Implement CLIMap-to-AI-Plugin conversion** - in `src/generator/plugin_generator.py` — delegates progressive disclosure to T040 implementation ([US1]) +- [x] **T050 Implement EC-01 handling** - graceful degradation for non-standard `--help` (warnings emitted to logger, partial CLIMap returned, confidence_score penalised); in `src/crawler/cli_crawler.py` ([P] [NEW] [US1]) +- [x] **T051 Implement EC-02 handling** - detect auth-required `--help` (exit-code, timeout, stderr patterns) → fail with structured error, no process hang; in `src/crawler/executor.py` / `subprocess_utils.py` ([P] [NEW] [US1]) +- [x] **T052 Implement EC-05 handling** - when raw `--help` > configurable line threshold (default 10 000), activate chunked progressive loading in pipeline; store full detail in `references/` only; in `src/crawler/pipeline.py` ([P] [NEW] [US1]) +- [x] **T080 Implement parser support for pnpm-style grouped help** - in `src/crawler/parsers/sections.py` + `src/crawler/parsers/commands.py` — detect category headers like `Manage your dependencies:` as command sections and parse multi-line/wrapped tabular command descriptions ([NEW] [US1]) +- [ ] **T017 Integration test for `CLI --help → CLIMap JSON` (docker)** - in `tests/integration/test_docker_climap_generation.py` ([P] [US1]) +- [ ] **T018 E2E test for `CLI → CLIMap → Plugin` (mocking AI Assistant interaction)** - in `tests/end_to_end/test_docker_plugin_e2e.py` ([P] [US1]) +- [x] **T053 Performance smoke test** - `docker` crawl + generate completes in <30 s; parsing a 1 000-line help fixture in <5 s; in `tests/performance/test_smoke_perf.py` *(SC-004 early validation — prevents late discovery)* ([P] [NEW]) +- [x] **T081 Integration/E2E regression for pnpm** - in `tests/integration/test_pnpm_climap_generation.py` and/or `tests/end_to_end/test_pnpm_plugin_e2e.py` — AC: `cli-crawler pnpm` extracts >=12 commands and generated plugin contains `pnpm add`, `pnpm install`, `pnpm run` ([P] [NEW] [US1]) +- [x] **T083 Implement embedded-help deduplication strategy in parser/serialization** - (`src/crawler/parser.py` and/or `src/crawler/formatter.py`) so repeated flags from wrapper+embedded tool help do not duplicate in plugin output (deterministic merge rule) ([NEW] [US1]) +- [ ] **T085 Implement recursion/echo loop guard for self-referential subcommand trees** - in `src/crawler/discovery.py`/`src/crawler/pipeline.py` — AC: bun-style recursive help cannot explode node count; crawl stays bounded by structural fingerprints + depth ([NEW] [US1]) +- [ ] **T087 Implement examples fallback for flag-only CLIs** - in `src/generator/plugin_generator.py` — AC: when no command tree exists, synthesize compact examples from root usage/global flags instead of `_No examples extracted._` ([NEW] [US1]) +- [x] **T090 Implement embedded-help boundary filter in parser (`src/crawler/parser.py`** - and/or `src/crawler/parsers/sections.py`) — AC: stop/segment parsing when secondary tool help starts (yq+jq style), preserving parent-only metadata ([NEW] [US1]) +- [x] **T092 Implement usage-line option extraction fallback for sectionless CLIs** - in `src/crawler/parser.py`/`src/crawler/parsers/usage.py` — AC: python3-style help yields non-zero root flags and usable plugin references ([NEW] [US1]) +- [x] **T094 Expand placeholder-version handling** - in `src/crawler/version.py` — AC: values like `0.0.0-dev`/`unknown` treated as non-informative, allowing fallback to real versions when available ([NEW] [US1]) +- [x] **T096 Implement description normalization in sectionless option fallback** - (`src/crawler/parsers/usage.py` / `src/crawler/parser.py`) — AC: strip leading `:` artifacts; avoid empty/noisy descriptions in generated plugin references ([NEW] [US1]) +- [x] **T098 Implement sectionless long-option enrichment (`src/crawler/parsers/usage.py`)** - AC: when usage preamble declares long options, correlate with body/help lines to populate descriptions and avoid sparse long-only entries ([NEW] [US1]) +- [x] **T100 Implement robust sectionless option atom parsing** - in `src/crawler/parsers/usage.py` — AC: attached-value and combined-short forms are parsed without relying on strict word-boundary regex; preserve deterministic output ordering ([NEW] [US1]) +- [x] **T102 Implement CLI-name-aware version preference** - in `src/crawler/version.py` — AC: prefer versions attributable to target CLI; keep `pkg-` pattern as last-resort to avoid dependency-version false positives ([NEW] [US1]) +- [x] **T119 Extend `src/crawler/parsers/flags.py` to support GNU single-dash long options** - and pass-through families (`-print-*`, `-dump*`, `-Wa,`, `-Wp,`, `-Wl,`, `-Xassembler`, `-Xpreprocessor`, `-Xlinker`) with deterministic normalization and no duplicate erosion ([NEW] [US1]) +- [x] **T120 Integration/E2E regression for gcc** - in `tests/integration/test_gcc_climap_generation.py` and/or `tests/end_to_end/test_gcc_plugin_e2e.py` — AC: `cli-crawler gcc` extracts representative compiler flags (>=20) including `-print-file-name`, `-Wa,`, `-Xassembler`, `-std=`, and generated plugin references remain coherent ([P] [NEW] [US1]) +- [ ] **T122 Harden sectionless usage parsing** - in `src/crawler/parsers/usage.py` for single-space description inference and strict option gating (first token after `-` must be alpha for short-option paths) to prevent numeric false positives ([NEW] [US1]) +- [ ] **T123 Unit test for short-option attached metavars in sectionless fallback** - in `tests/unit/test_usage_line_option_extraction.py` — AC: perl-style `-Idirectory` normalizes to `-I` (`type=string`) and `-V[:configvar]` normalizes to `-V` with optional string hint ([P] [NEW] [US1]) +- [ ] **T124 Improve short-option attached metavars parsing** - in `src/crawler/parsers/usage.py` — AC: normalize suffix-encoded metavars deterministically while preserving option coverage and type inference quality for short attached forms ([NEW] [US1]) +- [ ] **T125 Unit test for vendor-prefixed multiword version lines** - in `tests/unit/test_version_detection_fallback.py` — AC: detect CLI version from outputs like `GNU Make 4.3` when crawling `make`, without regressing CLI-attributed preference rules ([P] [NEW] [US1]) +- [ ] **T126 Extend version parser for multiword vendor-prefixed names** - in `src/crawler/version.py` — AC: parse version from lines like `GNU Make ` / `Foo Bar ` as fallback when explicit ` version` is absent, preserving anti-false-positive safeguards from T101-T102 +- [ ] **T127 Unit test for bool inference in wrapped long flags without values** - in `tests/unit/test_flag_parsing_make_wrapped.py` — AC: make-style entries like `-e, --environment-overrides` and `-v, --version` remain `bool` when no explicit value token exists +- [ ] **T128 Improve bool/string inference for wrapped long flags** - in `src/crawler/parsers/flags.py` — AC: avoid promoting no-value long flags to `string` solely due wrapped descriptions; keep deterministic parsing for both inline and continuation formats +- [ ] **T129 Unit test for GNU bracketed optional-assignment option forms** - in `tests/unit/test_flag_parsing_gnu_single_dash.py` — AC: parse options like `-fcompare-debug[=]` and `-fplugin-arg--[=]` without silently dropping valid GNU flags ([P] [NEW] [US1]) +- [ ] **T130 Extend GNU single-dash token parser for bracketed optional-assignment suffixes** - in `src/crawler/parsers/flags.py` — AC: normalize and retain bracketed optional-assignment GNU options while preserving existing T118-T120 behavior and deterministic deduplication ([NEW] [US1]) +- [x] **T131 Unit test for non-mutating subcommand help fallback** - in `tests/unit/test_subcommand_help_safety.py` — AC: when `--help`/`-h`/`help ` fail to produce help text (git.exe-on-WSL style), crawler must not execute bare mutating subcommands (`init`, `reset`, etc.) as fallback ([P] [NEW] [US1]) +- [x] **T132 Implement safe subcommand help fallback policy** - in `src/crawler/detector.py` (and call sites if needed) — AC: remove/gate bare subcommand fallback behind non-mutating safety rules and return `unknown`/warning instead of executing potentially state-changing commands ([NEW] [US1]) +- [x] **T133 Unit test for executable-suffix CLI canonicalization (`.exe`)** - in `tests/unit/test_cli_name_canonicalization.py` — AC: canonical CLI identity strips executable suffix for version attribution and plugin slug/id generation, while preserving original invocation command for execution ([P] [NEW] [US1]) +- [x] **T134 Implement CLI canonicalization layer for executable names** - in `src/crawler/version.py` and `src/generator/plugin_generator.py` (plus shared helper if required) — AC: `git.exe` can map to canonical `git` for version parsing and generated plugin naming avoids extension-bearing IDs (`cli-git`, not `cli-git.exe`) without regressing existing CLI names ([NEW] [US1]) +- [ ] **T135 Unit test for compact paired short-option rows in sectionless fallback** - in `tests/unit/test_usage_line_option_extraction.py` — AC: zip-style rows containing paired options (`-f ... -u ...`, `-q ... -v ...`) produce separate flag entries with non-empty, option-specific descriptions ([P] [NEW] [US1]) +- [ ] **T136 Improve sectionless parser for compact paired short-option rows** - in `src/crawler/parsers/usage.py` — AC: split and normalize multi-option short-flag rows without reducing existing sectionless parsing quality or determinism ([NEW] [US1]) +- [ ] **T137 Unit test for root help auth-precedence over best-result fallback** - in `tests/unit/test_edge_case_auth_help.py` (or dedicated detector test) — AC: when early help probes return auth-required failures, detector should not return a generic best-result fallback that masks `auth_required` semantics ([P] [NEW] [US1]) +- [ ] **T138 Harden root help detection auth precedence** - in `src/crawler/detector.py` — AC: prefer structured `auth_required` outcome over non-help fallback candidates when auth signals are present, preserving existing success-path detection behavior ([NEW] [US1]) +- [ ] **T139 Unit test for global-only CLI documentation fallbacks** - in `tests/unit/test_progressive_disclosure.py` (or dedicated generator test) — AC: when CLIMap has zero commands (e.g., `awk`), SKILL.md must render a non-empty version label (`unknown`), a meaningful top-level usage line, and `references/examples.md` must contain at least one generated usage example ([P] [NEW] [US1]) +- [ ] **T140 Implement generator fallbacks for global-only CLIs** - in `src/generator/plugin_generator.py` — AC: avoid `v.` rendering, emit stable command-format fallback (` --help` / ` --version`) when no commands exist, and synthesize examples from global/root context so plugin quality remains useful for command-line-only tools ([NEW] [US1]) **Checkpoint — US1 AC**: T009–T018, T047–T053 all pass; `pytest` exits 0; `docker` plugin generates with correct SKILL.md + `references/` structure; progressive disclosure active; Rich-Click extraction >50%; zero error-message descriptions; crawl+generate <30 s. @@ -106,14 +153,14 @@ ### Tests for User Story 2 -- [ ] T019 [P] [US2] Unit test for CLI version detection in `tests/unit/test_version_detection.py` -- [ ] T020 [P] [US2] Unit test for plugin outdated status logic in `tests/unit/test_plugin_status.py` +- [ ] **T019 Unit test for CLI version detection** - in `tests/unit/test_version_detection.py` ([P] [US2]) +- [ ] **T020 Unit test for plugin outdated status logic** - in `tests/unit/test_plugin_status.py` ([P] [US2]) ### Implementation for User Story 2 -- [ ] T021 [US2] Implement CLI version detection in `src/crawler/cli_crawler.py` -- [ ] T022 [US2] Implement logic to signal/regenerate outdated plugins (based on version comparison) in `src/generator/plugin_generator.py` -- [ ] T023 [P] [US2] Integration test for `uv` CLI update and plugin regeneration in `tests/integration/test_uv_plugin_update.py` +- [ ] **T021 Implement CLI version detection** - in `src/crawler/cli_crawler.py` ([US2]) +- [ ] **T022 Implement logic to signal/regenerate outdated plugins (based on version comparison)** - in `src/generator/plugin_generator.py` ([US2]) +- [ ] **T023 Integration test for `uv` CLI update and plugin regeneration** - in `tests/integration/test_uv_plugin_update.py` ([P] [US2]) **Checkpoint — US2 AC**: T019–T023 pass; US1 remains unbroken; version change in fixture CLI triggers regeneration. @@ -127,13 +174,13 @@ ### Tests for User Story 3 -- [ ] T024 [P] [US3] Unit test for CLIMap JSON schema validation in `tests/unit/test_climap_schema_validation.py` — validates against canonical schema from T044-reconciled `data-model.md` +- [ ] **T024 Unit test for CLIMap JSON schema validation** - in `tests/unit/test_climap_schema_validation.py` — validates against canonical schema from T044-reconciled `data-model.md` ([P] [US3]) ### Implementation for User Story 3 -- [ ] T025 [US3] Implement robust CLIMap JSON serialisation/deserialisation in `src/crawler/models.py` -- [ ] T026 [US3] Ensure CLIMap output adheres strictly to reconciled `data-model.md` schema (post-T044) -- [ ] T027 [P] [US3] Integration test for external tool (JSON validator / Python `jsonschema`) consumption of CLIMap in `tests/integration/test_climap_external_validation.py` +- [ ] **T025 Implement robust CLIMap JSON serialisation/deserialisation** - in `src/crawler/models.py` ([US3]) +- [ ] **T026 Ensure CLIMap output adheres strictly to reconciled `data-model.md` schema** - (post-T044) ([US3]) +- [ ] **T027 Integration test for external tool (JSON validator / Python `jsonschema`)** - consumption of CLIMap in `tests/integration/test_climap_external_validation.py` ([P] [US3]) **Checkpoint — US3 AC**: T024–T027 pass; all US1–US3 independently functional and non-regressive. @@ -145,44 +192,92 @@ ### Tests for Phase 6 implementations (write FIRST) -- [ ] T054 [P] [NEW] Unit test for semantic keyword generation (T028) in `tests/unit/test_keyword_generation.py` — AC: keywords are domain-relevant, not first-word extractions; no stopwords -- [ ] T055 [P] [NEW] Unit test for author configuration (T029) in `tests/unit/test_author_config.py` — AC: `--author` flag sets author; omitting flag produces no `author` field in `plugin.json` -- [ ] T056 [P] [NEW] Unit test for observability/structured logging (T030) in `tests/unit/test_observability.py` — AC: pipeline emits structured log entries with level, stage, CLI name, duration -- [ ] T057 [P] [NEW] Unit test for confidence score calculation (T034) in `tests/unit/test_confidence_calculator.py` — AC: score in [0.0, 1.0]; known-good fixture produces score >0.8; ambiguous type fixture produces score <0.6 -- [ ] T058 [P] [NEW] Unit test for token optimizer (T035) in `tests/unit/test_token_optimizer.py` — AC: SKILL.md output for docker is <800 tokens; references/commands.md is not loaded in compact path +- [x] **T054 Unit test for semantic keyword generation (T028)** - in `tests/unit/test_keyword_generation.py` — AC: keywords are domain-relevant, not first-word extractions; no stopwords ([P] [NEW]) +- [x] **T055 Unit test for author configuration (T029)** - in `tests/unit/test_author_config.py` — AC: `--author` flag sets author; omitting flag produces no `author` field in `plugin.json` ([P] [NEW]) +- [ ] **T056 Unit test for observability/structured logging (T030)** - in `tests/unit/test_observability.py` — AC: pipeline emits structured log entries with level, stage, CLI name, duration ([P] [NEW]) +- [ ] **T057 Unit test for confidence score calculation (T034)** - in `tests/unit/test_confidence_calculator.py` — AC: score in [0.0, 1.0]; known-good fixture produces score >0.8; ambiguous type fixture produces score <0.6 ([P] [NEW]) +- [ ] **T058 Unit test for token optimizer (T035)** - in `tests/unit/test_token_optimizer.py` — AC: SKILL.md output for docker is <800 tokens; references/commands.md is not loaded in compact path ([P] [NEW]) ### Implementations -- [ ] T030 Implement detailed observability in `src/lib/observability.py` — structured logging (stdlib only), per-stage metrics (crawl/parse/generate), basic tracing for bottleneck identification -- [ ] T031 Integrate logging, metrics, tracing across crawler and generator components -- [ ] T033 Code cleanup and refactoring across the codebase — fix `_separate_global_flags` no-op (both paths return same result); add `TypedDict` annotations to `src/crawler/formatter.py` serialisation shapes; add `py.typed` marker -- [ ] T034 [P] Implement confidence score calculation logic in `src/crawler/confidence_calculator.py` — per-flag score (FR-010); penalise ambiguous types, empty descriptions, runtime-error descriptions -- [ ] T035 [P] Benchmark and optimise token cost in `src/generator/token_optimizer.py` — validate SC-002 (≥5× reduction vs raw `--help`); document comparison table -- [ ] T036 [P] Benchmark and optimise crawl/parsing performance in `tests/performance/test_performance.py` — validate SC-004 (<30 s for docker), SC-009 (<5 s for 10 000-line fixture), SC-008 (50 CLIs in batches of 10 in parallel) -- [ ] T037 Update `README.md` — add demo GIF (`vhs`), "Why Not Just `--help`?" section, comparison table vs tldr/cheat.sh/man/jc, PyPI badges, shorten to <250 lines *(SC-007)* +- [ ] **T030 Implement detailed observability** - in `src/lib/observability.py` — structured logging (stdlib only), per-stage metrics (crawl/parse/generate), basic tracing for bottleneck identification +- [ ] **T031 Integrate logging, metrics, tracing across crawler and generator components** +- [ ] **T033 Code cleanup and refactoring across the codebase** - fix `_separate_global_flags` no-op (both paths return same result); add `TypedDict` annotations to `src/crawler/formatter.py` serialisation shapes; add `py.typed` marker +- [ ] **T034 Implement confidence score calculation logic** - in `src/crawler/confidence_calculator.py` — per-flag score (FR-010); penalise ambiguous types, empty descriptions, runtime-error descriptions ([P]) +- [ ] **T035 Benchmark and optimise token cost** - in `src/generator/token_optimizer.py` — validate SC-002 (≥5× reduction vs raw `--help`); document comparison table ([P]) +- [ ] **T036 Benchmark and optimise crawl/parsing performance** - in `tests/performance/test_performance.py` — validate SC-004 (<30 s for docker), SC-009 (<5 s for 10 000-line fixture), SC-008 (50 CLIs in batches of 10 in parallel) ([P]) +- [ ] **T037 Update `README.md`** - add demo GIF (`vhs`), "Why Not Just `--help`?" section, comparison table vs tldr/cheat.sh/man/jc, PyPI badges, shorten to <250 lines *(SC-007)* ### Distribution & Launch Readiness -- [ ] T059 [NEW] Verify pyproject.toml PyPI publish readiness (post-T038): `uv build && twine check dist/*` produces no errors; all metadata renders correctly on PyPI test server -- [ ] T060 [P] [NEW] Test clean install on fresh Python 3.11 venv: `pip install cli-plugins && cli-crawler docker -o docker.json && generate-plugin docker.json` succeeds end-to-end *(SC-005)* -- [ ] T061 [NEW] Publish to PyPI: `twine upload dist/*`; verify `pip install cli-plugins` works from PyPI *(SC-005)* +- [ ] **T059 Verify pyproject.toml PyPI publish readiness (post-T038)** - `uv build && twine check dist/*` produces no errors; all metadata renders correctly on PyPI test server ([NEW]) +- [ ] **T060 Test clean install on fresh Python 3.11 venv** - `pip install cli-plugins && cli-crawler docker -o docker.json && generate-plugin docker.json` succeeds end-to-end *(SC-005)* ([P] [NEW]) +- [ ] **T061 Publish to PyPI** - `twine upload dist/*`; verify `pip install cli-plugins` works from PyPI *(SC-005)* ([NEW]) ### CLI Coverage Expansion -- [ ] T062 [NEW] Crawl 13+ additional CLIs and fix parser issues found: `kubectl`, `terraform`, `aws`, `gcloud`, `az`, `cargo`, `go`, `rustup`, `helm`, `poetry`, `pdm`, `rye`, `mise`, `pnpm` — AC: each produces valid CLIMap JSON, no crashes *(SC-001; execution-plan A11)* +- [ ] **T062 Crawl 13+ additional CLIs and fix parser issues found** - `kubectl`, `terraform`, `aws`, `gcloud`, `az`, `cargo`, `go`, `rustup`, `helm`, `poetry`, `pdm`, `rye`, `mise`, `pnpm` — AC: each produces valid CLIMap JSON, no crashes *(SC-001; execution-plan A11)* ([NEW]) ### Quality Validation Gates -- [ ] T063 [P] [NEW] SC-006 manual validation: test 10 representative CLI tasks with real Claude Code + generated plugin (docker, git, gh, uv); document results; AC: ≥90% correct answers without hallucination or unnecessary clarification *(SC-006)* -- [ ] T064 [P] [NEW] SC-010 reliability test: crawl 20 CLIs sequentially and in parallel batches; measure success rate; AC: >99% crawl success rate *(SC-010)* +- [ ] **T063 SC-006 manual validation** - test 10 representative CLI tasks with real Claude Code + generated plugin (docker, git, gh, uv); document results; AC: ≥90% correct answers without hallucination or unnecessary clarification *(SC-006)* ([P] [NEW]) +- [ ] **T064 SC-010 reliability test** - crawl 20 CLIs sequentially and in parallel batches; measure success rate; AC: >99% crawl success rate *(SC-010)* ([P] [NEW]) ### Documentation & Contracts -- [ ] T065 [P] [NEW] Update `specs/001-cli-plugins-base/contracts/plugin-contract.md` to reflect actual Markdown output format (SKILL.md + references/commands.md + references/examples.md + plugin.json) — remove YAML structural references which do not match the real generator output *(M4)* +- [ ] **T065 Update `specs/001-cli-plugins-base/contracts/plugin-contract.md` to reflect** - actual Markdown output format (SKILL.md + references/commands.md + references/examples.md + plugin.json) — remove YAML structural references which do not match the real generator output *(M4)* ([P] [NEW]) + +### Configuration Hygiene (Operational, Repetitive) + +- [x] **T069 Define and document minimal-override policy for `config.yaml`** - in `README.md` (and/or `docs/config-policy.md`): keep only CLI-specific overrides that differ from defaults (`environment`, `help_pattern`, `max_depth`, `max_concurrent`, `plugins.discovery_command`); avoid inventory-style static entries duplicated from `output/*.json`/`plugins/` ([NEW]) +- [x] **T070 Implement config inventory audit tool (`scripts/config_audit.py` or** - `src/config/audit.py` + CLI hook) that compares configured CLIs (`config.yaml`) vs crawled CLIs (`output/*.json`, excluding `*.raw.json`) vs generated plugins (`plugins/cli-*`); output categories: `missing_in_config`, `stale_in_config`, `missing_output`, `missing_plugin`, plus suggested minimal overrides ([P] [NEW]) +- [x] **T071 Add unit tests for config audit logic** - in `tests/unit/test_config_audit.py` covering empty dirs, partial overlap, stale config entries, and CLIs present only in output/plugins ([P] [NEW]) +- [x] **T072 Add repeatable runbook/checklist** - in `README.md` and `CLAUDE.local.md`: run config audit after each crawl batch and before PR; store latest report in `output/config-audit.json` ([NEW]) + +### CLI Group Inference (No-LLM, Deterministic + Classical ML) + +- [ ] **T073 Define canonical CLI group taxonomy + evidence schema** - in `src/config/group_taxonomy.yaml` (or `src/config/group_taxonomy.py`) and document allowed groups/evidence signals in `README.md` (no LLM/agent dependency) ([NEW]) +- [ ] **T074 Add RED tests for deterministic group inference** - in `tests/unit/test_group_inference_deterministic.py` using fixtures from `output/*.json`: validate group inference from explicit config group, command lexicon, flags, and package metadata hints ([P] [NEW]) +- [ ] **T075 Implement deterministic group inference engine** - in `src/config/group_inference.py` (rule-based scoring + fuzzy matching via `rapidfuzz`) and integrate with `config-audit` report ([NEW]) +- [ ] **T076 Add RED tests for classical-ML fallback** - in `tests/unit/test_group_inference_ml.py`: TF-IDF feature extraction from CLIMap text, confidence thresholding, and deterministic-first precedence ([P] [NEW]) +- [ ] **T077 Implement classical-ML fallback** - scikit-learn `TfidfVectorizer` + `LogisticRegression`/`LinearSVC`) in `src/config/group_classifier.py` with train/eval utility script `scripts/train_group_classifier.py` ([NEW]) +- [ ] **T078 Extend audit/report contract and docs** - include `group_inferred`, `group_confidence`, `group_evidence`, `group_conflict_with_config` in `output/config-audit.json` and update operational runbook in `README.md` + `CLAUDE.local.md` ([NEW]) + +## Phase 7: Dashboard UI & Operations Cockpit (Research → Architecture → Build) (Priority: P3) + +**Goal**: Expor as capacidades da solução via UI ergonómica (gestão, observabilidade, ações, inventário) com operação segura e auditável. + +**Independent Test**: Operador abre dashboard, valida inventário de CLIs/plugins, executa ação (`cli-crawler`/`generate-plugin`/`config-audit`) e observa resultado/logs sem usar terminal. + +### Discovery & Architecture (skill-driven) + +- [ ] **T103 Run structured discovery for dashboard scope using skills** - `requirements-discovery` + `discovery-pack`; produce `specs/002-dashboard-ui/discovery.md` with personas, JTBD, primary workflows, non-goals, risks, and success metrics ([NEW]) +- [ ] **T104 Produce architecture blueprint** - via `blueprint-maturation` + `architect-review`; deliver `specs/002-dashboard-ui/plan.md` + ADR candidates for backend/frontend stack, data flow (`output/`, `plugins/`, audit reports), security model, and deployment topology ([P] [NEW]) +- [ ] **T105 Produce UX/IA specification** - via `frontend-design`; create `specs/002-dashboard-ui/ux-spec.md` with navigation map, panel wireframes, accessibility constraints (WCAG AA), and responsive breakpoints (desktop/mobile) ([P] [NEW]) + +### Tests for Phase 7 (write FIRST) + +- [ ] **T106 Add backend API contract tests** - in `tests/integration/test_dashboard_api_contracts.py` for inventory summary, plugin detail, observability metrics, and action status endpoints ([P] [NEW]) +- [ ] **T107 Add Playwright E2E tests** - in `tests/end_to_end/test_dashboard_e2e.py` covering inventory exploration, plugin inspection, action trigger, and execution-log/result validation ([P] [NEW]) +- [ ] **T108 Add accessibility/responsive tests** - in `tests/end_to_end/test_dashboard_accessibility.py` — keyboard navigation, landmarks/labels, contrast checks, and mobile viewport sanity ([P] [NEW]) + +### Implementation + +- [ ] **T109 Implement dashboard backend read API** - in `src/dashboard/api.py` (or equivalent) aggregating inventory/health from `output/*.json`, `plugins/cli-*`, `output/config-audit.json`, and observability metrics ([NEW]) +- [ ] **T110 Implement controlled action service** - in `src/dashboard/actions.py` for `cli-crawler`, `generate-plugin`, and `config-audit` with status lifecycle, timeout guards, and structured execution logs ([NEW]) +- [ ] **T111 Implement frontend app shell** - in `web/dashboard/` with ergonomic navigation (sidebar + top status bar + quick actions), design tokens, and responsive layout ([NEW]) +- [ ] **T112 Build inventory management panels** - in `web/dashboard/src/features/inventory/` with CLI/plugin lists, health badges, and drift indicators (`stale_in_config`, `missing_output`, `missing_plugin`) ([P] [NEW]) +- [ ] **T113 Build observability panels** - in `web/dashboard/src/features/observability/` with run durations, success/failure trends, warning/error timeline, and filter controls ([P] [NEW]) +- [ ] **T114 Build operations/actions console** - in `web/dashboard/src/features/actions/` to trigger workflows, stream logs, and surface generated artifacts ([P] [NEW]) +- [ ] **T115 Build plugin workbench views** - in `web/dashboard/src/features/plugins/` (command tree, flags table, examples preview, run-to-run diff) ([NEW]) +- [ ] **T116 Integrate backend/frontend contracts** - typed models (e.g., `web/dashboard/src/lib/api-types.ts`) and schema validation to prevent UI/API drift ([NEW]) +- [ ] **T117 Document dashboard operations and troubleshooting** - in `docs/dashboard.md` + `README.md` (startup, env vars, safety notes, rollback path) ([NEW]) + +**Checkpoint — Phase 7 AC**: Dashboard loads in desktop/mobile, inventory and observability panels render real project data, actions execute safely with visible status/logs, and E2E + accessibility tests pass. ### Future Backlog (DEFERRED — post-v1.0) -- [ ] T068 [DEFERRED] [NEW] co-author-injection: add `Co-Authored-By: ` trailer to generated `scripts/rescan.sh` commit template (Option A from `.ideas/co-author-injection.md`); low priority, implement after v1.0 stabilises *(L1)* +- [ ] **T068 co-author-injection** - add `Co-Authored-By: ` trailer to generated `scripts/rescan.sh` commit template (Option A from `.ideas/co-author-injection.md`); low priority, implement after v1.0 stabilises *(L1)* ([DEFERRED] [NEW]) --- @@ -194,6 +289,7 @@ - **Phase 2 (Foundational)**: Depends on Phase 1 completion — BLOCKS all user stories. Includes parser quality gate (T041, T042) and progressive disclosure (T040). - **User Stories (Phases 3–5)**: All depend on Phase 2 completion; can proceed in parallel if staffed. Priority order: US1 (P1) → US2/US3 (P2). - **Polish (Phase 6)**: Depends on all desired user stories being complete. Tests for Phase 6 tasks written before their implementations. +- **Phase 7 (Dashboard UI)**: Discovery (`T103–T105`) can start after current US1 hardening; implementation depends on observability + inventory baselines from Phase 6 (`T030–T031`, `T070–T072`, `T078`). ### Task-Level Dependencies (key) @@ -203,8 +299,38 @@ - T041, T042 → T012 (parser improvements before parser quality test is meaningful) - T043 → T036 (thread safety before parallel performance benchmark) - T045, T046 → T049, T050, T051 (parser fixes before edge-case implementation) +- T079 → T080 → T081 (pnpm grouped-help regression path: RED parser test → parser implementation → integration/E2E gate) +- T082 → T083 (embedded-help dedup test before dedup implementation) +- T089 → T090 (embedded-help boundary filtering: RED contamination test before parser boundary implementation) +- T091 → T092 (sectionless usage-line option extraction: RED test before parser fallback implementation) +- T093 → T094 (placeholder-version suffix tests before expanded fallback logic) +- T095 → T096 (description normalization for sectionless fallback) +- T097 → T098 (long-option enrichment for sectionless fallback) +- T099 → T100 (attached-value/combined-short parsing tests before robust option-atom parser update) +- T101 → T102 (version-source preference tests before CLI-name-aware version selection) +- T118 → T119 → T120 (gcc/GNU single-dash flags: RED unit → parser extension → integration/E2E gate) +- T121 → T122 (sectionless single-space + numeric pseudo-flag hardening) +- T123 → T124 (perl-style short attached metavars normalization hardening) +- T125 → T126 (vendor-prefixed multiword version parsing hardening) +- T127 → T128 (wrapped long-flag bool/string inference hardening) +- T129 → T130 (GNU bracketed optional-assignment flag-form hardening) +- T131 → T132 (subcommand help fallback safety hardening) +- T133 → T134 (executable-suffix CLI canonicalization hardening) +- T135 → T136 (compact paired short-option row parsing hardening) +- T137 → T138 (root help auth-precedence hardening) +- T139 → T140 (global-only CLI doc quality hardening) - T028, T029 → T054, T055 (implementations before their unit tests — Phase 6 tests write after Phase 2 impl) - T059 → T060 → T061 (PyPI readiness → clean install test → publish) +- T069 → T070 → T071 → T072 (minimal config policy before automated drift audit + operational checklist) +- T070/T071/T072 → T062 (stabilise config/inventory hygiene before large CLI coverage expansion) +- T073 → T074 → T075 → T076 → T077 → T078 (taxonomy-first, deterministic inference first, ML fallback second) +- T075/T078 → future grouped-plugin generation epic (multi-CLI plugin per inferred domain/group) +- T103 → T104 → T105 (discovery before architecture before UX spec) +- T103/T104/T105 → T106/T107/T108 (contracts and E2E tests written from approved spec set) +- T030/T031 + T070/T072 + T078 → T109/T110 (dashboard backend depends on observability + inventory audit baselines) +- T106 → T109/T110 (backend contracts before implementation) +- T107/T108 → T111/T112/T113/T114/T115 (UX + accessibility tests before frontend feature implementation) +- T109/T110/T111/T112/T113/T114/T115 → T116 → T117 (integration typing, then operational documentation) ### Parallel Opportunities From 455ed1b75319a06adf50e7b87c0bef5baa74169b Mon Sep 17 00:00:00 2001 From: NUNO MIGUEL DA SILVA SALVACAO Date: Mon, 16 Feb 2026 12:29:16 +0000 Subject: [PATCH 4/5] chore(plugins): regenerate and normalize generated CLI plugins --- plugins/cli-awk/.claude-plugin/plugin.json | 10 + plugins/cli-awk/commands/scan-cli.md | 24 + plugins/cli-awk/scripts/rescan.sh | 26 + plugins/cli-awk/skills/cli-awk/SKILL.md | 69 ++ .../skills/cli-awk/references/commands.md | 35 + .../skills/cli-awk/references/examples.md | 2 + plugins/cli-curl/.claude-plugin/plugin.json | 10 + plugins/cli-curl/commands/scan-cli.md | 24 + plugins/cli-curl/scripts/rescan.sh | 26 + plugins/cli-curl/skills/cli-curl/SKILL.md | 53 ++ .../skills/cli-curl/references/commands.md | 19 + .../skills/cli-curl/references/examples.md | 2 + plugins/cli-docker/.claude-plugin/plugin.json | 17 +- plugins/cli-docker/skills/cli-docker/SKILL.md | 124 +-- plugins/cli-g++/.claude-plugin/plugin.json | 10 + plugins/cli-g++/commands/scan-cli.md | 24 + plugins/cli-g++/scripts/rescan.sh | 26 + plugins/cli-g++/skills/cli-g++/SKILL.md | 49 ++ .../skills/cli-g++/references/commands.md | 15 + .../skills/cli-g++/references/examples.md | 2 + plugins/cli-gcc/.claude-plugin/plugin.json | 10 + plugins/cli-gcc/commands/scan-cli.md | 24 + plugins/cli-gcc/scripts/rescan.sh | 26 + plugins/cli-gcc/skills/cli-gcc/SKILL.md | 49 ++ .../skills/cli-gcc/references/commands.md | 15 + .../skills/cli-gcc/references/examples.md | 2 + plugins/cli-gh/.claude-plugin/plugin.json | 17 +- plugins/cli-gh/skills/cli-gh/SKILL.md | 104 +-- .../skills/cli-gh/references/commands.md | 2 +- plugins/cli-git/.claude-plugin/plugin.json | 17 +- plugins/cli-git/skills/cli-git/SKILL.md | 108 +-- .../skills/cli-git/references/commands.md | 2 +- .../skills/cli-git/references/examples.md | 607 ++------------ plugins/cli-grep/.claude-plugin/plugin.json | 10 + plugins/cli-grep/commands/scan-cli.md | 24 + plugins/cli-grep/scripts/rescan.sh | 26 + plugins/cli-grep/skills/cli-grep/SKILL.md | 90 ++ .../skills/cli-grep/references/commands.md | 56 ++ .../skills/cli-grep/references/examples.md | 2 + .../{cli-jq => }/.claude-plugin/plugin.json | 5 - .../cli-jq/{cli-jq => }/commands/scan-cli.md | 0 plugins/cli-jq/{cli-jq => }/scripts/rescan.sh | 0 .../{cli-jq => }/skills/cli-jq/SKILL.md | 45 +- .../skills/cli-jq/references/commands.md | 0 .../skills/cli-jq/references/examples.md | 0 plugins/cli-make/.claude-plugin/plugin.json | 10 + plugins/cli-make/commands/scan-cli.md | 24 + plugins/cli-make/scripts/rescan.sh | 26 + plugins/cli-make/skills/cli-make/SKILL.md | 59 ++ .../skills/cli-make/references/commands.md | 25 + .../skills/cli-make/references/examples.md | 2 + plugins/cli-node/.claude-plugin/plugin.json | 10 + plugins/cli-node/commands/scan-cli.md | 24 + plugins/cli-node/scripts/rescan.sh | 26 + plugins/cli-node/skills/cli-node/SKILL.md | 185 ++++ .../skills/cli-node/references/commands.md | 151 ++++ .../skills/cli-node/references/examples.md | 2 + plugins/cli-npx/.claude-plugin/plugin.json | 10 + plugins/cli-npx/commands/scan-cli.md | 24 + plugins/cli-npx/scripts/rescan.sh | 26 + plugins/cli-npx/skills/cli-npx/SKILL.md | 44 + .../skills/cli-npx/references/commands.md | 10 + .../skills/cli-npx/references/examples.md | 2 + plugins/cli-perl/.claude-plugin/plugin.json | 10 + plugins/cli-perl/commands/scan-cli.md | 24 + plugins/cli-perl/scripts/rescan.sh | 26 + plugins/cli-perl/skills/cli-perl/SKILL.md | 69 ++ .../skills/cli-perl/references/commands.md | 35 + .../skills/cli-perl/references/examples.md | 2 + plugins/cli-pip/.claude-plugin/plugin.json | 17 + plugins/cli-pip/commands/scan-cli.md | 24 + plugins/cli-pip/scripts/rescan.sh | 26 + plugins/cli-pip/skills/cli-pip/SKILL.md | 104 +++ .../skills/cli-pip/references/commands.md | 792 ++++++++++++++++++ .../skills/cli-pip/references/examples.md | 2 + plugins/cli-pnpm/.claude-plugin/plugin.json | 17 + plugins/cli-pnpm/commands/scan-cli.md | 24 + plugins/cli-pnpm/scripts/rescan.sh | 26 + plugins/cli-pnpm/skills/cli-pnpm/SKILL.md | 70 ++ .../skills/cli-pnpm/references/commands.md | 582 +++++++++++++ .../skills/cli-pnpm/references/examples.md | 158 ++++ plugins/cli-python/.claude-plugin/plugin.json | 10 + plugins/cli-python/commands/scan-cli.md | 24 + plugins/cli-python/scripts/rescan.sh | 26 + plugins/cli-python/skills/cli-python/SKILL.md | 65 ++ .../skills/cli-python/references/commands.md | 31 + .../skills/cli-python/references/examples.md | 2 + .../cli-python3/.claude-plugin/plugin.json | 10 + plugins/cli-python3/commands/scan-cli.md | 24 + plugins/cli-python3/scripts/rescan.sh | 26 + .../cli-python3/skills/cli-python3/SKILL.md | 40 + .../skills/cli-python3/references/commands.md | 2 + .../skills/cli-python3/references/examples.md | 2 + plugins/cli-tar/.claude-plugin/plugin.json | 10 + plugins/cli-tar/commands/scan-cli.md | 24 + plugins/cli-tar/scripts/rescan.sh | 26 + plugins/cli-tar/skills/cli-tar/SKILL.md | 74 ++ .../skills/cli-tar/references/commands.md | 40 + .../skills/cli-tar/references/examples.md | 2 + plugins/cli-yq/.claude-plugin/plugin.json | 10 + plugins/cli-yq/commands/scan-cli.md | 24 + plugins/cli-yq/scripts/rescan.sh | 26 + plugins/cli-yq/skills/cli-yq/SKILL.md | 65 ++ .../skills/cli-yq/references/commands.md | 31 + .../skills/cli-yq/references/examples.md | 2 + plugins/cli-zip/.claude-plugin/plugin.json | 10 + plugins/cli-zip/commands/scan-cli.md | 24 + plugins/cli-zip/scripts/rescan.sh | 26 + plugins/cli-zip/skills/cli-zip/SKILL.md | 55 ++ .../skills/cli-zip/references/commands.md | 21 + .../skills/cli-zip/references/examples.md | 2 + 111 files changed, 4326 insertions(+), 884 deletions(-) create mode 100644 plugins/cli-awk/.claude-plugin/plugin.json create mode 100644 plugins/cli-awk/commands/scan-cli.md create mode 100644 plugins/cli-awk/scripts/rescan.sh create mode 100644 plugins/cli-awk/skills/cli-awk/SKILL.md create mode 100644 plugins/cli-awk/skills/cli-awk/references/commands.md create mode 100644 plugins/cli-awk/skills/cli-awk/references/examples.md create mode 100644 plugins/cli-curl/.claude-plugin/plugin.json create mode 100644 plugins/cli-curl/commands/scan-cli.md create mode 100644 plugins/cli-curl/scripts/rescan.sh create mode 100644 plugins/cli-curl/skills/cli-curl/SKILL.md create mode 100644 plugins/cli-curl/skills/cli-curl/references/commands.md create mode 100644 plugins/cli-curl/skills/cli-curl/references/examples.md create mode 100644 plugins/cli-g++/.claude-plugin/plugin.json create mode 100644 plugins/cli-g++/commands/scan-cli.md create mode 100644 plugins/cli-g++/scripts/rescan.sh create mode 100644 plugins/cli-g++/skills/cli-g++/SKILL.md create mode 100644 plugins/cli-g++/skills/cli-g++/references/commands.md create mode 100644 plugins/cli-g++/skills/cli-g++/references/examples.md create mode 100644 plugins/cli-gcc/.claude-plugin/plugin.json create mode 100644 plugins/cli-gcc/commands/scan-cli.md create mode 100644 plugins/cli-gcc/scripts/rescan.sh create mode 100644 plugins/cli-gcc/skills/cli-gcc/SKILL.md create mode 100644 plugins/cli-gcc/skills/cli-gcc/references/commands.md create mode 100644 plugins/cli-gcc/skills/cli-gcc/references/examples.md create mode 100644 plugins/cli-grep/.claude-plugin/plugin.json create mode 100644 plugins/cli-grep/commands/scan-cli.md create mode 100644 plugins/cli-grep/scripts/rescan.sh create mode 100644 plugins/cli-grep/skills/cli-grep/SKILL.md create mode 100644 plugins/cli-grep/skills/cli-grep/references/commands.md create mode 100644 plugins/cli-grep/skills/cli-grep/references/examples.md rename plugins/cli-jq/{cli-jq => }/.claude-plugin/plugin.json (61%) rename plugins/cli-jq/{cli-jq => }/commands/scan-cli.md (100%) rename plugins/cli-jq/{cli-jq => }/scripts/rescan.sh (100%) rename plugins/cli-jq/{cli-jq => }/skills/cli-jq/SKILL.md (67%) rename plugins/cli-jq/{cli-jq => }/skills/cli-jq/references/commands.md (100%) rename plugins/cli-jq/{cli-jq => }/skills/cli-jq/references/examples.md (100%) create mode 100644 plugins/cli-make/.claude-plugin/plugin.json create mode 100644 plugins/cli-make/commands/scan-cli.md create mode 100644 plugins/cli-make/scripts/rescan.sh create mode 100644 plugins/cli-make/skills/cli-make/SKILL.md create mode 100644 plugins/cli-make/skills/cli-make/references/commands.md create mode 100644 plugins/cli-make/skills/cli-make/references/examples.md create mode 100644 plugins/cli-node/.claude-plugin/plugin.json create mode 100644 plugins/cli-node/commands/scan-cli.md create mode 100644 plugins/cli-node/scripts/rescan.sh create mode 100644 plugins/cli-node/skills/cli-node/SKILL.md create mode 100644 plugins/cli-node/skills/cli-node/references/commands.md create mode 100644 plugins/cli-node/skills/cli-node/references/examples.md create mode 100644 plugins/cli-npx/.claude-plugin/plugin.json create mode 100644 plugins/cli-npx/commands/scan-cli.md create mode 100644 plugins/cli-npx/scripts/rescan.sh create mode 100644 plugins/cli-npx/skills/cli-npx/SKILL.md create mode 100644 plugins/cli-npx/skills/cli-npx/references/commands.md create mode 100644 plugins/cli-npx/skills/cli-npx/references/examples.md create mode 100644 plugins/cli-perl/.claude-plugin/plugin.json create mode 100644 plugins/cli-perl/commands/scan-cli.md create mode 100644 plugins/cli-perl/scripts/rescan.sh create mode 100644 plugins/cli-perl/skills/cli-perl/SKILL.md create mode 100644 plugins/cli-perl/skills/cli-perl/references/commands.md create mode 100644 plugins/cli-perl/skills/cli-perl/references/examples.md create mode 100644 plugins/cli-pip/.claude-plugin/plugin.json create mode 100644 plugins/cli-pip/commands/scan-cli.md create mode 100644 plugins/cli-pip/scripts/rescan.sh create mode 100644 plugins/cli-pip/skills/cli-pip/SKILL.md create mode 100644 plugins/cli-pip/skills/cli-pip/references/commands.md create mode 100644 plugins/cli-pip/skills/cli-pip/references/examples.md create mode 100644 plugins/cli-pnpm/.claude-plugin/plugin.json create mode 100644 plugins/cli-pnpm/commands/scan-cli.md create mode 100644 plugins/cli-pnpm/scripts/rescan.sh create mode 100644 plugins/cli-pnpm/skills/cli-pnpm/SKILL.md create mode 100644 plugins/cli-pnpm/skills/cli-pnpm/references/commands.md create mode 100644 plugins/cli-pnpm/skills/cli-pnpm/references/examples.md create mode 100644 plugins/cli-python/.claude-plugin/plugin.json create mode 100644 plugins/cli-python/commands/scan-cli.md create mode 100644 plugins/cli-python/scripts/rescan.sh create mode 100644 plugins/cli-python/skills/cli-python/SKILL.md create mode 100644 plugins/cli-python/skills/cli-python/references/commands.md create mode 100644 plugins/cli-python/skills/cli-python/references/examples.md create mode 100644 plugins/cli-python3/.claude-plugin/plugin.json create mode 100644 plugins/cli-python3/commands/scan-cli.md create mode 100644 plugins/cli-python3/scripts/rescan.sh create mode 100644 plugins/cli-python3/skills/cli-python3/SKILL.md create mode 100644 plugins/cli-python3/skills/cli-python3/references/commands.md create mode 100644 plugins/cli-python3/skills/cli-python3/references/examples.md create mode 100644 plugins/cli-tar/.claude-plugin/plugin.json create mode 100644 plugins/cli-tar/commands/scan-cli.md create mode 100644 plugins/cli-tar/scripts/rescan.sh create mode 100644 plugins/cli-tar/skills/cli-tar/SKILL.md create mode 100644 plugins/cli-tar/skills/cli-tar/references/commands.md create mode 100644 plugins/cli-tar/skills/cli-tar/references/examples.md create mode 100644 plugins/cli-yq/.claude-plugin/plugin.json create mode 100644 plugins/cli-yq/commands/scan-cli.md create mode 100644 plugins/cli-yq/scripts/rescan.sh create mode 100644 plugins/cli-yq/skills/cli-yq/SKILL.md create mode 100644 plugins/cli-yq/skills/cli-yq/references/commands.md create mode 100644 plugins/cli-yq/skills/cli-yq/references/examples.md create mode 100644 plugins/cli-zip/.claude-plugin/plugin.json create mode 100644 plugins/cli-zip/commands/scan-cli.md create mode 100644 plugins/cli-zip/scripts/rescan.sh create mode 100644 plugins/cli-zip/skills/cli-zip/SKILL.md create mode 100644 plugins/cli-zip/skills/cli-zip/references/commands.md create mode 100644 plugins/cli-zip/skills/cli-zip/references/examples.md diff --git a/plugins/cli-awk/.claude-plugin/plugin.json b/plugins/cli-awk/.claude-plugin/plugin.json new file mode 100644 index 0000000..5696a6a --- /dev/null +++ b/plugins/cli-awk/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-awk", + "version": "", + "description": "Command reference plugin for awk CLI", + "keywords": [ + "awk" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-awk/commands/scan-cli.md b/plugins/cli-awk/commands/scan-cli.md new file mode 100644 index 0000000..1304c76 --- /dev/null +++ b/plugins/cli-awk/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the awk CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan awk CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `awk` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-awk/scripts/rescan.sh b/plugins/cli-awk/scripts/rescan.sh new file mode 100644 index 0000000..b1a13b1 --- /dev/null +++ b/plugins/cli-awk/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan awk CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="awk" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-awk" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-awk/skills/cli-awk/SKILL.md b/plugins/cli-awk/skills/cli-awk/SKILL.md new file mode 100644 index 0000000..5f5b372 --- /dev/null +++ b/plugins/cli-awk/skills/cli-awk/SKILL.md @@ -0,0 +1,69 @@ +--- +name: cli-awk +description: >- + This skill should be used when the user needs help with awk CLI commands, flags, and troubleshooting. +--- + +# awk CLI Reference + +Compact command reference for **awk** v. + +- **0** total commands +- **0** command flags + **28** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `awk` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--assign` | `-v` | string | assign=var=val | +| `--bignum` | `-M` | bool | bignum | +| `--characters-as-bytes` | `-b` | bool | characters-as-bytes | +| `--copyright` | `-C` | bool | copyright | +| `--debug` | `-D` | string | debug[=file] | +| `--dump-variables` | `-d` | string | dump-variables[=file] | +| `--exec` | `-E` | string | exec=file | +| `--field-separator` | `-F` | string | field-separator=fs | +| `--file` | `-f` | string | file=progfile | +| `--gen-pot` | `-g` | bool | gen-pot | +| `--help` | `-h` | bool | help | +| `--include` | `-i` | string | include=includefile | +| `--lint` | `-L` | string | lint[=fatal|invalid|no-ext] | +| `--lint-old` | `-t` | bool | lint-old | +| `--load` | `-l` | string | load=library | +| `--no-optimize` | `-s` | bool | no-optimize | +| `--non-decimal-data` | `-n` | bool | non-decimal-data | +| `--optimize` | `-O` | bool | optimize | +| `--posix` | `-P` | bool | posix | +| `--pretty-print` | `-o` | string | pretty-print[=file] | +| `--profile` | `-p` | string | profile[=file] | +| `--re-interval` | `-r` | bool | re-interval | +| `--sandbox` | `-S` | bool | sandbox | +| `--source` | `-e` | string | source='program-text' | +| `--trace` | `-I` | bool | trace | +| `--traditional` | `-c` | bool | traditional | +| `--use-lc-numeric` | `-N` | bool | use-lc-numeric | +| `--version` | `-V` | bool | version | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-awk/skills/cli-awk/references/commands.md b/plugins/cli-awk/skills/cli-awk/references/commands.md new file mode 100644 index 0000000..2a68393 --- /dev/null +++ b/plugins/cli-awk/skills/cli-awk/references/commands.md @@ -0,0 +1,35 @@ +# awk -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--assign` | `-v` | string | assign=var=val | +| `--bignum` | `-M` | bool | bignum | +| `--characters-as-bytes` | `-b` | bool | characters-as-bytes | +| `--copyright` | `-C` | bool | copyright | +| `--debug` | `-D` | string | debug[=file] | +| `--dump-variables` | `-d` | string | dump-variables[=file] | +| `--exec` | `-E` | string | exec=file | +| `--field-separator` | `-F` | string | field-separator=fs | +| `--file` | `-f` | string | file=progfile | +| `--gen-pot` | `-g` | bool | gen-pot | +| `--help` | `-h` | bool | help | +| `--include` | `-i` | string | include=includefile | +| `--lint` | `-L` | string | lint[=fatal|invalid|no-ext] | +| `--lint-old` | `-t` | bool | lint-old | +| `--load` | `-l` | string | load=library | +| `--no-optimize` | `-s` | bool | no-optimize | +| `--non-decimal-data` | `-n` | bool | non-decimal-data | +| `--optimize` | `-O` | bool | optimize | +| `--posix` | `-P` | bool | posix | +| `--pretty-print` | `-o` | string | pretty-print[=file] | +| `--profile` | `-p` | string | profile[=file] | +| `--re-interval` | `-r` | bool | re-interval | +| `--sandbox` | `-S` | bool | sandbox | +| `--source` | `-e` | string | source='program-text' | +| `--trace` | `-I` | bool | trace | +| `--traditional` | `-c` | bool | traditional | +| `--use-lc-numeric` | `-N` | bool | use-lc-numeric | +| `--version` | `-V` | bool | version | + diff --git a/plugins/cli-awk/skills/cli-awk/references/examples.md b/plugins/cli-awk/skills/cli-awk/references/examples.md new file mode 100644 index 0000000..9bda6c4 --- /dev/null +++ b/plugins/cli-awk/skills/cli-awk/references/examples.md @@ -0,0 +1,2 @@ +# awk -- Usage Examples + diff --git a/plugins/cli-curl/.claude-plugin/plugin.json b/plugins/cli-curl/.claude-plugin/plugin.json new file mode 100644 index 0000000..18b24c2 --- /dev/null +++ b/plugins/cli-curl/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-curl", + "version": "8.5.0", + "description": "Command reference plugin for curl CLI", + "keywords": [ + "curl" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-curl/commands/scan-cli.md b/plugins/cli-curl/commands/scan-cli.md new file mode 100644 index 0000000..d7ba8cc --- /dev/null +++ b/plugins/cli-curl/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the curl CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan curl CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `curl` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-curl/scripts/rescan.sh b/plugins/cli-curl/scripts/rescan.sh new file mode 100644 index 0000000..9b7c70c --- /dev/null +++ b/plugins/cli-curl/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan curl CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="curl" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-curl/skills/cli-curl/SKILL.md b/plugins/cli-curl/skills/cli-curl/SKILL.md new file mode 100644 index 0000000..b80b01a --- /dev/null +++ b/plugins/cli-curl/skills/cli-curl/SKILL.md @@ -0,0 +1,53 @@ +--- +name: cli-curl +description: >- + This skill should be used when the user needs help with curl CLI commands, flags, and troubleshooting. +--- + +# curl CLI Reference + +Compact command reference for **curl** v8.5.0. + +- **0** total commands +- **0** command flags + **12** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `curl` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--data` | `-d` | string | HTTP POST data | +| `--fail` | `-f` | bool | Fail fast with no output on HTTP errors | +| `--help` | `-h` | string | Get help for commands | +| `--include` | `-i` | bool | Include protocol response headers in the output | +| `--output` | `-o` | string | Write to file instead of stdout | +| `--remote-name` | `-O` | bool | Write output to a file named as the remote file | +| `--silent` | `-s` | bool | Silent mode | +| `--upload-file` | `-T` | string | Transfer local FILE to destination | +| `--user` | `-u` | string | Server user and password | +| `--user-agent` | `-A` | string | Send User-Agent to server | +| `--verbose` | `-v` | bool | Make the operation more talkative | +| `--version` | `-V` | bool | Show version number and quit | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-curl/skills/cli-curl/references/commands.md b/plugins/cli-curl/skills/cli-curl/references/commands.md new file mode 100644 index 0000000..8d8000d --- /dev/null +++ b/plugins/cli-curl/skills/cli-curl/references/commands.md @@ -0,0 +1,19 @@ +# curl -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--data` | `-d` | string | HTTP POST data | +| `--fail` | `-f` | bool | Fail fast with no output on HTTP errors | +| `--help` | `-h` | string | Get help for commands | +| `--include` | `-i` | bool | Include protocol response headers in the output | +| `--output` | `-o` | string | Write to file instead of stdout | +| `--remote-name` | `-O` | bool | Write output to a file named as the remote file | +| `--silent` | `-s` | bool | Silent mode | +| `--upload-file` | `-T` | string | Transfer local FILE to destination | +| `--user` | `-u` | string | Server user and password | +| `--user-agent` | `-A` | string | Send User-Agent to server | +| `--verbose` | `-v` | bool | Make the operation more talkative | +| `--version` | `-V` | bool | Show version number and quit | + diff --git a/plugins/cli-curl/skills/cli-curl/references/examples.md b/plugins/cli-curl/skills/cli-curl/references/examples.md new file mode 100644 index 0000000..1fc3e9f --- /dev/null +++ b/plugins/cli-curl/skills/cli-curl/references/examples.md @@ -0,0 +1,2 @@ +# curl -- Usage Examples + diff --git a/plugins/cli-docker/.claude-plugin/plugin.json b/plugins/cli-docker/.claude-plugin/plugin.json index e955e96..15c942e 100644 --- a/plugins/cli-docker/.claude-plugin/plugin.json +++ b/plugins/cli-docker/.claude-plugin/plugin.json @@ -4,17 +4,14 @@ "description": "Command reference plugin for docker CLI", "keywords": [ "docker", - "attach", - "local", - "standard", - "input", - "output" + "compose", + "container", + "builder", + "buildx", + "image", + "mcp", + "plugin" ], - "author": { - "name": "Nuno Salvacao", - "email": "nuno.salvacao@gmail.com", - "url": "https://github.com/nsalvacao" - }, "repository": "https://github.com/nsalvacao/cli-plugins", "license": "MIT" } diff --git a/plugins/cli-docker/skills/cli-docker/SKILL.md b/plugins/cli-docker/skills/cli-docker/SKILL.md index 0239179..cbc398d 100644 --- a/plugins/cli-docker/skills/cli-docker/SKILL.md +++ b/plugins/cli-docker/skills/cli-docker/SKILL.md @@ -1,88 +1,32 @@ --- name: cli-docker description: >- - This skill should be used when the user needs help with docker CLI commands, including builder (extended build capabilities with buildkit), buildx (extended build capabilities with buildkit), compose, container (manage containers), context (manage contexts), image (manage images), manifest, mcp, network (manage networks), plugin (manage plugins), swarm (manage swarm), system (manage docker), volume (manage volumes), attach, bake, build, commit, cp, create, diff, events, exec, export, history, images, import, info, inspect, kill, load, login, logout, logs, pause, port, ps, pull, push, rename, restart, rm, rmi, run, save, search, start, stats, stop, tag, top, unpause, update, version, wait. Covers flags, subcommands, usage patterns, and troubleshooting for all 54 docker commands. + This skill should be used when the user needs help with docker CLI commands, flags, and troubleshooting. --- # docker CLI Reference -Expert command reference for **docker** v29.2.0. +Compact command reference for **docker** v29.2.0. -- **271** commands (31 with subcommands) +- **271** total commands - **1408** command flags + **11** global flags -- **7** usage examples +- **7** extracted usage examples - Max nesting depth: 3 ## When to Use -This skill applies when: - Constructing or validating `docker` commands -- Looking up flags, options, or subcommands -- Troubleshooting `docker` invocations or errors -- Needing correct syntax for `docker` operations - -## Prerequisites - -Ensure `docker` is installed and available on PATH. - -## Quick Reference - -| Command | Description | -| --- | --- | -| `docker attach` | Attach local standard input, output, and error streams to a running container | -| `docker bake` | Build from a file | -| `docker build` | Start a build | -| `docker builder` | Extended build capabilities with BuildKit | -| `docker buildx` | Extended build capabilities with BuildKit | -| `docker commit` | Create a new image from a container's changes | -| `docker compose` | Define and run multi-container applications with Docker | -| `docker container` | Manage containers | -| `docker context` | Manage contexts | -| `docker cp` | docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH | -| `docker create` | Create a new container | -| `docker diff` | | -| `docker events` | Get real time events from the server | -| `docker exec` | Execute a command in a running container | -| `docker export` | Export a container's filesystem as a tar archive | -| `docker history` | Show the history of an image | -| `docker image` | Manage images | -| `docker images` | List images | -| `docker import` | Import the contents from a tarball to create a filesystem image | -| `docker info` | Display system-wide information | -| `docker inspect` | Return low-level information on Docker objects | -| `docker kill` | Kill one or more running containers | -| `docker load` | Load an image from a tar archive or STDIN | -| `docker login` | Authenticate to a registry. | -| `docker logout` | | -| `docker logs` | Fetch the logs of a container | -| `docker manifest` | The **docker manifest** command has subcommands for managing image manifests and | -| `docker mcp` | Docker MCP Toolkit's CLI - Manage your MCP servers and clients. | -| `docker network` | Manage networks | -| `docker pause` | | -| `docker plugin` | Manage plugins | -| `docker port` | | -| `docker ps` | List containers | -| `docker pull` | Download an image from a registry | -| `docker push` | Upload an image to a registry | -| `docker rename` | | -| `docker restart` | Restart one or more containers | -| `docker rm` | Remove one or more containers | -| `docker rmi` | Remove one or more images | -| `docker run` | Create and run a new container from an image | -| `docker save` | Save one or more images to a tar archive (streamed to STDOUT by default) | -| `docker search` | Search Docker Hub for images | -| `docker start` | Start one or more stopped containers | -| `docker stats` | Display a live stream of container(s) resource usage statistics | -| `docker stop` | Stop one or more running containers | -| `docker swarm` | Manage Swarm | -| `docker system` | Manage Docker | -| `docker tag` | | -| `docker top` | Display the running processes of a container | -| `docker unpause` | | -| `docker update` | Update configuration of one or more containers | -| `docker version` | Show the Docker version information | -| `docker volume` | Manage volumes | -| `docker wait` | | +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command Groups: +`builder`, `buildx`, `compose`, `container`, `context`, `image`, `manifest`, `mcp`, `network`, `plugin`, `swarm`, `system`, `volume` +Standalone Commands: +`attach`, `bake`, `build`, `commit`, `cp`, `create`, `diff`, `events`, `exec`, `export`, `history`, `images`, `import`, `info`, `inspect`, `kill`, `load`, `login`, `logout`, `logs`, `pause`, `port`, `ps`, `pull`, `push` +... +16 more in `references/commands.md` +Command format examples: `docker swarm`, `docker attach`, `docker bake` ### Global Flags @@ -100,18 +44,7 @@ Ensure `docker` is installed and available on PATH. | `--tlsverify` | `` | bool | Use TLS and verify the remote | | `--version` | `-v` | bool | Print version information and quit | -## Command Overview - - -### Commands - -`attach`, `bake`, `build`, `commit`, `cp`, `create`, `diff`, `events`, `exec`, `export`, `history`, `images`, `import`, `info`, `inspect`, `kill`, `load`, `login`, `logout`, `logs`, `pause`, `port`, `ps`, `pull`, `push`, `rename`, `restart`, `rm`, `rmi`, `run`, `save`, `search`, `start`, `stats`, `stop`, `tag`, `top`, `unpause`, `update`, `version`, `wait` - -### Command Groups - -`builder`, `buildx`, `compose`, `container`, `context`, `image`, `manifest`, `mcp`, `network`, `plugin`, `swarm`, `system`, `volume` - -## Common Usage Patterns +## Common Usage Patterns (Compact) ```bash docker mcp catalog add my-catalog github-server ./github-catalog.yaml @@ -138,28 +71,11 @@ docker mcp catalog ls ``` docker mcp catalog ls --format=json -```bash -docker mcp catalog show -``` -docker mcp catalog show my-catalog --format=json - -```bash -docker mcp catalog update -``` -docker mcp catalog update team-servers - ## Detailed References -For complete command documentation including all flags and subcommands: -- **Full command tree:** see `references/commands.md` -- **All usage examples:** see `references/examples.md` - -## Troubleshooting - -- Use `docker --help` or `docker --help` for inline help -- Add `--verbose` for detailed output during debugging +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` -## Re-scanning +## Re-Scanning -To update this plugin after a CLI version change, run the `/scan-cli` command -or manually execute the crawler and generator. +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-g++/.claude-plugin/plugin.json b/plugins/cli-g++/.claude-plugin/plugin.json new file mode 100644 index 0000000..660bdcd --- /dev/null +++ b/plugins/cli-g++/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-g++", + "version": "13.3.0", + "description": "Command reference plugin for g++ CLI", + "keywords": [ + "g++" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-g++/commands/scan-cli.md b/plugins/cli-g++/commands/scan-cli.md new file mode 100644 index 0000000..8bd311f --- /dev/null +++ b/plugins/cli-g++/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the g++ CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan g++ CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `g++` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-g++/scripts/rescan.sh b/plugins/cli-g++/scripts/rescan.sh new file mode 100644 index 0000000..3b82bb7 --- /dev/null +++ b/plugins/cli-g++/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan g++ CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="g++" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-g++/skills/cli-g++/SKILL.md b/plugins/cli-g++/skills/cli-g++/SKILL.md new file mode 100644 index 0000000..1fcb417 --- /dev/null +++ b/plugins/cli-g++/skills/cli-g++/SKILL.md @@ -0,0 +1,49 @@ +--- +name: cli-g++ +description: >- + This skill should be used when the user needs help with g++ CLI commands, flags, and troubleshooting. +--- + +# g++ CLI Reference + +Compact command reference for **g++** v13.3.0. + +- **0** total commands +- **0** command flags + **8** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `g++` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--help` | `` | bool | Display this information. | +| `--sysroot` | `` | string | Use as the root directory for headers | +| `--target-help` | `` | bool | Display target specific command line options (including assembler and linker options). | +| `--version` | `` | bool | Display compiler version information. | +| `-E` | `-E` | bool | Preprocess only; do not compile, assemble or link. | +| `-S` | `-S` | bool | Compile only; do not assemble or link. | +| `-c` | `-c` | bool | Compile and assemble, but do not link. | +| `-v` | `-v` | bool | Display the programs invoked by the compiler. | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-g++/skills/cli-g++/references/commands.md b/plugins/cli-g++/skills/cli-g++/references/commands.md new file mode 100644 index 0000000..8745592 --- /dev/null +++ b/plugins/cli-g++/skills/cli-g++/references/commands.md @@ -0,0 +1,15 @@ +# g++ -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--help` | `` | bool | Display this information. | +| `--sysroot` | `` | string | Use as the root directory for headers | +| `--target-help` | `` | bool | Display target specific command line options (including assembler and linker options). | +| `--version` | `` | bool | Display compiler version information. | +| `-E` | `-E` | bool | Preprocess only; do not compile, assemble or link. | +| `-S` | `-S` | bool | Compile only; do not assemble or link. | +| `-c` | `-c` | bool | Compile and assemble, but do not link. | +| `-v` | `-v` | bool | Display the programs invoked by the compiler. | + diff --git a/plugins/cli-g++/skills/cli-g++/references/examples.md b/plugins/cli-g++/skills/cli-g++/references/examples.md new file mode 100644 index 0000000..b4e87ac --- /dev/null +++ b/plugins/cli-g++/skills/cli-g++/references/examples.md @@ -0,0 +1,2 @@ +# g++ -- Usage Examples + diff --git a/plugins/cli-gcc/.claude-plugin/plugin.json b/plugins/cli-gcc/.claude-plugin/plugin.json new file mode 100644 index 0000000..6f1eff1 --- /dev/null +++ b/plugins/cli-gcc/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-gcc", + "version": "13.3.0", + "description": "Command reference plugin for gcc CLI", + "keywords": [ + "gcc" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-gcc/commands/scan-cli.md b/plugins/cli-gcc/commands/scan-cli.md new file mode 100644 index 0000000..478943f --- /dev/null +++ b/plugins/cli-gcc/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the gcc CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan gcc CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `gcc` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-gcc/scripts/rescan.sh b/plugins/cli-gcc/scripts/rescan.sh new file mode 100644 index 0000000..a4a11a0 --- /dev/null +++ b/plugins/cli-gcc/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan gcc CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="gcc" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-gcc/skills/cli-gcc/SKILL.md b/plugins/cli-gcc/skills/cli-gcc/SKILL.md new file mode 100644 index 0000000..e2989b3 --- /dev/null +++ b/plugins/cli-gcc/skills/cli-gcc/SKILL.md @@ -0,0 +1,49 @@ +--- +name: cli-gcc +description: >- + This skill should be used when the user needs help with gcc CLI commands, flags, and troubleshooting. +--- + +# gcc CLI Reference + +Compact command reference for **gcc** v13.3.0. + +- **0** total commands +- **0** command flags + **8** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `gcc` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--help` | `` | bool | Display this information. | +| `--sysroot` | `` | string | Use as the root directory for headers | +| `--target-help` | `` | bool | Display target specific command line options (including assembler and linker options). | +| `--version` | `` | bool | Display compiler version information. | +| `-E` | `-E` | bool | Preprocess only; do not compile, assemble or link. | +| `-S` | `-S` | bool | Compile only; do not assemble or link. | +| `-c` | `-c` | bool | Compile and assemble, but do not link. | +| `-v` | `-v` | bool | Display the programs invoked by the compiler. | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-gcc/skills/cli-gcc/references/commands.md b/plugins/cli-gcc/skills/cli-gcc/references/commands.md new file mode 100644 index 0000000..c12a0e6 --- /dev/null +++ b/plugins/cli-gcc/skills/cli-gcc/references/commands.md @@ -0,0 +1,15 @@ +# gcc -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--help` | `` | bool | Display this information. | +| `--sysroot` | `` | string | Use as the root directory for headers | +| `--target-help` | `` | bool | Display target specific command line options (including assembler and linker options). | +| `--version` | `` | bool | Display compiler version information. | +| `-E` | `-E` | bool | Preprocess only; do not compile, assemble or link. | +| `-S` | `-S` | bool | Compile only; do not assemble or link. | +| `-c` | `-c` | bool | Compile and assemble, but do not link. | +| `-v` | `-v` | bool | Display the programs invoked by the compiler. | + diff --git a/plugins/cli-gcc/skills/cli-gcc/references/examples.md b/plugins/cli-gcc/skills/cli-gcc/references/examples.md new file mode 100644 index 0000000..59d91b5 --- /dev/null +++ b/plugins/cli-gcc/skills/cli-gcc/references/examples.md @@ -0,0 +1,2 @@ +# gcc -- Usage Examples + diff --git a/plugins/cli-gh/.claude-plugin/plugin.json b/plugins/cli-gh/.claude-plugin/plugin.json index 8fdc9c7..a44d3e9 100644 --- a/plugins/cli-gh/.claude-plugin/plugin.json +++ b/plugins/cli-gh/.claude-plugin/plugin.json @@ -4,17 +4,14 @@ "description": "Command reference plugin for gh CLI", "keywords": [ "gh", - "working", - "with", - "agent", - "tasks", - "github" + "project", + "pr", + "repo", + "issue", + "codespace", + "release", + "extension" ], - "author": { - "name": "Nuno Salvacao", - "email": "nuno.salvacao@gmail.com", - "url": "https://github.com/nsalvacao" - }, "repository": "https://github.com/nsalvacao/cli-plugins", "license": "MIT" } diff --git a/plugins/cli-gh/skills/cli-gh/SKILL.md b/plugins/cli-gh/skills/cli-gh/SKILL.md index 7966242..446b5ba 100644 --- a/plugins/cli-gh/skills/cli-gh/SKILL.md +++ b/plugins/cli-gh/skills/cli-gh/SKILL.md @@ -1,64 +1,31 @@ --- name: cli-gh description: >- - This skill should be used when the user needs help with gh CLI commands, including agent-task, alias, attestation (download and verify artifact attestations), auth (authenticate gh and git with github), cache (work with github actions caches), codespace (connect to and manage codespaces), config (display or change configuration settings for gh), extension, gist (work with github gists), gpg-key, issue (work with github issues), label (work with github labels), org (work with github organizations), pr (work with github pull requests), preview (preview commands are for testing), project (work with github projects), release (manage releases), repo (work with github repositories), ruleset, run (list), search (search across all of github), secret (secrets can be set at the repository), ssh-key, variable (variables can be set at the repository), workflow (list), api, browse, completion, copilot, status. Covers flags, subcommands, usage patterns, and troubleshooting for all 30 gh commands. + This skill should be used when the user needs help with gh CLI commands, flags, and troubleshooting. --- # gh CLI Reference -Expert command reference for **gh** v2.86.0. +Compact command reference for **gh** v2.86.0. -- **212** commands (30 with subcommands) +- **212** total commands - **1095** command flags + **2** global flags -- **166** usage examples +- **166** extracted usage examples - Max nesting depth: 2 ## When to Use -This skill applies when: - Constructing or validating `gh` commands -- Looking up flags, options, or subcommands -- Troubleshooting `gh` invocations or errors -- Needing correct syntax for `gh` operations - -## Prerequisites - -Ensure `gh` is installed and available on PATH. - -## Quick Reference - -| Command | Description | -| --- | --- | -| `gh agent-task` | Working with agent tasks in the GitHub CLI is in preview and | -| `gh alias` | Aliases can be used to make shortcuts for gh commands or to compose multiple commands. | -| `gh api` | accepts 1 arg(s), received 0 | -| `gh attestation` | Download and verify artifact attestations. | -| `gh auth` | Authenticate gh and git with GitHub | -| `gh browse` | Transition from the terminal to the web browser to view and interact with: | -| `gh cache` | Work with GitHub Actions caches. | -| `gh codespace` | Connect to and manage codespaces | -| `gh completion` | Generate shell completion scripts for GitHub CLI commands. | -| `gh config` | Display or change configuration settings for gh. | -| `gh copilot` | Runs the GitHub Copilot CLI. | -| `gh extension` | GitHub CLI extensions are repositories that provide additional gh commands. | -| `gh gist` | Work with GitHub gists. | -| `gh gpg-key` | Manage GPG keys registered with your GitHub account. | -| `gh issue` | Work with GitHub issues. | -| `gh label` | Work with GitHub labels. | -| `gh org` | Work with GitHub organizations. | -| `gh pr` | Work with GitHub pull requests. | -| `gh preview` | Preview commands are for testing, demonstrative, and development purposes only. | -| `gh project` | Work with GitHub Projects. | -| `gh release` | Manage releases | -| `gh repo` | Work with GitHub repositories. | -| `gh ruleset` | Repository rulesets are a way to define a set of rules that apply to a repository. | -| `gh run` | List, view, and watch recent workflow runs from GitHub Actions. | -| `gh search` | Search across all of GitHub. | -| `gh secret` | Secrets can be set at the repository, or organization level for use in | -| `gh ssh-key` | Manage SSH keys registered with your GitHub account. | -| `gh status` | The status command prints information about your work on GitHub across all the repositories you're subscribed to, including: | -| `gh variable` | Variables can be set at the repository, environment or organization level for use in | -| `gh workflow` | List, view, and run workflows in GitHub Actions. | +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command Groups: +`agent-task`, `alias`, `attestation`, `auth`, `cache`, `codespace`, `config`, `extension`, `gist`, `gpg-key`, `issue`, `label`, `org`, `pr`, `preview`, `project`, `release`, `repo`, `ruleset`, `run`, `search`, `secret`, `ssh-key`, `variable`, `workflow` +Standalone Commands: +`api`, `browse`, `completion`, `copilot`, `status` +Command format examples: `gh agent-task`, `gh alias`, `gh api` ### Global Flags @@ -67,18 +34,7 @@ Ensure `gh` is installed and available on PATH. | `--help` | `` | bool | Show help for command | | `--version` | `` | bool | Show gh version | -## Command Overview - - -### Command Groups - -`agent-task`, `alias`, `attestation`, `auth`, `cache`, `codespace`, `config`, `extension`, `gist`, `gpg-key`, `issue`, `label`, `org`, `pr`, `preview`, `project`, `release`, `repo`, `ruleset`, `run`, `search`, `secret`, `ssh-key`, `variable`, `workflow` - -### Commands - -`api`, `browse`, `completion`, `copilot`, `status` - -## Common Usage Patterns +## Common Usage Patterns (Compact) ```bash gh agent-task list @@ -105,33 +61,11 @@ gh agent-task create ``` gh agent-task create -F task-desc.md -```bash -gh agent-task create "fix errors" --base branch -``` -gh agent-task create "build me a new app" --custom-agent my-agent - -```bash -gh agent-task view e2fa49d2-f164-4a56-ab99-498090b8fcdf -``` -gh agent-task view 12345 - -```bash -gh agent-task view --repo OWNER/REPO 12345 -``` -gh agent-task view OWNER/REPO#12345 - ## Detailed References -For complete command documentation including all flags and subcommands: -- **Full command tree:** see `references/commands.md` -- **All usage examples:** see `references/examples.md` - -## Troubleshooting - -- Use `gh --help` or `gh --help` for inline help -- Add `--verbose` for detailed output during debugging +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` -## Re-scanning +## Re-Scanning -To update this plugin after a CLI version change, run the `/scan-cli` command -or manually execute the crawler and generator. +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-gh/skills/cli-gh/references/commands.md b/plugins/cli-gh/skills/cli-gh/references/commands.md index 119fe9d..17dbac1 100644 --- a/plugins/cli-gh/skills/cli-gh/references/commands.md +++ b/plugins/cli-gh/skills/cli-gh/references/commands.md @@ -2618,7 +2618,7 @@ View the summary of a workflow ### `gh api` -accepts 1 arg(s), received 0 + ### `gh browse` diff --git a/plugins/cli-git/.claude-plugin/plugin.json b/plugins/cli-git/.claude-plugin/plugin.json index 18bdb58..063cb03 100644 --- a/plugins/cli-git/.claude-plugin/plugin.json +++ b/plugins/cli-git/.claude-plugin/plugin.json @@ -4,17 +4,14 @@ "description": "Command reference plugin for git CLI", "keywords": [ "git", - "file", - "contents", - "index", - "binary", - "search" + "repository", + "working", + "tree", + "commit", + "create", + "changes", + "objects" ], - "author": { - "name": "Nuno Salvacao", - "email": "nuno.salvacao@gmail.com", - "url": "https://github.com/nsalvacao" - }, "repository": "https://github.com/nsalvacao/cli-plugins", "license": "MIT" } diff --git a/plugins/cli-git/skills/cli-git/SKILL.md b/plugins/cli-git/skills/cli-git/SKILL.md index 8c22f5b..ebef8ee 100644 --- a/plugins/cli-git/skills/cli-git/SKILL.md +++ b/plugins/cli-git/skills/cli-git/SKILL.md @@ -1,122 +1,66 @@ --- name: cli-git description: >- - This skill should be used when the user needs help with git CLI commands, including add, bisect, branch, clone, commit, diff, fetch, grep, init, log, merge, mv, pull, push, rebase, reset, restore, rm, show, status, switch, tag. Covers flags, subcommands, usage patterns, and troubleshooting for all 22 git commands. + This skill should be used when the user needs help with git CLI commands, flags, and troubleshooting. --- # git CLI Reference -Expert command reference for **git** v2.43.0. +Compact command reference for **git** v2.43.0. -- **22** commands (0 with subcommands) +- **22** total commands - **441** command flags + **0** global flags -- **126** usage examples +- **31** extracted usage examples - Max nesting depth: 0 ## When to Use -This skill applies when: - Constructing or validating `git` commands -- Looking up flags, options, or subcommands -- Troubleshooting `git` invocations or errors -- Needing correct syntax for `git` operations - -## Prerequisites - -Ensure `git` is installed and available on PATH. - -## Quick Reference - -| Command | Description | -| --- | --- | -| `git add` | Add file contents to the index | -| `git bisect` | Use binary search to find the commit that introduced a bug | -| `git branch` | List, create, or delete branches | -| `git clone` | Clone a repository into a new directory | -| `git commit` | Record changes to the repository | -| `git diff` | Show changes between commits, commit and working tree, etc | -| `git fetch` | Download objects and refs from another repository | -| `git grep` | Print lines matching a pattern | -| `git init` | Create an empty Git repository or reinitialize an existing one | -| `git log` | Show commit logs | -| `git merge` | or: git merge --abort | -| `git mv` | Move or rename a file, a directory, or a symlink | -| `git pull` | Fetch from and integrate with another repository or a local branch | -| `git push` | Update remote refs along with associated objects | -| `git rebase` | Reapply commits on top of another base tip | -| `git reset` | fatal: not a git repository (or any parent up to mount point /mnt) | -| `git restore` | Restore working tree files | -| `git rm` | Remove files from the working tree and from the index | -| `git show` | Show various types of objects | -| `git status` | Show the working tree status | -| `git switch` | Switch branches | -| `git tag` | Create, list, delete or verify a tag object signed with GPG | +- Looking up flags/options fast +- Troubleshooting failed invocations -### Global Flags - - - -## Command Overview - - -### Commands +## Top-Level Commands +Standalone Commands: `add`, `bisect`, `branch`, `clone`, `commit`, `diff`, `fetch`, `grep`, `init`, `log`, `merge`, `mv`, `pull`, `push`, `rebase`, `reset`, `restore`, `rm`, `show`, `status`, `switch`, `tag` +Command format examples: `git add`, `git bisect`, `git branch` -## Common Usage Patterns - -```bash -git add Documentation/\*.txt -``` -Note that the asterisk * is quoted from the shell in this example; this lets the command include the files +### Global Flags -```bash -from subdirectories of Documentation/ directory. -``` -git add git-*.sh +_No global flags detected._ -```bash -Because this example lets the shell expand the asterisk (i.e. you are listing the files explicitly), it -``` -does not consider subdir/git-foo.sh. +## Common Usage Patterns (Compact) ```bash -git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good +o Adds content from all *.txt files under Documentation directory and its subdirectories: ``` -git bisect run make # "make" builds the app +o Considers adding content from all git-*.sh scripts: ```bash -git bisect reset # quit the bisect session +o Clone from upstream: ``` -git bisect start HEAD origin -- # HEAD is bad, origin is good +o Make a local clone that borrows from the current directory, without checking things out: ```bash -git bisect run make test # "make test" builds and tests +o Clone from upstream while borrowing from an existing local directory: ``` -git bisect reset # quit the bisect session +o Create a bare repository to publish your changes to the public: ```bash -cat ~/test.sh +staging area called the "index" with git add. A file can be reverted back, only in the index but not in the ``` -make || exit 125 # this skips broken builds +working tree, to that of the last commit with git restore --staged , which effectively reverts git add ```bash -git bisect start HEAD HEAD~10 -- # culprit is among the last 10 +and prevents the changes to this file from participating in the next commit. After building the state to be ``` -git bisect run ~/test.sh +committed incrementally with these commands, git commit (without any pathname parameter) is used to record ## Detailed References -For complete command documentation including all flags and subcommands: -- **Full command tree:** see `references/commands.md` -- **All usage examples:** see `references/examples.md` - -## Troubleshooting - -- Use `git --help` or `git --help` for inline help -- Add `--verbose` for detailed output during debugging +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` -## Re-scanning +## Re-Scanning -To update this plugin after a CLI version change, run the `/scan-cli` command -or manually execute the crawler and generator. +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-git/skills/cli-git/references/commands.md b/plugins/cli-git/skills/cli-git/references/commands.md index 09d05d4..c649df0 100644 --- a/plugins/cli-git/skills/cli-git/references/commands.md +++ b/plugins/cli-git/skills/cli-git/references/commands.md @@ -544,7 +544,7 @@ git rebase [-i | --interactive] [] [--exec ] [--onto | - ### `git reset` -fatal: not a git repository (or any parent up to mount point /mnt) +Unstaged changes after reset: ### `git restore` diff --git a/plugins/cli-git/skills/cli-git/references/examples.md b/plugins/cli-git/skills/cli-git/references/examples.md index 12ef5cc..19271f4 100644 --- a/plugins/cli-git/skills/cli-git/references/examples.md +++ b/plugins/cli-git/skills/cli-git/references/examples.md @@ -3,662 +3,179 @@ ## `git add` ```bash -git add Documentation/\*.txt +o Adds content from all *.txt files under Documentation directory and its subdirectories: ``` -Note that the asterisk * is quoted from the shell in this example; this lets the command include the files - -```bash -from subdirectories of Documentation/ directory. -``` -git add git-*.sh - -```bash -Because this example lets the shell expand the asterisk (i.e. you are listing the files explicitly), it -``` -does not consider subdir/git-foo.sh. - -## `git bisect` - -```bash -git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good -``` -git bisect run make # "make" builds the app - -```bash -git bisect reset # quit the bisect session -``` -git bisect start HEAD origin -- # HEAD is bad, origin is good - -```bash -git bisect run make test # "make test" builds and tests -``` -git bisect reset # quit the bisect session - -```bash -cat ~/test.sh -``` -make || exit 125 # this skips broken builds - -```bash -git bisect start HEAD HEAD~10 -- # culprit is among the last 10 -``` -git bisect run ~/test.sh - -```bash -check_test_case.sh should exit 0 if the test case passes, and exit 1 otherwise. -``` -It is safer if both test.sh and check_test_case.sh are outside the repository to prevent interactions - -```bash -between the bisect, make and test processes and the scripts. -``` -cat ~/test.sh - -```bash -if git merge --no-commit --no-ff hot-fix && -``` -make - -```bash -then -``` -status=$? - -```bash -else -``` -status=125 - -```bash -fi -``` -git reset --hard - -```bash -exit $status -``` -This applies modifications from a hot-fix branch before each test run, e.g. in case your build or test - -```bash -environment changed so that older revisions may need a fix which newer ones have already. (Make sure the -``` -hot-fix branch is based off a commit which is contained in all revisions which you are bisecting, so that - -```bash -the merge does not pull in too much, or use git cherry-pick instead of git merge.) -``` -git bisect start HEAD HEAD~10 -- # culprit is among the last 10 - -```bash -git bisect run sh -c "make || exit 125; ~/check_test_case.sh" -``` -git bisect reset # quit the bisect session - -```bash -This shows that you can do without a run script if you write the test on a single line. -``` -git bisect start HEAD [ ... ] --no-checkout - -```bash -git bisect run sh -c ' -``` -GOOD=$(git for-each-ref "--format=%(objectname)" refs/bisect/good-*) && - -```bash -git rev-list --objects BISECT_HEAD --not $GOOD >tmp.$$ && -``` -git pack-objects --stdout >/dev/null /' (1) -``` -git for-each-ref 'refs/remotes//' (2) - -```bash -1. Using -a would conflate with any local branches you happen to have been -``` -prefixed with the same pattern. +o Considers adding content from all git-*.sh scripts: ## `git clone` ```bash -git clone git://git.kernel.org/pub/scm/.../linux.git my-linux -``` -cd my-linux - -```bash -make +o Clone from upstream: ``` -git clone -l -s -n . ../copy +o Make a local clone that borrows from the current directory, without checking things out: ```bash -cd ../copy +o Clone from upstream while borrowing from an existing local directory: ``` -git show-branch - -```bash -git clone --reference /git/linux.git \ -``` -git://git.kernel.org/pub/scm/.../linux.git \ - -```bash -my-linux -``` -cd my-linux +o Create a bare repository to publish your changes to the public: ## `git commit` ```bash -edit hello.c -``` -git rm goodbye.c - -```bash -git add hello.c -``` -git commit - -```bash -git commit -a -``` -edit hello.c hello.h - -```bash -git add hello.c hello.h -``` -edit Makefile - -```bash -git commit Makefile -``` -git commit - -```bash -git status | grep unmerged -``` -unmerged: hello.c - -## `git diff` - -```bash -git diff (1) -``` -git diff --cached (2) - -```bash -git diff HEAD (3) -``` -git diff AUTO_MERGE (4) - -```bash -1. Changes in the working tree not yet staged for the next commit. +staging area called the "index" with git add. A file can be reverted back, only in the index but not in the ``` -2. Changes between the index and your last commit; what you would be committing if you run +working tree, to that of the last commit with git restore --staged , which effectively reverts git add ```bash -git commit without -a option. +and prevents the changes to this file from participating in the next commit. After building the state to be ``` -3. Changes in the working tree since your last commit; what you would be committing if you +committed incrementally with these commands, git commit (without any pathname parameter) is used to record ```bash -run git commit -a +what has been staged so far. This is the most basic form of the command. An example: ``` -4. Changes in the working tree you've made to resolve textual conflicts so far. +files whose contents are tracked in your working tree and do corresponding git add and git rm for you. That ```bash -git diff test (1) +to git commit. When pathnames are given, the command makes a commit that only records the changes made to the ``` -git diff HEAD -- ./test (2) +named paths: ```bash -git diff HEAD^ HEAD (3) +not included in the resulting commit. However, their changes are not lost -- they are still staged and merely ``` -1. Instead of using the tip of the current branch, compare with the tip of "test" branch. +held back. After the above sequence, if you do: ```bash -2. Instead of comparing with the tip of "test" branch, compare with the tip of the current +already staged to be committed for you, and paths that conflicted are left in unmerged state. You would have ``` -branch, but limit the comparison to the file "test". +to first check which paths are conflicting with git status and after fixing them manually in your working ```bash -3. Compare the version before the last commit and the last commit. +during a merge resolution, you cannot use git commit with pathnames to alter the order the changes are ``` -git diff topic master (1) - -```bash -git diff topic..master (2) -``` -git diff topic...master (3) - -```bash -1. Changes between the tips of the topic and the master branches. -``` -2. Same as above. - -```bash -3. Changes that occurred on the master branch since when the topic branch was started off -``` -it. - -```bash -git diff --diff-filter=MRC (1) -``` -git diff --name-status (2) - -```bash -git diff arch/i386 include/asm-i386 (3) -``` -1. Show only modification, rename, and copy, but not addition or deletion. - -```bash -2. Show only names and the nature of change, but not actual diff output. -``` -3. Limit diff output to named subtrees. - -```bash -git diff --find-copies-harder -B -C (1) -``` -git diff -R (2) - -```bash -1. Spend extra cycles to find renames, copies and complete rewrites (very expensive). -``` -2. Output diff in reverse. +given pathnames (but see -i option). ## `git fetch` ```bash -git fetch origin -``` -The above command copies all branches from the remote refs/heads/ namespace and stores them to the local - -```bash -refs/remotes/origin/ namespace, unless the remote..fetch option is used to specify a -``` -non-default refspec. - -```bash -git fetch origin +seen:seen maint:tmp -``` -This updates (or creates, as necessary) branches seen and tmp in the local repository by fetching from the - -```bash -branches (respectively) seen and maint from the remote repository. -``` -The seen branch will be updated even if it does not fast-forward, because it is prefixed with a plus sign; - -```bash -tmp will not be. -``` -git fetch git://git.kernel.org/pub/scm/git/git.git maint - -```bash -git log FETCH_HEAD -``` -The first command fetches the maint branch from the repository at git://git.kernel.org/pub/scm/git/git.git - -```bash -and the second command uses FETCH_HEAD to examine the branch with git-log(1). The fetched objects will +o Update the remote-tracking branches: ``` -eventually be removed by git's built-in housekeeping (see git-gc(1)). +o Using refspecs explicitly: ## `git grep` ```bash -Looks for time_t in all tracked .c and .h files in the working directory and its subdirectories. -``` -Looks for a line that has #define and either MAX_PATH or PATH_MAX. - -```bash -Looks for a line that has NODE or Unexpected in files that have lines that match both. -``` -Looks for solution, excluding files in Documentation. - -## `git init` - -```bash -cd /path/to/my/codebase +git grep 'time_t' -- '*.[ch]' ``` -git init (1) +git grep -e '#define' --and \( -e MAX_PATH -e PATH_MAX \) ```bash -git add . (2) +git grep --all-match -e NODE -e Unexpected ``` -git commit (3) - -```bash -1. Create a /path/to/my/codebase/.git directory. -``` -2. Add all existing files to the index. +git grep solution -- :^Documentation ## `git log` ```bash -Show the whole commit history, but skip any merges -``` -Show all commits since version v2.6.12 that changed any file in the include/scsi or drivers/scsi - -```bash -subdirectories -``` -Show the changes during the last two weeks to the file gitk. The -- is necessary to avoid confusion with - -```bash -the branch named gitk +git log --no-merges ``` -Show the commits that are in the "test" branch but not yet in the "release" branch, along with the list of +git log v2.6.12.. include/scsi drivers/scsi ```bash -paths each commit modifies. +git log --since="2 weeks ago" -- gitk ``` -Shows the commits that changed builtin/rev-list.c, including those commits that occurred before the file +git log --name-status release..test ```bash -was given its present name. +git log --follow builtin/rev-list.c ``` -Shows all commits that are in any of local branches but not in any of remote-tracking branches for origin +git log --branches --not --remotes=origin ```bash -Shows all commits that are in local master but not in any remote repository master branches. +git log master --not --remotes=*/master ``` -Shows the history including change diffs, but only from the "main branch" perspective, skipping commits +git log -p -m --first-parent ```bash -that come from merged branches, and showing full diffs of changes introduced by the merges. This makes +git log -L '/int main/',/^}/:main.c ``` -sense only when following a strict policy of merging all topic branches when staying on a single - -```bash -integration branch. -``` -Shows how the function main() in the file main.c evolved over time. +git log -3 ## `git pull` ```bash -current branch: -``` -git pull - -```bash -git pull origin -``` -Normally the branch merged in is the HEAD of the remote repository, but the choice is determined by the - -```bash -branch..remote and branch..merge options; see git-config(1) for details. +o Update the remote-tracking branches for the repository you cloned from, then merge one of them into your ``` -git pull origin next - -```bash -This leaves a copy of next temporarily in FETCH_HEAD, and updates the remote-tracking branch origin/next. -``` -The same can be done by invoking fetch and merge: +o Merge into the current branch the remote branch next: ## `git push` ```bash -Works like git push , where is the current branch's remote (or origin, if no remote is -``` -configured for the current branch). - -```bash -Without additional configuration, pushes the current branch to the configured upstream -``` -without pushing otherwise. - -```bash -The default behavior of this command when no is given can be configured by setting the push +git push ``` -option of the remote, or the push.default configuration variable. +git push origin ```bash -For example, to default to pushing only the current branch to origin use git config remote.origin.push +git push origin : ``` -HEAD. Any valid (like the ones in the examples below) can be configured as the default for git +git push origin master ```bash -push origin. +git push origin HEAD ``` -Push "matching" branches to origin. See in the OPTIONS section above for a description of +git push mothership master:satellite/master dev:satellite/dev ```bash -Find a ref that matches master in the source repository (most likely, it would find refs/heads/master), +git push origin HEAD:master ``` -and update the same ref (e.g. refs/heads/master) in origin repository with it. If master did not exist +git push origin master:refs/heads/experimental ```bash -remotely, it would be created. +git push origin :experimental ``` -A handy way to push the current branch to the same name on the remote. - -```bash -Use the source ref that matches master (e.g. refs/heads/master) to update the ref that matches -``` -satellite/master (most probably refs/remotes/satellite/master) in the mothership repository; do the same - -```bash -for dev and satellite/dev. -``` -See the section describing ... above for a discussion of the matching semantics. - -```bash -This is to emulate git fetch run on the mothership using git push that is run in the opposite direction in -``` -order to integrate the work done on satellite, and is often necessary when you can only make connection in - -```bash -one way (i.e. satellite can ssh into mothership but mothership cannot initiate connection to satellite -``` -because the latter is behind a firewall or does not run sshd). - -```bash -After running this git push on the satellite machine, you would ssh into the mothership and run git merge -``` -there to complete the emulation of git pull that were run on mothership to pull changes made on satellite. - -```bash -Push the current branch to the remote ref matching master in the origin repository. This form is -``` -convenient to push the current branch without thinking about its local name. - -```bash -Create the branch experimental in the origin repository by copying the current master branch. This form is -``` -only needed to create a new branch or tag in the remote repository when the local name and the remote name - -```bash -are different; otherwise, the ref name on its own will work. -``` -Find a ref that matches experimental in the origin repository (e.g. refs/heads/experimental), and delete - -```bash -it. -``` -Update the origin repository's master branch with the dev branch, allowing non-fast-forward updates. This - -```bash -can leave unreferenced commits dangling in the origin repository. Consider the following situation, where -``` -a fast-forward is not possible: - -```bash -o---o---o---A---B origin/master -``` -X---Y---Z dev - -```bash -The above command would change the origin repository to -``` -A---B (unnamed branch) - -```bash -/ -``` -o---o---o---X---Y---Z master - -```bash -Commits A and B would no longer belong to a branch with a symbolic name, and so would be unreachable. As -``` -such, these commits would be removed by a git gc command on the origin repository. +git push origin +dev:master ## `git restore` ```bash -git switch master -``` -git restore --source master~2 Makefile (1) - -```bash -rm -f hello.c +hello.c by mistake, and gets it back from the index. ``` -git restore hello.c (2) +take a file out of another commit ```bash -1. take a file out of another commit +restore hello.c from the index ``` -2. restore hello.c from the index +or to restore all working tree files with top pathspec magic (see gitglossary(7)) ```bash -git restore '*.c' +or you can restore both the index and the working tree (this is the same as using git-checkout(1)) ``` -git restore . - -```bash -git restore :/ -``` -git restore --staged hello.c - -```bash -git restore --source=HEAD --staged --worktree hello.c -``` -git restore -s@ -SW hello.c +or the short form which is more practical but less readable: ## `git rm` ```bash -Removes all *.txt files from the index that are under the Documentation directory and any of its -``` -subdirectories. - -```bash -Note that the asterisk * is quoted from the shell in this example; this lets Git, and not the shell, +git rm Documentation/\*.txt ``` -expand the pathnames of files and subdirectories under the Documentation/ directory. +git rm -f git-*.sh ## `git show` ```bash -Shows the tag v1.0.0, along with the object the tag points at. +git show v1.0.0 ``` -Shows the tree pointed to by the tag v1.0.0. +git show v1.0.0^{tree} ```bash -Shows the subject of the commit pointed to by the tag v1.0.0. +git show -s --format=%s v1.0.0^{commit} ``` -Shows the contents of the file Documentation/README as they were current in the 10th last commit of the - -```bash -branch next. -``` -Concatenates the contents of said Makefiles in the head of the branch master. +git show next~10:Documentation/README ## `git switch` ```bash -git switch mytopic -``` -error: You have local changes to 'frotz'; not switching branches. - -```bash -git switch -m mytopic -``` -Auto-merging frotz - -```bash -git switch - -``` -git switch -c fixup HEAD~3 - -```bash -Switched to a new branch 'fixup' -``` -git switch new-topic - -```bash -Branch 'new-topic' set up to track remote branch 'new-topic' from 'origin' -``` -Switched to a new branch 'new-topic' - -```bash -git switch --detach HEAD~3 +in which case the above switch would fail like this: ``` -HEAD is now at 9fc9555312 Merge branch 'cc/shared-index-permbits' +show you what changes you made since the tip of the new branch. diff --git a/plugins/cli-grep/.claude-plugin/plugin.json b/plugins/cli-grep/.claude-plugin/plugin.json new file mode 100644 index 0000000..ee1db3b --- /dev/null +++ b/plugins/cli-grep/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-grep", + "version": "", + "description": "Command reference plugin for grep CLI", + "keywords": [ + "grep" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-grep/commands/scan-cli.md b/plugins/cli-grep/commands/scan-cli.md new file mode 100644 index 0000000..752af92 --- /dev/null +++ b/plugins/cli-grep/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the grep CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan grep CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `grep` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-grep/scripts/rescan.sh b/plugins/cli-grep/scripts/rescan.sh new file mode 100644 index 0000000..1019608 --- /dev/null +++ b/plugins/cli-grep/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan grep CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="grep" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-grep/skills/cli-grep/SKILL.md b/plugins/cli-grep/skills/cli-grep/SKILL.md new file mode 100644 index 0000000..65486a7 --- /dev/null +++ b/plugins/cli-grep/skills/cli-grep/SKILL.md @@ -0,0 +1,90 @@ +--- +name: cli-grep +description: >- + This skill should be used when the user needs help with grep CLI commands, flags, and troubleshooting. +--- + +# grep CLI Reference + +Compact command reference for **grep** v. + +- **0** total commands +- **0** command flags + **49** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `grep` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--after-context` | `-A` | string | print NUM lines of trailing context | +| `--basic-regexp` | `-G` | bool | PATTERNS are basic regular expressions | +| `--before-context` | `-B` | string | print NUM lines of leading context | +| `--binary` | `-U` | bool | do not strip CR characters at EOL (MSDOS/Windows) | +| `--binary-files` | `` | string | assume that binary files are TYPE; | +| `--byte-offset` | `-b` | bool | print the byte offset with output lines | +| `--color` | `` | string | | +| `--colour` | `` | string | use markers to highlight the matching strings; | +| `--context` | `-C` | string | print NUM lines of output context | +| `--count` | `-c` | bool | print only a count of selected lines per FILE | +| `--dereference-recursive` | `-R` | bool | likewise, but follow all symlinks | +| `--devices` | `-D` | string | how to handle devices, FIFOs and sockets; | +| `--directories` | `-d` | string | how to handle directories; | +| `--exclude` | `` | string | skip files that match GLOB | +| `--exclude-dir` | `` | string | skip directories that match GLOB | +| `--exclude-from` | `` | string | skip files that match any file pattern from FILE | +| `--extended-regexp` | `-E` | bool | PATTERNS are extended regular expressions | +| `--file` | `-f` | string | take PATTERNS from FILE | +| `--files-with-matches` | `-l` | bool | print only names of FILEs with selected lines | +| `--files-without-match` | `-L` | bool | print only names of FILEs with no selected lines | +| `--fixed-strings` | `-F` | bool | PATTERNS are strings | +| `--group-separator` | `` | string | print SEP on line between matches with context | +| `--help` | `` | bool | display this help text and exit | +| `--ignore-case` | `-i` | bool | ignore case distinctions in patterns and data | +| `--include` | `` | string | search only files that match GLOB (a file pattern) | +| `--initial-tab` | `-T` | bool | make tabs line up (if needed) | +| `--invert-match` | `-v` | bool | select non-matching lines | +| `--label` | `` | string | use LABEL as the standard input file name prefix | +| `--line-buffered` | `` | bool | flush output on every line | +| `--line-number` | `-n` | bool | print line number with output lines | +| `--line-regexp` | `-x` | bool | match only whole lines | +| `--max-count` | `-m` | string | stop after NUM selected lines | +| `--no-filename` | `-h` | bool | suppress the file name prefix on output | +| `--no-group-separator` | `` | bool | do not print separator for matches with context | +| `--no-ignore-case` | `` | bool | do not ignore case distinctions (default) | +| `--no-messages` | `-s` | bool | suppress error messages | +| `--null` | `-Z` | bool | print 0 byte after FILE name | +| `--null-data` | `-z` | bool | a data line ends in 0 byte, not newline | +| `--only-matching` | `-o` | bool | show only nonempty parts of lines that match | +| `--perl-regexp` | `-P` | bool | PATTERNS are Perl regular expressions | +| `--quiet` | `-q` | bool | suppress all normal output | +| `--recursive` | `-r` | bool | like --directories=recurse | +| `--regexp` | `-e` | string | use PATTERNS for matching | +| `--text` | `-a` | bool | equivalent to --binary-files=text | +| `--version` | `-V` | bool | display version information and exit | +| `--with-filename` | `-H` | bool | print file name with output lines | +| `--word-regexp` | `-w` | bool | match only whole words | +| `-I` | `-I` | bool | equivalent to --binary-files=without-match | +| `-NUM` | `-NUM` | bool | same as --context=NUM | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-grep/skills/cli-grep/references/commands.md b/plugins/cli-grep/skills/cli-grep/references/commands.md new file mode 100644 index 0000000..84b6c24 --- /dev/null +++ b/plugins/cli-grep/skills/cli-grep/references/commands.md @@ -0,0 +1,56 @@ +# grep -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--after-context` | `-A` | string | print NUM lines of trailing context | +| `--basic-regexp` | `-G` | bool | PATTERNS are basic regular expressions | +| `--before-context` | `-B` | string | print NUM lines of leading context | +| `--binary` | `-U` | bool | do not strip CR characters at EOL (MSDOS/Windows) | +| `--binary-files` | `` | string | assume that binary files are TYPE; | +| `--byte-offset` | `-b` | bool | print the byte offset with output lines | +| `--color` | `` | string | | +| `--colour` | `` | string | use markers to highlight the matching strings; | +| `--context` | `-C` | string | print NUM lines of output context | +| `--count` | `-c` | bool | print only a count of selected lines per FILE | +| `--dereference-recursive` | `-R` | bool | likewise, but follow all symlinks | +| `--devices` | `-D` | string | how to handle devices, FIFOs and sockets; | +| `--directories` | `-d` | string | how to handle directories; | +| `--exclude` | `` | string | skip files that match GLOB | +| `--exclude-dir` | `` | string | skip directories that match GLOB | +| `--exclude-from` | `` | string | skip files that match any file pattern from FILE | +| `--extended-regexp` | `-E` | bool | PATTERNS are extended regular expressions | +| `--file` | `-f` | string | take PATTERNS from FILE | +| `--files-with-matches` | `-l` | bool | print only names of FILEs with selected lines | +| `--files-without-match` | `-L` | bool | print only names of FILEs with no selected lines | +| `--fixed-strings` | `-F` | bool | PATTERNS are strings | +| `--group-separator` | `` | string | print SEP on line between matches with context | +| `--help` | `` | bool | display this help text and exit | +| `--ignore-case` | `-i` | bool | ignore case distinctions in patterns and data | +| `--include` | `` | string | search only files that match GLOB (a file pattern) | +| `--initial-tab` | `-T` | bool | make tabs line up (if needed) | +| `--invert-match` | `-v` | bool | select non-matching lines | +| `--label` | `` | string | use LABEL as the standard input file name prefix | +| `--line-buffered` | `` | bool | flush output on every line | +| `--line-number` | `-n` | bool | print line number with output lines | +| `--line-regexp` | `-x` | bool | match only whole lines | +| `--max-count` | `-m` | string | stop after NUM selected lines | +| `--no-filename` | `-h` | bool | suppress the file name prefix on output | +| `--no-group-separator` | `` | bool | do not print separator for matches with context | +| `--no-ignore-case` | `` | bool | do not ignore case distinctions (default) | +| `--no-messages` | `-s` | bool | suppress error messages | +| `--null` | `-Z` | bool | print 0 byte after FILE name | +| `--null-data` | `-z` | bool | a data line ends in 0 byte, not newline | +| `--only-matching` | `-o` | bool | show only nonempty parts of lines that match | +| `--perl-regexp` | `-P` | bool | PATTERNS are Perl regular expressions | +| `--quiet` | `-q` | bool | suppress all normal output | +| `--recursive` | `-r` | bool | like --directories=recurse | +| `--regexp` | `-e` | string | use PATTERNS for matching | +| `--text` | `-a` | bool | equivalent to --binary-files=text | +| `--version` | `-V` | bool | display version information and exit | +| `--with-filename` | `-H` | bool | print file name with output lines | +| `--word-regexp` | `-w` | bool | match only whole words | +| `-I` | `-I` | bool | equivalent to --binary-files=without-match | +| `-NUM` | `-NUM` | bool | same as --context=NUM | + diff --git a/plugins/cli-grep/skills/cli-grep/references/examples.md b/plugins/cli-grep/skills/cli-grep/references/examples.md new file mode 100644 index 0000000..03cdc68 --- /dev/null +++ b/plugins/cli-grep/skills/cli-grep/references/examples.md @@ -0,0 +1,2 @@ +# grep -- Usage Examples + diff --git a/plugins/cli-jq/cli-jq/.claude-plugin/plugin.json b/plugins/cli-jq/.claude-plugin/plugin.json similarity index 61% rename from plugins/cli-jq/cli-jq/.claude-plugin/plugin.json rename to plugins/cli-jq/.claude-plugin/plugin.json index 629fea2..b7d3953 100644 --- a/plugins/cli-jq/cli-jq/.claude-plugin/plugin.json +++ b/plugins/cli-jq/.claude-plugin/plugin.json @@ -5,11 +5,6 @@ "keywords": [ "jq" ], - "author": { - "name": "Nuno Salvacao", - "email": "nuno.salvacao@gmail.com", - "url": "https://github.com/nsalvacao" - }, "repository": "https://github.com/nsalvacao/cli-plugins", "license": "MIT" } diff --git a/plugins/cli-jq/cli-jq/commands/scan-cli.md b/plugins/cli-jq/commands/scan-cli.md similarity index 100% rename from plugins/cli-jq/cli-jq/commands/scan-cli.md rename to plugins/cli-jq/commands/scan-cli.md diff --git a/plugins/cli-jq/cli-jq/scripts/rescan.sh b/plugins/cli-jq/scripts/rescan.sh similarity index 100% rename from plugins/cli-jq/cli-jq/scripts/rescan.sh rename to plugins/cli-jq/scripts/rescan.sh diff --git a/plugins/cli-jq/cli-jq/skills/cli-jq/SKILL.md b/plugins/cli-jq/skills/cli-jq/SKILL.md similarity index 67% rename from plugins/cli-jq/cli-jq/skills/cli-jq/SKILL.md rename to plugins/cli-jq/skills/cli-jq/SKILL.md index bf84224..9ad632a 100644 --- a/plugins/cli-jq/cli-jq/skills/cli-jq/SKILL.md +++ b/plugins/cli-jq/skills/cli-jq/SKILL.md @@ -1,34 +1,27 @@ --- name: cli-jq description: >- - This skill should be used when the user needs help with jq CLI commands, including . Covers flags, subcommands, usage patterns, and troubleshooting for all 0 jq commands. + This skill should be used when the user needs help with jq CLI commands, flags, and troubleshooting. --- # jq CLI Reference -Expert command reference for **jq** v. +Compact command reference for **jq** v. -- **0** commands (0 with subcommands) +- **0** total commands - **0** command flags + **24** global flags -- **0** usage examples +- **0** extracted usage examples - Max nesting depth: 0 ## When to Use -This skill applies when: - Constructing or validating `jq` commands -- Looking up flags, options, or subcommands -- Troubleshooting `jq` invocations or errors -- Needing correct syntax for `jq` operations +- Looking up flags/options fast +- Troubleshooting failed invocations -## Prerequisites +## Top-Level Commands -Ensure `jq` is installed and available on PATH. - -## Quick Reference - -| Command | Description | -| --- | --- | +Command format examples: ### Global Flags @@ -59,24 +52,14 @@ Ensure `jq` is installed and available on PATH. | `--unbuffered` | `` | bool | flush output stream after each output; | | `--version` | `-V` | bool | show the version; | -## Command Overview - - -## Common Usage Patterns - +## Common Usage Patterns (Compact) +_No examples extracted._ ## Detailed References -For complete command documentation including all flags and subcommands: -- **Full command tree:** see `references/commands.md` -- **All usage examples:** see `references/examples.md` - -## Troubleshooting - -- Use `jq --help` or `jq --help` for inline help -- Add `--verbose` for detailed output during debugging +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` -## Re-scanning +## Re-Scanning -To update this plugin after a CLI version change, run the `/scan-cli` command -or manually execute the crawler and generator. +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-jq/cli-jq/skills/cli-jq/references/commands.md b/plugins/cli-jq/skills/cli-jq/references/commands.md similarity index 100% rename from plugins/cli-jq/cli-jq/skills/cli-jq/references/commands.md rename to plugins/cli-jq/skills/cli-jq/references/commands.md diff --git a/plugins/cli-jq/cli-jq/skills/cli-jq/references/examples.md b/plugins/cli-jq/skills/cli-jq/references/examples.md similarity index 100% rename from plugins/cli-jq/cli-jq/skills/cli-jq/references/examples.md rename to plugins/cli-jq/skills/cli-jq/references/examples.md diff --git a/plugins/cli-make/.claude-plugin/plugin.json b/plugins/cli-make/.claude-plugin/plugin.json new file mode 100644 index 0000000..61f5a52 --- /dev/null +++ b/plugins/cli-make/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-make", + "version": "", + "description": "Command reference plugin for make CLI", + "keywords": [ + "make" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-make/commands/scan-cli.md b/plugins/cli-make/commands/scan-cli.md new file mode 100644 index 0000000..986145f --- /dev/null +++ b/plugins/cli-make/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the make CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan make CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `make` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-make/scripts/rescan.sh b/plugins/cli-make/scripts/rescan.sh new file mode 100644 index 0000000..57dc151 --- /dev/null +++ b/plugins/cli-make/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan make CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="make" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-make/skills/cli-make/SKILL.md b/plugins/cli-make/skills/cli-make/SKILL.md new file mode 100644 index 0000000..42adf36 --- /dev/null +++ b/plugins/cli-make/skills/cli-make/SKILL.md @@ -0,0 +1,59 @@ +--- +name: cli-make +description: >- + This skill should be used when the user needs help with make CLI commands, flags, and troubleshooting. +--- + +# make CLI Reference + +Compact command reference for **make** v. + +- **0** total commands +- **0** command flags + **18** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `make` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--always-make` | `-B` | bool | Unconditionally make all targets. | +| `--check-symlink-times` | `-L` | bool | Use the latest mtime between symlinks and target. | +| `--environment-overrides` | `-e` | string | Environment variables override makefiles. | +| `--help` | `-h` | bool | Print this message and exit. | +| `--ignore-errors` | `-i` | bool | Ignore errors from recipes. | +| `--keep-going` | `-k` | bool | Keep going when some targets can't be made. | +| `--no-builtin-rules` | `-r` | bool | Disable the built-in implicit rules. | +| `--no-builtin-variables` | `-R` | bool | Disable the built-in variable settings. | +| `--no-print-directory` | `` | bool | Turn off -w, even if it was turned on implicitly. | +| `--no-silent` | `` | bool | Echo recipes (disable --silent mode). | +| `--print-data-base` | `-p` | bool | Print make's internal database. | +| `--print-directory` | `-w` | string | Print the current directory. | +| `--question` | `-q` | bool | Run no recipe; exit status says if up to date. | +| `--touch` | `-t` | bool | Touch targets instead of remaking them. | +| `--trace` | `` | bool | Print tracing information. | +| `--version` | `-v` | string | Print the version number of make and exit. | +| `--warn-undefined-variables` | `` | bool | Warn when an undefined variable is referenced. | +| `-d` | `-d` | bool | Print lots of debugging information. | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-make/skills/cli-make/references/commands.md b/plugins/cli-make/skills/cli-make/references/commands.md new file mode 100644 index 0000000..c2c2560 --- /dev/null +++ b/plugins/cli-make/skills/cli-make/references/commands.md @@ -0,0 +1,25 @@ +# make -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--always-make` | `-B` | bool | Unconditionally make all targets. | +| `--check-symlink-times` | `-L` | bool | Use the latest mtime between symlinks and target. | +| `--environment-overrides` | `-e` | string | Environment variables override makefiles. | +| `--help` | `-h` | bool | Print this message and exit. | +| `--ignore-errors` | `-i` | bool | Ignore errors from recipes. | +| `--keep-going` | `-k` | bool | Keep going when some targets can't be made. | +| `--no-builtin-rules` | `-r` | bool | Disable the built-in implicit rules. | +| `--no-builtin-variables` | `-R` | bool | Disable the built-in variable settings. | +| `--no-print-directory` | `` | bool | Turn off -w, even if it was turned on implicitly. | +| `--no-silent` | `` | bool | Echo recipes (disable --silent mode). | +| `--print-data-base` | `-p` | bool | Print make's internal database. | +| `--print-directory` | `-w` | string | Print the current directory. | +| `--question` | `-q` | bool | Run no recipe; exit status says if up to date. | +| `--touch` | `-t` | bool | Touch targets instead of remaking them. | +| `--trace` | `` | bool | Print tracing information. | +| `--version` | `-v` | string | Print the version number of make and exit. | +| `--warn-undefined-variables` | `` | bool | Warn when an undefined variable is referenced. | +| `-d` | `-d` | bool | Print lots of debugging information. | + diff --git a/plugins/cli-make/skills/cli-make/references/examples.md b/plugins/cli-make/skills/cli-make/references/examples.md new file mode 100644 index 0000000..87f6b07 --- /dev/null +++ b/plugins/cli-make/skills/cli-make/references/examples.md @@ -0,0 +1,2 @@ +# make -- Usage Examples + diff --git a/plugins/cli-node/.claude-plugin/plugin.json b/plugins/cli-node/.claude-plugin/plugin.json new file mode 100644 index 0000000..480a894 --- /dev/null +++ b/plugins/cli-node/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-node", + "version": "25.2.1", + "description": "Command reference plugin for node CLI", + "keywords": [ + "node" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-node/commands/scan-cli.md b/plugins/cli-node/commands/scan-cli.md new file mode 100644 index 0000000..b9cf25a --- /dev/null +++ b/plugins/cli-node/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the node CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan node CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `node` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-node/scripts/rescan.sh b/plugins/cli-node/scripts/rescan.sh new file mode 100644 index 0000000..63362b1 --- /dev/null +++ b/plugins/cli-node/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan node CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="node" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-node/skills/cli-node/SKILL.md b/plugins/cli-node/skills/cli-node/SKILL.md new file mode 100644 index 0000000..a935951 --- /dev/null +++ b/plugins/cli-node/skills/cli-node/SKILL.md @@ -0,0 +1,185 @@ +--- +name: cli-node +description: >- + This skill should be used when the user needs help with node CLI commands, flags, and troubleshooting. +--- + +# node CLI Reference + +Compact command reference for **node** v25.2.1. + +- **0** total commands +- **0** command flags + **144** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `node` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--abort-on-uncaught-exception` | `` | string | aborting instead of exiting causes a core file to be generated for analysis | +| `--allow-addons` | `` | bool | allow use of addons when any | +| `--allow-child-process` | `` | bool | allow use of child process when any | +| `--allow-fs-read` | `` | string | allow permissions to read the | +| `--allow-fs-write` | `` | string | allow permissions to write in the | +| `--allow-inspector` | `` | bool | allow use of inspector when any | +| `--allow-net` | `` | bool | allow use of network when any | +| `--allow-wasi` | `` | bool | allow wasi when any permissions are set | +| `--allow-worker` | `` | bool | allow worker threads when any | +| `--build-snapshot` | `` | bool | Generate a snapshot blob when the | +| `--check` | `-c` | bool | syntax check script without executing | +| `--completion-bash` | `` | bool | print source-able bash completion | +| `--conditions` | `-C` | string | additional user conditions for | +| `--cpu-prof` | `` | string | Start the V8 CPU profiler on start up, | +| `--cpu-prof-dir` | `` | string | Directory where the V8 profiles | +| `--cpu-prof-interval` | `` | string | specified sampling interval in | +| `--cpu-prof-name` | `` | string | specified file name of the V8 CPU | +| `--diagnostic-dir` | `` | string | set dir for all output files (default: | +| `--disable-proto` | `` | string | disable Object.prototype.__proto__ | +| `--disable-sigusr1` | `` | bool | Disable inspector thread to be | +| `--disable-warning` | `` | string | silence specific process warnings | +| `--disallow-code-generation-from-strings` | `` | bool | disallow eval and friends | +| `--dns-result-order` | `` | string | set default value of verbatim in | +| `--enable-etw-stack-walking` | `` | bool | provides heap data to ETW Windows | +| `--enable-fips` | `` | bool | enable FIPS crypto at startup | +| `--enable-source-maps` | `` | bool | Source Map V3 support for stack traces | +| `--entry-url` | `` | string | Treat the entrypoint as a URL | +| `--env-file` | `` | string | set environment variables from supplied | +| `--env-file-if-exists` | `` | string | set environment variables from supplied | +| `--eval` | `-e` | string | evaluate script | +| `--experimental-addon-modules` | `` | bool | experimental import support for addons | +| `--experimental-default-config-file` | `` | string | set config file from default config file | +| `--experimental-eventsource` | `` | bool | experimental EventSource API | +| `--experimental-import-meta-resolve` | `` | string | experimental ES Module import.meta.resolve() parentURL support | +| `--experimental-inspector-network-resource` | `` | bool | experimental load network resources via the inspector | +| `--experimental-network-inspection` | `` | bool | experimental network inspection support | +| `--experimental-print-required-tla` | `` | bool | Print pending top-level await. If | +| `--experimental-quic` | `` | bool | experimental QUIC support | +| `--experimental-test-coverage` | `` | bool | enable code coverage in the test runner | +| `--experimental-test-module-mocks` | `` | bool | enable module mocking in the test runner | +| `--experimental-transform-types` | `` | bool | enable transformation of TypeScript-onlysyntax into JavaScript code | +| `--experimental-vm-modules` | `` | bool | experimental ES Module support in vm | +| `--experimental-worker-inspection` | `` | bool | experimental worker inspection support | +| `--expose-gc` | `` | bool | expose gc extension | +| `--force-context-aware` | `` | bool | disable loading non-context-aware | +| `--force-fips` | `` | bool | force FIPS crypto (cannot be disabled) | +| `--force-node-api-uncaught-exceptions-policy` | `` | bool | enforces 'uncaughtException' event on Node API asynchronous callbacks | +| `--frozen-intrinsics` | `` | bool | experimental frozen intrinsics support | +| `--heap-prof` | `` | string | Start the V8 heap profiler on start up, | +| `--heap-prof-dir` | `` | string | Directory where the V8 heap profiles | +| `--heap-prof-interval` | `` | string | specified sampling interval in bytes | +| `--heap-prof-name` | `` | string | specified file name of the V8 heap | +| `--heapsnapshot-signal` | `` | string | Generate heap snapshot on specified | +| `--help` | `-h` | bool | print node command line options | +| `--icu-data-dir` | `` | string | set ICU data load path to dir | +| `--import` | `` | string | ES module to preload (option can be | +| `--input-type` | `` | string | set module type for string input | +| `--insecure-http-parser` | `` | bool | use an insecure HTTP parser that | +| `--inspect-publish-uid` | `` | string | comma separated list of destinations | +| `--interactive` | `-i` | bool | always enter the REPL even if stdin | +| `--interpreted-frames-native-stack` | `` | string | help system profilers to translate JavaScript interpreted frames | +| `--jitless` | `` | bool | disable runtime allocation of | +| `--localstorage-file` | `` | string | file used to persist localStorage data | +| `--max-http-header-size` | `` | string | set the maximum size of HTTP headers | +| `--no-addons` | `` | bool | disable loading native addons | +| `--no-async-context-frame` | `` | bool | Improve AsyncLocalStorage performance | +| `--no-deprecation` | `` | bool | silence deprecation warnings | +| `--no-experimental-detect-module` | `` | bool | when ambiguous modules fail to evaluate because they contain ES module syntax, try again to evaluate them as ES modules | +| `--no-experimental-global-navigator` | `` | bool | expose experimental Navigator API on the global scope | +| `--no-experimental-repl-await` | `` | bool | experimental await keyword support in REPL | +| `--no-experimental-require-module` | `` | bool | Allow loading synchronous ES Modules in require(). | +| `--no-experimental-sqlite` | `` | bool | experimental node:sqlite module | +| `--no-extra-info-on-fatal-exception` | `` | bool | hide extra information on fatal exception that causes exit | +| `--no-force-async-hooks-checks` | `` | bool | disable checks for async_hooks | +| `--no-global-search-paths` | `` | bool | disable global module search paths | +| `--no-warnings` | `` | bool | silence all process warnings | +| `--node-memory-debug` | `` | bool | Run with extra debug checks for memory | +| `--openssl-config` | `` | string | load OpenSSL configuration from the | +| `--openssl-legacy-provider` | `` | bool | enable OpenSSL 3.0 legacy provider | +| `--openssl-shared-config` | `` | bool | enable OpenSSL shared configuration | +| `--pending-deprecation` | `` | bool | emit pending deprecation warnings | +| `--permission` | `` | bool | enable the permission system | +| `--preserve-symlinks` | `` | bool | preserve symbolic links when resolving | +| `--preserve-symlinks-main` | `` | bool | preserve symbolic links when resolving | +| `--print` | `-p` | string | evaluate script and print result | +| `--prof` | `` | string | Generate V8 profiler output. | +| `--prof-process` | `` | string | process V8 profiler output generated | +| `--redirect-warnings` | `` | string | write warnings to file instead of | +| `--report-compact` | `` | bool | output compact single-line JSON | +| `--report-exclude-env` | `` | bool | Exclude environment variables when | +| `--report-exclude-network` | `` | bool | exclude network interface diagnostics. | +| `--report-filename` | `` | string | define custom report file name. | +| `--report-on-fatalerror` | `` | bool | generate diagnostic report on fatal | +| `--report-on-signal` | `` | bool | generate diagnostic report upon | +| `--report-signal` | `` | string | causes diagnostic report to be produced | +| `--require` | `-r` | string | CommonJS module to preload (option can | +| `--run` | `` | string | Run a script specified in package.json | +| `--secure-heap` | `` | string | total size of the OpenSSL secure heap | +| `--secure-heap-min` | `` | string | minimum allocation size from the | +| `--snapshot-blob` | `` | string | Path to the snapshot blob that's either | +| `--test` | `` | bool | launch test runner on startup | +| `--test-concurrency` | `` | string | specify test runner concurrency | +| `--test-coverage-lines` | `` | string | the line coverage minimum threshold | +| `--test-force-exit` | `` | bool | force test runner to exit upon | +| `--test-global-setup` | `` | string | specifies the path to the global setup | +| `--test-name-pattern` | `` | string | run tests whose name matches this | +| `--test-only` | `` | bool | run tests with 'only' option set | +| `--test-reporter` | `` | string | report test output using the given | +| `--test-rerun-failures` | `` | string | specifies the path to the rerun state | +| `--test-shard` | `` | string | run test at specific shard | +| `--test-skip-pattern` | `` | string | run tests whose name do not match this | +| `--test-timeout` | `` | string | specify test runner timeout | +| `--test-update-snapshots` | `` | bool | regenerate test snapshots | +| `--throw-deprecation` | `` | bool | throw an exception on deprecations | +| `--title` | `` | string | the process title to use on startup | +| `--tls-cipher-list` | `` | string | use an alternative default TLS cipher | +| `--tls-keylog` | `` | string | log TLS decryption keys to named file | +| `--trace-deprecation` | `` | bool | show stack traces on deprecations | +| `--trace-env` | `` | bool | Print accesses to the environment | +| `--trace-env-js-stack` | `` | bool | Print accesses to the environment | +| `--trace-env-native-stack` | `` | bool | Print accesses to the environment | +| `--trace-exit` | `` | bool | show stack trace when an environment | +| `--trace-promises` | `` | bool | show stack traces on promise | +| `--trace-require-module` | `` | string | Print access to require(esm). Options | +| `--trace-sigint` | `` | bool | enable printing JavaScript stacktrace | +| `--trace-sync-io` | `` | bool | show stack trace when use of sync IO is | +| `--trace-tls` | `` | bool | prints TLS packet trace information to | +| `--trace-uncaught` | `` | bool | show stack traces for the `throw` | +| `--trace-warnings` | `` | bool | show stack traces on process warnings | +| `--track-heap-objects` | `` | bool | track heap object allocations for heap | +| `--unhandled-rejections` | `` | string | define unhandled rejections behavior. | +| `--use-bundled-ca` | `` | bool | use bundled CA store (default) | +| `--use-env-proxy` | `` | bool | parse proxy settings from | +| `--use-largepages` | `` | string | Map the Node.js static code to large | +| `--use-openssl-ca` | `` | bool | use OpenSSL's default CA store | +| `--use-system-ca` | `` | bool | use system's CA store | +| `--v8-options` | `` | bool | print V8 command line options | +| `--v8-pool-size` | `` | string | set V8's thread pool size | +| `--version` | `-v` | bool | print Node.js version | +| `--watch` | `` | bool | run in watch mode | +| `--watch-kill-signal` | `` | string | kill signal to send to the process on | +| `--watch-path` | `` | string | path to watch | +| `--watch-preserve-output` | `` | bool | preserve outputs on watch mode restart | +| `--zero-fill-buffers` | `` | bool | automatically zero-fill all newly | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-node/skills/cli-node/references/commands.md b/plugins/cli-node/skills/cli-node/references/commands.md new file mode 100644 index 0000000..e6739b6 --- /dev/null +++ b/plugins/cli-node/skills/cli-node/references/commands.md @@ -0,0 +1,151 @@ +# node -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--abort-on-uncaught-exception` | `` | string | aborting instead of exiting causes a core file to be generated for analysis | +| `--allow-addons` | `` | bool | allow use of addons when any | +| `--allow-child-process` | `` | bool | allow use of child process when any | +| `--allow-fs-read` | `` | string | allow permissions to read the | +| `--allow-fs-write` | `` | string | allow permissions to write in the | +| `--allow-inspector` | `` | bool | allow use of inspector when any | +| `--allow-net` | `` | bool | allow use of network when any | +| `--allow-wasi` | `` | bool | allow wasi when any permissions are set | +| `--allow-worker` | `` | bool | allow worker threads when any | +| `--build-snapshot` | `` | bool | Generate a snapshot blob when the | +| `--check` | `-c` | bool | syntax check script without executing | +| `--completion-bash` | `` | bool | print source-able bash completion | +| `--conditions` | `-C` | string | additional user conditions for | +| `--cpu-prof` | `` | string | Start the V8 CPU profiler on start up, | +| `--cpu-prof-dir` | `` | string | Directory where the V8 profiles | +| `--cpu-prof-interval` | `` | string | specified sampling interval in | +| `--cpu-prof-name` | `` | string | specified file name of the V8 CPU | +| `--diagnostic-dir` | `` | string | set dir for all output files (default: | +| `--disable-proto` | `` | string | disable Object.prototype.__proto__ | +| `--disable-sigusr1` | `` | bool | Disable inspector thread to be | +| `--disable-warning` | `` | string | silence specific process warnings | +| `--disallow-code-generation-from-strings` | `` | bool | disallow eval and friends | +| `--dns-result-order` | `` | string | set default value of verbatim in | +| `--enable-etw-stack-walking` | `` | bool | provides heap data to ETW Windows | +| `--enable-fips` | `` | bool | enable FIPS crypto at startup | +| `--enable-source-maps` | `` | bool | Source Map V3 support for stack traces | +| `--entry-url` | `` | string | Treat the entrypoint as a URL | +| `--env-file` | `` | string | set environment variables from supplied | +| `--env-file-if-exists` | `` | string | set environment variables from supplied | +| `--eval` | `-e` | string | evaluate script | +| `--experimental-addon-modules` | `` | bool | experimental import support for addons | +| `--experimental-default-config-file` | `` | string | set config file from default config file | +| `--experimental-eventsource` | `` | bool | experimental EventSource API | +| `--experimental-import-meta-resolve` | `` | string | experimental ES Module import.meta.resolve() parentURL support | +| `--experimental-inspector-network-resource` | `` | bool | experimental load network resources via the inspector | +| `--experimental-network-inspection` | `` | bool | experimental network inspection support | +| `--experimental-print-required-tla` | `` | bool | Print pending top-level await. If | +| `--experimental-quic` | `` | bool | experimental QUIC support | +| `--experimental-test-coverage` | `` | bool | enable code coverage in the test runner | +| `--experimental-test-module-mocks` | `` | bool | enable module mocking in the test runner | +| `--experimental-transform-types` | `` | bool | enable transformation of TypeScript-onlysyntax into JavaScript code | +| `--experimental-vm-modules` | `` | bool | experimental ES Module support in vm | +| `--experimental-worker-inspection` | `` | bool | experimental worker inspection support | +| `--expose-gc` | `` | bool | expose gc extension | +| `--force-context-aware` | `` | bool | disable loading non-context-aware | +| `--force-fips` | `` | bool | force FIPS crypto (cannot be disabled) | +| `--force-node-api-uncaught-exceptions-policy` | `` | bool | enforces 'uncaughtException' event on Node API asynchronous callbacks | +| `--frozen-intrinsics` | `` | bool | experimental frozen intrinsics support | +| `--heap-prof` | `` | string | Start the V8 heap profiler on start up, | +| `--heap-prof-dir` | `` | string | Directory where the V8 heap profiles | +| `--heap-prof-interval` | `` | string | specified sampling interval in bytes | +| `--heap-prof-name` | `` | string | specified file name of the V8 heap | +| `--heapsnapshot-signal` | `` | string | Generate heap snapshot on specified | +| `--help` | `-h` | bool | print node command line options | +| `--icu-data-dir` | `` | string | set ICU data load path to dir | +| `--import` | `` | string | ES module to preload (option can be | +| `--input-type` | `` | string | set module type for string input | +| `--insecure-http-parser` | `` | bool | use an insecure HTTP parser that | +| `--inspect-publish-uid` | `` | string | comma separated list of destinations | +| `--interactive` | `-i` | bool | always enter the REPL even if stdin | +| `--interpreted-frames-native-stack` | `` | string | help system profilers to translate JavaScript interpreted frames | +| `--jitless` | `` | bool | disable runtime allocation of | +| `--localstorage-file` | `` | string | file used to persist localStorage data | +| `--max-http-header-size` | `` | string | set the maximum size of HTTP headers | +| `--no-addons` | `` | bool | disable loading native addons | +| `--no-async-context-frame` | `` | bool | Improve AsyncLocalStorage performance | +| `--no-deprecation` | `` | bool | silence deprecation warnings | +| `--no-experimental-detect-module` | `` | bool | when ambiguous modules fail to evaluate because they contain ES module syntax, try again to evaluate them as ES modules | +| `--no-experimental-global-navigator` | `` | bool | expose experimental Navigator API on the global scope | +| `--no-experimental-repl-await` | `` | bool | experimental await keyword support in REPL | +| `--no-experimental-require-module` | `` | bool | Allow loading synchronous ES Modules in require(). | +| `--no-experimental-sqlite` | `` | bool | experimental node:sqlite module | +| `--no-extra-info-on-fatal-exception` | `` | bool | hide extra information on fatal exception that causes exit | +| `--no-force-async-hooks-checks` | `` | bool | disable checks for async_hooks | +| `--no-global-search-paths` | `` | bool | disable global module search paths | +| `--no-warnings` | `` | bool | silence all process warnings | +| `--node-memory-debug` | `` | bool | Run with extra debug checks for memory | +| `--openssl-config` | `` | string | load OpenSSL configuration from the | +| `--openssl-legacy-provider` | `` | bool | enable OpenSSL 3.0 legacy provider | +| `--openssl-shared-config` | `` | bool | enable OpenSSL shared configuration | +| `--pending-deprecation` | `` | bool | emit pending deprecation warnings | +| `--permission` | `` | bool | enable the permission system | +| `--preserve-symlinks` | `` | bool | preserve symbolic links when resolving | +| `--preserve-symlinks-main` | `` | bool | preserve symbolic links when resolving | +| `--print` | `-p` | string | evaluate script and print result | +| `--prof` | `` | string | Generate V8 profiler output. | +| `--prof-process` | `` | string | process V8 profiler output generated | +| `--redirect-warnings` | `` | string | write warnings to file instead of | +| `--report-compact` | `` | bool | output compact single-line JSON | +| `--report-exclude-env` | `` | bool | Exclude environment variables when | +| `--report-exclude-network` | `` | bool | exclude network interface diagnostics. | +| `--report-filename` | `` | string | define custom report file name. | +| `--report-on-fatalerror` | `` | bool | generate diagnostic report on fatal | +| `--report-on-signal` | `` | bool | generate diagnostic report upon | +| `--report-signal` | `` | string | causes diagnostic report to be produced | +| `--require` | `-r` | string | CommonJS module to preload (option can | +| `--run` | `` | string | Run a script specified in package.json | +| `--secure-heap` | `` | string | total size of the OpenSSL secure heap | +| `--secure-heap-min` | `` | string | minimum allocation size from the | +| `--snapshot-blob` | `` | string | Path to the snapshot blob that's either | +| `--test` | `` | bool | launch test runner on startup | +| `--test-concurrency` | `` | string | specify test runner concurrency | +| `--test-coverage-lines` | `` | string | the line coverage minimum threshold | +| `--test-force-exit` | `` | bool | force test runner to exit upon | +| `--test-global-setup` | `` | string | specifies the path to the global setup | +| `--test-name-pattern` | `` | string | run tests whose name matches this | +| `--test-only` | `` | bool | run tests with 'only' option set | +| `--test-reporter` | `` | string | report test output using the given | +| `--test-rerun-failures` | `` | string | specifies the path to the rerun state | +| `--test-shard` | `` | string | run test at specific shard | +| `--test-skip-pattern` | `` | string | run tests whose name do not match this | +| `--test-timeout` | `` | string | specify test runner timeout | +| `--test-update-snapshots` | `` | bool | regenerate test snapshots | +| `--throw-deprecation` | `` | bool | throw an exception on deprecations | +| `--title` | `` | string | the process title to use on startup | +| `--tls-cipher-list` | `` | string | use an alternative default TLS cipher | +| `--tls-keylog` | `` | string | log TLS decryption keys to named file | +| `--trace-deprecation` | `` | bool | show stack traces on deprecations | +| `--trace-env` | `` | bool | Print accesses to the environment | +| `--trace-env-js-stack` | `` | bool | Print accesses to the environment | +| `--trace-env-native-stack` | `` | bool | Print accesses to the environment | +| `--trace-exit` | `` | bool | show stack trace when an environment | +| `--trace-promises` | `` | bool | show stack traces on promise | +| `--trace-require-module` | `` | string | Print access to require(esm). Options | +| `--trace-sigint` | `` | bool | enable printing JavaScript stacktrace | +| `--trace-sync-io` | `` | bool | show stack trace when use of sync IO is | +| `--trace-tls` | `` | bool | prints TLS packet trace information to | +| `--trace-uncaught` | `` | bool | show stack traces for the `throw` | +| `--trace-warnings` | `` | bool | show stack traces on process warnings | +| `--track-heap-objects` | `` | bool | track heap object allocations for heap | +| `--unhandled-rejections` | `` | string | define unhandled rejections behavior. | +| `--use-bundled-ca` | `` | bool | use bundled CA store (default) | +| `--use-env-proxy` | `` | bool | parse proxy settings from | +| `--use-largepages` | `` | string | Map the Node.js static code to large | +| `--use-openssl-ca` | `` | bool | use OpenSSL's default CA store | +| `--use-system-ca` | `` | bool | use system's CA store | +| `--v8-options` | `` | bool | print V8 command line options | +| `--v8-pool-size` | `` | string | set V8's thread pool size | +| `--version` | `-v` | bool | print Node.js version | +| `--watch` | `` | bool | run in watch mode | +| `--watch-kill-signal` | `` | string | kill signal to send to the process on | +| `--watch-path` | `` | string | path to watch | +| `--watch-preserve-output` | `` | bool | preserve outputs on watch mode restart | +| `--zero-fill-buffers` | `` | bool | automatically zero-fill all newly | + diff --git a/plugins/cli-node/skills/cli-node/references/examples.md b/plugins/cli-node/skills/cli-node/references/examples.md new file mode 100644 index 0000000..e960888 --- /dev/null +++ b/plugins/cli-node/skills/cli-node/references/examples.md @@ -0,0 +1,2 @@ +# node -- Usage Examples + diff --git a/plugins/cli-npx/.claude-plugin/plugin.json b/plugins/cli-npx/.claude-plugin/plugin.json new file mode 100644 index 0000000..39859df --- /dev/null +++ b/plugins/cli-npx/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-npx", + "version": "11.8.0", + "description": "Command reference plugin for npx CLI", + "keywords": [ + "npx" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-npx/commands/scan-cli.md b/plugins/cli-npx/commands/scan-cli.md new file mode 100644 index 0000000..ddf5a17 --- /dev/null +++ b/plugins/cli-npx/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the npx CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan npx CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `npx` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-npx/scripts/rescan.sh b/plugins/cli-npx/scripts/rescan.sh new file mode 100644 index 0000000..69a445a --- /dev/null +++ b/plugins/cli-npx/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan npx CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="npx" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-npx/skills/cli-npx/SKILL.md b/plugins/cli-npx/skills/cli-npx/SKILL.md new file mode 100644 index 0000000..a01271e --- /dev/null +++ b/plugins/cli-npx/skills/cli-npx/SKILL.md @@ -0,0 +1,44 @@ +--- +name: cli-npx +description: >- + This skill should be used when the user needs help with npx CLI commands, flags, and troubleshooting. +--- + +# npx CLI Reference + +Compact command reference for **npx** v11.8.0. + +- **0** total commands +- **0** command flags + **3** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `npx` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--call` | `-c` | string | | +| `--include-workspace-root` | `` | bool | | +| `--workspaces` | `` | bool | | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-npx/skills/cli-npx/references/commands.md b/plugins/cli-npx/skills/cli-npx/references/commands.md new file mode 100644 index 0000000..0d912f3 --- /dev/null +++ b/plugins/cli-npx/skills/cli-npx/references/commands.md @@ -0,0 +1,10 @@ +# npx -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--call` | `-c` | string | | +| `--include-workspace-root` | `` | bool | | +| `--workspaces` | `` | bool | | + diff --git a/plugins/cli-npx/skills/cli-npx/references/examples.md b/plugins/cli-npx/skills/cli-npx/references/examples.md new file mode 100644 index 0000000..7a42c71 --- /dev/null +++ b/plugins/cli-npx/skills/cli-npx/references/examples.md @@ -0,0 +1,2 @@ +# npx -- Usage Examples + diff --git a/plugins/cli-perl/.claude-plugin/plugin.json b/plugins/cli-perl/.claude-plugin/plugin.json new file mode 100644 index 0000000..a146ce1 --- /dev/null +++ b/plugins/cli-perl/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-perl", + "version": "5.38.2", + "description": "Command reference plugin for perl CLI", + "keywords": [ + "perl" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-perl/commands/scan-cli.md b/plugins/cli-perl/commands/scan-cli.md new file mode 100644 index 0000000..80d2569 --- /dev/null +++ b/plugins/cli-perl/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the perl CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan perl CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `perl` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-perl/scripts/rescan.sh b/plugins/cli-perl/scripts/rescan.sh new file mode 100644 index 0000000..642644d --- /dev/null +++ b/plugins/cli-perl/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan perl CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="perl" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-perl/skills/cli-perl/SKILL.md b/plugins/cli-perl/skills/cli-perl/SKILL.md new file mode 100644 index 0000000..fe44039 --- /dev/null +++ b/plugins/cli-perl/skills/cli-perl/SKILL.md @@ -0,0 +1,69 @@ +--- +name: cli-perl +description: >- + This skill should be used when the user needs help with perl CLI commands, flags, and troubleshooting. +--- + +# perl CLI Reference + +Compact command reference for **perl** v5.38.2. + +- **0** total commands +- **0** command flags + **28** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `perl` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `-0` | `-0` | string | specify record separator (\0, if no argument) | +| `-C` | `-C` | string | enables the listed Unicode features | +| `-D` | `-D` | string | set debugging flags (argument is a bit mask or alphabets) | +| `-E` | `-E` | string | like -e, but enables all optional features | +| `-F` | `-F` | string | split() pattern for -a switch (//'s are optional) | +| `-Idirectory` | `-Idirectory` | bool | specify @INC/#include directory (several -I's allowed) | +| `-S` | `-S` | bool | look for programfile using PATH environment variable | +| `-T` | `-T` | bool | enable tainting checks | +| `-U` | `-U` | bool | allow unsafe operations | +| `-V[:configvar]` | `-V[:configvar]` | string | print configuration summary (or a single Config.pm variable) | +| `-W` | `-W` | bool | enable all warnings | +| `-X` | `-X` | bool | disable all warnings | +| `-a` | `-a` | bool | autosplit mode with -n or -p (splits $_ into @F) | +| `-c` | `-c` | bool | check syntax only (runs BEGIN and CHECK blocks) | +| `-d[t][:MOD]` | `-d[t][:MOD]` | string | run program under debugger or module Devel::MOD | +| `-e` | `-e` | string | one line of program (several -e's allowed, omit programfile) | +| `-f` | `-f` | bool | don't do $sitelib/sitecustomize.pl at startup | +| `-g` | `-g` | bool | read all input in one go (slurp), rather than line-by-line (alias for -0777) | +| `-i` | `-i` | string | edit <> files in place (makes backup if extension supplied) | +| `-l` | `-l` | string | enable line ending processing, specifies line terminator | +| `-n` | `-n` | bool | assume "while (<>) { ... }" loop around program | +| `-p` | `-p` | bool | assume loop like -n but print line also, like sed | +| `-s` | `-s` | bool | enable rudimentary parsing for switches after programfile | +| `-t` | `-t` | bool | enable tainting warnings | +| `-u` | `-u` | bool | dump core after parsing program | +| `-v` | `-v` | bool | print version, patchlevel and license | +| `-w` | `-w` | bool | enable many useful warnings | +| `-x` | `-x` | string | ignore text before #!perl line (optionally cd to directory) | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-perl/skills/cli-perl/references/commands.md b/plugins/cli-perl/skills/cli-perl/references/commands.md new file mode 100644 index 0000000..29fa9e5 --- /dev/null +++ b/plugins/cli-perl/skills/cli-perl/references/commands.md @@ -0,0 +1,35 @@ +# perl -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `-0` | `-0` | string | specify record separator (\0, if no argument) | +| `-C` | `-C` | string | enables the listed Unicode features | +| `-D` | `-D` | string | set debugging flags (argument is a bit mask or alphabets) | +| `-E` | `-E` | string | like -e, but enables all optional features | +| `-F` | `-F` | string | split() pattern for -a switch (//'s are optional) | +| `-Idirectory` | `-Idirectory` | bool | specify @INC/#include directory (several -I's allowed) | +| `-S` | `-S` | bool | look for programfile using PATH environment variable | +| `-T` | `-T` | bool | enable tainting checks | +| `-U` | `-U` | bool | allow unsafe operations | +| `-V[:configvar]` | `-V[:configvar]` | string | print configuration summary (or a single Config.pm variable) | +| `-W` | `-W` | bool | enable all warnings | +| `-X` | `-X` | bool | disable all warnings | +| `-a` | `-a` | bool | autosplit mode with -n or -p (splits $_ into @F) | +| `-c` | `-c` | bool | check syntax only (runs BEGIN and CHECK blocks) | +| `-d[t][:MOD]` | `-d[t][:MOD]` | string | run program under debugger or module Devel::MOD | +| `-e` | `-e` | string | one line of program (several -e's allowed, omit programfile) | +| `-f` | `-f` | bool | don't do $sitelib/sitecustomize.pl at startup | +| `-g` | `-g` | bool | read all input in one go (slurp), rather than line-by-line (alias for -0777) | +| `-i` | `-i` | string | edit <> files in place (makes backup if extension supplied) | +| `-l` | `-l` | string | enable line ending processing, specifies line terminator | +| `-n` | `-n` | bool | assume "while (<>) { ... }" loop around program | +| `-p` | `-p` | bool | assume loop like -n but print line also, like sed | +| `-s` | `-s` | bool | enable rudimentary parsing for switches after programfile | +| `-t` | `-t` | bool | enable tainting warnings | +| `-u` | `-u` | bool | dump core after parsing program | +| `-v` | `-v` | bool | print version, patchlevel and license | +| `-w` | `-w` | bool | enable many useful warnings | +| `-x` | `-x` | string | ignore text before #!perl line (optionally cd to directory) | + diff --git a/plugins/cli-perl/skills/cli-perl/references/examples.md b/plugins/cli-perl/skills/cli-perl/references/examples.md new file mode 100644 index 0000000..581d7e0 --- /dev/null +++ b/plugins/cli-perl/skills/cli-perl/references/examples.md @@ -0,0 +1,2 @@ +# perl -- Usage Examples + diff --git a/plugins/cli-pip/.claude-plugin/plugin.json b/plugins/cli-pip/.claude-plugin/plugin.json new file mode 100644 index 0000000..6d5f79d --- /dev/null +++ b/plugins/cli-pip/.claude-plugin/plugin.json @@ -0,0 +1,17 @@ +{ + "name": "cli-pip", + "version": "24.0", + "description": "Command reference plugin for pip CLI", + "keywords": [ + "pip", + "packages", + "installed", + "inspect", + "information", + "wheel", + "dependencies", + "local" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-pip/commands/scan-cli.md b/plugins/cli-pip/commands/scan-cli.md new file mode 100644 index 0000000..90fdf42 --- /dev/null +++ b/plugins/cli-pip/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the pip CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan pip CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `pip` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-pip/scripts/rescan.sh b/plugins/cli-pip/scripts/rescan.sh new file mode 100644 index 0000000..3d7c84a --- /dev/null +++ b/plugins/cli-pip/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan pip CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="pip" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-pip/skills/cli-pip/SKILL.md b/plugins/cli-pip/skills/cli-pip/SKILL.md new file mode 100644 index 0000000..ca5ef91 --- /dev/null +++ b/plugins/cli-pip/skills/cli-pip/SKILL.md @@ -0,0 +1,104 @@ +--- +name: cli-pip +description: >- + This skill should be used when the user needs help with pip CLI commands, including cache, check, completion, config, debug, download, freeze, hash, help, index, inspect, install, list, search, show, uninstall, wheel. Covers flags, subcommands, usage patterns, and troubleshooting for all 17 pip commands. +--- + +# pip CLI Reference + +Expert command reference for **pip** v24.0. + +- **17** commands (0 with subcommands) +- **575** command flags + **25** global flags +- **0** usage examples +- Max nesting depth: 0 + +## When to Use + +This skill applies when: +- Constructing or validating `pip` commands +- Looking up flags, options, or subcommands +- Troubleshooting `pip` invocations or errors +- Needing correct syntax for `pip` operations + +## Prerequisites + +Ensure `pip` is installed and available on PATH. + +## Quick Reference + +| Command | Description | +| --- | --- | +| `pip cache` | Inspect and manage pip's wheel cache. | +| `pip check` | Verify installed packages have compatible dependencies. | +| `pip completion` | A helper command to be used for command completion. | +| `pip config` | Manage local and global configuration. | +| `pip debug` | Display debug information. | +| `pip download` | Download packages from: | +| `pip freeze` | Output installed packages in requirements format. | +| `pip hash` | Compute a hash of a local package archive. | +| `pip help` | Show help for commands | +| `pip index` | Inspect information available from package indexes. | +| `pip inspect` | Inspect the content of a Python environment and produce a report in JSON format. | +| `pip install` | Install packages from: | +| `pip list` | List installed packages, including editables. | +| `pip search` | Search for PyPI packages whose name or summary contains . | +| `pip show` | Show information about one or more installed packages. | +| `pip uninstall` | Uninstall packages. | +| `pip wheel` | Build Wheel archives for your requirements and dependencies. | + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +## Command Overview + + +### Commands + +`cache`, `check`, `completion`, `config`, `debug`, `download`, `freeze`, `hash`, `help`, `index`, `inspect`, `install`, `list`, `search`, `show`, `uninstall`, `wheel` + +## Common Usage Patterns + + +## Detailed References + +For complete command documentation including all flags and subcommands: +- **Full command tree:** see `references/commands.md` +- **All usage examples:** see `references/examples.md` + +## Troubleshooting + +- Use `pip --help` or `pip --help` for inline help +- Add `--verbose` for detailed output during debugging + +## Re-scanning + +To update this plugin after a CLI version change, run the `/scan-cli` command +or manually execute the crawler and generator. diff --git a/plugins/cli-pip/skills/cli-pip/references/commands.md b/plugins/cli-pip/skills/cli-pip/references/commands.md new file mode 100644 index 0000000..b9d11a2 --- /dev/null +++ b/plugins/cli-pip/skills/cli-pip/references/commands.md @@ -0,0 +1,792 @@ +# pip -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +## Commands + +### `pip cache` + +Inspect and manage pip's wheel cache. + +``` +pip cache dir +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--format` | `` | string | Select the output format among: human (default) or abspath | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip check` + +Verify installed packages have compatible dependencies. + +``` +pip check [options] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip completion` + +A helper command to be used for command completion. + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--bash` | `-b` | bool | Emit completion code for bash | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--fish` | `-f` | bool | Emit completion code for fish | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--powershell` | `-p` | bool | Emit completion code for powershell | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | +| `--zsh` | `-z` | bool | Emit completion code for zsh | + +### `pip config` + +Manage local and global configuration. + +``` +pip config [] list +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--editor` | `` | string | Editor to use to edit the file. Uses VISUAL or EDITOR environment variables if not | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--global` | `` | string | Use the system-wide configuration file only | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--site` | `` | string | Use the current environment configuration file only | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--user` | `` | string | Use the user configuration file only | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip debug` + +Display debug information. + +``` +pip debug +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--abi` | `` | string | Only use wheels compatible with Python abi , e.g. 'pypy_41'. If not specified, then | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--implementation` | `` | string | Only use wheels compatible with Python implementation , e.g. 'pp', 'jy', 'cp', or 'ip'. If not specified, then the current interpreter implementation is used. Use 'py' to force implementation-agnostic wheels. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--platform` | `` | string | Only use wheels compatible with . Defaults to the platform of the running | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--python-version` | `` | string | The Python interpreter version to use for wheel and "Requires-Python" compatibility checks. Defaults to a version derived from the running interpreter. The version can be specified using up to three dot-separated integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor version can also be given as a string without dots (e.g. "37" for 3.7.0). | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip download` + +Download packages from: + +``` +pip download [options] [package-index-options] ... +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--abi` | `` | string | Only use wheels compatible with Python abi , e.g. 'pypy_41'. If not specified, then | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--check-build-dependencies` | `` | bool | Check the build dependencies when PEP517 is used. | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--constraint` | `-c` | string | Constrain versions using the given constraints file. This option can be used multiple | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--dest` | `-d` | string | Download packages into . | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--extra-index-url` | `` | string | Extra URLs of package indexes to use in addition to --index-url. Should follow the same | +| `--find-links` | `-f` | string | If a URL or path to an html file, then parse for links to archives such as sdist | +| `--global-option` | `` | string | Extra global options to be supplied to the setup.py call before the install or | +| `--help` | `-h` | bool | Show help. | +| `--ignore-requires-python` | `` | bool | Ignore the Requires-Python information. | +| `--implementation` | `` | string | Only use wheels compatible with Python implementation , e.g. 'pp', 'jy', 'cp', or 'ip'. If not specified, then the current interpreter implementation is used. Use 'py' to force implementation-agnostic wheels. | +| `--index-url` | `-i` | string | Base URL of the Python Package Index (default https://pypi.org/simple). This should (default: https://pypi.org/simple) | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-binary` | `` | string | Do not use binary packages. Can be supplied multiple times, and each time adds to the existing value. Accepts either ":all:" to disable all binary packages, ":none:" to empty the set (notice the colons), or one or more package names with commas between them (no colons). Note that some packages are tricky to compile and may fail to install when this option is used on them. | +| `--no-build-isolation` | `` | bool | Disable isolation when building a modern source distribution. Build dependencies | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-clean` | `` | bool | Don't clean up build directories. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-deps` | `` | bool | Don't install package dependencies. | +| `--no-index` | `` | string | Ignore package index (only looking at --find-links URLs instead). | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--only-binary` | `` | string | Do not use source packages. Can be supplied multiple times, and each time adds to the existing value. Accepts either ":all:" to disable all source packages, ":none:" to empty the set, or one or more package names with commas between them. Packages without binary distributions will fail to install when this option is used on them. | +| `--platform` | `` | string | Only use wheels compatible with . Defaults to the platform of the running | +| `--pre` | `` | bool | Include pre-release and development versions. By default, pip only finds stable | +| `--prefer-binary` | `` | bool | Prefer binary packages over source packages, even if the source packages are newer. | +| `--progress-bar` | `` | string | Specify whether the progress bar should be used [on, off] (default: on) (default: on) | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--python-version` | `` | string | The Python interpreter version to use for wheel and "Requires-Python" compatibility checks. Defaults to a version derived from the running interpreter. The version can be specified using up to three dot-separated integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor version can also be given as a string without dots (e.g. "37" for 3.7.0). | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-hashes` | `` | bool | Require a hash to check each requirement against, for repeatable installs. This option | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--requirement` | `-r` | string | Install from the given requirements file. This option can be used multiple times. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--src` | `` | string | Directory to check out editable projects into. The default in a virtualenv is ". | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exclude` | `` | string | Exclude specified package from the output | +| `--exclude-editable` | `` | bool | Exclude editable package from output. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--local` | `-l` | bool | If in a virtualenv that has global access, do not output globally-installed packages. | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--path` | `` | string | Restrict to the specified installation path for listing packages (can be used multiple | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--requirement` | `-r` | string | Use the order in the given requirements file and its comments when generating output. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--user` | `` | bool | Only output packages installed in user-site. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip hash` + +Compute a hash of a local package archive. + +``` +pip hash [options] ... +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--algorithm` | `-a` | string | The hash algorithm to use: one of sha256, sha384, sha512 | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip help` + +Show help for commands + +``` +pip help +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip index` + +Inspect information available from package indexes. + +``` +pip index versions +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--abi` | `` | string | Only use wheels compatible with Python abi , e.g. 'pypy_41'. If not specified, then | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--extra-index-url` | `` | string | Extra URLs of package indexes to use in addition to --index-url. Should follow the same | +| `--find-links` | `-f` | string | If a URL or path to an html file, then parse for links to archives such as sdist | +| `--help` | `-h` | bool | Show help. | +| `--ignore-requires-python` | `` | bool | Ignore the Requires-Python information. | +| `--implementation` | `` | string | Only use wheels compatible with Python implementation , e.g. 'pp', 'jy', 'cp', or 'ip'. If not specified, then the current interpreter implementation is used. Use 'py' to force implementation-agnostic wheels. | +| `--index-url` | `-i` | string | Base URL of the Python Package Index (default https://pypi.org/simple). This should (default: https://pypi.org/simple) | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-binary` | `` | string | Do not use binary packages. Can be supplied multiple times, and each time adds to the existing value. Accepts either ":all:" to disable all binary packages, ":none:" to empty the set (notice the colons), or one or more package names with commas between them (no colons). Note that some packages are tricky to compile and may fail to install when this option is used on them. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-index` | `` | string | Ignore package index (only looking at --find-links URLs instead). | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--only-binary` | `` | string | Do not use source packages. Can be supplied multiple times, and each time adds to the existing value. Accepts either ":all:" to disable all source packages, ":none:" to empty the set, or one or more package names with commas between them. Packages without binary distributions will fail to install when this option is used on them. | +| `--platform` | `` | string | Only use wheels compatible with . Defaults to the platform of the running | +| `--pre` | `` | bool | Include pre-release and development versions. By default, pip only finds stable | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--python-version` | `` | string | The Python interpreter version to use for wheel and "Requires-Python" compatibility checks. Defaults to a version derived from the running interpreter. The version can be specified using up to three dot-separated integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor version can also be given as a string without dots (e.g. "37" for 3.7.0). | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip inspect` + +Inspect the content of a Python environment and produce a report in JSON format. + +``` +pip inspect [options] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--local` | `` | bool | If in a virtualenv that has global access, do not list globally-installed packages. | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--path` | `` | string | Restrict to the specified installation path for listing packages (can be used multiple | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--user` | `` | bool | Only output packages installed in user-site. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip install` + +Install packages from: + +``` +pip install [options] [package-index-options] ... +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--abi` | `` | string | Only use wheels compatible with Python abi , e.g. 'pypy_41'. If not specified, then | +| `--break-system-packages` | `` | bool | Allow pip to modify an EXTERNALLY-MANAGED Python installation | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--check-build-dependencies` | `` | bool | Check the build dependencies when PEP517 is used. | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--compile` | `` | string | Compile Python source files to bytecode | +| `--config-settings` | `-C` | string | Configuration settings to be passed to the PEP 517 build backend. Settings take the form KEY=VALUE. Use multiple --config-settings options to pass multiple keys to the backend. | +| `--constraint` | `-c` | string | Constrain versions using the given constraints file. This option can be used multiple | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--dry-run` | `` | bool | Don't actually install anything, just print what would be. Can be used in combination | +| `--editable` | `-e` | string | Install a project in editable mode (i.e. setuptools "develop mode") from a local project | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--extra-index-url` | `` | string | Extra URLs of package indexes to use in addition to --index-url. Should follow the same | +| `--find-links` | `-f` | string | If a URL or path to an html file, then parse for links to archives such as sdist | +| `--force-reinstall` | `` | bool | Reinstall all packages even if they are already up-to-date. | +| `--global-option` | `` | string | Extra global options to be supplied to the setup.py call before the install or | +| `--help` | `-h` | bool | Show help. | +| `--ignore-installed` | `-I` | bool | Ignore the installed packages, overwriting them. This can break your system if the | +| `--ignore-requires-python` | `` | bool | Ignore the Requires-Python information. | +| `--implementation` | `` | string | Only use wheels compatible with Python implementation , e.g. 'pp', 'jy', 'cp', or 'ip'. If not specified, then the current interpreter implementation is used. Use 'py' to force implementation-agnostic wheels. | +| `--index-url` | `-i` | string | Base URL of the Python Package Index (default https://pypi.org/simple). This should (default: https://pypi.org/simple) | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-binary` | `` | string | Do not use binary packages. Can be supplied multiple times, and each time adds to the existing value. Accepts either ":all:" to disable all binary packages, ":none:" to empty the set (notice the colons), or one or more package names with commas between them (no colons). Note that some packages are tricky to compile and may fail to install when this option is used on them. | +| `--no-build-isolation` | `` | bool | Disable isolation when building a modern source distribution. Build dependencies | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-clean` | `` | bool | Don't clean up build directories. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-compile` | `` | string | Do not compile Python source files to bytecode | +| `--no-deps` | `` | bool | Don't install package dependencies. | +| `--no-index` | `` | string | Ignore package index (only looking at --find-links URLs instead). | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--no-warn-conflicts` | `` | bool | Do not warn about broken dependencies | +| `--no-warn-script-location` | `` | bool | Do not warn when installing scripts outside PATH | +| `--only-binary` | `` | string | Do not use source packages. Can be supplied multiple times, and each time adds to the existing value. Accepts either ":all:" to disable all source packages, ":none:" to empty the set, or one or more package names with commas between them. Packages without binary distributions will fail to install when this option is used on them. | +| `--platform` | `` | string | Only use wheels compatible with . Defaults to the platform of the running | +| `--pre` | `` | bool | Include pre-release and development versions. By default, pip only finds stable | +| `--prefer-binary` | `` | bool | Prefer binary packages over source packages, even if the source packages are newer. | +| `--prefix` | `` | string | Installation prefix where lib, bin and other top-level folders are placed. Note that the | +| `--progress-bar` | `` | string | Specify whether the progress bar should be used [on, off] (default: on) (default: on) | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--python-version` | `` | string | The Python interpreter version to use for wheel and "Requires-Python" compatibility checks. Defaults to a version derived from the running interpreter. The version can be specified using up to three dot-separated integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor version can also be given as a string without dots (e.g. "37" for 3.7.0). | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--report` | `` | string | Generate a JSON file describing what pip did to install the provided requirements. Can | +| `--require-hashes` | `` | bool | Require a hash to check each requirement against, for repeatable installs. This option | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--requirement` | `-r` | string | Install from the given requirements file. This option can be used multiple times. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--root` | `` | string | Install everything relative to this alternate root directory. | +| `--root-user-action` | `` | string | Action if pip is run as a root user. By default, a warning message is shown. | +| `--src` | `` | string | Directory to check out editable projects into. The default in a virtualenv is ". By default this will not replace existing files/folders in | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--upgrade` | `-U` | bool | Upgrade all specified packages to the newest available version. The handling of | +| `--upgrade-strategy` | `` | string | Determines how dependency upgrading should be handled [default: only-if-needed]. "eager" (default: only-if-needed) | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--use-pep517` | `` | bool | Use PEP 517 for building source distributions (use --no-use-pep517 to force legacy | +| `--user` | `` | string | Install to the Python user install directory for your platform. Typically ~/.local/, or | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip list` + +List installed packages, including editables. + +``` +pip list [options] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--editable` | `-e` | bool | List editable projects. | +| `--exclude` | `` | string | Exclude specified package from the output | +| `--exclude-editable` | `` | bool | Exclude editable package from output. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--extra-index-url` | `` | string | Extra URLs of package indexes to use in addition to --index-url. Should follow the same | +| `--find-links` | `-f` | string | If a URL or path to an html file, then parse for links to archives such as sdist | +| `--format` | `` | string | Select the output format among: columns (default), freeze, or json. The 'freeze' format | +| `--help` | `-h` | bool | Show help. | +| `--include-editable` | `` | bool | Include editable package from output. | +| `--index-url` | `-i` | string | Base URL of the Python Package Index (default https://pypi.org/simple). This should (default: https://pypi.org/simple) | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--local` | `-l` | bool | If in a virtualenv that has global access, do not list globally-installed packages. | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-index` | `` | string | Ignore package index (only looking at --find-links URLs instead). | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--not-required` | `` | bool | List packages that are not dependencies of installed packages. | +| `--outdated` | `-o` | bool | List outdated packages | +| `--path` | `` | string | Restrict to the specified installation path for listing packages (can be used multiple | +| `--pre` | `` | bool | Include pre-release and development versions. By default, pip only finds stable | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--uptodate` | `-u` | bool | List uptodate packages | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--user` | `` | bool | Only output packages installed in user-site. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip search` + +Search for PyPI packages whose name or summary contains . + +``` +pip search [options] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--index` | `-i` | string | Base URL of Python Package Index (default https://pypi.org/pypi) (default: https://pypi.org/pypi) | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip show` + +Show information about one or more installed packages. + +``` +pip show [options] ... +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--files` | `-f` | string | Show the full list of installed files for each package. | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | + +### `pip uninstall` + +Uninstall packages. + +``` +pip uninstall [options] ... +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--break-system-packages` | `` | bool | Allow pip to modify an EXTERNALLY-MANAGED Python installation | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--help` | `-h` | bool | Show help. | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--requirement` | `-r` | string | Uninstall all the packages listed in the given requirements file. This option can be | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--root-user-action` | `` | string | Action if pip is run as a root user. By default, a warning message is shown. | +| `--timeout` | `` | string | Set the socket timeout (default 15 seconds). | +| `--trusted-host` | `` | string | Mark this host or host:port pair as trusted, even though it does not have valid or any | +| `--use-deprecated` | `` | string | Enable deprecated functionality, that will be removed in the future. | +| `--use-feature` | `` | string | Enable new functionality, that may be backward incompatible. | +| `--verbose` | `-v` | bool | Give more output. Option is additive, and can be used up to 3 times. | +| `--version` | `-V` | bool | Show version and exit. | +| `--yes` | `-y` | bool | Don't ask for confirmation of uninstall deletions. | + +### `pip wheel` + +Build Wheel archives for your requirements and dependencies. + +``` +pip wheel [options] ... +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--build-option` | `` | string | Extra arguments to be supplied to 'setup.py bdist_wheel'. | +| `--cache-dir` | `` | string | Store the cache data in . | +| `--cert` | `` | string | Path to PEM-encoded CA certificate bundle. If provided, overrides the default. See 'SSL | +| `--check-build-dependencies` | `` | bool | Check the build dependencies when PEP517 is used. | +| `--client-cert` | `` | string | Path to SSL client certificate, a single file containing the private key and the | +| `--config-settings` | `-C` | string | Configuration settings to be passed to the PEP 517 build backend. Settings take the form KEY=VALUE. Use multiple --config-settings options to pass multiple keys to the backend. | +| `--constraint` | `-c` | string | Constrain versions using the given constraints file. This option can be used multiple | +| `--debug` | `` | bool | Let unhandled exceptions propagate outside the main subroutine, instead of logging them | +| `--disable-pip-version-check` | `` | bool | Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index. | +| `--editable` | `-e` | string | Install a project in editable mode (i.e. setuptools "develop mode") from a local project | +| `--exists-action` | `` | string | Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, | +| `--extra-index-url` | `` | string | Extra URLs of package indexes to use in addition to --index-url. Should follow the same | +| `--find-links` | `-f` | string | If a URL or path to an html file, then parse for links to archives such as sdist | +| `--global-option` | `` | string | Extra global options to be supplied to the setup.py call before the install or | +| `--help` | `-h` | bool | Show help. | +| `--ignore-requires-python` | `` | bool | Ignore the Requires-Python information. | +| `--index-url` | `-i` | string | Base URL of the Python Package Index (default https://pypi.org/simple). This should (default: https://pypi.org/simple) | +| `--isolated` | `` | bool | Run pip in an isolated mode, ignoring environment variables and user configuration. | +| `--keyring-provider` | `` | string | Enable the credential lookup via the keyring library if user input is allowed. Specify which mechanism to use [disabled, import, subprocess]. (default: disabled) (default: disabled) | +| `--log` | `` | string | Path to a verbose appending log. | +| `--no-binary` | `` | string | Do not use binary packages. Can be supplied multiple times, and each time adds to the existing value. Accepts either ":all:" to disable all binary packages, ":none:" to empty the set (notice the colons), or one or more package names with commas between them (no colons). Note that some packages are tricky to compile and may fail to install when this option is used on them. | +| `--no-build-isolation` | `` | bool | Disable isolation when building a modern source distribution. Build dependencies | +| `--no-cache-dir` | `` | bool | Disable the cache. | +| `--no-clean` | `` | bool | Don't clean up build directories. | +| `--no-color` | `` | bool | Suppress colored output. | +| `--no-deps` | `` | bool | Don't install package dependencies. | +| `--no-index` | `` | string | Ignore package index (only looking at --find-links URLs instead). | +| `--no-input` | `` | bool | Disable prompting for input. | +| `--no-python-version-warning` | `` | bool | Silence deprecation warnings for upcoming unsupported Pythons. | +| `--no-verify` | `` | bool | Don't verify if built wheel is valid. | +| `--only-binary` | `` | string | Do not use source packages. Can be supplied multiple times, and each time adds to the existing value. Accepts either ":all:" to disable all source packages, ":none:" to empty the set, or one or more package names with commas between them. Packages without binary distributions will fail to install when this option is used on them. | +| `--pre` | `` | bool | Include pre-release and development versions. By default, pip only finds stable | +| `--prefer-binary` | `` | bool | Prefer binary packages over source packages, even if the source packages are newer. | +| `--progress-bar` | `` | string | Specify whether the progress bar should be used [on, off] (default: on) (default: on) | +| `--proxy` | `` | string | Specify a proxy in the form scheme://[user:passwd@]proxy.server:port. | +| `--python` | `` | string | Run pip with the specified Python interpreter. | +| `--quiet` | `-q` | bool | Give less output. Option is additive, and can be used up to 3 times (corresponding to | +| `--require-hashes` | `` | bool | Require a hash to check each requirement against, for repeatable installs. This option | +| `--require-virtualenv` | `` | bool | Allow pip to only run in a virtual environment; exit with an error otherwise. | +| `--requirement` | `-r` | string | Install from the given requirements file. This option can be used multiple times. | +| `--retries` | `` | string | Maximum number of retries each connection should attempt (default 5 times). | +| `--src` | `` | string | Directory to check out editable projects into. The default in a virtualenv is ", where the default is the current working directory. | + diff --git a/plugins/cli-pip/skills/cli-pip/references/examples.md b/plugins/cli-pip/skills/cli-pip/references/examples.md new file mode 100644 index 0000000..c8ec3e3 --- /dev/null +++ b/plugins/cli-pip/skills/cli-pip/references/examples.md @@ -0,0 +1,2 @@ +# pip -- Usage Examples + diff --git a/plugins/cli-pnpm/.claude-plugin/plugin.json b/plugins/cli-pnpm/.claude-plugin/plugin.json new file mode 100644 index 0000000..0b4188c --- /dev/null +++ b/plugins/cli-pnpm/.claude-plugin/plugin.json @@ -0,0 +1,17 @@ +{ + "name": "cli-pnpm", + "version": "10.29.3", + "description": "Command reference plugin for pnpm CLI", + "keywords": [ + "pnpm", + "config", + "package", + "packages", + "project", + "all", + "installed", + "dependency" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-pnpm/commands/scan-cli.md b/plugins/cli-pnpm/commands/scan-cli.md new file mode 100644 index 0000000..126f859 --- /dev/null +++ b/plugins/cli-pnpm/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the pnpm CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan pnpm CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `pnpm` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-pnpm/scripts/rescan.sh b/plugins/cli-pnpm/scripts/rescan.sh new file mode 100644 index 0000000..357dbed --- /dev/null +++ b/plugins/cli-pnpm/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan pnpm CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="pnpm" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-pnpm/skills/cli-pnpm/SKILL.md b/plugins/cli-pnpm/skills/cli-pnpm/SKILL.md new file mode 100644 index 0000000..7fd8649 --- /dev/null +++ b/plugins/cli-pnpm/skills/cli-pnpm/SKILL.md @@ -0,0 +1,70 @@ +--- +name: cli-pnpm +description: >- + This skill should be used when the user needs help with pnpm CLI commands, flags, and troubleshooting. +--- + +# pnpm CLI Reference + +Compact command reference for **pnpm** v10.29.3. + +- **22** total commands +- **361** command flags + **1** global flags +- **0** extracted usage examples +- Max nesting depth: 1 + +## When to Use + +- Constructing or validating `pnpm` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command Groups: +`config` +Standalone Commands: +`add`, `audit`, `create`, `dlx`, `exec`, `init`, `install`, `link`, `list`, `outdated`, `publish`, `remove`, `run`, `self-update`, `unlink`, `update`, `why` +Command format examples: `pnpm add`, `pnpm audit`, `pnpm config` + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--recursive` | `-r` | bool | Run the command for each project in the workspace. | + +## Common Usage Patterns (Compact) + +```bash +pnpm add +``` +Installs a package and any packages that it depends on. By default, any new package is installed as a prod dependency + +```bash +pnpm audit [options] +``` +Checks for known security issues with the installed packages + +```bash +pnpm config set +``` +Manage the pnpm configuration files + +```bash +pnpm config delete +``` +Remove the config key from the config file + +```bash +pnpm config get +``` +Print the config value for the provided key + +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-pnpm/skills/cli-pnpm/references/commands.md b/plugins/cli-pnpm/skills/cli-pnpm/references/commands.md new file mode 100644 index 0000000..5c10298 --- /dev/null +++ b/plugins/cli-pnpm/skills/cli-pnpm/references/commands.md @@ -0,0 +1,582 @@ +# pnpm -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--recursive` | `-r` | bool | Run the command for each project in the workspace. | + +## Commands + +### `pnpm add` + +Installs a package and any packages that it depends on. By default, any new package is installed as a prod dependency + +``` +pnpm add +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, | +| `--[no-]save-exact` | `-E` | bool | Install exact version | +| `--[no-]save-workspace-protocol` | `` | bool | Save packages from the workspace with a | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that | +| `--allow-build` | `` | bool | A list of package names that are allowed | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--config` | `` | bool | Save the dependency to configurational | +| `--dir` | `-C` | string | Change to directory (default: | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--global` | `-g` | bool | Install as a global package | +| `--global-dir` | `` | string | Specify a custom directory to store global | +| `--help` | `-h` | bool | Output usage information | +| `--ignore-scripts` | `` | bool | Don't run lifecycle scripts | +| `--loglevel` | `` | string | What level of logs to report. Any logs at | +| `--offline` | `` | bool | Trigger an error if any required | +| `--prefer-offline` | `` | bool | Skip staleness checks for cached data, but | +| `--recursive` | `-r` | bool | Run installation recursively in every | +| `--save-catalog` | `` | bool | Save package to the default catalog | +| `--save-catalog-name` | `` | string | Save package to the specified catalog | +| `--save-dev` | `-D` | bool | Save package to your `devDependencies` | +| `--save-optional` | `-O` | bool | Save package to your | +| `--save-peer` | `` | bool | Save package to your `peerDependencies` | +| `--save-prod` | `-P` | bool | Save package to your `dependencies`. The | +| `--store-dir` | `` | string | The directory in which all the packages | +| `--stream` | `` | bool | Stream output from child processes | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--virtual-store-dir` | `` | string | The directory with links to the store | +| `--workspace` | `` | bool | Only adds the new dependency if it is | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace | + +### `pnpm audit` + +Checks for known security issues with the installed packages + +``` +pnpm audit [options] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--audit-level` | `` | string | Only print advisories with severity greater than | +| `--dev` | `-D` | bool | Only audit "devDependencies" | +| `--fix` | `` | string | Add overrides to the package.json file in order | +| `--ignore` | `` | string | Ignore a vulnerability by CVE | +| `--ignore-registry-errors` | `` | bool | Use exit code 0 if the registry responds with an | +| `--ignore-unfixable` | `` | bool | Ignore all CVEs with no resolution | +| `--json` | `` | bool | Output audit report in JSON format | +| `--no-optional` | `` | bool | Don't audit "optionalDependencies" | +| `--prod` | `-P` | bool | Only audit "dependencies" and | + +### `pnpm create` + +pnpm create + +``` +pnpm create +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--allow-build` | `` | bool | A list of package names that are allowed to run | + +### `pnpm dlx` + +Fetches a package from the registry without installing it as a dependency, hot loads it, and runs whatever default command binary it exposes + +``` +pnpm dlx [args...] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--allow-build` | `` | bool | A list of package names that are allowed to run | +| `--package` | `` | bool | The package to install before running the command | +| `--reporter` | `` | string | The output is always appended to the end. No | +| `--reporter` | `` | string | The default reporter when the stdout is TTY | +| `--reporter` | `` | string | The most verbose reporter. Prints all logs in | +| `--shell-mode` | `-c` | bool | Runs the script inside of a shell. Uses /bin/sh on | + +### `pnpm exec` + +Executes a shell command in scope of a project + +``` +pnpm [-r] [-c] exec [args...] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--dir` | `-C` | string | Change to directory (default: | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--help` | `-h` | bool | Output usage information | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or | +| `--no-reporter-hide-prefix` | `` | bool | Do not hide project name prefix from output of | +| `--parallel` | `` | bool | Completely disregard concurrency and | +| `--recursive` | `-r` | bool | Run the shell command in every package found in | +| `--report-summary` | `` | bool | Save the execution results of every package to | +| `--resume-from` | `` | bool | Command executed from given package | +| `--shell-mode` | `-c` | string | If exist, runs file inside of a shell. Uses | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +### `pnpm init` + +Create a package.json file + +``` +pnpm init +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--bare` | `` | string | Create a package.json file with the bare | +| `--init-package-manager` | `` | bool | Pin the project to the current pnpm version | +| `--init-type` | `` | string | Set the module system for the package. | + +### `pnpm install` + +Install all dependencies for a project + +``` +pnpm install [options] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By | +| `--[no-]frozen-lockfile` | `` | string | Don't generate a lockfile and fail | +| `--[no-]verify-store-integrity` | `` | bool | If false, doesn't check whether | +| `--aggregate-output` | `` | bool | Aggregate output from child | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--child-concurrency` | `` | string | Controls the number of child | +| `--dev` | `-D` | bool | Only `devDependencies` are | +| `--dir` | `-C` | string | Change to directory (default: | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--fix-lockfile` | `` | string | Fix broken lockfile entries | +| `--force` | `` | bool | Force reinstall dependencies: | +| `--global-dir` | `` | string | Specify a custom directory to store | +| `--help` | `-h` | bool | Output usage information | +| `--hoist-pattern` | `` | string | Hoist all dependencies matching the | +| `--ignore-pnpmfile` | `` | bool | Disable pnpm hooks defined in | +| `--ignore-scripts` | `` | bool | Don't run lifecycle scripts | +| `--ignore-workspace` | `` | bool | Ignore pnpm-workspace.yaml if | +| `--lockfile-dir` | `` | string | The directory in which the | +| `--lockfile-only` | `` | bool | Dependencies are not downloaded. | +| `--loglevel` | `` | string | What level of logs to report. Any | +| `--merge-git-branch-lockfiles` | `` | string | Merge lockfiles were generated on | +| `--modules-dir` | `` | string | The directory in which dependencies | +| `--network-concurrency` | `` | string | Maximum number of concurrent | +| `--no-hoist` | `` | bool | Dependencies inside the modules | +| `--no-lockfile` | `` | bool | Don't read or generate a | +| `--no-optional` | `` | bool | `optionalDependencies` are not | +| `--offline` | `` | bool | Trigger an error if any required | +| `--optimistic-repeat-install` | `` | bool | Skip reinstall if the workspace | +| `--package-import-method` | `` | string | Clones/hardlinks or copies | +| `--package-import-method` | `` | string | Clone (aka copy-on-write) packages | +| `--package-import-method` | `` | string | Copy packages from the store | +| `--package-import-method` | `` | string | Hardlink packages from the store | +| `--prefer-frozen-lockfile` | `` | bool | If the available `pnpm-lock.yaml` | +| `--prefer-offline` | `` | bool | Skip staleness checks for cached | +| `--prod` | `-P` | bool | Packages in `devDependencies` won't | +| `--public-hoist-pattern` | `` | string | Hoist all dependencies matching the | +| `--recursive` | `-r` | bool | Run installation recursively in | +| `--reporter` | `` | string | The output is always appended to the end. No | +| `--reporter` | `` | string | The default reporter when the stdout is TTY | +| `--reporter` | `` | string | The most verbose reporter. Prints all logs in | +| `--resolution-only` | `` | bool | Re-runs resolution: useful for | +| `--shamefully-hoist` | `` | bool | All the subdeps will be hoisted | +| `--side-effects-cache` | `` | bool | Use or cache the results of | +| `--side-effects-cache-readonly` | `` | bool | Only use the side effects cache if | +| `--store-dir` | `` | string | The directory in which all the | +| `--stream` | `` | bool | Stream output from child processes | +| `--strict-peer-dependencies` | `` | bool | Fail on missing or invalid peer | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--trust-policy` | `` | string | Fail when a package's trust level | +| `--trust-policy-exclude` | `` | string | Exclude specific packages from | +| `--trust-policy-ignore-after` | `` | string | Ignore trust downgrades for | +| `--use-running-store-server` | `` | bool | Only allows installation with a | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--use-store-server` | `` | bool | Starts a store server in the | +| `--virtual-store-dir` | `` | string | The directory with links to the | +| `--workspace-root` | `-w` | bool | Run the command on the root | + +### `pnpm link` + +Connect the local project to another one + +``` +pnpm link +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, output is | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are run in | +| `--dir` | `-C` | string | Change to directory (default: | +| `--help` | `-h` | bool | Output usage information | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or higher | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +### `pnpm list` + +Print all the versions of packages that are installed, as well as their dependencies, in a tree-structure + +``` +pnpm ls [ ...] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, output is | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are run in | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--depth` | `-1` | bool | Display only projects. Useful in a monorepo. `pnpm ls | +| `--depth` | `` | string | Max display depth of the dependency tree | +| `--depth` | `` | string | Display only direct dependencies | +| `--dev` | `-D` | bool | Display only the dependency graph for packages in | +| `--dir` | `-C` | string | Change to directory (default: | +| `--exclude-peers` | `` | bool | Exclude peer dependencies | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--global` | `-g` | bool | List packages in the global install prefix instead of | +| `--global-dir` | `` | string | Specify a custom directory to store global packages | +| `--help` | `-h` | bool | Output usage information | +| `--json` | `` | bool | Show information in JSON format | +| `--lockfile-only` | `` | string | List packages from the lockfile only, without | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or higher | +| `--long` | `` | bool | Show extended information | +| `--no-optional` | `` | bool | Don't display packages from `optionalDependencies` | +| `--only-projects` | `` | bool | Display only dependencies that are also projects | +| `--parseable` | `` | bool | Show parseable output instead of tree view | +| `--prod` | `-P` | bool | Display only the dependency graph for packages in | +| `--recursive` | `-r` | bool | Perform command on every package in subdirectories or | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +### `pnpm outdated` + +Check for outdated packages + +``` +pnpm outdated [ ...] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, output is | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are run in | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--compatible` | `` | bool | Print only versions that satisfy specs in | +| `--dev` | `-D` | bool | Check only "devDependencies" | +| `--dir` | `-C` | string | Change to directory (default: | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--format` | `` | string | Prints the outdated dependencies in the given format. | +| `--global-dir` | `` | string | Specify a custom directory to store global packages | +| `--help` | `-h` | bool | Output usage information | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or higher | +| `--long` | `` | bool | By default, details about the outdated packages (such | +| `--no-optional` | `` | bool | Don't check "optionalDependencies" | +| `--no-table` | `` | bool | Prints the outdated packages in a list. Good for | +| `--prod` | `-P` | bool | Check only "dependencies" and "optionalDependencies" | +| `--recursive` | `-r` | bool | Check for outdated dependencies in every package | +| `--sort-by` | `` | string | Specify the sorting method. Currently only `name` is | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +### `pnpm publish` + +Publishes a package to the registry + +``` +pnpm publish [|] [--tag ] [--access ] [options] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--access` | `` | string | Tells the registry whether this package | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--dry-run` | `` | bool | Does everything a publish would do except | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--force` | `` | bool | Packages are proceeded to be published even | +| `--ignore-scripts` | `` | bool | Ignores any publish related lifecycle | +| `--json` | `` | bool | Show information in JSON format | +| `--no-git-checks` | `` | bool | Don't check if current branch is your | +| `--otp` | `` | bool | When publishing packages that require | +| `--publish-branch` | `` | bool | Sets branch name to publish. Default is | +| `--recursive` | `-r` | bool | Publish all packages from the workspace | +| `--report-summary` | `` | bool | Save the list of the newly published | +| `--tag` | `` | string | Registers the published package with the | +| `--test-pattern` | `` | string | Defines files related to tests. | + +### `pnpm remove` + +Removes packages from node_modules and from the project's package.json + +``` +pnpm remove [@]... +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, output is | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are run in | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--dir` | `-C` | string | Change to directory (default: | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--global-dir` | `` | string | Specify a custom directory to store global packages | +| `--help` | `-h` | bool | Output usage information | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or higher | +| `--recursive` | `-r` | bool | Remove from every package found in subdirectories or | +| `--save-dev` | `-D` | bool | Remove the dependency only from "devDependencies" | +| `--save-optional` | `-O` | bool | Remove the dependency only from | +| `--save-prod` | `-P` | bool | Remove the dependency only from "dependencies" | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +### `pnpm run` + +Runs a defined package script + +``` +pnpm run [...] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, output | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are run | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--dir` | `-C` | string | Change to directory (default: | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--help` | `-h` | bool | Output usage information | +| `--if-present` | `` | bool | Avoid exiting with a non-zero exit code when the | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or | +| `--no-bail` | `` | bool | The command will exit with a 0 exit code even if | +| `--parallel` | `` | bool | Completely disregard concurrency and topological | +| `--recursive` | `-r` | bool | Run the defined package script in every package | +| `--report-summary` | `` | bool | Save the execution results of every package to | +| `--reporter-hide-prefix` | `` | bool | Hide project name prefix from output of running | +| `--resume-from` | `` | bool | Command executed from given package | +| `--sequential` | `` | bool | Run the specified scripts one by one | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +### `pnpm self-update` + +Nothing to stop. No server is running for the store at /mnt/d/.pnpm-store/v10 + +### `pnpm unlink` + +Unlinks a package. Like yarn unlink but pnpm re-installs the dependency after removing the external link + +``` +pnpm unlink (in package dir) +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, output is | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are run in | +| `--dir` | `-C` | string | Change to directory (default: | +| `--help` | `-h` | bool | Output usage information | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or higher | +| `--recursive` | `-r` | bool | Unlink in every package found in subdirectories or in | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +### `pnpm update` + +Updates packages to their latest version based on the specified range + +``` +pnpm update [-g] [...] +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, output is | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are run in | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--depth` | `` | string | How deep should levels of dependencies be inspected. | +| `--dev` | `-D` | bool | Update packages only in "devDependencies" | +| `--dir` | `-C` | string | Change to directory (default: | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--global` | `-g` | bool | Update globally installed packages | +| `--global-dir` | `` | string | Specify a custom directory to store global packages | +| `--help` | `-h` | bool | Output usage information | +| `--interactive` | `-i` | bool | Show outdated dependencies and select which ones to | +| `--latest` | `-L` | bool | Ignore version ranges in package.json | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or higher | +| `--no-optional` | `` | bool | Don't update packages in "optionalDependencies" | +| `--prod` | `-P` | bool | Update packages only in "dependencies" and | +| `--recursive` | `-r` | bool | Update in every package found in subdirectories or | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace` | `` | bool | Tries to link all packages from the workspace. | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +### `pnpm why` + +Shows all packages that depend on the specified package + +``` +pnpm why ... +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--[no-]color` | `` | bool | Controls colors in the output. By default, output is | +| `--aggregate-output` | `` | bool | Aggregate output from child processes that are run in | +| `--changed-files-ignore-` | `` | bool | | +| `--changed-files-ignore-pattern` | `` | string | Defines files to ignore when | +| `--depth` | `` | string | Max display depth of the dependency graph | +| `--dev` | `-D` | bool | Display only the dependency graph for packages in | +| `--dir` | `-C` | string | Change to directory (default: | +| `--exclude-peers` | `` | bool | Exclude peer dependencies | +| `--fail-if-no-match` | `` | bool | If no projects are matched by | +| `--filter` | `` | string | If a selector starts with ! (or | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Includes only the direct and | +| `--filter` | `` | string | Includes all direct and indirect | +| `--filter` | `` | string | Includes all packages that are | +| `--filter` | `` | string | Restricts the scope to package | +| `--filter-prod` | `` | string | Restricts the scope to package | +| `--global` | `-g` | bool | List packages in the global install prefix instead of | +| `--global-dir` | `` | string | Specify a custom directory to store global packages | +| `--help` | `-h` | bool | Output usage information | +| `--json` | `` | bool | Show information in JSON format | +| `--loglevel` | `` | string | What level of logs to report. Any logs at or higher | +| `--long` | `` | bool | Show extended information | +| `--no-optional` | `` | bool | Don't display packages from `optionalDependencies` | +| `--parseable` | `` | bool | Show parseable output instead of tree view | +| `--prod` | `-P` | bool | Display only the dependency graph for packages in | +| `--recursive` | `-r` | bool | Perform command on every package in subdirectories or | +| `--stream` | `` | bool | Stream output from child processes immediately, | +| `--test-pattern` | `` | string | Defines files related to tests. | +| `--use-stderr` | `` | bool | Divert all output to stderr | +| `--workspace-root` | `-w` | bool | Run the command on the root workspace project | + +## Command Groups + +### `pnpm config` + +Manage the pnpm configuration files + +``` +pnpm config set +``` + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--global` | `-g` | bool | Sets the configuration in the global config | +| `--json` | `` | bool | Show all the config settings in JSON format | +| `--location` | `` | string | When set to "project", the | + +**Subcommands:** + +#### `pnpm config delete` + +Remove the config key from the config file + +#### `pnpm config get` + +Print the config value for the provided key + +#### `pnpm config list` + +Show all the config settings + +#### `pnpm config set` + +Set the config key to the value provided + diff --git a/plugins/cli-pnpm/skills/cli-pnpm/references/examples.md b/plugins/cli-pnpm/skills/cli-pnpm/references/examples.md new file mode 100644 index 0000000..d8c75ad --- /dev/null +++ b/plugins/cli-pnpm/skills/cli-pnpm/references/examples.md @@ -0,0 +1,158 @@ +# pnpm -- Usage Examples + +_No explicit examples found in CLI help; generated from usage patterns._ + +## `pnpm add` + +```bash +pnpm add +``` +Installs a package and any packages that it depends on. By default, any new package is installed as a prod dependency + +## `pnpm audit` + +```bash +pnpm audit [options] +``` +Checks for known security issues with the installed packages + +## `pnpm config` + +```bash +pnpm config set +``` +Manage the pnpm configuration files + +### `pnpm config delete` + +```bash +pnpm config delete +``` +Remove the config key from the config file + +### `pnpm config get` + +```bash +pnpm config get +``` +Print the config value for the provided key + +### `pnpm config list` + +```bash +pnpm config list +``` +Show all the config settings + +### `pnpm config set` + +```bash +pnpm config set +``` +Set the config key to the value provided + +## `pnpm create` + +```bash +pnpm create +``` +pnpm create + +## `pnpm dlx` + +```bash +pnpm dlx [args...] +``` +Fetches a package from the registry without installing it as a dependency, hot loads it, and runs whatever default command binary it exposes + +## `pnpm exec` + +```bash +pnpm [-r] [-c] exec [args...] +``` +Executes a shell command in scope of a project + +## `pnpm init` + +```bash +pnpm init +``` +Create a package.json file + +## `pnpm install` + +```bash +pnpm install [options] +``` +Install all dependencies for a project + +## `pnpm link` + +```bash +pnpm link +``` +Connect the local project to another one + +## `pnpm list` + +```bash +pnpm ls [ ...] +``` +Print all the versions of packages that are installed, as well as their dependencies, in a tree-structure + +## `pnpm outdated` + +```bash +pnpm outdated [ ...] +``` +Check for outdated packages + +## `pnpm publish` + +```bash +pnpm publish [|] [--tag ] [--access ] [options] +``` +Publishes a package to the registry + +## `pnpm remove` + +```bash +pnpm remove [@]... +``` +Removes packages from node_modules and from the project's package.json + +## `pnpm run` + +```bash +pnpm run [...] +``` +Runs a defined package script + +## `pnpm self-update` + +```bash +pnpm self-update +``` +Nothing to stop. No server is running for the store at /mnt/d/.pnpm-store/v10 + +## `pnpm unlink` + +```bash +pnpm unlink (in package dir) +``` +Unlinks a package. Like yarn unlink but pnpm re-installs the dependency after removing the external link + +## `pnpm update` + +```bash +pnpm update [-g] [...] +``` +Updates packages to their latest version based on the specified range + +## `pnpm why` + +```bash +pnpm why ... +``` +Shows all packages that depend on the specified package + diff --git a/plugins/cli-python/.claude-plugin/plugin.json b/plugins/cli-python/.claude-plugin/plugin.json new file mode 100644 index 0000000..965b1e2 --- /dev/null +++ b/plugins/cli-python/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-python", + "version": "3.12.3", + "description": "Command reference plugin for python CLI", + "keywords": [ + "python" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-python/commands/scan-cli.md b/plugins/cli-python/commands/scan-cli.md new file mode 100644 index 0000000..5b2da8f --- /dev/null +++ b/plugins/cli-python/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the python CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan python CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `python` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-python/scripts/rescan.sh b/plugins/cli-python/scripts/rescan.sh new file mode 100644 index 0000000..8f5f708 --- /dev/null +++ b/plugins/cli-python/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan python CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="python" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-python/skills/cli-python/SKILL.md b/plugins/cli-python/skills/cli-python/SKILL.md new file mode 100644 index 0000000..b09b39c --- /dev/null +++ b/plugins/cli-python/skills/cli-python/SKILL.md @@ -0,0 +1,65 @@ +--- +name: cli-python +description: >- + This skill should be used when the user needs help with python CLI commands, flags, and troubleshooting. +--- + +# python CLI Reference + +Compact command reference for **python** v3.12.3. + +- **0** total commands +- **0** command flags + **24** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `python` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--check-hash-based-pycs` | `` | string | | +| `--help-all` | `` | string | | +| `--help-env` | `` | string | | +| `--help-xoptions` | `-X` | string | | +| `-B` | `-B` | bool | : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x | +| `-E` | `-E` | bool | : ignore PYTHON* environment variables (such as PYTHONPATH) | +| `-I` | `-I` | bool | : isolate Python from the user's environment (implies -E and -s) | +| `-O` | `-O` | bool | : remove assert and __debug__-dependent statements; add .opt-1 before | +| `-P` | `-P` | bool | : don't prepend a potentially unsafe path to sys.path; also | +| `-S` | `-S` | bool | : don't imply 'import site' on initialization | +| `-V` | `-V` | bool | : print the Python version number and exit (also --version) | +| `-W` | `-W` | string | warning control; arg is action:message:category:module:lineno | +| `-X` | `-X` | string | set implementation-specific option | +| `-b` | `-b` | bool | : issue warnings about converting bytes/bytearray to str and comparing | +| `-c` | `-c` | string | program passed in as string (terminates option list) | +| `-d` | `-d` | bool | : turn on parser debugging output (for experts only, only works on | +| `-h` | `-h` | bool | : print this help message and exit (also -? or --help) | +| `-i` | `-i` | bool | : inspect interactively after running script; forces a prompt even | +| `-m` | `-m` | string | run library module as a script (terminates option list) | +| `-q` | `-q` | bool | : don't print version and copyright messages on interactive startup | +| `-s` | `-s` | bool | : don't add user site directory to sys.path; also PYTHONNOUSERSITE=x | +| `-u` | `-u` | bool | : force the stdout and stderr streams to be unbuffered; | +| `-v` | `-v` | bool | : verbose (trace import statements); also PYTHONVERBOSE=x | +| `-x` | `-x` | bool | : skip first line of source, allowing use of non-Unix forms of #!cmd | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-python/skills/cli-python/references/commands.md b/plugins/cli-python/skills/cli-python/references/commands.md new file mode 100644 index 0000000..7b7a8da --- /dev/null +++ b/plugins/cli-python/skills/cli-python/references/commands.md @@ -0,0 +1,31 @@ +# python -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--check-hash-based-pycs` | `` | string | | +| `--help-all` | `` | string | | +| `--help-env` | `` | string | | +| `--help-xoptions` | `-X` | string | | +| `-B` | `-B` | bool | : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x | +| `-E` | `-E` | bool | : ignore PYTHON* environment variables (such as PYTHONPATH) | +| `-I` | `-I` | bool | : isolate Python from the user's environment (implies -E and -s) | +| `-O` | `-O` | bool | : remove assert and __debug__-dependent statements; add .opt-1 before | +| `-P` | `-P` | bool | : don't prepend a potentially unsafe path to sys.path; also | +| `-S` | `-S` | bool | : don't imply 'import site' on initialization | +| `-V` | `-V` | bool | : print the Python version number and exit (also --version) | +| `-W` | `-W` | string | warning control; arg is action:message:category:module:lineno | +| `-X` | `-X` | string | set implementation-specific option | +| `-b` | `-b` | bool | : issue warnings about converting bytes/bytearray to str and comparing | +| `-c` | `-c` | string | program passed in as string (terminates option list) | +| `-d` | `-d` | bool | : turn on parser debugging output (for experts only, only works on | +| `-h` | `-h` | bool | : print this help message and exit (also -? or --help) | +| `-i` | `-i` | bool | : inspect interactively after running script; forces a prompt even | +| `-m` | `-m` | string | run library module as a script (terminates option list) | +| `-q` | `-q` | bool | : don't print version and copyright messages on interactive startup | +| `-s` | `-s` | bool | : don't add user site directory to sys.path; also PYTHONNOUSERSITE=x | +| `-u` | `-u` | bool | : force the stdout and stderr streams to be unbuffered; | +| `-v` | `-v` | bool | : verbose (trace import statements); also PYTHONVERBOSE=x | +| `-x` | `-x` | bool | : skip first line of source, allowing use of non-Unix forms of #!cmd | + diff --git a/plugins/cli-python/skills/cli-python/references/examples.md b/plugins/cli-python/skills/cli-python/references/examples.md new file mode 100644 index 0000000..706d7b9 --- /dev/null +++ b/plugins/cli-python/skills/cli-python/references/examples.md @@ -0,0 +1,2 @@ +# python -- Usage Examples + diff --git a/plugins/cli-python3/.claude-plugin/plugin.json b/plugins/cli-python3/.claude-plugin/plugin.json new file mode 100644 index 0000000..a4e169b --- /dev/null +++ b/plugins/cli-python3/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-python3", + "version": "3.12.3", + "description": "Command reference plugin for python3 CLI", + "keywords": [ + "python3" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-python3/commands/scan-cli.md b/plugins/cli-python3/commands/scan-cli.md new file mode 100644 index 0000000..e8ec2ca --- /dev/null +++ b/plugins/cli-python3/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the python3 CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan python3 CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `python3` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-python3/scripts/rescan.sh b/plugins/cli-python3/scripts/rescan.sh new file mode 100644 index 0000000..aaa07af --- /dev/null +++ b/plugins/cli-python3/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan python3 CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="python3" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-python3/skills/cli-python3/SKILL.md b/plugins/cli-python3/skills/cli-python3/SKILL.md new file mode 100644 index 0000000..9655148 --- /dev/null +++ b/plugins/cli-python3/skills/cli-python3/SKILL.md @@ -0,0 +1,40 @@ +--- +name: cli-python3 +description: >- + This skill should be used when the user needs help with python3 CLI commands, flags, and troubleshooting. +--- + +# python3 CLI Reference + +Compact command reference for **python3** v3.12.3. + +- **0** total commands +- **0** command flags + **0** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `python3` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +_No global flags detected._ + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-python3/skills/cli-python3/references/commands.md b/plugins/cli-python3/skills/cli-python3/references/commands.md new file mode 100644 index 0000000..3eb9a04 --- /dev/null +++ b/plugins/cli-python3/skills/cli-python3/references/commands.md @@ -0,0 +1,2 @@ +# python3 -- Complete Command Reference + diff --git a/plugins/cli-python3/skills/cli-python3/references/examples.md b/plugins/cli-python3/skills/cli-python3/references/examples.md new file mode 100644 index 0000000..12949d6 --- /dev/null +++ b/plugins/cli-python3/skills/cli-python3/references/examples.md @@ -0,0 +1,2 @@ +# python3 -- Usage Examples + diff --git a/plugins/cli-tar/.claude-plugin/plugin.json b/plugins/cli-tar/.claude-plugin/plugin.json new file mode 100644 index 0000000..6940557 --- /dev/null +++ b/plugins/cli-tar/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-tar", + "version": "", + "description": "Command reference plugin for tar CLI", + "keywords": [ + "tar" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-tar/commands/scan-cli.md b/plugins/cli-tar/commands/scan-cli.md new file mode 100644 index 0000000..9ba74c0 --- /dev/null +++ b/plugins/cli-tar/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the tar CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan tar CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `tar` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-tar/scripts/rescan.sh b/plugins/cli-tar/scripts/rescan.sh new file mode 100644 index 0000000..ed2ea8b --- /dev/null +++ b/plugins/cli-tar/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan tar CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="tar" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-tar/skills/cli-tar/SKILL.md b/plugins/cli-tar/skills/cli-tar/SKILL.md new file mode 100644 index 0000000..ed6f465 --- /dev/null +++ b/plugins/cli-tar/skills/cli-tar/SKILL.md @@ -0,0 +1,74 @@ +--- +name: cli-tar +description: >- + This skill should be used when the user needs help with tar CLI commands, flags, and troubleshooting. +--- + +# tar CLI Reference + +Compact command reference for **tar** v. + +- **0** total commands +- **0** command flags + **33** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `tar` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--absolute-names` | `-P` | string | don't strip leading '/'s from file names | +| `--auto-compress` | `-a` | bool | use archive suffix to determine the compression | +| `--block-number` | `-R` | bool | show block number within archive with each message | +| `--bzip2` | `-j` | bool | filter the archive through bzip2 | +| `--check-links` | `-l` | bool | print a message if not all links are dumped | +| `--checkpoint-action` | `` | string | execute ACTION on each checkpoint | +| `--dereference` | `-h` | string | follow symlinks; archive and dump the files they | +| `--full-time` | `` | string | print file time to its full resolution | +| `--hard-dereference` | `` | string | follow hard links; archive and dump the files they | +| `--index-file` | `` | string | send verbose output to FILE | +| `--lzip` | `` | bool | filter the archive through lzip | +| `--lzma` | `` | bool | filter the archive through xz | +| `--lzop` | `` | bool | filter the archive through lzop | +| `--newer-mtime` | `` | string | compare date and time when data changed only | +| `--no-auto-compress` | `` | bool | do not use archive suffix to determine the | +| `--no-quote-chars` | `` | string | disable quoting for characters from STRING | +| `--one-file-system` | `` | string | stay in local file system when creating archive | +| `--quote-chars` | `` | string | additionally quote characters from STRING | +| `--quoting-style` | `` | string | set name quoting style; see below for valid STYLE | +| `--restrict` | `` | bool | disable use of some potentially harmful options | +| `--show-defaults` | `` | bool | show tar defaults | +| `--show-omitted-dirs` | `` | string | when listing or extracting, list each directory | +| `--show-snapshot-field-ranges` | `` | string | show valid ranges for snapshot-file fields | +| `--strip-components` | `` | string | strip NUMBER leading components from file | +| `--suffix` | `` | string | backup before removal, override usual suffix ('~' | +| `--usage` | `` | bool | give a short usage message | +| `--utc` | `` | string | print file modification times in UTC | +| `--verbose` | `-v` | string | verbosely list files processed | +| `--version` | `` | bool | print program version | +| `--warning` | `` | string | warning control | +| `--xz` | `-J` | bool | filter the archive through xz | +| `--zstd` | `` | bool | filter the archive through zstd | +| `-o` | `-o` | bool | when creating, same as --old-archive; when | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-tar/skills/cli-tar/references/commands.md b/plugins/cli-tar/skills/cli-tar/references/commands.md new file mode 100644 index 0000000..8c1f2c8 --- /dev/null +++ b/plugins/cli-tar/skills/cli-tar/references/commands.md @@ -0,0 +1,40 @@ +# tar -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--absolute-names` | `-P` | string | don't strip leading '/'s from file names | +| `--auto-compress` | `-a` | bool | use archive suffix to determine the compression | +| `--block-number` | `-R` | bool | show block number within archive with each message | +| `--bzip2` | `-j` | bool | filter the archive through bzip2 | +| `--check-links` | `-l` | bool | print a message if not all links are dumped | +| `--checkpoint-action` | `` | string | execute ACTION on each checkpoint | +| `--dereference` | `-h` | string | follow symlinks; archive and dump the files they | +| `--full-time` | `` | string | print file time to its full resolution | +| `--hard-dereference` | `` | string | follow hard links; archive and dump the files they | +| `--index-file` | `` | string | send verbose output to FILE | +| `--lzip` | `` | bool | filter the archive through lzip | +| `--lzma` | `` | bool | filter the archive through xz | +| `--lzop` | `` | bool | filter the archive through lzop | +| `--newer-mtime` | `` | string | compare date and time when data changed only | +| `--no-auto-compress` | `` | bool | do not use archive suffix to determine the | +| `--no-quote-chars` | `` | string | disable quoting for characters from STRING | +| `--one-file-system` | `` | string | stay in local file system when creating archive | +| `--quote-chars` | `` | string | additionally quote characters from STRING | +| `--quoting-style` | `` | string | set name quoting style; see below for valid STYLE | +| `--restrict` | `` | bool | disable use of some potentially harmful options | +| `--show-defaults` | `` | bool | show tar defaults | +| `--show-omitted-dirs` | `` | string | when listing or extracting, list each directory | +| `--show-snapshot-field-ranges` | `` | string | show valid ranges for snapshot-file fields | +| `--strip-components` | `` | string | strip NUMBER leading components from file | +| `--suffix` | `` | string | backup before removal, override usual suffix ('~' | +| `--usage` | `` | bool | give a short usage message | +| `--utc` | `` | string | print file modification times in UTC | +| `--verbose` | `-v` | string | verbosely list files processed | +| `--version` | `` | bool | print program version | +| `--warning` | `` | string | warning control | +| `--xz` | `-J` | bool | filter the archive through xz | +| `--zstd` | `` | bool | filter the archive through zstd | +| `-o` | `-o` | bool | when creating, same as --old-archive; when | + diff --git a/plugins/cli-tar/skills/cli-tar/references/examples.md b/plugins/cli-tar/skills/cli-tar/references/examples.md new file mode 100644 index 0000000..584272f --- /dev/null +++ b/plugins/cli-tar/skills/cli-tar/references/examples.md @@ -0,0 +1,2 @@ +# tar -- Usage Examples + diff --git a/plugins/cli-yq/.claude-plugin/plugin.json b/plugins/cli-yq/.claude-plugin/plugin.json new file mode 100644 index 0000000..9431c21 --- /dev/null +++ b/plugins/cli-yq/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-yq", + "version": "1.7", + "description": "Command reference plugin for yq CLI", + "keywords": [ + "yq" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-yq/commands/scan-cli.md b/plugins/cli-yq/commands/scan-cli.md new file mode 100644 index 0000000..be6d98b --- /dev/null +++ b/plugins/cli-yq/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the yq CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan yq CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `yq` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-yq/scripts/rescan.sh b/plugins/cli-yq/scripts/rescan.sh new file mode 100644 index 0000000..56f3d16 --- /dev/null +++ b/plugins/cli-yq/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan yq CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="yq" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-yq/skills/cli-yq/SKILL.md b/plugins/cli-yq/skills/cli-yq/SKILL.md new file mode 100644 index 0000000..94e438f --- /dev/null +++ b/plugins/cli-yq/skills/cli-yq/SKILL.md @@ -0,0 +1,65 @@ +--- +name: cli-yq +description: >- + This skill should be used when the user needs help with yq CLI commands, flags, and troubleshooting. +--- + +# yq CLI Reference + +Compact command reference for **yq** v1.7. + +- **0** total commands +- **0** command flags + **24** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `yq` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--args` | `` | bool | consume remaining arguments as positional | +| `--ascii-output` | `-a` | string | output strings by only ASCII characters | +| `--build-configuration` | `` | bool | show jq's build configuration; | +| `--color-output` | `-C` | bool | colorize JSON output; | +| `--compact-output` | `-c` | bool | compact instead of pretty-printed output; | +| `--exit-status` | `-e` | bool | set exit status code based on the output; | +| `--from-file` | `-f` | string | load filter from the file; | +| `--help` | `-h` | bool | show this help message and exit | +| `--indent` | `` | string | use n spaces for indentation (max 7 spaces); | +| `--join-output` | `-j` | bool | implies -r and output without newline after | +| `--jsonargs` | `` | bool | consume remaining arguments as positional | +| `--monochrome-output` | `-M` | bool | disable colored output; | +| `--null-input` | `-n` | string | use `null` as the single input value; | +| `--raw-input` | `-R` | string | read each line as string instead of JSON; | +| `--raw-output` | `-r` | string | output strings without escapes and quotes; | +| `--raw-output0` | `` | bool | implies -r and output NUL after each output; | +| `--seq` | `` | bool | parse input/output as application/json-seq; | +| `--slurp` | `-s` | bool | read all inputs into an array and use it as | +| `--sort-keys` | `-S` | bool | sort keys of each object on output; | +| `--stream` | `` | string | parse the input value in streaming fashion; | +| `--stream-errors` | `` | bool | implies --stream and report parse error as | +| `--tab` | `` | bool | use tabs for indentation; | +| `--unbuffered` | `` | bool | flush output stream after each output; | +| `--version` | `-V` | bool | show the version; | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-yq/skills/cli-yq/references/commands.md b/plugins/cli-yq/skills/cli-yq/references/commands.md new file mode 100644 index 0000000..8d51abf --- /dev/null +++ b/plugins/cli-yq/skills/cli-yq/references/commands.md @@ -0,0 +1,31 @@ +# yq -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `--args` | `` | bool | consume remaining arguments as positional | +| `--ascii-output` | `-a` | string | output strings by only ASCII characters | +| `--build-configuration` | `` | bool | show jq's build configuration; | +| `--color-output` | `-C` | bool | colorize JSON output; | +| `--compact-output` | `-c` | bool | compact instead of pretty-printed output; | +| `--exit-status` | `-e` | bool | set exit status code based on the output; | +| `--from-file` | `-f` | string | load filter from the file; | +| `--help` | `-h` | bool | show this help message and exit | +| `--indent` | `` | string | use n spaces for indentation (max 7 spaces); | +| `--join-output` | `-j` | bool | implies -r and output without newline after | +| `--jsonargs` | `` | bool | consume remaining arguments as positional | +| `--monochrome-output` | `-M` | bool | disable colored output; | +| `--null-input` | `-n` | string | use `null` as the single input value; | +| `--raw-input` | `-R` | string | read each line as string instead of JSON; | +| `--raw-output` | `-r` | string | output strings without escapes and quotes; | +| `--raw-output0` | `` | bool | implies -r and output NUL after each output; | +| `--seq` | `` | bool | parse input/output as application/json-seq; | +| `--slurp` | `-s` | bool | read all inputs into an array and use it as | +| `--sort-keys` | `-S` | bool | sort keys of each object on output; | +| `--stream` | `` | string | parse the input value in streaming fashion; | +| `--stream-errors` | `` | bool | implies --stream and report parse error as | +| `--tab` | `` | bool | use tabs for indentation; | +| `--unbuffered` | `` | bool | flush output stream after each output; | +| `--version` | `-V` | bool | show the version; | + diff --git a/plugins/cli-yq/skills/cli-yq/references/examples.md b/plugins/cli-yq/skills/cli-yq/references/examples.md new file mode 100644 index 0000000..482b013 --- /dev/null +++ b/plugins/cli-yq/skills/cli-yq/references/examples.md @@ -0,0 +1,2 @@ +# yq -- Usage Examples + diff --git a/plugins/cli-zip/.claude-plugin/plugin.json b/plugins/cli-zip/.claude-plugin/plugin.json new file mode 100644 index 0000000..4116e4b --- /dev/null +++ b/plugins/cli-zip/.claude-plugin/plugin.json @@ -0,0 +1,10 @@ +{ + "name": "cli-zip", + "version": "2.91", + "description": "Command reference plugin for zip CLI", + "keywords": [ + "zip" + ], + "repository": "https://github.com/nsalvacao/cli-plugins", + "license": "MIT" +} diff --git a/plugins/cli-zip/commands/scan-cli.md b/plugins/cli-zip/commands/scan-cli.md new file mode 100644 index 0000000..73eaa68 --- /dev/null +++ b/plugins/cli-zip/commands/scan-cli.md @@ -0,0 +1,24 @@ +--- +name: scan-cli +description: Re-scan the zip CLI and regenerate plugin reference files +allowed-tools: ["Bash"] +--- + +# Re-scan zip CLI + +Run the rescan script to crawl the CLI and regenerate this plugin: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh +``` + +Add `--dry-run` to preview without writing files: + +```bash +bash $CLAUDE_PLUGIN_ROOT/scripts/rescan.sh --dry-run +``` + +## Notes + +- Requires `zip` installed and on PATH +- Idempotent -- re-running overwrites existing files cleanly diff --git a/plugins/cli-zip/scripts/rescan.sh b/plugins/cli-zip/scripts/rescan.sh new file mode 100644 index 0000000..31eb85a --- /dev/null +++ b/plugins/cli-zip/scripts/rescan.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Re-scan zip CLI and regenerate this plugin. +# Usage: bash scripts/rescan.sh [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" + +CLI_NAME="zip" +JSON_PATH="$PROJECT_ROOT/output/$CLI_NAME.json" +PLUGIN_DIR="$PROJECT_ROOT/plugins/cli-$CLI_NAME" + +# Check CLI is available +if ! command -v "$CLI_NAME" &>/dev/null; then + echo "ERROR: $CLI_NAME not found on PATH. Install it first." >&2 + exit 1 +fi + +echo "==> Crawling $CLI_NAME..." +python3 "$PROJECT_ROOT/cli_crawler.py" "$CLI_NAME" + +echo "==> Generating plugin..." +python3 "$PROJECT_ROOT/scripts/generate_plugin.py" "$JSON_PATH" "$@" + +echo "==> Done. Plugin at: $PLUGIN_DIR" +ls -la "$PLUGIN_DIR" diff --git a/plugins/cli-zip/skills/cli-zip/SKILL.md b/plugins/cli-zip/skills/cli-zip/SKILL.md new file mode 100644 index 0000000..52c9c0f --- /dev/null +++ b/plugins/cli-zip/skills/cli-zip/SKILL.md @@ -0,0 +1,55 @@ +--- +name: cli-zip +description: >- + This skill should be used when the user needs help with zip CLI commands, flags, and troubleshooting. +--- + +# zip CLI Reference + +Compact command reference for **zip** v2.91. + +- **0** total commands +- **0** command flags + **14** global flags +- **0** extracted usage examples +- Max nesting depth: 0 + +## When to Use + +- Constructing or validating `zip` commands +- Looking up flags/options fast +- Troubleshooting failed invocations + +## Top-Level Commands + +Command format examples: + +### Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `-0` | `-0` | bool | store only -l convert LF to CR LF (-ll CR LF to LF) | +| `-1` | `-1` | bool | compress faster -9 compress better | +| `-A` | `-A` | bool | adjust self-extracting exe -J junk zipfile prefix (unzipsfx) | +| `-F` | `-F` | bool | fix zipfile (-FF try harder) -D do not add directory entries | +| `-T` | `-T` | bool | test zipfile integrity -X eXclude eXtra file attributes | +| `-c` | `-c` | bool | add one-line comments -z add zipfile comment | +| `-d` | `-d` | bool | delete entries in zipfile -m move into zipfile (delete OS files) | +| `-e` | `-e` | bool | encrypt -n don't compress these suffixes | +| `-f` | `-f` | bool | freshen: only changed files -u update: only changed or new files | +| `-h` | `-h` | string | show more help | +| `-q` | `-q` | bool | quiet operation -v verbose operation/print version info | +| `-r` | `-r` | bool | recurse into directories -j junk (don't record) directory names | +| `-x` | `-x` | bool | exclude the following names -i include only the following names | +| `-y` | `-y` | bool | store symbolic links as the link instead of the referenced file | + +## Common Usage Patterns (Compact) + +_No examples extracted._ +## Detailed References + +- Full command tree: `references/commands.md` +- Full examples catalog: `references/examples.md` + +## Re-Scanning + +After a CLI update, run `/scan-cli` or execute crawler + generator again. diff --git a/plugins/cli-zip/skills/cli-zip/references/commands.md b/plugins/cli-zip/skills/cli-zip/references/commands.md new file mode 100644 index 0000000..8753859 --- /dev/null +++ b/plugins/cli-zip/skills/cli-zip/references/commands.md @@ -0,0 +1,21 @@ +# zip -- Complete Command Reference + +## Global Flags + +| Flag | Short | Type | Description | +| --- | --- | --- | --- | +| `-0` | `-0` | bool | store only -l convert LF to CR LF (-ll CR LF to LF) | +| `-1` | `-1` | bool | compress faster -9 compress better | +| `-A` | `-A` | bool | adjust self-extracting exe -J junk zipfile prefix (unzipsfx) | +| `-F` | `-F` | bool | fix zipfile (-FF try harder) -D do not add directory entries | +| `-T` | `-T` | bool | test zipfile integrity -X eXclude eXtra file attributes | +| `-c` | `-c` | bool | add one-line comments -z add zipfile comment | +| `-d` | `-d` | bool | delete entries in zipfile -m move into zipfile (delete OS files) | +| `-e` | `-e` | bool | encrypt -n don't compress these suffixes | +| `-f` | `-f` | bool | freshen: only changed files -u update: only changed or new files | +| `-h` | `-h` | string | show more help | +| `-q` | `-q` | bool | quiet operation -v verbose operation/print version info | +| `-r` | `-r` | bool | recurse into directories -j junk (don't record) directory names | +| `-x` | `-x` | bool | exclude the following names -i include only the following names | +| `-y` | `-y` | bool | store symbolic links as the link instead of the referenced file | + diff --git a/plugins/cli-zip/skills/cli-zip/references/examples.md b/plugins/cli-zip/skills/cli-zip/references/examples.md new file mode 100644 index 0000000..4e9d9c5 --- /dev/null +++ b/plugins/cli-zip/skills/cli-zip/references/examples.md @@ -0,0 +1,2 @@ +# zip -- Usage Examples + From ec1aa8b4794a24bf529d62a93a4cc3662efeca50 Mon Sep 17 00:00:00 2001 From: NUNO MIGUEL DA SILVA SALVACAO Date: Mon, 16 Feb 2026 12:31:58 +0000 Subject: [PATCH 5/5] fix(lint): apply ruff formatting for CI --- src/config/audit.py | 8 ++++++-- src/crawler/pipeline.py | 8 +++++--- src/generator/plugin_generator.py | 7 ++----- tests/integration/test_gcc_climap_generation.py | 6 +++--- tests/integration/test_pnpm_climap_generation.py | 12 ++++++------ tests/performance/test_smoke_perf.py | 3 ++- tests/test_parser_manpage.py | 4 +++- tests/unit/test_edge_case_auth_help.py | 7 +++---- tests/unit/test_edge_case_long_help.py | 4 +++- tests/unit/test_progressive_disclosure.py | 12 ++++++------ tests/unit/test_subcommand_help_safety.py | 4 +++- 11 files changed, 42 insertions(+), 33 deletions(-) diff --git a/src/config/audit.py b/src/config/audit.py index f092879..e497edc 100644 --- a/src/config/audit.py +++ b/src/config/audit.py @@ -47,7 +47,9 @@ def _collect_plugin_clis(plugins_dir: Path) -> set[str]: return clis -def _suggest_minimal_overrides(config: CrawlerConfig) -> tuple[dict[str, dict[str, Any]], list[str]]: +def _suggest_minimal_overrides( + config: CrawlerConfig, +) -> tuple[dict[str, dict[str, Any]], list[str]]: """Keep only operational overrides that differ from defaults.""" defaults = config.defaults suggestions: dict[str, dict[str, Any]] = {} @@ -107,7 +109,9 @@ def build_config_audit_report( def write_config_audit_report(report: dict[str, Any], report_path: Path) -> None: report_path.parent.mkdir(parents=True, exist_ok=True) - report_path.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + report_path.write_text( + json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8" + ) def _build_parser() -> argparse.ArgumentParser: diff --git a/src/crawler/pipeline.py b/src/crawler/pipeline.py index 1b35863..e1cc4f0 100644 --- a/src/crawler/pipeline.py +++ b/src/crawler/pipeline.py @@ -71,9 +71,11 @@ def crawl_cli( logger.info("Help pattern: %s (manpage=%s)", detection.pattern, detection.is_manpage) root_help = "" if detection.pattern == "auth_required" else detection.result.stdout - parse_input, progressive_loading, raw_line_count, parsed_line_count = _apply_progressive_loading( - root_help, - config.raw_threshold, + parse_input, progressive_loading, raw_line_count, parsed_line_count = ( + _apply_progressive_loading( + root_help, + config.raw_threshold, + ) ) progressive_warning = "" if progressive_loading: diff --git a/src/generator/plugin_generator.py b/src/generator/plugin_generator.py index 19763eb..56ac5d1 100644 --- a/src/generator/plugin_generator.py +++ b/src/generator/plugin_generator.py @@ -495,8 +495,7 @@ def _render_top_level_section(group_limit: int, leaf_limit: int) -> str: f"... +{len(leaf_commands) - len(shown_leaf)} more in `references/commands.md`" ) lines.append( - "Command format examples: " - + ", ".join(f"`{cli} {name}`" for name in command_examples) + "Command format examples: " + ", ".join(f"`{cli} {name}`" for name in command_examples) ) return "\n".join(lines) if lines else "_No commands._" @@ -530,9 +529,7 @@ def _render_skill_md(group_limit: int, leaf_limit: int) -> str: "- Full examples catalog: `references/examples.md`\n" ) parts.append("## Re-Scanning\n") - parts.append( - "After a CLI update, run `/scan-cli` or execute crawler + generator again.\n" - ) + parts.append("After a CLI update, run `/scan-cli` or execute crawler + generator again.\n") return "\n".join(parts) group_limit = max(1, len(command_groups)) diff --git a/tests/integration/test_gcc_climap_generation.py b/tests/integration/test_gcc_climap_generation.py index 9188d5c..b4e56ce 100644 --- a/tests/integration/test_gcc_climap_generation.py +++ b/tests/integration/test_gcc_climap_generation.py @@ -43,9 +43,9 @@ def test_gcc_climap_and_plugin_quality_regression(tmp_path) -> None: loaded = load_cli_map(output_path) plugin_root = generate_plugin(loaded, tmp_path / "plugins", str(output_path)) - commands_md = ( - plugin_root / "skills" / "cli-gcc" / "references" / "commands.md" - ).read_text(encoding="utf-8") + commands_md = (plugin_root / "skills" / "cli-gcc" / "references" / "commands.md").read_text( + encoding="utf-8" + ) assert "-print-file-name" in commands_md assert "-Wa" in commands_md diff --git a/tests/integration/test_pnpm_climap_generation.py b/tests/integration/test_pnpm_climap_generation.py index e4fbfda..2731d3d 100644 --- a/tests/integration/test_pnpm_climap_generation.py +++ b/tests/integration/test_pnpm_climap_generation.py @@ -29,12 +29,12 @@ def test_pnpm_climap_and_plugin_quality_regression(tmp_path) -> None: loaded = load_cli_map(output_path) plugin_root = generate_plugin(loaded, tmp_path / "plugins", str(output_path)) - commands_md = ( - plugin_root / "skills" / "cli-pnpm" / "references" / "commands.md" - ).read_text(encoding="utf-8") - examples_md = ( - plugin_root / "skills" / "cli-pnpm" / "references" / "examples.md" - ).read_text(encoding="utf-8") + commands_md = (plugin_root / "skills" / "cli-pnpm" / "references" / "commands.md").read_text( + encoding="utf-8" + ) + examples_md = (plugin_root / "skills" / "cli-pnpm" / "references" / "examples.md").read_text( + encoding="utf-8" + ) skill_md = (plugin_root / "skills" / "cli-pnpm" / "SKILL.md").read_text(encoding="utf-8") assert "pnpm add" in commands_md diff --git a/tests/performance/test_smoke_perf.py b/tests/performance/test_smoke_perf.py index 592ff61..de6a6e4 100644 --- a/tests/performance/test_smoke_perf.py +++ b/tests/performance/test_smoke_perf.py @@ -17,7 +17,8 @@ @pytest.mark.performance def test_parse_1000_line_fixture_under_5_seconds() -> None: help_text = "\n".join( - ["Usage: perf-cli [OPTIONS] COMMAND"] + [f"--flag-{i} Description {i}" for i in range(1000)] + ["Usage: perf-cli [OPTIONS] COMMAND"] + + [f"--flag-{i} Description {i}" for i in range(1000)] ) start = time.monotonic() diff --git a/tests/test_parser_manpage.py b/tests/test_parser_manpage.py index 73caf4d..52d86b2 100644 --- a/tests/test_parser_manpage.py +++ b/tests/test_parser_manpage.py @@ -57,7 +57,9 @@ def test_npm_style_examples_are_extracted(self): assert "npm install" in cmd.examples assert "npm install sax" in cmd.examples assert all(not ex.lower().startswith("for more information") for ex in cmd.examples) - assert all("install dependencies from package.json." not in ex.lower() for ex in cmd.examples) + assert all( + "install dependencies from package.json." not in ex.lower() for ex in cmd.examples + ) def test_examples_with_left_aligned_prose_still_extract_commands(self): manpage_text = """TOOL(1) user manual TOOL(1) diff --git a/tests/unit/test_edge_case_auth_help.py b/tests/unit/test_edge_case_auth_help.py index 9c65c51..e9eee58 100644 --- a/tests/unit/test_edge_case_auth_help.py +++ b/tests/unit/test_edge_case_auth_help.py @@ -55,10 +55,9 @@ def test_auth_required_help_returns_clear_warning_without_hanging( assert cli_map.commands == {} assert int(cli_map.metadata.get("parse_warnings", "0")) >= 1 assert float(cli_map.metadata.get("confidence_score", "1.0")) <= 0.30 - assert ( - cli_map.metadata.get("help_error", "").startswith("AUTH_REQUIRED:") - or "AUTH_REQUIRED:" in cli_map.metadata.get("help_error", "") - ) + assert cli_map.metadata.get("help_error", "").startswith( + "AUTH_REQUIRED:" + ) or "AUTH_REQUIRED:" in cli_map.metadata.get("help_error", "") def test_auth_required_help_raises_in_strict_mode(monkeypatch, tmp_path: Path) -> None: diff --git a/tests/unit/test_edge_case_long_help.py b/tests/unit/test_edge_case_long_help.py index 9159701..d9ba13e 100644 --- a/tests/unit/test_edge_case_long_help.py +++ b/tests/unit/test_edge_case_long_help.py @@ -28,7 +28,9 @@ def test_long_help_activates_progressive_loading(monkeypatch, tmp_path: Path) -> seen: dict[str, int] = {} - def _fake_parse(text: str, _cli: str, command_path: str, force_manpage: bool = False) -> ParseResult: + def _fake_parse( + text: str, _cli: str, command_path: str, force_manpage: bool = False + ) -> ParseResult: del force_manpage seen["parsed_lines"] = len(text.splitlines()) return ParseResult( diff --git a/tests/unit/test_progressive_disclosure.py b/tests/unit/test_progressive_disclosure.py index 3ec7d71..373f380 100644 --- a/tests/unit/test_progressive_disclosure.py +++ b/tests/unit/test_progressive_disclosure.py @@ -44,12 +44,12 @@ def test_references_keep_full_details_while_skill_is_compact( ) -> None: plugin_root = generate_plugin(docker_map, tmp_path, str(DOCKER_JSON)) skill_md = (plugin_root / "skills" / "cli-docker" / "SKILL.md").read_text(encoding="utf-8") - commands_md = ( - plugin_root / "skills" / "cli-docker" / "references" / "commands.md" - ).read_text(encoding="utf-8") - examples_md = ( - plugin_root / "skills" / "cli-docker" / "references" / "examples.md" - ).read_text(encoding="utf-8") + commands_md = (plugin_root / "skills" / "cli-docker" / "references" / "commands.md").read_text( + encoding="utf-8" + ) + examples_md = (plugin_root / "skills" / "cli-docker" / "references" / "examples.md").read_text( + encoding="utf-8" + ) assert _approx_token_count(skill_md) <= 800 assert "docker run" in commands_md diff --git a/tests/unit/test_subcommand_help_safety.py b/tests/unit/test_subcommand_help_safety.py index 190bac4..68bef08 100644 --- a/tests/unit/test_subcommand_help_safety.py +++ b/tests/unit/test_subcommand_help_safety.py @@ -28,7 +28,9 @@ def run(self, command: list[str], timeout: int | None = None) -> ExecutionResult del timeout self.calls.append(command) stdout = self.bare_help_output if command == [self.cli_name, "help"] else "" - return ExecutionResult(stdout=stdout, stderr="", exit_code=0 if stdout else 1, command=command) + return ExecutionResult( + stdout=stdout, stderr="", exit_code=0 if stdout else 1, command=command + ) class _MappingExecutor: