codesecure-cli 1.0.0b10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: codesecure-cli
3
+ Version: 1.0.0b10
4
+ Summary: CodeSecure Command Line Interface
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: codesecure-core
9
+ Requires-Dist: click>=8.0.0
10
+ Requires-Dist: rich>=13.0.0
11
+
12
+ # CodeSecure CLI (`codesecure-cli`)
13
+
14
+ The `codesecure-cli` package is the user-facing terminal interface for the CodeSecure monorepo. It wraps the functionality of the core orchestration engine within an interactive, rich terminal application.
15
+
16
+ ## šŸŽÆ Module Purpose
17
+
18
+ This package focuses entirely on **Developer Experience (DX)** inside the terminal and CI/CD environments. It parses CLI arguments using `Click`, dynamically structures interactive menus with `Rich`, and connects seamlessly to the Python business logic embedded in `codesecure-core`.
19
+
20
+ *Note: The CLI is designed to be a "Thin Wrapper". Core scanning logic does not exist in this package.*
21
+
22
+ ## šŸ“¦ Local Installation
23
+
24
+ Installing the CLI relies on standard Python setups but requires the workspace dependency mapped to `codesecure-core`.
25
+
26
+ ```bash
27
+ cd packages/cli
28
+ python -m venv .venv
29
+ source .venv/bin/activate
30
+
31
+ # Recommended: Install via pip from the project root rather than locally
32
+ # This dynamically resolves workspace dependencies
33
+ cd ../../
34
+ pip install -e ./packages/cli
35
+ ```
36
+
37
+ ## šŸ”Œ Exported Commands & Features
38
+
39
+ The entry point script installs a global binary command: `codesecure`. Check the top-level commands below.
40
+
41
+ ```bash
42
+ # Validates the active scanners provided by Core
43
+ codesecure list-scanners
44
+
45
+ # Initializes Beta agreements
46
+ codesecure init
47
+
48
+ # Prompts setup for Gemini or Kiro CLI
49
+ codesecure login
50
+
51
+ # The main workhorse: Initiates an interactive async scan on a target path
52
+ codesecure scan ./my-project --provider kiro --output json,html
53
+ ```
54
+
55
+ ## šŸ› ļø Integration Example
56
+
57
+ Since the CLI is a consumption edge-node in the monorepo architecture, its integrations primarily concern reading from `core`. Below is a snippet of how the CLI bypasses MCP overhead to list scanners efficiently from the programmatic core.
58
+
59
+ ```python
60
+ import click
61
+ from codesecure.common.models import ScanMode
62
+
63
+ @click.command()
64
+ def list_scanners():
65
+ """List available security scanners from Core."""
66
+ from codesecure.scanners.engine import get_scanner_engine
67
+ engine = get_scanner_engine()
68
+
69
+ try:
70
+ local_scanners = engine.get_available_scanners(ScanMode.LOCAL)
71
+ print(f"Available Local Scanners: {', '.join(local_scanners)}")
72
+ except Exception as e:
73
+ print(f"Failed to list scanners: {e}")
74
+ ```
@@ -0,0 +1,63 @@
1
+ # CodeSecure CLI (`codesecure-cli`)
2
+
3
+ The `codesecure-cli` package is the user-facing terminal interface for the CodeSecure monorepo. It wraps the functionality of the core orchestration engine within an interactive, rich terminal application.
4
+
5
+ ## šŸŽÆ Module Purpose
6
+
7
+ This package focuses entirely on **Developer Experience (DX)** inside the terminal and CI/CD environments. It parses CLI arguments using `Click`, dynamically structures interactive menus with `Rich`, and connects seamlessly to the Python business logic embedded in `codesecure-core`.
8
+
9
+ *Note: The CLI is designed to be a "Thin Wrapper". Core scanning logic does not exist in this package.*
10
+
11
+ ## šŸ“¦ Local Installation
12
+
13
+ Installing the CLI relies on standard Python setups but requires the workspace dependency mapped to `codesecure-core`.
14
+
15
+ ```bash
16
+ cd packages/cli
17
+ python -m venv .venv
18
+ source .venv/bin/activate
19
+
20
+ # Recommended: Install via pip from the project root rather than locally
21
+ # This dynamically resolves workspace dependencies
22
+ cd ../../
23
+ pip install -e ./packages/cli
24
+ ```
25
+
26
+ ## šŸ”Œ Exported Commands & Features
27
+
28
+ The entry point script installs a global binary command: `codesecure`. Check the top-level commands below.
29
+
30
+ ```bash
31
+ # Validates the active scanners provided by Core
32
+ codesecure list-scanners
33
+
34
+ # Initializes Beta agreements
35
+ codesecure init
36
+
37
+ # Prompts setup for Gemini or Kiro CLI
38
+ codesecure login
39
+
40
+ # The main workhorse: Initiates an interactive async scan on a target path
41
+ codesecure scan ./my-project --provider kiro --output json,html
42
+ ```
43
+
44
+ ## šŸ› ļø Integration Example
45
+
46
+ Since the CLI is a consumption edge-node in the monorepo architecture, its integrations primarily concern reading from `core`. Below is a snippet of how the CLI bypasses MCP overhead to list scanners efficiently from the programmatic core.
47
+
48
+ ```python
49
+ import click
50
+ from codesecure.common.models import ScanMode
51
+
52
+ @click.command()
53
+ def list_scanners():
54
+ """List available security scanners from Core."""
55
+ from codesecure.scanners.engine import get_scanner_engine
56
+ engine = get_scanner_engine()
57
+
58
+ try:
59
+ local_scanners = engine.get_available_scanners(ScanMode.LOCAL)
60
+ print(f"Available Local Scanners: {', '.join(local_scanners)}")
61
+ except Exception as e:
62
+ print(f"Failed to list scanners: {e}")
63
+ ```
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "codesecure-cli"
7
+ version = "1.0.0b10"
8
+ description = "CodeSecure Command Line Interface"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ dependencies = [
13
+ "codesecure-core",
14
+ "click>=8.0.0",
15
+ "rich>=13.0.0",
16
+ ]
17
+
18
+ [project.scripts]
19
+ codesecure = "codesecure_cli.main:main_entry"
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
23
+ include = ["codesecure_cli*"]
24
+
25
+ [tool.uv.sources]
26
+ codesecure-core = { workspace = true }
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from setuptools import setup
2
+
3
+ # Delegate to pyproject.toml
4
+ if __name__ == "__main__":
5
+ setup()
@@ -0,0 +1,9 @@
1
+ """
2
+ CLI Module for CodeSecure.
3
+
4
+ Cross-platform command-line interface.
5
+ """
6
+
7
+ from .main import cli, main_entry
8
+
9
+ __all__ = ["cli", "main_entry"]
@@ -0,0 +1,61 @@
1
+
2
+ import click
3
+ from rich.console import Console
4
+ from rich.prompt import Prompt, Confirm
5
+ from rich.panel import Panel
6
+
7
+ from codesecure.telemetry.client import TelemetryClient
8
+
9
+ # Define local helpers to avoid circular import with main.py
10
+ console = Console()
11
+
12
+ def print_success(message: str):
13
+ """Print success message."""
14
+ console.print(f"[green]āœ” {message}[/green]")
15
+
16
+ def print_error(message: str):
17
+ """Print error message."""
18
+ console.print(f"[red]āœ– {message}[/red]")
19
+
20
+ def print_info(message: str):
21
+ """Print info message."""
22
+ console.print(f"[cyan]āžœ {message}[/cyan]")
23
+
24
+ @click.command()
25
+ def feedback():
26
+ """
27
+ Share feedback or report bugs to the CodeSecure team.
28
+
29
+ We value your input! Usage data and feedback help us improve CodeSecure.
30
+ """
31
+ console.print(Panel(
32
+ "[bold cyan]CodeSecure Feedback[/bold cyan]\n"
33
+ "Help us improve by sharing your thoughts, feature requests, or bug reports.",
34
+ border_style="cyan"
35
+ ))
36
+
37
+ # 1. Collect Feedback
38
+ user_feedback = Prompt.ask("\n[bold]What would you like to tell us?[/bold]")
39
+
40
+ if not user_feedback.strip():
41
+ print_error("Feedback cannot be empty.")
42
+ return
43
+
44
+ # 2. Optional Email
45
+ email = Prompt.ask("[bold]Email (optional, for follow-up)[/bold]", default="")
46
+
47
+ # 3. Confirm and Send
48
+ if Confirm.ask("\nSend this feedback to the CodeSecure team?", default=True, console=console):
49
+ # Instantiate client only when needed
50
+ client = TelemetryClient()
51
+
52
+ with console.status("[bold green]Sending feedback...", spinner="dots"):
53
+ # Mocking the call or actual call
54
+ success = client.send_feedback(improvement=user_feedback, email=email if email else None)
55
+
56
+ if success:
57
+ print_success("Thank you! Your feedback has been received.")
58
+ else:
59
+ print_error("Failed to send feedback. Please try again later.")
60
+ else:
61
+ print_info("Feedback cancelled.")
@@ -0,0 +1,786 @@
1
+ """
2
+ CodeSecure CLI - Cross-Platform Security Scanner.
3
+
4
+ Cross-platform CLI for Windows, Mac, and Linux.
5
+ PRD Section 5.1: IDE Integration - "Direct Python"
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import sys
11
+ import traceback
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path
14
+ from typing import List, Optional
15
+
16
+ import click
17
+
18
+ from codesecure import __version__
19
+
20
+ # Removed direct ScannerEngine imports to enforce Layered Isolation
21
+ from codesecure.common.logging import get_logger
22
+ from codesecure.common.models import ScanMode, JobStatus
23
+ from codesecure.common.cloud_provider import CloudProvider, PROVIDER_CAPABILITIES, ProviderAvailability, SUPPORTED_PROVIDERS
24
+ from codesecure.common.config import is_terms_accepted, accept_terms
25
+ from codesecure.reports.generator import get_report_generator, ReportMetadata
26
+ from codesecure_cli.feedback import feedback
27
+ from codesecure.telemetry.client import TelemetryClient
28
+
29
+ # MCP Imports
30
+ import json
31
+ from mcp import ClientSession, StdioServerParameters
32
+ from mcp.client.stdio import stdio_client
33
+
34
+ # Rich Imports
35
+ from rich.console import Console
36
+ from rich.progress import (
37
+ Progress,
38
+ SpinnerColumn,
39
+ TextColumn,
40
+ BarColumn,
41
+ TaskProgressColumn,
42
+ TimeElapsedColumn
43
+ )
44
+ from rich.live import Live
45
+ from rich.panel import Panel
46
+ from rich.prompt import Confirm
47
+
48
+ console = Console()
49
+
50
+ # Logger initialization
51
+ logger = get_logger(__name__)
52
+
53
+
54
+ def print_banner():
55
+ """Print CodeSecure banner."""
56
+ banner_content = "[bold deep_sky_blue1]šŸ”’ CodeSecure Scanner[/bold deep_sky_blue1]\n[dim white]Enterprise-Grade Security Analysis Platform[/dim white]"
57
+
58
+ panel = Panel(
59
+ banner_content,
60
+ subtitle="[bold gold1]BETA RELEASE[/bold gold1]",
61
+ subtitle_align="right",
62
+ border_style="bright_cyan",
63
+ padding=(1, 4),
64
+ expand=False
65
+ )
66
+ console.print(panel)
67
+
68
+
69
+ def print_success(message: str):
70
+ """Print success message."""
71
+ console.print(f"[bold spring_green3]āœ”[/bold spring_green3] [white]{message}[/white]")
72
+
73
+
74
+ def print_error(message: str):
75
+ """Print error message."""
76
+ console.print(f"[bold red]āœ–[/bold red] [orange_red1]{message}[/orange_red1]")
77
+
78
+
79
+ def print_info(message: str):
80
+ """Print info message."""
81
+ console.print(f"[bold sky_blue1]āžœ[/bold sky_blue1] [grey85]{message}[/grey85]")
82
+
83
+
84
+ def print_warning(message: str):
85
+ """Print warning message."""
86
+ console.print(f"[bold gold1]⚠ {message}[/bold gold1]")
87
+
88
+
89
+ def show_beta_terms():
90
+ """Show Beta terms and ask for acceptance."""
91
+ console.print("\n[bold white]Welcome to the CodeSecure CLI Beta![/bold white]")
92
+ console.print("─" * 50)
93
+ console.print("[bold yellow][!] This is pre-release software. Use at your own risk.[/bold yellow]")
94
+ console.print("[bold green][āœ“] Privacy First: This tool collects ZERO telemetry or usage data.[/bold green]")
95
+ console.print("\nBy continuing, you agree to our Beta Terms: [blue underline]https://codesecure.dev/beta-terms[/blue underline]")
96
+ console.print()
97
+
98
+ if Confirm.ask("Do you accept these terms?", default=False, console=console):
99
+ accept_terms()
100
+ print_success("Terms accepted. Welcome aboard!")
101
+ return True
102
+ else:
103
+ print_error("You must accept the terms to use CodeSecure CLI.")
104
+ return False
105
+
106
+
107
+ @click.group()
108
+ @click.version_option(version=__version__, prog_name="codesecure")
109
+ @click.pass_context
110
+ def cli(ctx):
111
+ """
112
+ CodeSecure - Enterprise Security Analysis CLI (Beta).
113
+
114
+ Cross-platform security scanner for Windows, Mac, and Linux.
115
+ This product is currently in Beta and undergoing active development.
116
+ """
117
+ # Skip terms check for help or version commands if possible
118
+ # In Click, help/version usually short-circuit before the group callback
119
+ # but we can check if a subcommand is about to be invoked.
120
+ if not is_terms_accepted():
121
+ # Only prompt if a subcommand is being invoked and it's not 'init'
122
+ if ctx.invoked_subcommand and ctx.invoked_subcommand != 'init':
123
+ if not show_beta_terms():
124
+ ctx.exit(0)
125
+
126
+
127
+ @cli.command()
128
+ def init():
129
+ """Initialize CodeSecure and accept Beta terms."""
130
+ print_banner()
131
+ if is_terms_accepted():
132
+ print_info("CodeSecure is already initialized and terms are accepted.")
133
+ else:
134
+ if show_beta_terms():
135
+ print_success("Initialization complete.")
136
+ else:
137
+ sys.exit(1)
138
+
139
+
140
+ @cli.command()
141
+ def login():
142
+ """
143
+ Login to AI providers and CodeSecure services.
144
+
145
+ This command helps you authenticate with the AI providers used for enrichment.
146
+ """
147
+ print_banner()
148
+ console.print("\n[bold sky_blue1]Authentication Guidance[/bold sky_blue1]")
149
+ console.print("─" * 30)
150
+
151
+ console.print("\n[bold bright_cyan]Google Gemini (Default)[/bold bright_cyan]")
152
+ console.print(" 1. Ensure Google Cloud SDK is installed.")
153
+ console.print(" 2. Run: [bold white]gcloud auth login[/bold white]")
154
+ console.print(" 3. Run: [bold white]gcloud auth application-default login[/bold white]")
155
+
156
+ console.print("\n[bold bright_cyan]Kiro CLI (AWS)[/bold bright_cyan]")
157
+ console.print(" 1. Ensure Kiro CLI is installed.")
158
+ console.print(" 2. Run: [bold white]kiro-cli login[/bold white]")
159
+
160
+ console.print("\n[bold bright_cyan]Azure AI (Azure OpenAI)[/bold bright_cyan]")
161
+ console.print(" 1. Ensure Azure CLI is installed.")
162
+ console.print(" 2. Run: [bold white]az login[/bold white]")
163
+ console.print(" 3. Set Env: [bold white]$env:AZURE_OPENAI_ENDPOINT=\"https://...\"[/bold white]")
164
+ console.print(" 4. Set Env: [bold white]$env:AZURE_OPENAI_DEPLOYMENT=\"gpt-4o\"[/bold white]")
165
+
166
+ console.print("\n[bold bright_cyan]OpenAI (GPT-4o / GPT-4o-mini)[/bold bright_cyan]")
167
+ console.print(" 1. Get an API key from [bold white]https://platform.openai.com/api-keys[/bold white]")
168
+ console.print(" 2. Set Env: [bold white]$env:OPENAI_API_KEY=\"sk-...\"[/bold white]")
169
+ console.print(" 3. (Optional) Set Env: [bold white]$env:OPENAI_MODEL=\"gpt-4o-mini\"[/bold white]")
170
+ console.print(" 4. (Optional) Set Env: [bold white]$env:OPENAI_BASE_URL=\"https://...\"[/bold white] for custom endpoints")
171
+
172
+
173
+
174
+
175
+ @cli.command(name="mcp-server", hidden=True)
176
+ def mcp_server_command():
177
+ """Internal command to start the MCP server."""
178
+ # Import here to avoid circular dependencies
179
+ from codesecure_mcp.scanner.server import main as server_main
180
+ server_main()
181
+
182
+
183
+ @cli.command()
184
+ @click.argument("path", type=click.Path(exists=True, path_type=Path))
185
+ @click.option(
186
+ "--format", "-f",
187
+ type=click.Choice(["html", "json", "sarif", "markdown", "all"]),
188
+ multiple=True,
189
+ default=["all"],
190
+ help="Output report format (default: all)"
191
+ )
192
+ @click.option(
193
+ "--output", "-o",
194
+ type=click.Path(path_type=Path),
195
+ default=None,
196
+ help="Output directory for reports (default: 'codesecure_scan_reports' in the scanned directory)"
197
+ )
198
+ @click.option(
199
+ "--scanners", "-s",
200
+ multiple=True,
201
+ help="Specific scanners to run (default: all available)"
202
+ )
203
+ @click.option(
204
+ "--mode", "-m",
205
+ type=click.Choice(["local", "container"]),
206
+ default="local",
207
+ help="Execution mode (default: local)"
208
+ )
209
+ @click.option(
210
+ "--timeout", "-t",
211
+ type=int,
212
+ default=480,
213
+ help="Timeout per scanner in seconds (default: 480)"
214
+ )
215
+ @click.option(
216
+ "--ai-provider",
217
+ type=click.Choice(["google", "openai", "anthropic", "aws", "azure", "none"], case_sensitive=False),
218
+ default=None,
219
+ help="AI provider override for explicit false positive filtering (e.g., 'openai')"
220
+ )
221
+ @click.option(
222
+ "--ai-api-key",
223
+ type=str,
224
+ default=None,
225
+ envvar='CODESECURE_AI_API_KEY',
226
+ help="API Key for the chosen AI provider (also via CODESECURE_AI_API_KEY)"
227
+ )
228
+ @click.option(
229
+ "--fail-on", "-F",
230
+ type=str,
231
+ default=None,
232
+ help="Comma-separated list of severities to fail the pipeline (e.g., 'critical,high')"
233
+ )
234
+ def scan(
235
+ path: Path,
236
+ format: tuple,
237
+ output: Optional[Path],
238
+ scanners: tuple,
239
+ mode: str,
240
+ timeout: int,
241
+ ai_provider: Optional[str] = None,
242
+ ai_api_key: Optional[str] = None,
243
+ fail_on: Optional[str] = None,
244
+ ):
245
+ """
246
+ Run security scan on target path.
247
+
248
+ PATH: Directory or file to scan.
249
+
250
+ Example:
251
+ codesecure scan ./my-project --format html
252
+ """
253
+ print_banner()
254
+
255
+ start_time = datetime.now()
256
+ console.print(f"\n[bold white]šŸš€ Scan Initiated:[/bold white] [bright_cyan]{start_time.strftime('%Y-%m-%d %H:%M:%S')}[/bright_cyan]")
257
+ console.print("─" * 50)
258
+
259
+ # Silence noise from report generator
260
+ logging.getLogger("codesecure.reports.generator").setLevel(logging.WARNING)
261
+
262
+ # Silence noise from MCP/Starlette/HTTPX
263
+ logging.getLogger("mcp").setLevel(logging.WARNING)
264
+ logging.getLogger("fastmcp").setLevel(logging.WARNING)
265
+ logging.getLogger("starlette").setLevel(logging.WARNING)
266
+ logging.getLogger("uvicorn").setLevel(logging.WARNING)
267
+ logging.getLogger("httpcore").setLevel(logging.WARNING)
268
+ logging.getLogger("httpx").setLevel(logging.WARNING)
269
+
270
+ # Set base output directory
271
+ if output:
272
+ base_output_dir = output
273
+ else:
274
+ # Default to 'codesecure_scan_reports' relative to the path being scanned
275
+ base_path = path if path.is_dir() else path.parent
276
+ base_output_dir = base_path / "codesecure_scan_reports"
277
+
278
+ base_output_dir.mkdir(parents=True, exist_ok=True)
279
+
280
+ # Parse mode
281
+ scan_mode = ScanMode.LOCAL if mode == "local" else ScanMode.CONTAINER
282
+
283
+ # Parse scanners
284
+ scanner_list = list(scanners) if scanners else None
285
+
286
+ # Interactive provider selection if not provided
287
+ if ai_provider is None:
288
+ console.print("\n[bold cyan]šŸ¤– AI Enrichment Options[/bold cyan]")
289
+ console.print("CodeSecure can use AI to provide deeper security insights, remediation guidance, and false positive reduction.")
290
+
291
+ if Confirm.ask("Would you like to enable AI-powered analysis?", default=False, console=console):
292
+ console.print("\n[bold white]Available AI Providers & Pre-requisites:[/bold white]")
293
+ console.print(" [bold blue][1] Google Gemini (Primary)[/bold blue]")
294
+ console.print(" • Install: 'npm install -g @google/gemini-cli'")
295
+ console.print(" • Authenticate: 'gcloud auth login'")
296
+
297
+ console.print(" [bold blue][2] Kiro CLI (AWS)[/bold blue]")
298
+ console.print(" • Install: Kiro CLI (https://kiro.ai/docs)")
299
+ console.print(" • Authenticate: 'kiro-cli login'")
300
+
301
+ console.print(" [bold blue][3] Azure AI (Enterprise)[/bold blue]")
302
+ console.print(" • Pre-req: Azure CLI + 'az login'")
303
+ console.print(" • Config: Set AZURE_OPENAI_ENDPOINT & AZURE_OPENAI_DEPLOYMENT")
304
+
305
+ choice = click.prompt("\nSelect a provider (or 0 to cancel)", type=click.Choice(["0", "1", "2", "3"]), default="1")
306
+
307
+ if choice == "0":
308
+ print_info("AI enrichment declined. Proceeding with None.")
309
+ cloud_provider = CloudProvider.NONE
310
+ else:
311
+ mapping = {"1": CloudProvider.GOOGLE, "2": CloudProvider.AWS, "3": CloudProvider.AZURE}
312
+ cloud_provider = mapping[choice]
313
+ console.print(f"\n[bold green]Selected: {cloud_provider.value.upper()}[/bold green]")
314
+ else:
315
+ print_info("Proceeding with AI features disabled (None).")
316
+ cloud_provider = CloudProvider.NONE
317
+ else:
318
+ cloud_provider = CloudProvider(ai_provider)
319
+
320
+ # Validate provider availability if not None
321
+ if cloud_provider != CloudProvider.NONE:
322
+ status = ProviderAvailability.check_provider(cloud_provider, check_quota=True)
323
+ if not status["available"]:
324
+ console.print(f"\n[bold yellow]⚠ AI Provider '{cloud_provider.value}' not ready: {status['error']}[/bold yellow]")
325
+ if Confirm.ask("Would you like to proceed with the scan without AI enrichment?", default=False, console=console):
326
+ print_info("Proceeding with AI capabilities disabled.")
327
+ cloud_provider = CloudProvider.NONE
328
+ ai_provider = None
329
+ else:
330
+ print_error("Scan aborted. Please configure the AI provider and try again.")
331
+ sys.exit(0)
332
+
333
+ # Parse formats
334
+ formats_to_gen = list(format)
335
+ if "all" in formats_to_gen:
336
+ formats_to_gen = ["html", "json", "sarif", "markdown"]
337
+
338
+ # Set scan output directory
339
+ scan_output_dir = base_output_dir / f"scan_{start_time.strftime('%Y%m%d_%H%M%S')}"
340
+ scan_output_dir.mkdir(parents=True, exist_ok=True)
341
+
342
+ # Provider capabilities for UI
343
+ caps = PROVIDER_CAPABILITIES.get(cloud_provider)
344
+
345
+ # UI display string for AI Provider override
346
+ effective_ai_display = ai_provider.upper() if ai_provider else (
347
+ cloud_provider.value.upper() if cloud_provider != CloudProvider.NONE else "None"
348
+ )
349
+
350
+ # Show configuration
351
+ conf_content = (
352
+ f"[bold sky_blue1]Target:[/bold sky_blue1] [blue]{path.resolve()}[/blue]\n"
353
+ f"[bold sky_blue1]Mode:[/bold sky_blue1] [white]{mode.capitalize()}[/white]\n"
354
+ f"[bold sky_blue1]Output:[/bold sky_blue1] [white]{', '.join([f.upper() for f in formats_to_gen])}[/white]\n"
355
+ f"[bold sky_blue1]Provider:[/bold sky_blue1] [bright_cyan]{effective_ai_display}[/bright_cyan]\n"
356
+ f"[dim]AI Review: {'āœ…' if caps.code_review_enabled or ai_provider else 'āŒ'} | FP Detection: {'āœ…' if caps.false_positive_detection or ai_provider else 'āŒ'}[/dim]"
357
+ )
358
+
359
+ console.print(Panel(conf_content, title="[bold white]Scan Configuration[/bold white]", border_style="bright_cyan", expand=False))
360
+ console.print()
361
+
362
+ # Run scan
363
+ try:
364
+ result = asyncio.run(_run_scan(
365
+ path,
366
+ scanner_list,
367
+ scan_mode,
368
+ timeout,
369
+ cloud_provider,
370
+ ai_provider_override=ai_provider,
371
+ api_key_override=ai_api_key
372
+ ))
373
+ except Exception as e:
374
+ print_error(f"Scan failed: {e}")
375
+ sys.exit(1)
376
+
377
+ # Evaluate Quality Gate
378
+ gate_failed = False
379
+ if fail_on:
380
+ thresholds = [s.strip().lower() for s in fail_on.split(",")]
381
+ for sev in thresholds:
382
+ if result.findings_by_severity.get(sev, 0) > 0:
383
+ gate_failed = True
384
+ break
385
+
386
+ end_time = datetime.now()
387
+ duration = end_time - start_time
388
+
389
+ console.print("─" * 50)
390
+ console.print("\n[bold white]šŸ“Š Scan Summary[/bold white]")
391
+ console.print("─" * 50)
392
+
393
+ # Show summary
394
+ if result.success:
395
+ files_suffix = f" across {result.scanned_files} files" if result.scanned_files > 0 else ""
396
+ if result.total_findings > 0:
397
+ print_success(f"Scan completed: {result.total_findings} findings identified{files_suffix}")
398
+ else:
399
+ print_success(f"Scan completed: No security issues found{files_suffix} šŸŽ‰")
400
+
401
+ if result.errors:
402
+ print_warning(f"Note: {len(result.errors)} tool errors occurred during execution")
403
+
404
+ # Show severity breakdown
405
+ severity_counts = result.findings_by_severity
406
+ if result.total_findings > 0:
407
+ console.print("\n[dim]Findings Breakdown:[/dim]")
408
+ for sev in ["critical", "high", "medium", "low"]:
409
+ count = severity_counts.get(sev, 0)
410
+ if count > 0:
411
+ color = {"critical": "bold red", "high": "orange_red1", "medium": "gold1", "low": "deep_sky_blue1"}[sev]
412
+ console.print(f" [bold {color}]• {sev.upper():<8}:[/bold {color}] [white]{count}[/white]")
413
+
414
+ # Show errors for partial success
415
+ if result.errors:
416
+ console.print("\n[bold gold1]āš ļø Scanner Diagnostics[/bold gold1]")
417
+ for error in result.errors:
418
+ print_error(f" {error}")
419
+
420
+ else:
421
+ print_error(f"Scan failed with {len(result.errors)} errors")
422
+ for error in result.errors:
423
+ print_error(f" {error}")
424
+
425
+ # Evidence Location
426
+ console.print(f"\n[bold white]šŸ“‚ Evidence Location[/bold white]")
427
+ console.print(f" [dim cyan]{scan_output_dir}[/dim cyan]")
428
+
429
+ # Generate reports
430
+ console.print(f"\n[bold white]šŸ“ Security Reports Generated[/bold white]")
431
+ report_files = []
432
+ for fmt in formats_to_gen:
433
+ try:
434
+ report_path = _generate_report(result, fmt, scan_output_dir, path)
435
+ report_files.append((fmt.upper(), report_path.name))
436
+ except Exception as e:
437
+ print_error(f" • {fmt.upper():<8} : Failed ({e})")
438
+
439
+ for fmt_name, filename in report_files:
440
+ console.print(f" • [bold sky_blue1]{fmt_name:<10}:[/bold sky_blue1] [white]{filename}[/white]")
441
+
442
+ console.print("─" * 50)
443
+ console.print(f"[bold white]šŸ Scan Finished:[/bold white] [bright_cyan]{end_time.strftime('%Y-%m-%d %H:%M:%S')}[/bright_cyan]")
444
+
445
+ # Calculate minutes and seconds
446
+ mins, secs = divmod(int(duration.total_seconds()), 60)
447
+ duration_str = f"{mins}m {secs}s" if mins > 0 else f"{secs}s"
448
+ console.print(f"[bold spring_green3]ā±ļø Total Duration:[/bold spring_green3] [white]{duration_str}[/white]\n")
449
+
450
+ console.print("[dim italic cyan]šŸ’” Have feedback? Run 'codesecure feedback' to help us improve![/dim italic cyan]\n")
451
+
452
+ # Exit code based on Quality Gate
453
+ if gate_failed:
454
+ print_error(f"Quality Gate Failed: Found findings matching --fail-on thresholds ({fail_on})")
455
+ sys.exit(1)
456
+
457
+
458
+ async def _run_scan(
459
+ path: Path,
460
+ scanners: Optional[List[str]],
461
+ mode: ScanMode,
462
+ timeout: int,
463
+ cloud_provider: CloudProvider,
464
+ ai_provider_override: Optional[str] = None,
465
+ api_key_override: Optional[str] = None
466
+ ):
467
+ """Run the scan asynchronously via Direct Python Core API."""
468
+ from codesecure.scanners.engine import get_scanner_engine
469
+ from codesecure.jobs.manager import get_job_manager
470
+ from codesecure.ai_providers.manager import get_ai_manager
471
+ from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
472
+
473
+ # Initialize the overriding AI Manager before the engine starts
474
+ get_ai_manager(
475
+ cloud_provider=cloud_provider,
476
+ ai_provider_override=ai_provider_override,
477
+ api_key_override=api_key_override
478
+ )
479
+
480
+ engine = get_scanner_engine()
481
+ job_manager = get_job_manager()
482
+
483
+ print_info("Initializing Hexagonal Core...")
484
+
485
+ job_id_box = {"id": None}
486
+
487
+ async def scan_task():
488
+ for _ in range(50):
489
+ if job_id_box["id"]:
490
+ break
491
+ await asyncio.sleep(0.01)
492
+
493
+ job_id = job_id_box["id"]
494
+
495
+ async def progress_cb(pct, msg):
496
+ if job_id:
497
+ await job_manager.update_job(job_id, pct, status=JobStatus.RUNNING)
498
+
499
+ return await engine.run_scan_with_progress(
500
+ path, job_id, scanners, mode, timeout=timeout,
501
+ progress_callback=progress_cb, cloud_provider=cloud_provider
502
+ )
503
+
504
+ scan_job_id = await job_manager.create_job("scan", scan_task())
505
+ job_id_box["id"] = scan_job_id
506
+
507
+ click.echo(click.style("āžœ ", fg="cyan") + "Scan Engine Started")
508
+
509
+ with Progress(
510
+ SpinnerColumn(spinner_name="dots", style="cyan"),
511
+ TextColumn("[bold blue]{task.description}"),
512
+ BarColumn(bar_width=40, style="blue", complete_style="green"),
513
+ TaskProgressColumn(),
514
+ "•",
515
+ TimeElapsedColumn(),
516
+ console=console
517
+ ) as progress_bar:
518
+ scan_task_bar = progress_bar.add_task("Initializing...", total=100)
519
+
520
+ while True:
521
+ status_data = await job_manager.get_status(scan_job_id)
522
+ if not status_data:
523
+ await asyncio.sleep(0.5)
524
+ continue
525
+
526
+ progress_val = status_data.get("progress", 0)
527
+ state = status_data.get("state", "pending").lower()
528
+
529
+ if progress_val < 5:
530
+ desc = f"Initializing scan engine..."
531
+ elif progress_val < 15:
532
+ desc = f"Starting security tools..."
533
+ elif progress_val < 75:
534
+ desc = f"Running scanners..."
535
+ elif progress_val < 80:
536
+ desc = f"Merging findings..."
537
+ elif progress_val < 90:
538
+ desc = f"Enriching with AI analysis..."
539
+ else:
540
+ desc = f"Finalizing results..."
541
+
542
+ progress_bar.update(scan_task_bar, completed=progress_val, description=desc)
543
+
544
+ if state == "completed":
545
+ progress_bar.update(scan_task_bar, completed=100, description="[bold green]Scan completed successfully[/bold green]")
546
+ break
547
+ elif state in ["failed", "cancelled", "timeout"]:
548
+ error = status_data.get("error", "Unknown error")
549
+ progress_bar.update(scan_task_bar, description=f"[bold red]Scan {state}[/bold red]")
550
+ raise Exception(f"Scan failed: {error}")
551
+
552
+ await asyncio.sleep(0.5)
553
+
554
+ result = await job_manager.get_result(scan_job_id)
555
+ if not result:
556
+ raise Exception("Scan completed but no result was found.")
557
+ return result
558
+
559
+
560
+ def _get_available_scanners(mode: ScanMode) -> List[str]:
561
+ """Get available scanners directly from the core engine."""
562
+ from codesecure.scanners.engine import get_scanner_engine
563
+ engine = get_scanner_engine()
564
+ return engine.get_available_scanners(mode)
565
+
566
+
567
+ def _generate_report(result, format: str, output_dir: Path, scan_target: Path) -> Path:
568
+ """Generate report and return path."""
569
+ generator = get_report_generator()
570
+
571
+ metadata = ReportMetadata(
572
+ title="CodeSecure Security Scan Report",
573
+ scan_target=str(scan_target.resolve()),
574
+ scan_duration=result.duration_seconds,
575
+ job_id=result.job_id,
576
+ )
577
+
578
+ return generator.generate(result, format, output_dir, metadata)
579
+
580
+
581
+ @cli.command()
582
+ @click.argument("path", type=click.Path(exists=True, path_type=Path))
583
+ @click.option(
584
+ "--format", "-f",
585
+ type=click.Choice(["html", "json", "sarif", "markdown", "all"]),
586
+ multiple=True,
587
+ default=["all"],
588
+ help="Output report format (default: all)"
589
+ )
590
+ @click.option(
591
+ "--output", "-o",
592
+ type=click.Path(path_type=Path),
593
+ default=None,
594
+ help="Output directory for reports (default: 'codesecure_reports' in the scanned directory)"
595
+ )
596
+ def report(path: Path, format: tuple, output: Optional[Path]):
597
+ """
598
+ Generate report from previous scan results.
599
+
600
+ PATH: Path to a previously generated CodeSecure JSON report file.
601
+
602
+ Example:
603
+ codesecure report ./codesecure_scan_reports/scan_1/report.json --format html
604
+ """
605
+ print_banner()
606
+
607
+ start_time = datetime.now()
608
+ console.print(f"\n[bold white]šŸ“ Report Generation Initiated:[/bold white] [bright_cyan]{start_time.strftime('%Y-%m-%d %H:%M:%S')}[/bright_cyan]")
609
+ console.print("─" * 50)
610
+
611
+ if path.is_dir() or not path.name.endswith(".json"):
612
+ print_error("PATH must be a .json file generated by a previous CodeSecure scan.")
613
+ sys.exit(1)
614
+
615
+ formats_to_gen = list(format)
616
+ if "all" in formats_to_gen:
617
+ formats_to_gen = ["html", "json", "sarif", "markdown"]
618
+
619
+ if output:
620
+ base_output_dir = output
621
+ else:
622
+ base_output_dir = path.parent
623
+
624
+ base_output_dir.mkdir(parents=True, exist_ok=True)
625
+
626
+ # Show configuration
627
+ conf_content = (
628
+ f"[bold sky_blue1]Input JSON:[/bold sky_blue1] [blue]{path.resolve()}[/blue]\n"
629
+ f"[bold sky_blue1]Output Dir:[/bold sky_blue1] [blue]{base_output_dir.resolve()}[/blue]\n"
630
+ f"[bold sky_blue1]Formats:[/bold sky_blue1] [white]{', '.join([f.upper() for f in formats_to_gen])}[/white]\n"
631
+ )
632
+ console.print(Panel(conf_content, title="[bold white]Report Configuration[/bold white]", border_style="bright_cyan", expand=False))
633
+ console.print()
634
+
635
+ try:
636
+ from codesecure.common.models import ScanResult
637
+
638
+ with open(path, "r", encoding="utf-8") as f:
639
+ data = json.load(f)
640
+
641
+ print_info(f"Loaded JSON payload from '{path.name}'...")
642
+
643
+ # Validating the JSON string data against the Pydantic ScanResult model
644
+ result = ScanResult.model_validate(data)
645
+ print_success(f"Successfully unpacked {result.total_findings} findings.")
646
+
647
+ except Exception as e:
648
+ print_error(f"Failed to load or parse JSON scan result: {e}")
649
+ sys.exit(1)
650
+
651
+ console.print(f"\n[bold white]šŸ“ Generated Reports[/bold white]")
652
+ report_files = []
653
+
654
+ # Re-use the _generate_report logic passing the re-created result obj
655
+ for fmt in formats_to_gen:
656
+ try:
657
+ report_path = _generate_report(result, fmt, base_output_dir, path)
658
+ report_files.append((fmt.upper(), report_path.name))
659
+ except Exception as e:
660
+ print_error(f" • {fmt.upper():<8} : Failed ({e})")
661
+
662
+ for fmt_name, filename in report_files:
663
+ console.print(f" • [bold sky_blue1]{fmt_name:<10}:[/bold sky_blue1] [white]{filename}[/white]")
664
+
665
+ console.print("─" * 50)
666
+ console.print(f"[bold white]šŸ Finished:[/bold white] [bright_cyan]{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}[/bright_cyan]\n")
667
+
668
+
669
+ @cli.command("threat-model", hidden=True)
670
+ @click.argument("path", type=click.Path(exists=True, path_type=Path))
671
+ @click.option(
672
+ "--provider", "-p",
673
+ type=click.Choice(["google", "aws", "azure"]),
674
+ default="google",
675
+ help="AI provider for analysis"
676
+ )
677
+ @click.option(
678
+ "--output", "-o",
679
+ type=click.Path(path_type=Path),
680
+ default=None,
681
+ help="Output directory"
682
+ )
683
+ def threat_model(path: Path, provider: str, output: Optional[Path]):
684
+ """
685
+ Generate STRIDE threat model.
686
+
687
+ PATH: Repository or architecture document path.
688
+ """
689
+ print_info("Threat model generation using ThreatModel module")
690
+ print_warning("Threat model CLI integration pending TM-001 implementation")
691
+
692
+
693
+ @cli.command(hidden=True)
694
+ @click.argument("path", type=click.Path(exists=True, path_type=Path))
695
+ @click.option(
696
+ "--type", "-t",
697
+ type=click.Choice(["terraform", "cdk", "cloudformation"]),
698
+ default="terraform",
699
+ help="IaC type"
700
+ )
701
+ def validate(path: Path, type: str):
702
+ """
703
+ Detect infrastructure drift.
704
+
705
+ PATH: IaC project path.
706
+ """
707
+ print_info("Drift detection using SecurityValidate module")
708
+ print_warning("Validate CLI integration pending SV-001 implementation")
709
+
710
+
711
+ @cli.command(hidden=True)
712
+ @click.argument("path", type=click.Path(exists=True, path_type=Path))
713
+ def matrix(path: Path):
714
+ """
715
+ Generate DSR Security Matrix via core framework.
716
+
717
+ PATH: Project path.
718
+ """
719
+ print_info("Security Matrix generation using NeoLifter module")
720
+ print_warning("Matrix CLI integration pending SM-001 implementation")
721
+
722
+
723
+ @cli.command()
724
+ def list_scanners():
725
+ """List available security scanners from Core."""
726
+ print_banner()
727
+
728
+ try:
729
+ local_scanners = _get_available_scanners(ScanMode.LOCAL)
730
+ all_scanners = _get_available_scanners(ScanMode.CONTAINER)
731
+ except Exception as e:
732
+ print_error(f"Failed to list scanners: {e}")
733
+ return
734
+
735
+ click.echo(click.style("\nšŸ“‹ Available Scanners:\n", bold=True))
736
+
737
+ click.echo(click.style("Local + Container Mode:", fg="cyan", bold=True))
738
+ for name in local_scanners:
739
+ click.echo(f" • {name}")
740
+
741
+ click.echo(click.style("\nContainer Mode Only:", fg="yellow", bold=True))
742
+ container_only = set(all_scanners) - set(local_scanners)
743
+ for name in container_only:
744
+ click.echo(f" • {name}")
745
+
746
+ # Platform specific notes
747
+ if sys.platform == "win32":
748
+ click.echo()
749
+ print_warning("Note for Windows users:")
750
+ click.echo(" • 'semgrep' is not supported natively on Windows.")
751
+ click.echo(" • Use WSL or Docker mode for full coverage.")
752
+
753
+
754
+ def main_entry():
755
+ """Main entry point."""
756
+ # Register external commands
757
+ cli.add_command(feedback)
758
+
759
+ try:
760
+ cli()
761
+ except Exception as e:
762
+ # Let Click handle its own exceptions (Abort, UsageError, etc.)
763
+ if isinstance(e, click.ClickException) or isinstance(e, SystemExit):
764
+ raise
765
+
766
+ # Handle unexpected exceptions
767
+ console.print(f"\n[bold red]An unexpected error occurred: {e}[/bold red]")
768
+
769
+ # Ask for permission to send error report
770
+ if Confirm.ask("Would you like to send an automated error report to help us fix this?", default=True, console=console):
771
+ client = TelemetryClient()
772
+ with console.status("[bold green]Sending error report...", spinner="dots"):
773
+ tb = traceback.format_exc()
774
+ success = client.send_error_report(str(e), tb)
775
+
776
+ if success:
777
+ console.print("[green]Error report sent. Thank you![/green]")
778
+ else:
779
+ console.print("[yellow]Failed to send error report, but thank you for trying![/yellow]")
780
+
781
+ # Exit with error code
782
+ sys.exit(1)
783
+
784
+
785
+ if __name__ == "__main__":
786
+ main_entry()
@@ -0,0 +1,95 @@
1
+ """
2
+ Setup utility to automatically configure CodeSecure PATH.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import platform
8
+ import subprocess
9
+ from pathlib import Path
10
+
11
+
12
+ def get_scripts_dir():
13
+ """Detect where Python scripts are installed."""
14
+ if platform.system() == "Windows":
15
+ # Usually AppData\Roaming\Python\Python31x\Scripts for --user installs
16
+ # or the Python installation directory for global installs
17
+ import site
18
+ user_base = site.getuserbase()
19
+ if user_base:
20
+ py_version = f"Python{sys.version_info.major}{sys.version_info.minor}"
21
+ path = Path(user_base) / py_version / "Scripts"
22
+ if path.exists():
23
+ return path
24
+
25
+ # Fallback to sys.executable's directory
26
+ return Path(sys.executable).parent / "Scripts"
27
+ else:
28
+ # Linux/Mac
29
+ return Path.home() / ".local" / "bin"
30
+
31
+
32
+ def setup_windows(scripts_path: Path):
33
+ """Add path to Windows User Environment Variables."""
34
+ try:
35
+ scripts_dir = str(scripts_path.resolve())
36
+ # Use PowerShell to safely add to User PATH
37
+ cmd = f'[Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "User") + ";{scripts_dir}", "User")'
38
+ subprocess.run(["powershell", "-Command", cmd], check=True)
39
+ return True
40
+ except Exception as e:
41
+ print(f"Error updating Windows PATH: {e}")
42
+ return False
43
+
44
+
45
+ def setup_unix(scripts_path: Path):
46
+ """Add path to shell profile for Linux/Mac."""
47
+ try:
48
+ scripts_dir = str(scripts_path.resolve())
49
+ shell = os.environ.get("SHELL", "").split("/")[-1]
50
+
51
+ if "zsh" in shell:
52
+ profile = Path.home() / ".zshrc"
53
+ elif "bash" in shell:
54
+ profile = Path.home() / ".bashrc"
55
+ else:
56
+ profile = Path.home() / ".profile"
57
+
58
+ line = f'\nexport PATH="$PATH:{scripts_dir}"\n'
59
+
60
+ with open(profile, "a") as f:
61
+ f.write(line)
62
+ return True, profile
63
+ except Exception as e:
64
+ print(f"Error updating Unix PATH: {e}")
65
+ return False, None
66
+
67
+
68
+ def main():
69
+ print("šŸ”§ CodeSecure CLI Setup")
70
+ scripts_dir = get_scripts_dir()
71
+
72
+ if not scripts_dir.exists():
73
+ print(f"āŒ Could not find scripts directory at {scripts_dir}")
74
+ sys.exit(1)
75
+
76
+ print(f"šŸ“ Detected scripts directory: {scripts_dir}")
77
+
78
+ confirm = input("Do you want to add this directory to your PATH? (y/n): ")
79
+ if confirm.lower() != 'y':
80
+ print("Setup cancelled.")
81
+ return
82
+
83
+ if platform.system() == "Windows":
84
+ if setup_windows(scripts_dir):
85
+ print("\nāœ… PATH updated successfully!")
86
+ print("šŸ‘‰ Please RESTART your terminal/PowerShell for changes to take effect.")
87
+ else:
88
+ success, profile = setup_unix(scripts_dir)
89
+ if success:
90
+ print(f"\nāœ… PATH added to {profile}")
91
+ print(f"šŸ‘‰ Run 'source {profile}' or restart your terminal.")
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: codesecure-cli
3
+ Version: 1.0.0b10
4
+ Summary: CodeSecure Command Line Interface
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: codesecure-core
9
+ Requires-Dist: click>=8.0.0
10
+ Requires-Dist: rich>=13.0.0
11
+
12
+ # CodeSecure CLI (`codesecure-cli`)
13
+
14
+ The `codesecure-cli` package is the user-facing terminal interface for the CodeSecure monorepo. It wraps the functionality of the core orchestration engine within an interactive, rich terminal application.
15
+
16
+ ## šŸŽÆ Module Purpose
17
+
18
+ This package focuses entirely on **Developer Experience (DX)** inside the terminal and CI/CD environments. It parses CLI arguments using `Click`, dynamically structures interactive menus with `Rich`, and connects seamlessly to the Python business logic embedded in `codesecure-core`.
19
+
20
+ *Note: The CLI is designed to be a "Thin Wrapper". Core scanning logic does not exist in this package.*
21
+
22
+ ## šŸ“¦ Local Installation
23
+
24
+ Installing the CLI relies on standard Python setups but requires the workspace dependency mapped to `codesecure-core`.
25
+
26
+ ```bash
27
+ cd packages/cli
28
+ python -m venv .venv
29
+ source .venv/bin/activate
30
+
31
+ # Recommended: Install via pip from the project root rather than locally
32
+ # This dynamically resolves workspace dependencies
33
+ cd ../../
34
+ pip install -e ./packages/cli
35
+ ```
36
+
37
+ ## šŸ”Œ Exported Commands & Features
38
+
39
+ The entry point script installs a global binary command: `codesecure`. Check the top-level commands below.
40
+
41
+ ```bash
42
+ # Validates the active scanners provided by Core
43
+ codesecure list-scanners
44
+
45
+ # Initializes Beta agreements
46
+ codesecure init
47
+
48
+ # Prompts setup for Gemini or Kiro CLI
49
+ codesecure login
50
+
51
+ # The main workhorse: Initiates an interactive async scan on a target path
52
+ codesecure scan ./my-project --provider kiro --output json,html
53
+ ```
54
+
55
+ ## šŸ› ļø Integration Example
56
+
57
+ Since the CLI is a consumption edge-node in the monorepo architecture, its integrations primarily concern reading from `core`. Below is a snippet of how the CLI bypasses MCP overhead to list scanners efficiently from the programmatic core.
58
+
59
+ ```python
60
+ import click
61
+ from codesecure.common.models import ScanMode
62
+
63
+ @click.command()
64
+ def list_scanners():
65
+ """List available security scanners from Core."""
66
+ from codesecure.scanners.engine import get_scanner_engine
67
+ engine = get_scanner_engine()
68
+
69
+ try:
70
+ local_scanners = engine.get_available_scanners(ScanMode.LOCAL)
71
+ print(f"Available Local Scanners: {', '.join(local_scanners)}")
72
+ except Exception as e:
73
+ print(f"Failed to list scanners: {e}")
74
+ ```
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ src/codesecure_cli/__init__.py
5
+ src/codesecure_cli/feedback.py
6
+ src/codesecure_cli/main.py
7
+ src/codesecure_cli/setup.py
8
+ src/codesecure_cli.egg-info/PKG-INFO
9
+ src/codesecure_cli.egg-info/SOURCES.txt
10
+ src/codesecure_cli.egg-info/dependency_links.txt
11
+ src/codesecure_cli.egg-info/entry_points.txt
12
+ src/codesecure_cli.egg-info/requires.txt
13
+ src/codesecure_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codesecure = codesecure_cli.main:main_entry
@@ -0,0 +1,3 @@
1
+ codesecure-core
2
+ click>=8.0.0
3
+ rich>=13.0.0