Add source code files
This commit is contained in:
202
src/codeguard/cli/__init__.py
Normal file
202
src/codeguard/cli/__init__.py
Normal file
@@ -0,0 +1,202 @@
|
||||
"""CLI module for CodeGuard."""
|
||||
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from codeguard.core.scanner import CodeScanner
|
||||
from codeguard.git.hooks import HookManager
|
||||
from codeguard.utils.config import ConfigLoader
|
||||
from codeguard.utils.output import OutputFormatter
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.option(
|
||||
"--ollama-url",
|
||||
default="http://localhost:11434",
|
||||
help="Ollama server URL",
|
||||
envvar="CODEGUARD_OLLAMA_URL",
|
||||
)
|
||||
@click.option(
|
||||
"--model",
|
||||
default="codellama",
|
||||
help="Ollama model to use",
|
||||
envvar="CODEGUARD_MODEL",
|
||||
)
|
||||
@click.option(
|
||||
"--timeout",
|
||||
default=120,
|
||||
help="Request timeout in seconds",
|
||||
envvar="CODEGUARD_TIMEOUT",
|
||||
type=int,
|
||||
)
|
||||
@click.option(
|
||||
"--config",
|
||||
default="codeguard.yaml",
|
||||
help="Path to config file",
|
||||
envvar="CODEGUARD_CONFIG",
|
||||
)
|
||||
@click.pass_context
|
||||
def main(
|
||||
ctx: click.Context,
|
||||
ollama_url: str,
|
||||
model: str,
|
||||
timeout: int,
|
||||
config: str,
|
||||
) -> None:
|
||||
"""CodeGuard: Local LLM-based code security analysis."""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["ollama_url"] = ollama_url
|
||||
ctx.obj["model"] = model
|
||||
ctx.obj["timeout"] = timeout
|
||||
ctx.obj["config"] = config
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"--path",
|
||||
default=".",
|
||||
help="Path to scan",
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
type=click.Choice(["text", "json"]),
|
||||
default="text",
|
||||
help="Output format",
|
||||
)
|
||||
@click.option(
|
||||
"--fail-level",
|
||||
type=click.Choice(["critical", "high", "medium", "low", "none"]),
|
||||
default="none",
|
||||
help="Exit with error if findings at or above this level",
|
||||
)
|
||||
@click.option(
|
||||
"--include",
|
||||
multiple=True,
|
||||
help="File patterns to include",
|
||||
)
|
||||
@click.option(
|
||||
"--exclude",
|
||||
multiple=True,
|
||||
help="File patterns to exclude",
|
||||
)
|
||||
@click.pass_context
|
||||
def scan(
|
||||
ctx: click.Context,
|
||||
path: str,
|
||||
output: str,
|
||||
fail_level: str,
|
||||
include: tuple,
|
||||
exclude: tuple,
|
||||
) -> None:
|
||||
"""Scan code for security vulnerabilities."""
|
||||
config = ConfigLoader.load(ctx.obj["config"])
|
||||
scanner = CodeScanner(
|
||||
ollama_url=ctx.obj["ollama_url"],
|
||||
model=ctx.obj["model"],
|
||||
timeout=ctx.obj["timeout"],
|
||||
config=config,
|
||||
)
|
||||
findings = scanner.scan(path, include=list(include), exclude=list(exclude))
|
||||
OutputFormatter.print(findings, output_format=output)
|
||||
if fail_level != "none":
|
||||
severity_levels = ["low", "medium", "high", "critical"]
|
||||
fail_index = severity_levels.index(fail_level)
|
||||
for finding in findings:
|
||||
if finding.severity.value in severity_levels[: fail_index + 1]:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"--path",
|
||||
default=".",
|
||||
help="Path to git repository",
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
||||
)
|
||||
@click.option(
|
||||
"--force",
|
||||
is_flag=True,
|
||||
help="Force reinstall existing hook",
|
||||
)
|
||||
def install_hook(path: str, force: bool) -> None:
|
||||
"""Install git pre-commit hook."""
|
||||
manager = HookManager(path)
|
||||
manager.install(force=force)
|
||||
click.echo("Pre-commit hook installed successfully")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument(
|
||||
"paths",
|
||||
nargs=-1,
|
||||
type=click.Path(exists=True),
|
||||
)
|
||||
@click.option(
|
||||
"--output",
|
||||
type=click.Choice(["text", "json"]),
|
||||
default="text",
|
||||
help="Output format",
|
||||
)
|
||||
@click.pass_context
|
||||
def check(
|
||||
ctx: click.Context,
|
||||
paths: tuple[str, ...],
|
||||
output: str,
|
||||
) -> None:
|
||||
"""Check specific files for security issues."""
|
||||
if not paths:
|
||||
click.echo("No files specified", err=True)
|
||||
sys.exit(1)
|
||||
config = ConfigLoader.load(ctx.obj["config"])
|
||||
scanner = CodeScanner(
|
||||
ollama_url=ctx.obj["ollama_url"],
|
||||
model=ctx.obj["model"],
|
||||
timeout=ctx.obj["timeout"],
|
||||
config=config,
|
||||
)
|
||||
findings = scanner.check_files(list(paths))
|
||||
OutputFormatter.print(findings, output_format=output)
|
||||
|
||||
|
||||
@main.command()
|
||||
def version() -> None:
|
||||
"""Show version."""
|
||||
from codeguard import __version__
|
||||
click.echo(f"CodeGuard-CLI v{__version__}")
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option(
|
||||
"--ollama-url",
|
||||
default=None,
|
||||
help="Ollama server URL",
|
||||
)
|
||||
@click.option(
|
||||
"--model",
|
||||
default=None,
|
||||
help="Ollama model to use",
|
||||
)
|
||||
@click.option(
|
||||
"--timeout",
|
||||
default=None,
|
||||
help="Request timeout in seconds",
|
||||
type=int,
|
||||
)
|
||||
def status(ollama_url: Optional[str], model: Optional[str], timeout: Optional[str]) -> None:
|
||||
"""Check CodeGuard and Ollama status."""
|
||||
from codeguard.llm.client import OllamaClient
|
||||
|
||||
url = ollama_url or "http://localhost:11434"
|
||||
client = OllamaClient(url)
|
||||
|
||||
click.echo("Checking Ollama connection...")
|
||||
if client.health_check():
|
||||
click.secho("Ollama is running", fg="green")
|
||||
else:
|
||||
click.secho("Ollama is not accessible", fg="red")
|
||||
return
|
||||
|
||||
click.echo(f"Available models: {', '.join(client.list_models())}")
|
||||
Reference in New Issue
Block a user