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