"""CLI entry point for the modernized legacy PentestGPT. pentestgpt-legacy # interactive session (auto-picks models) pentestgpt-legacy --list-models # show the supported-model registry pentestgpt-legacy --smoke-test # live round-trip every configured model pentestgpt-legacy --reasoning-model claude-opus-4-8 --parsing-model gemini-3.5-flash pentestgpt-legacy --reasoning-model ollama:qwen3 --base-url http://localhost:11434/v1 """ from __future__ import annotations import argparse import os import sys from pentestgpt_legacy._version import __version__ from pentestgpt_legacy.config import ( configured_providers, default_parsing_model, default_reasoning_model, ) from pentestgpt_legacy.llm.registry import PROVIDERS, models_by_provider, resolve def _create_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( prog="pentestgpt-legacy", description="PentestGPT (modernized legacy) — interactive, multi-LLM pentest assistant.", ) parser.add_argument( "--reasoning-model", type=str, default=None, help="Model for reasoning / the Pentesting Task Tree.", ) parser.add_argument( "--parsing-model", type=str, default=None, help="Model for summarizing tool/web output." ) parser.add_argument( "--generation-model", type=str, default=None, help="Model for expanding tasks into steps (defaults to reasoning model).", ) parser.add_argument( "--base-url", type=str, default=None, help="Override the API base URL for the selected models' provider " "(useful for Ollama / OpenAI-compatible endpoints).", ) parser.add_argument("--log-dir", type=str, default="logs", help="Directory for logs.") parser.add_argument( "--list-models", action="store_true", help="List all supported models and exit." ) parser.add_argument( "--smoke-test", action="store_true", help="Make a real API call to every configured model and report a matrix.", ) parser.add_argument( "--provider", type=str, default=None, help="Restrict --smoke-test to one provider (e.g. openai, anthropic).", ) parser.add_argument( "--model", type=str, default=None, help="Restrict --smoke-test to a single model id." ) parser.add_argument("--version", action="version", version=f"pentestgpt-legacy {__version__}") return parser def display_models() -> None: """Render the supported-model registry grouped by provider.""" ready = set(configured_providers()) print(f"\nPentestGPT (modernized legacy) v{__version__} — supported models\n") for provider_key, specs in models_by_provider().items(): info = PROVIDERS[provider_key] status = "configured" if provider_key in ready else "no key" env_hint = "" if info.requires_key and provider_key not in ready and info.env: env_hint = f" (set {info.env})" print(f"== {info.label} [{status}]{env_hint} ==") for spec in specs: flag = "legacy" if spec.legacy else spec.tier ctx = f"{spec.context_window // 1000}K ctx" note = f" — {spec.notes}" if spec.notes else "" print(f" {spec.id:<32} [{flag:<8}] {ctx}{note}") print() print("Local models: pass 'ollama:' (e.g. ollama:qwen3) with --base-url if needed.\n") def _apply_base_url_override(models: list[str], base_url: str) -> None: """Set the per-provider *_BASE_URL env override for the selected models.""" for model_name in models: spec = resolve(model_name) if spec is None: continue info = PROVIDERS.get(spec.provider) if info is None or (info.base_url is None and spec.provider in ("anthropic", "gemini")): print(f"Note: --base-url is not applicable to provider '{spec.provider}'.") continue os.environ[f"{spec.provider.upper()}_BASE_URL"] = base_url def run_interactive(args: argparse.Namespace) -> None: reasoning_model = args.reasoning_model or default_reasoning_model() parsing_model = args.parsing_model or default_parsing_model() if reasoning_model is None or parsing_model is None: print( "No LLM provider is configured. Set an API key (e.g. OPENAI_API_KEY, " "ANTHROPIC_API_KEY, GEMINI_API_KEY, DEEPSEEK_API_KEY) in your environment " "or .env file, or run with --list-models to see options.", file=sys.stderr, ) sys.exit(1) if args.base_url: selected = [m for m in (reasoning_model, parsing_model, args.generation_model) if m] _apply_base_url_override(selected, args.base_url) # Imported lazily so --list-models / --smoke-test don't require prompt_toolkit/rich. from pentestgpt_legacy.utils.pentest_gpt import pentestGPT try: session = pentestGPT( log_dir=args.log_dir, reasoning_model=reasoning_model, parsing_model=parsing_model, generation_model=args.generation_model, ) session.main() except Exception as e: print(f"PentestGPT execution failed: {e}", file=sys.stderr) sys.exit(1) def main() -> None: args = _create_parser().parse_args() if args.list_models: display_models() return if args.smoke_test: from pentestgpt_legacy.smoke_test import run_smoke_test sys.exit(run_smoke_test(provider=args.provider, model=args.model)) run_interactive(args) if __name__ == "__main__": main()