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

1""" 

2Main CLI application for Alprina. 

3Integrates Typer for command handling with Rich for beautiful output. 

4""" 

5 

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 

16 

17# Load environment variables from .env file 

18load_dotenv() 

19 

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) 

29 

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 

40 

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) 

48 

49# Auth commands 

50auth_app = typer.Typer(help="Authentication commands") 

51app.add_typer(auth_app, name="auth") 

52 

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. 

61 

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) 

73 

74@auth_app.command("logout") 

75def logout(): 

76 """ 

77 👋 Logout from Alprina. 

78 """ 

79 logout_command() 

80 

81@auth_app.command("status") 

82def auth_status(): 

83 """ 

84 ℹ️ Check authentication status. 

85 """ 

86 status_command() 

87 

88 

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. 

115 

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 

126 

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 ) 

137 

138 

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) 

148 

149 

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. 

159 

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) 

167 

168 

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) 

179 

180 

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. 

191 

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) 

199 

200 

201# Policy commands 

202policy_app = typer.Typer(help="Policy and compliance commands") 

203app.add_typer(policy_app, name="policy") 

204 

205@policy_app.command("init") 

206def policy_init(): 

207 """ 

208 📋 Initialize a new policy configuration file. 

209 """ 

210 policy_init_command() 

211 

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) 

220 

221 

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]") 

234 

235 

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) 

246 

247 

248# Billing commands 

249billing_app = typer.Typer(help="Billing and subscription commands") 

250app.add_typer(billing_app, name="billing") 

251 

252@billing_app.command("status") 

253def billing_status(): 

254 """ 

255 💳 Check billing status and usage. 

256 """ 

257 billing_status_command() 

258 

259 

260# Quickstart command (tutorial for new users) 

261@app.command("quickstart") 

262def quickstart(): 

263 """ 

264 🎓 Interactive tutorial for first-time users. 

265  

266 Perfect for learning how Alprina works! Runs your first security 

267 scan with guided explanations in plain English. 

268  

269 Examples: 

270 alprina quickstart # Start the guided tutorial 

271 """ 

272 from .quickstart import quickstart_command 

273 quickstart_command() 

274 

275 

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. 

285 

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) 

294 

295 

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() 

304 

305 

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]") 

313 

314 

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. 

325 

326 An intelligent cybersecurity command-line tool for developers. 

327  

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() 

337 

338 if verbose: 

339 console.print("[dim]Verbose mode enabled[/dim]") 

340 if debug: 

341 console.print("[dim]Debug mode enabled[/dim]") 

342 

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() 

348 

349 

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) 

360 

361 

362if __name__ == "__main__": 

363 cli_main()