Coverage for src/alprina_cli/cli.py: 58%
113 statements
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-13 11:15 +0100
« prev ^ index » next coverage.py v7.11.3, created at 2025-11-13 11:15 +0100
1"""
2Main CLI application for Alprina.
3Integrates Typer for command handling with Rich for beautiful output.
4"""
6import typer
7from rich.console import Console
8from rich.panel import Panel
9from rich.table import Table
10from typing import Optional
11from pathlib import Path
12import sys
13from dotenv import load_dotenv
15# Load environment variables from .env file
16load_dotenv()
18from . import __version__
19from .auth import login_command, logout_command, status_command
20from .scanner import scan_command, recon_command
21from .policy import policy_test_command, policy_init_command
22from .reporting import report_command
23from .billing import billing_status_command
24from .acp_server import run_acp
25from .config import init_config_command
26from .history import history_command
27from .fix_command import fix_command, suggest_fixes_command
29console = Console()
30app = typer.Typer(
31 name="alprina",
32 help="🛡️ Alprina CLI - AI-powered cybersecurity tool for developers",
33 add_completion=True,
34 rich_markup_mode="rich",
35)
37# Auth commands
38auth_app = typer.Typer(help="Authentication commands")
39app.add_typer(auth_app, name="auth")
41@auth_app.command("login")
42def login(
43 api_key: Optional[str] = typer.Option(None, "--api-key", help="API key for authentication"),
44 oauth_provider: Optional[str] = typer.Option(None, "--provider", help="OAuth provider (github, google)"),
45 code: Optional[str] = typer.Option(None, "--code", help="6-digit CLI code from dashboard"),
46):
47 """
48 🔐 Authenticate with Alprina.
50 Examples:
51 alprina auth login # Browser OAuth (recommended)
52 alprina auth login --code ABC123 # Dashboard code (reverse flow)
53 alprina auth login --api-key sk_... # Direct API key
54 """
55 try:
56 login_command(api_key, oauth_provider, code)
57 except Exception as e:
58 from .utils.errors import handle_error
59 handle_error(e)
60 raise typer.Exit(1)
62@auth_app.command("logout")
63def logout():
64 """
65 👋 Logout from Alprina.
66 """
67 logout_command()
69@auth_app.command("status")
70def auth_status():
71 """
72 ℹ️ Check authentication status.
73 """
74 status_command()
77# Scanning commands
78@app.command("scan")
79def scan(
80 path: str = typer.Argument(
81 ".",
82 help="Path to scan (file or directory, defaults to current directory)"
83 ),
84 profile: str = typer.Option("default", "--profile", "-p", help="Scan profile to use"),
85 safe_only: bool = typer.Option(True, "--safe-only", help="Only run safe, non-intrusive scans"),
86 output: Optional[Path] = typer.Option(None, "--output", "-o", help="Output file path"),
87 quick: bool = typer.Option(False, "--quick", "-q", help="Quick 5-second scan for critical issues"),
88 container: bool = typer.Option(False, "--container", help="Scan Docker container image"),
89 agent: Optional[list[str]] = typer.Option(None, "--agent", "-a", help="Specific agent(s) to use"),
90 verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed output"),
91 # Week 4: Unified scanner flags for smart contracts
92 all_analyzers: bool = typer.Option(False, "--all", help="Run all security analyzers (symbolic, MEV, cross-contract, gas)"),
93 symbolic: bool = typer.Option(False, "--symbolic", help="Run symbolic execution with Z3"),
94 mev: bool = typer.Option(False, "--mev", help="Run MEV detection analysis"),
95 cross_contract: bool = typer.Option(False, "--cross-contract", help="Run cross-contract analysis"),
96 gas: bool = typer.Option(False, "--gas", help="Run gas optimization analysis (Week 4 Day 3)"),
97 tvl: Optional[float] = typer.Option(None, "--tvl", help="Protocol TVL for economic impact calculation"),
98 protocol_type: Optional[str] = typer.Option(None, "--protocol", help="Protocol type (dex, lending, bridge)"),
99 output_format: str = typer.Option("json", "--format", help="Output format (json, markdown, html, text)"),
100):
101 """
102 🔍 Run an AI-powered security scan.
104 Examples:
105 alprina scan # Scan current directory
106 alprina scan ./src # Scan src directory
107 alprina scan app.py # Scan single file
108 alprina scan . --quick # Quick 5-second check
109 alprina scan . -a red_teamer # Use specific agent
110 alprina scan . -a cicd_guardian # Use CI/CD Pipeline Guardian
111 alprina scan . -a web3_auditor # Use Web3/DeFi Security Auditor
112 alprina scan . --profile code-audit # Full comprehensive scan
113 alprina scan nginx:latest --container # Scan Docker image
115 Smart Contract Security (Week 4):
116 alprina scan contract.sol --all --tvl 50000000 --protocol dex
117 alprina scan contract.sol --symbolic --mev
118 alprina scan contract.sol --gas # Gas optimization (Day 3)
119 alprina scan . --cross-contract --format markdown
120 """
121 scan_command(
122 path, profile, safe_only, output, quick, container, agent, verbose,
123 all_analyzers, symbolic, mev, cross_contract, gas, tvl, protocol_type, output_format
124 )
127@app.command("recon")
128def recon(
129 target: str = typer.Argument(..., help="Target for reconnaissance"),
130 passive: bool = typer.Option(True, "--passive", help="Use only passive techniques"),
131):
132 """
133 🕵️ Perform reconnaissance on a target.
134 """
135 recon_command(target, passive)
138@app.command("history")
139def history(
140 scan_id: Optional[str] = typer.Option(None, "--scan-id", "-i", help="Specific scan ID to view details"),
141 limit: int = typer.Option(20, "--limit", "-l", help="Number of scans to display"),
142 severity: Optional[str] = typer.Option(None, "--severity", "-s", help="Filter by severity"),
143 page: int = typer.Option(1, "--page", "-p", help="Page number"),
144):
145 """
146 📜 View scan history and results.
148 Examples:
149 alprina history # List recent scans
150 alprina history --scan-id abc123 # View specific scan details
151 alprina history --severity high # Filter by severity
152 alprina history --page 2 --limit 10 # Pagination
153 """
154 history_command(scan_id, limit, severity, page)
157@app.command("mitigate")
158def mitigate(
159 finding_id: Optional[str] = typer.Argument(None, help="Specific finding ID to mitigate"),
160 report_file: Optional[Path] = typer.Option(None, "--report", "-r", help="Report file to process"),
161):
162 """
163 🛠️ Get AI-powered mitigation suggestions for findings.
164 """
165 from .mitigation import mitigate_command
166 mitigate_command(finding_id, report_file)
169@app.command("fix")
170def fix(
171 target: str = typer.Argument(..., help="Path to file or directory to fix"),
172 finding_id: Optional[str] = typer.Option(None, "--id", help="Specific finding ID to fix"),
173 auto_fix: bool = typer.Option(False, "--auto-fix", help="Automatically apply fixes without confirmation"),
174 severity: Optional[str] = typer.Option(None, "--severity", help="Fix only specific severity (critical, high, medium, low)"),
175 preview: bool = typer.Option(False, "--preview", help="Preview fixes without applying"),
176):
177 """
178 🤖 Generate AI-powered fixes for vulnerabilities.
180 Examples:
181 alprina fix ./app.py # Interactive fix for single file
182 alprina fix ./src --auto-fix # Auto-fix all findings
183 alprina fix ./src --severity critical # Fix only critical issues
184 alprina fix ./src --preview # Preview fixes without applying
185 """
186 fix_command(target, finding_id, auto_fix, severity, preview)
189# Policy commands
190policy_app = typer.Typer(help="Policy and compliance commands")
191app.add_typer(policy_app, name="policy")
193@policy_app.command("init")
194def policy_init():
195 """
196 📋 Initialize a new policy configuration file.
197 """
198 policy_init_command()
200@policy_app.command("test")
201def policy_test(
202 target: str = typer.Argument(..., help="Target to test against policy"),
203):
204 """
205 ✅ Test if a target is allowed by current policy.
206 """
207 policy_test_command(target)
210# Config commands
211@app.command("config")
212def config(
213 init: bool = typer.Option(False, "--init", help="Initialize default configuration"),
214):
215 """
216 ⚙️ Manage Alprina configuration.
217 """
218 if init:
219 init_config_command()
220 else:
221 console.print("[yellow]Use --init to create a default configuration[/yellow]")
224# Reporting commands
225@app.command("report")
226def report(
227 format: str = typer.Option("html", "--format", "-f", help="Report format (html, pdf, json)"),
228 output: Optional[Path] = typer.Option(None, "--output", "-o", help="Output file path"),
229):
230 """
231 📊 Generate a security report from scan results.
232 """
233 report_command(format, output)
236# Billing commands
237billing_app = typer.Typer(help="Billing and subscription commands")
238app.add_typer(billing_app, name="billing")
240@billing_app.command("status")
241def billing_status():
242 """
243 💳 Check billing status and usage.
244 """
245 billing_status_command()
248# Quickstart command (tutorial for new users)
249@app.command("quickstart")
250def quickstart():
251 """
252 🎓 Interactive tutorial for first-time users.
254 Perfect for learning how Alprina works! Runs your first security
255 scan with guided explanations in plain English.
257 Examples:
258 alprina quickstart # Start the guided tutorial
259 """
260 from .quickstart import quickstart_command
261 quickstart_command()
264# Chat command
265@app.command("chat")
266def chat(
267 model: str = typer.Option("claude-3-5-sonnet-20241022", "--model", "-m", help="LLM model to use"),
268 streaming: bool = typer.Option(True, "--streaming/--no-streaming", help="Enable streaming responses"),
269 load_results: Optional[Path] = typer.Option(None, "--load", "-l", help="Load scan results for context"),
270):
271 """
272 💬 Start interactive chat with Alprina AI assistant.
274 Examples:
275 alprina chat
276 alprina chat --model gpt-4
277 alprina chat --load ~/.alprina/out/latest-results.json
278 alprina chat --no-streaming
279 """
280 from .chat import chat_command
281 chat_command(model, streaming, load_results)
284# ACP mode for IDE integration
285@app.command("acp", hidden=True)
286def acp_mode():
287 """
288 🔌 Start Alprina in ACP mode for IDE integration.
289 """
290 console.print(Panel("Starting Alprina in ACP mode...", title="ACP Mode"))
291 run_acp()
294# Version command
295@app.command("version")
296def version():
297 """
298 📌 Show Alprina CLI version.
299 """
300 console.print(f"[bold cyan]Alprina CLI[/bold cyan] version [bold]{__version__}[/bold]")
303# Main callback for global options
304@app.callback(invoke_without_command=True)
305def main(
306 ctx: typer.Context,
307 verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output"),
308 debug: bool = typer.Option(False, "--debug", help="Enable debug mode"),
309 version_flag: bool = typer.Option(False, "--version", help="Show version and exit"),
310):
311 """
312 🛡️ Alprina CLI - Build fast. Guard faster.
314 An intelligent cybersecurity command-line tool for developers.
316 Examples:
317 alprina # Show welcome screen
318 alprina scan ./ # Scan current directory
319 alprina chat # Interactive AI assistant
320 alprina auth login # Sign in
321 """
322 if version_flag:
323 console.print(f"[bold cyan]Alprina CLI[/bold cyan] version [bold]{__version__}[/bold]")
324 raise typer.Exit()
326 if verbose:
327 console.print("[dim]Verbose mode enabled[/dim]")
328 if debug:
329 console.print("[dim]Debug mode enabled[/dim]")
331 # Show welcome screen if no command provided
332 if ctx.invoked_subcommand is None:
333 from .utils.welcome import show_welcome
334 show_welcome(force=True)
335 raise typer.Exit()
338def cli_main():
339 """Entry point for the CLI."""
340 try:
341 app()
342 except KeyboardInterrupt:
343 console.print("\n[yellow]Operation cancelled by user[/yellow]")
344 sys.exit(130)
345 except Exception as e:
346 console.print(f"[red]Error: {e}[/red]")
347 sys.exit(1)
350if __name__ == "__main__":
351 cli_main()