Coverage for src/alprina_cli/cli_interactive.py: 0%
248 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"""
2Interactive CLI Mode
4Provides a REPL-style interface for the Alprina CLI with:
5- Rich output formatting (colors, tables, syntax highlighting)
6- Progress indicators for long-running operations
7- Auto-completion and command history
8- Context-aware help
10Context Engineering:
11- Beautiful output doesn't inflate context
12- Progress updates keep user informed
13- Interactive mode for exploration
14"""
16import sys
17import asyncio
18from typing import Optional, List, Dict, Any
19from prompt_toolkit import PromptSession
20from prompt_toolkit.completion import WordCompleter
21from prompt_toolkit.history import FileHistory
22from prompt_toolkit.styles import Style
23from rich.console import Console
24from rich.table import Table
25from rich.panel import Panel
26from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TimeElapsedColumn
27from rich.syntax import Syntax
28from rich.markdown import Markdown
29from rich import print as rprint
30from rich.tree import Tree
31from loguru import logger
33from alprina_cli.auth_system import get_auth_service, get_authz_service, Role
34from alprina_cli.tools import ALL_TOOLS
37class AlprinaInteractiveCLI:
38 """
39 Interactive REPL for Alprina CLI.
41 Features:
42 - Command auto-completion
43 - Rich output formatting
44 - Progress indicators
45 - Command history
46 - Context-aware help
47 """
49 def __init__(self):
50 self.console = Console()
51 self.auth_service = get_auth_service()
52 self.authz_service = get_authz_service()
53 self.current_user = None
54 self.api_key = None
56 # Available commands
57 self.commands = {
58 "help": self.cmd_help,
59 "login": self.cmd_login,
60 "logout": self.cmd_logout,
61 "whoami": self.cmd_whoami,
62 "tools": self.cmd_tools,
63 "scan": self.cmd_scan,
64 "recon": self.cmd_recon,
65 "vuln-scan": self.cmd_vuln_scan,
66 "history": self.cmd_history,
67 "clear": self.cmd_clear,
68 "exit": self.cmd_exit,
69 "quit": self.cmd_exit,
70 }
72 # Setup prompt with auto-completion
73 self.completer = WordCompleter(list(self.commands.keys()), ignore_case=True)
75 # Setup custom style
76 self.style = Style.from_dict({
77 'prompt': '#00aa00 bold',
78 'command': '#00aaff bold',
79 })
81 # Command history
82 try:
83 self.history = FileHistory('.alprina_history')
84 except Exception:
85 self.history = None
87 self.session = PromptSession(
88 completer=self.completer,
89 style=self.style,
90 history=self.history
91 )
93 def show_banner(self):
94 """Display welcome banner"""
95 banner = """
96╔═══════════════════════════════════════════════════════════╗
97║ ║
98║ █████╗ ██╗ ██████╗ ██████╗ ██╗███╗ ██╗ █████╗ ║
99║ ██╔══██╗██║ ██╔══██╗██╔══██╗██║████╗ ██║██╔══██╗ ║
100║ ███████║██║ ██████╔╝██████╔╝██║██╔██╗ ██║███████║ ║
101║ ██╔══██║██║ ██╔═══╝ ██╔══██╗██║██║╚██╗██║██╔══██║ ║
102║ ██║ ██║███████╗██║ ██║ ██║██║██║ ╚████║██║ ██║ ║
103║ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ║
104║ ║
105║ Security Automation Platform - Interactive Mode ║
106║ Type 'help' to start ║
107║ ║
108╚═══════════════════════════════════════════════════════════╝
109 """
110 self.console.print(banner, style="bold cyan")
112 def get_prompt(self) -> str:
113 """Get the command prompt string"""
114 if self.current_user:
115 username = self.current_user.username
116 role = self.current_user.role.value
117 return f"[bold green]alprina[/bold green] [{role}@{username}]> "
118 return "[bold green]alprina[/bold green] [guest]> "
120 async def cmd_help(self, args: List[str]):
121 """Show help information"""
122 if args and args[0] in self.commands:
123 # Show help for specific command
124 cmd = args[0]
125 self.console.print(f"\n[bold cyan]Help for '{cmd}':[/bold cyan]\n")
126 # TODO: Add detailed help for each command
127 self.console.print(f"Command: {cmd}")
128 else:
129 # Show general help
130 table = Table(title="Available Commands", show_header=True, header_style="bold magenta")
131 table.add_column("Command", style="cyan", no_wrap=True)
132 table.add_column("Description")
134 table.add_row("help [command]", "Show this help or help for a specific command")
135 table.add_row("login", "Authenticate with API key")
136 table.add_row("logout", "Log out current user")
137 table.add_row("whoami", "Show current user information")
138 table.add_row("tools", "List available security tools")
139 table.add_row("scan <target>", "Perform security scan")
140 table.add_row("recon <target>", "Perform reconnaissance")
141 table.add_row("vuln-scan <target>", "Perform vulnerability scan")
142 table.add_row("history", "Show command history")
143 table.add_row("clear", "Clear screen")
144 table.add_row("exit/quit", "Exit interactive mode")
146 self.console.print(table)
148 async def cmd_login(self, args: List[str]):
149 """Authenticate user"""
150 if self.current_user:
151 self.console.print("[yellow]Already logged in. Use 'logout' first.[/yellow]")
152 return
154 if not args:
155 # Interactive login
156 from prompt_toolkit import prompt
157 api_key = prompt("API Key: ", is_password=True)
158 else:
159 api_key = args[0]
161 # Authenticate
162 with self.console.status("[bold green]Authenticating...", spinner="dots"):
163 user = self.auth_service.authenticate(api_key)
165 if user:
166 self.current_user = user
167 self.api_key = api_key
169 # Show success message
170 panel = Panel(
171 f"[bold green]✓[/bold green] Authenticated as [cyan]{user.username}[/cyan]\n"
172 f"Role: [yellow]{user.role.value}[/yellow]\n"
173 f"Email: {user.email}",
174 title="[bold green]Login Successful[/bold green]",
175 border_style="green"
176 )
177 self.console.print(panel)
178 else:
179 self.console.print("[bold red]✗ Authentication failed. Invalid API key.[/bold red]")
181 async def cmd_logout(self, args: List[str]):
182 """Log out current user"""
183 if not self.current_user:
184 self.console.print("[yellow]Not logged in.[/yellow]")
185 return
187 username = self.current_user.username
188 self.current_user = None
189 self.api_key = None
191 self.console.print(f"[green]Logged out {username}[/green]")
193 async def cmd_whoami(self, args: List[str]):
194 """Show current user information"""
195 if not self.current_user:
196 self.console.print("[yellow]Not logged in. Use 'login' to authenticate.[/yellow]")
197 return
199 user = self.current_user
201 # Get user permissions
202 permissions = self.authz_service.get_user_permissions(user)
204 # Create info panel
205 table = Table(show_header=False, box=None)
206 table.add_column("Field", style="cyan bold")
207 table.add_column("Value")
209 table.add_row("Username", user.username)
210 table.add_row("Email", user.email)
211 table.add_row("Role", user.role.value)
212 table.add_row("User ID", user.user_id)
213 table.add_row("Active", "✓ Yes" if user.is_active else "✗ No")
214 table.add_row("Permissions", f"{len(permissions)} permissions")
216 panel = Panel(table, title="[bold cyan]Current User[/bold cyan]", border_style="cyan")
217 self.console.print(panel)
219 # Show permissions
220 if permissions:
221 perm_tree = Tree("[bold cyan]Permissions[/bold cyan]")
222 for perm in permissions:
223 perm_tree.add(f"[green]✓[/green] {perm.value}")
224 self.console.print(perm_tree)
226 async def cmd_tools(self, args: List[str]):
227 """List available security tools"""
228 table = Table(title="Available Security Tools", show_header=True, header_style="bold magenta")
229 table.add_column("#", style="dim", width=4)
230 table.add_column("Tool Name", style="cyan")
231 table.add_column("Description")
232 table.add_column("Access", justify="center")
234 for i, tool in enumerate(ALL_TOOLS, 1):
235 # Check if user has access
236 if self.current_user:
237 has_access = self.authz_service.can_use_tool(self.current_user, tool.__class__.__name__)
238 access_indicator = "[green]✓[/green]" if has_access else "[red]✗[/red]"
239 else:
240 access_indicator = "[dim]?[/dim]"
242 table.add_row(
243 str(i),
244 tool.name,
245 tool.description,
246 access_indicator
247 )
249 self.console.print(table)
251 if not self.current_user:
252 self.console.print("\n[dim]Login to see your tool access permissions[/dim]")
254 async def cmd_scan(self, args: List[str]):
255 """Perform security scan"""
256 if not self.current_user:
257 self.console.print("[yellow]Please login first using 'login' command[/yellow]")
258 return
260 if not args:
261 self.console.print("[yellow]Usage: scan <target> [ports][/yellow]")
262 return
264 target = args[0]
265 ports = args[1] if len(args) > 1 else "80,443"
267 # Check authorization
268 if not self.authz_service.can_use_tool(self.current_user, "ScanTool"):
269 self.console.print("[bold red]✗ Permission denied. You don't have access to ScanTool.[/bold red]")
270 return
272 # Import and execute tool
273 from alprina_cli.tools.security.scan import ScanTool, ScanParams
275 tool = ScanTool()
276 params = ScanParams(target=target, scan_type="quick", ports=ports)
278 # Show progress
279 with Progress(
280 SpinnerColumn(),
281 TextColumn("[progress.description]{task.description}"),
282 BarColumn(),
283 TimeElapsedColumn(),
284 console=self.console
285 ) as progress:
286 task = progress.add_task(f"[cyan]Scanning {target}...", total=None)
288 # Execute scan
289 result = await tool(params)
291 progress.update(task, completed=True)
293 # Display results
294 if hasattr(result, 'content'):
295 self.display_scan_results(target, result.content)
296 else:
297 self.console.print(f"[red]Scan failed: {result.message}[/red]")
299 async def cmd_recon(self, args: List[str]):
300 """Perform reconnaissance"""
301 if not self.current_user:
302 self.console.print("[yellow]Please login first[/yellow]")
303 return
305 if not args:
306 self.console.print("[yellow]Usage: recon <target> [operation][/yellow]")
307 return
309 target = args[0]
310 operation = args[1] if len(args) > 1 else "whois"
312 # Import and execute
313 from alprina_cli.tools.security.recon import ReconTool, ReconParams
315 tool = ReconTool()
316 params = ReconParams(target=target, operation=operation, timeout=30)
318 with Progress(
319 SpinnerColumn(),
320 TextColumn("[progress.description]{task.description}"),
321 console=self.console
322 ) as progress:
323 task = progress.add_task(f"[cyan]Reconnaissance on {target}...", total=None)
324 result = await tool(params)
325 progress.update(task, completed=True)
327 # Display results
328 if hasattr(result, 'content'):
329 panel = Panel(
330 str(result.content),
331 title=f"[bold cyan]Recon Results: {target}[/bold cyan]",
332 border_style="cyan"
333 )
334 self.console.print(panel)
335 else:
336 self.console.print(f"[red]Recon failed: {result.message}[/red]")
338 async def cmd_vuln_scan(self, args: List[str]):
339 """Perform vulnerability scan"""
340 if not self.current_user:
341 self.console.print("[yellow]Please login first[/yellow]")
342 return
344 if not args:
345 self.console.print("[yellow]Usage: vuln-scan <target>[/yellow]")
346 return
348 target = args[0]
350 # Import and execute
351 from alprina_cli.tools.security.vuln_scan import VulnScanTool, VulnScanParams
353 tool = VulnScanTool()
354 params = VulnScanParams(target=target, scan_type="web", depth="basic")
356 with Progress(
357 SpinnerColumn(),
358 TextColumn("[progress.description]{task.description}"),
359 BarColumn(),
360 TimeElapsedColumn(),
361 console=self.console
362 ) as progress:
363 task = progress.add_task(f"[cyan]Vulnerability scanning {target}...", total=None)
364 result = await tool(params)
365 progress.update(task, completed=True)
367 # Display results
368 if hasattr(result, 'content'):
369 self.display_vuln_results(target, result.content)
370 else:
371 self.console.print(f"[red]Vulnerability scan failed: {result.message}[/red]")
373 def display_scan_results(self, target: str, results: Any):
374 """Display scan results in a formatted table"""
375 if isinstance(results, dict):
376 table = Table(title=f"Scan Results: {target}", show_header=True, header_style="bold magenta")
377 table.add_column("Property", style="cyan")
378 table.add_column("Value")
380 for key, value in results.items():
381 table.add_row(str(key), str(value))
383 self.console.print(table)
384 else:
385 panel = Panel(
386 str(results),
387 title=f"[bold cyan]Scan Results: {target}[/bold cyan]",
388 border_style="cyan"
389 )
390 self.console.print(panel)
392 def display_vuln_results(self, target: str, results: Any):
393 """Display vulnerability scan results"""
394 if isinstance(results, dict) and 'findings' in results:
395 findings = results.get('findings', [])
397 table = Table(title=f"Vulnerabilities Found: {target}", show_header=True, header_style="bold red")
398 table.add_column("#", style="dim", width=4)
399 table.add_column("Severity", style="bold")
400 table.add_column("Title")
401 table.add_column("Description")
403 for i, finding in enumerate(findings, 1):
404 severity = finding.get('severity', 'UNKNOWN')
405 severity_style = {
406 'CRITICAL': '[bold red]CRITICAL[/bold red]',
407 'HIGH': '[red]HIGH[/red]',
408 'MEDIUM': '[yellow]MEDIUM[/yellow]',
409 'LOW': '[blue]LOW[/blue]',
410 'INFO': '[dim]INFO[/dim]'
411 }.get(severity, severity)
413 table.add_row(
414 str(i),
415 severity_style,
416 finding.get('title', 'N/A'),
417 finding.get('description', 'N/A')[:50] + "..."
418 )
420 self.console.print(table)
421 else:
422 self.display_scan_results(target, results)
424 async def cmd_history(self, args: List[str]):
425 """Show command history"""
426 self.console.print("[dim]Command history feature coming soon...[/dim]")
428 async def cmd_clear(self, args: List[str]):
429 """Clear screen"""
430 self.console.clear()
432 async def cmd_exit(self, args: List[str]):
433 """Exit interactive mode"""
434 self.console.print("\n[cyan]Goodbye! Stay secure. 🔒[/cyan]\n")
435 sys.exit(0)
437 async def execute_command(self, command_line: str):
438 """Execute a command"""
439 if not command_line.strip():
440 return
442 parts = command_line.strip().split()
443 command = parts[0].lower()
444 args = parts[1:]
446 if command in self.commands:
447 try:
448 await self.commands[command](args)
449 except Exception as e:
450 self.console.print(f"[bold red]Error executing command: {e}[/bold red]")
451 logger.error(f"Command execution error: {e}", exc_info=True)
452 else:
453 self.console.print(f"[yellow]Unknown command: {command}. Type 'help' for available commands.[/yellow]")
455 async def run(self):
456 """Run the interactive CLI"""
457 self.show_banner()
459 # Show login prompt if not authenticated
460 if not self.current_user:
461 self.console.print("\n[dim]Tip: Use 'login' to authenticate or 'help' to see available commands[/dim]\n")
463 while True:
464 try:
465 # Get command from user
466 command_line = await asyncio.get_event_loop().run_in_executor(
467 None,
468 lambda: self.session.prompt(self.get_prompt(), default="")
469 )
471 # Execute command
472 await self.execute_command(command_line)
474 except KeyboardInterrupt:
475 self.console.print("\n[dim]Use 'exit' or 'quit' to exit[/dim]")
476 continue
477 except EOFError:
478 break
479 except Exception as e:
480 self.console.print(f"[bold red]Error: {e}[/bold red]")
481 logger.error(f"Interactive CLI error: {e}", exc_info=True)
484async def main():
485 """Main entry point for interactive CLI"""
486 cli = AlprinaInteractiveCLI()
487 await cli.run()
490if __name__ == "__main__":
491 asyncio.run(main())