@@ -159,7 +159,8 @@ def _filter_by_severity(
159159 # Filter components by severity
160160 original_count = len (result .components )
161161 result .components = [
162- comp for comp in result .components
162+ comp
163+ for comp in result .components
163164 if severity_order .get (comp .risk .severity .value , 0 ) >= min_level
164165 ]
165166
@@ -216,56 +217,85 @@ def _save_to_dashboard(result: ScanResult, scan_duration: float, quiet: bool = F
216217def scan (
217218 target : str = typer .Argument ("." , help = "Path to scan (file, directory, or git URL)" ),
218219 format : str = typer .Option (
219- "table" , "--format" , "-f" ,
220+ "table" ,
221+ "--format" ,
222+ "-f" ,
220223 help = "Output format: table, cyclonedx, json, html, markdown, sarif, spdx3, csv, junit" ,
221224 ),
222225 output : Optional [str ] = typer .Option (
223- None , "--output" , "-o" , help = "Output file path" ,
226+ None ,
227+ "--output" ,
228+ "-o" ,
229+ help = "Output file path" ,
224230 ),
225231 deep : bool = typer .Option (
226- False , "--deep" , help = "Enable deep AST-based Python analysis" ,
232+ False ,
233+ "--deep" ,
234+ help = "Enable deep AST-based Python analysis" ,
227235 ),
228236 severity : Optional [str ] = typer .Option (
229- None , "--severity" , "-s" ,
237+ None ,
238+ "--severity" ,
239+ "-s" ,
230240 help = "Minimum severity: critical, high, medium, low" ,
231241 ),
232242 no_color : bool = typer .Option (
233- False , "--no-color" , help = "Disable colored output" ,
243+ False ,
244+ "--no-color" ,
245+ help = "Disable colored output" ,
234246 ),
235247 n8n_url : Optional [str ] = typer .Option (
236- None , "--n8n-url" , help = "n8n instance URL for live scanning" ,
248+ None ,
249+ "--n8n-url" ,
250+ help = "n8n instance URL for live scanning" ,
237251 ),
238252 n8n_api_key : Optional [str ] = typer .Option (None , "--n8n-api-key" , help = "n8n API key" ),
239253 n8n_local : bool = typer .Option (False , "--n8n-local" , help = "Scan local ~/.n8n/ directory" ),
240254 quiet : bool = typer .Option (
241- False , "--quiet" , "-q" , help = "Suppress banner and progress (for CI)" ,
255+ False ,
256+ "--quiet" ,
257+ "-q" ,
258+ help = "Suppress banner and progress (for CI)" ,
242259 ),
243260 verbose : bool = typer .Option (
244- False , "--verbose" , "-v" , help = "Show scanner details, file counts, and timing" ,
261+ False ,
262+ "--verbose" ,
263+ "-v" ,
264+ help = "Show scanner details, file counts, and timing" ,
245265 ),
246266 debug : bool = typer .Option (
247- False , "--debug" , help = "Enable debug logging with full stack traces" ,
267+ False ,
268+ "--debug" ,
269+ help = "Enable debug logging with full stack traces" ,
248270 ),
249271 config : Optional [str ] = typer .Option (
250- None , "--config" , help = "Path to .ai-bom.yml config file" ,
272+ None ,
273+ "--config" ,
274+ help = "Path to .ai-bom.yml config file" ,
251275 ),
252276 save_dashboard : bool = typer .Option (
253- False , "--save-dashboard" , help = "Save scan results to the dashboard database" ,
277+ False ,
278+ "--save-dashboard" ,
279+ help = "Save scan results to the dashboard database" ,
254280 ),
255281 fail_on : Optional [str ] = typer .Option (
256- None , "--fail-on" ,
282+ None ,
283+ "--fail-on" ,
257284 help = "Exit code 1 if severity threshold met: critical, high, medium, low" ,
258285 ),
259286 policy : Optional [str ] = typer .Option (
260- None , "--policy" ,
287+ None ,
288+ "--policy" ,
261289 help = "Path to YAML policy file for CI/CD enforcement" ,
262290 ),
263291 workers : int = typer .Option (
264- 0 , "--workers" ,
292+ 0 ,
293+ "--workers" ,
265294 help = "Number of parallel scanner workers (0 = sequential)" ,
266295 ),
267296 cache : bool = typer .Option (
268- False , "--cache/--no-cache" ,
297+ False ,
298+ "--cache/--no-cache" ,
269299 help = "Enable incremental scanning cache" ,
270300 ),
271301) -> None :
@@ -276,6 +306,7 @@ def scan(
276306 # Load config file if specified or auto-discover
277307 if config or verbose :
278308 from ai_bom .config_file import load_config
309+
279310 cfg = load_config (Path (config ) if config else None )
280311 if cfg and verbose :
281312 console .print (f"[dim]Loaded config: { cfg } [/dim]" )
@@ -330,9 +361,7 @@ def scan(
330361 try :
331362 components = n8n_scanner .scan_from_api (client )
332363 except N8nAuthError :
333- console .print (
334- "[red]Authentication failed. Check your n8n API key.[/red]"
335- )
364+ console .print ("[red]Authentication failed. Check your n8n API key.[/red]" )
336365 raise typer .Exit (1 )
337366 except N8nConnectionError as exc :
338367 console .print (f"[red]Cannot connect to n8n: { exc } [/red]" )
@@ -358,19 +387,15 @@ def scan(
358387 if format == "table" and not quiet :
359388 console .print (f"[cyan]Scanning: { scan_path } [/cyan]" )
360389 if verbose :
361- active = ', ' .join (
362- s .name for s in scanners
363- if s .supports (scan_path )
364- )
365- console .print (
366- f"[dim]Scanners: { active } [/dim]"
367- )
390+ active = ", " .join (s .name for s in scanners if s .supports (scan_path ))
391+ console .print (f"[dim]Scanners: { active } [/dim]" )
368392 console .print ()
369393
370394 # Optionally initialise the incremental cache
371395 scan_cache = None
372396 if cache :
373397 from ai_bom .cache import ScanCache
398+
374399 scan_cache = ScanCache ()
375400
376401 if workers > 0 :
@@ -538,9 +563,7 @@ def scan(
538563 )
539564 else :
540565 console .print ()
541- console .print (
542- f"[red]POLICY VIOLATIONS ({ len (violations )} ):[/red]"
543- )
566+ console .print (f"[red]POLICY VIOLATIONS ({ len (violations )} ):[/red]" )
544567 for v in violations :
545568 console .print (f" [red]- { v } [/red]" )
546569 elif not quiet and format == "table" :
@@ -565,16 +588,16 @@ def scan(
565588 try :
566589 shutil .rmtree (scan_path )
567590 except Exception as e :
568- console .print (
569- f"[yellow]Warning: Failed to clean up temp directory: { e } [/yellow]"
570- )
591+ console .print (f"[yellow]Warning: Failed to clean up temp directory: { e } [/yellow]" )
571592
572593
573594@app .command (name = "scan-cloud" )
574595def scan_cloud (
575596 provider : str = typer .Argument (help = "Cloud provider: aws, gcp, azure" ),
576597 format : str = typer .Option (
577- "table" , "--format" , "-f" ,
598+ "table" ,
599+ "--format" ,
600+ "-f" ,
578601 help = "Output format: table, cyclonedx, json, html, markdown, sarif, spdx3, csv, junit" ,
579602 ),
580603 output : Optional [str ] = typer .Option (None , "--output" , "-o" , help = "Output file path" ),
@@ -590,8 +613,7 @@ def scan_cloud(
590613 scanner_name = provider_map .get (provider )
591614 if scanner_name is None :
592615 console .print (
593- f"[red]Unknown cloud provider: { provider } . "
594- f"Choose from: { ', ' .join (provider_map )} [/red]"
616+ f"[red]Unknown cloud provider: { provider } . Choose from: { ', ' .join (provider_map )} [/red]"
595617 )
596618 raise typer .Exit (1 )
597619
@@ -631,7 +653,8 @@ def scan_cloud(
631653 disable = (format != "table" or quiet ),
632654 ) as progress :
633655 task = progress .add_task (
634- f"Running { live_scanner .name } scanner..." , total = None ,
656+ f"Running { live_scanner .name } scanner..." ,
657+ total = None ,
635658 )
636659 components = live_scanner .scan (Path ("." ))
637660 for comp in components :
@@ -650,15 +673,11 @@ def scan_cloud(
650673 if not result .components :
651674 if format == "table" and not quiet :
652675 console .print ()
653- console .print (
654- f"[green]No AI/ML services found in { provider .upper ()} account.[/green]"
655- )
676+ console .print (f"[green]No AI/ML services found in { provider .upper ()} account.[/green]" )
656677 else :
657678 if format == "table" and not quiet :
658679 console .print ()
659- console .print (
660- f"[green]Found { len (result .components )} AI/ML service(s)[/green]"
661- )
680+ console .print (f"[green]Found { len (result .components )} AI/ML service(s)[/green]" )
662681 console .print ()
663682
664683 try :
@@ -739,7 +758,10 @@ def diff(
739758 scan1 : str = typer .Argument (help = "Path to first scan JSON file" ),
740759 scan2 : str = typer .Argument (help = "Path to second scan JSON file" ),
741760 format : str = typer .Option (
742- "table" , "--format" , "-f" , help = "Output format: table, json, markdown" ,
761+ "table" ,
762+ "--format" ,
763+ "-f" ,
764+ help = "Output format: table, json, markdown" ,
743765 ),
744766) -> None :
745767 """Compare two scan results and show differences."""
@@ -821,8 +843,7 @@ def serve(
821843 import uvicorn
822844 except ImportError :
823845 console .print (
824- "[red]Server dependencies not installed. "
825- "Install with: pip install ai-bom[server][/red]"
846+ "[red]Server dependencies not installed. Install with: pip install ai-bom[server][/red]"
826847 )
827848 raise typer .Exit (1 )
828849
@@ -848,10 +869,7 @@ def watch(
848869 from watchdog .events import FileSystemEventHandler
849870 from watchdog .observers import Observer
850871 except ImportError :
851- console .print (
852- "[red]watchdog is not installed. Install it with: "
853- "pip install watchdog[/red]"
854- )
872+ console .print ("[red]watchdog is not installed. Install it with: pip install watchdog[/red]" )
855873 raise typer .Exit (1 )
856874
857875 scan_path = Path (target ).resolve ()
@@ -897,6 +915,7 @@ def on_modified(self, event): # noqa: ANN001
897915 observer .start ()
898916 try :
899917 import time as _time
918+
900919 while True :
901920 _time .sleep (1 )
902921 except KeyboardInterrupt :
0 commit comments