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

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 

13from dotenv import load_dotenv 

14 

15# Load environment variables from .env file 

16load_dotenv() 

17 

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 

28 

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) 

36 

37# Auth commands 

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

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

40 

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. 

49 

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) 

61 

62@auth_app.command("logout") 

63def logout(): 

64 """ 

65 👋 Logout from Alprina. 

66 """ 

67 logout_command() 

68 

69@auth_app.command("status") 

70def auth_status(): 

71 """ 

72 ℹ️ Check authentication status. 

73 """ 

74 status_command() 

75 

76 

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. 

103 

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 

114 

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 ) 

125 

126 

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) 

136 

137 

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. 

147 

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) 

155 

156 

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) 

167 

168 

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. 

179 

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) 

187 

188 

189# Policy commands 

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

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

192 

193@policy_app.command("init") 

194def policy_init(): 

195 """ 

196 📋 Initialize a new policy configuration file. 

197 """ 

198 policy_init_command() 

199 

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) 

208 

209 

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

222 

223 

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) 

234 

235 

236# Billing commands 

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

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

239 

240@billing_app.command("status") 

241def billing_status(): 

242 """ 

243 💳 Check billing status and usage. 

244 """ 

245 billing_status_command() 

246 

247 

248# Quickstart command (tutorial for new users) 

249@app.command("quickstart") 

250def quickstart(): 

251 """ 

252 🎓 Interactive tutorial for first-time users. 

253  

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

255 scan with guided explanations in plain English. 

256  

257 Examples: 

258 alprina quickstart # Start the guided tutorial 

259 """ 

260 from .quickstart import quickstart_command 

261 quickstart_command() 

262 

263 

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. 

273 

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) 

282 

283 

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

292 

293 

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

301 

302 

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. 

313 

314 An intelligent cybersecurity command-line tool for developers. 

315  

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

325 

326 if verbose: 

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

328 if debug: 

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

330 

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

336 

337 

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) 

348 

349 

350if __name__ == "__main__": 

351 cli_main()