mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-07-10 00:18:34 +02:00
Merge pull request #11 from FuzzingLabs/fix/remove-erroneous-cli-example
fix: removed erroneous example
This commit is contained in:
@@ -12,22 +12,24 @@ Main CLI application with improved command structure.
|
|||||||
#
|
#
|
||||||
# Additional attribution and requirements are provided in the NOTICE file.
|
# Additional attribution and requirements are provided in the NOTICE file.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.traceback import install
|
from rich.traceback import install
|
||||||
from typing import Optional, List
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .commands import (
|
from .commands import (
|
||||||
init,
|
|
||||||
workflows,
|
|
||||||
workflow_exec,
|
|
||||||
findings,
|
|
||||||
monitor,
|
|
||||||
config as config_cmd,
|
|
||||||
ai,
|
ai,
|
||||||
|
findings,
|
||||||
ingest,
|
ingest,
|
||||||
|
init,
|
||||||
|
monitor,
|
||||||
|
workflow_exec,
|
||||||
|
workflows,
|
||||||
|
)
|
||||||
|
from .commands import (
|
||||||
|
config as config_cmd,
|
||||||
)
|
)
|
||||||
from .constants import DEFAULT_VOLUME_MODE
|
from .constants import DEFAULT_VOLUME_MODE
|
||||||
from .fuzzy import enhanced_command_not_found_handler
|
from .fuzzy import enhanced_command_not_found_handler
|
||||||
@@ -78,25 +80,30 @@ finding_app = typer.Typer(
|
|||||||
|
|
||||||
# === Top-level commands ===
|
# === Top-level commands ===
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def init(
|
def init(
|
||||||
name: Optional[str] = typer.Option(
|
name: Optional[str] = typer.Option(
|
||||||
None, "--name", "-n",
|
None, "--name", "-n", help="Project name (defaults to current directory name)"
|
||||||
help="Project name (defaults to current directory name)"
|
|
||||||
),
|
),
|
||||||
api_url: Optional[str] = typer.Option(
|
api_url: Optional[str] = typer.Option(
|
||||||
None, "--api-url", "-u",
|
None,
|
||||||
help="FuzzForge API URL (defaults to http://localhost:8000)"
|
"--api-url",
|
||||||
|
"-u",
|
||||||
|
help="FuzzForge API URL (defaults to http://localhost:8000)",
|
||||||
),
|
),
|
||||||
force: bool = typer.Option(
|
force: bool = typer.Option(
|
||||||
False, "--force", "-f",
|
False,
|
||||||
help="Force initialization even if project already exists"
|
"--force",
|
||||||
)
|
"-f",
|
||||||
|
help="Force initialization even if project already exists",
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
📁 Initialize a new FuzzForge project
|
📁 Initialize a new FuzzForge project
|
||||||
"""
|
"""
|
||||||
from .commands.init import project
|
from .commands.init import project
|
||||||
|
|
||||||
project(name=name, api_url=api_url, force=force)
|
project(name=name, api_url=api_url, force=force)
|
||||||
|
|
||||||
|
|
||||||
@@ -106,18 +113,18 @@ def status():
|
|||||||
📊 Show project and latest execution status
|
📊 Show project and latest execution status
|
||||||
"""
|
"""
|
||||||
from .commands.status import show_status
|
from .commands.status import show_status
|
||||||
|
|
||||||
show_status()
|
show_status()
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def config(
|
def config(
|
||||||
key: Optional[str] = typer.Argument(None, help="Configuration key"),
|
key: Optional[str] = typer.Argument(None, help="Configuration key"),
|
||||||
value: Optional[str] = typer.Argument(None, help="Configuration value to set")
|
value: Optional[str] = typer.Argument(None, help="Configuration value to set"),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
⚙️ Manage configuration (show all, get, or set values)
|
⚙️ Manage configuration (show all, get, or set values)
|
||||||
"""
|
"""
|
||||||
from .commands import config as config_cmd
|
|
||||||
|
|
||||||
if key is None:
|
if key is None:
|
||||||
# No arguments: show all config
|
# No arguments: show all config
|
||||||
@@ -133,13 +140,11 @@ def config(
|
|||||||
@app.command()
|
@app.command()
|
||||||
def clean(
|
def clean(
|
||||||
days: int = typer.Option(
|
days: int = typer.Option(
|
||||||
90, "--days", "-d",
|
90, "--days", "-d", help="Remove data older than this many days"
|
||||||
help="Remove data older than this many days"
|
|
||||||
),
|
),
|
||||||
dry_run: bool = typer.Option(
|
dry_run: bool = typer.Option(
|
||||||
False, "--dry-run",
|
False, "--dry-run", help="Show what would be deleted without actually deleting"
|
||||||
help="Show what would be deleted without actually deleting"
|
),
|
||||||
)
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
🧹 Clean old execution data and findings
|
🧹 Clean old execution data and findings
|
||||||
@@ -155,7 +160,9 @@ def clean(
|
|||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
console.print(f"🔍 [bold]Dry run:[/bold] Would clean data older than {days} days")
|
console.print(
|
||||||
|
f"🔍 [bold]Dry run:[/bold] Would clean data older than {days} days"
|
||||||
|
)
|
||||||
|
|
||||||
deleted = db.cleanup_old_runs(keep_days=days)
|
deleted = db.cleanup_old_runs(keep_days=days)
|
||||||
|
|
||||||
@@ -177,35 +184,41 @@ workflow_app.command("retry")(workflow_exec.retry_workflow)
|
|||||||
workflow_app.command("info")(workflows.workflow_info)
|
workflow_app.command("info")(workflows.workflow_info)
|
||||||
workflow_app.command("params")(workflows.workflow_parameters)
|
workflow_app.command("params")(workflows.workflow_parameters)
|
||||||
|
|
||||||
|
|
||||||
@workflow_app.command("run")
|
@workflow_app.command("run")
|
||||||
def run_workflow(
|
def run_workflow(
|
||||||
workflow: str = typer.Argument(help="Workflow name"),
|
workflow: str = typer.Argument(help="Workflow name"),
|
||||||
target: str = typer.Argument(help="Target path"),
|
target: str = typer.Argument(help="Target path"),
|
||||||
params: List[str] = typer.Argument(default=None, help="Parameters as key=value pairs"),
|
params: List[str] = typer.Argument(
|
||||||
|
default=None, help="Parameters as key=value pairs"
|
||||||
|
),
|
||||||
param_file: Optional[str] = typer.Option(
|
param_file: Optional[str] = typer.Option(
|
||||||
None, "--param-file", "-f",
|
None, "--param-file", "-f", help="JSON file containing workflow parameters"
|
||||||
help="JSON file containing workflow parameters"
|
|
||||||
),
|
),
|
||||||
volume_mode: str = typer.Option(
|
volume_mode: str = typer.Option(
|
||||||
DEFAULT_VOLUME_MODE, "--volume-mode", "-v",
|
DEFAULT_VOLUME_MODE,
|
||||||
help="Volume mount mode: ro (read-only) or rw (read-write)"
|
"--volume-mode",
|
||||||
|
"-v",
|
||||||
|
help="Volume mount mode: ro (read-only) or rw (read-write)",
|
||||||
),
|
),
|
||||||
timeout: Optional[int] = typer.Option(
|
timeout: Optional[int] = typer.Option(
|
||||||
None, "--timeout", "-t",
|
None, "--timeout", "-t", help="Execution timeout in seconds"
|
||||||
help="Execution timeout in seconds"
|
|
||||||
),
|
),
|
||||||
interactive: bool = typer.Option(
|
interactive: bool = typer.Option(
|
||||||
True, "--interactive/--no-interactive", "-i/-n",
|
True,
|
||||||
help="Interactive parameter input for missing required parameters"
|
"--interactive/--no-interactive",
|
||||||
|
"-i/-n",
|
||||||
|
help="Interactive parameter input for missing required parameters",
|
||||||
),
|
),
|
||||||
wait: bool = typer.Option(
|
wait: bool = typer.Option(
|
||||||
False, "--wait", "-w",
|
False, "--wait", "-w", help="Wait for execution to complete"
|
||||||
help="Wait for execution to complete"
|
|
||||||
),
|
),
|
||||||
live: bool = typer.Option(
|
live: bool = typer.Option(
|
||||||
False, "--live", "-l",
|
False,
|
||||||
help="Start live monitoring after execution (useful for fuzzing workflows)"
|
"--live",
|
||||||
)
|
"-l",
|
||||||
|
help="Start live monitoring after execution (useful for fuzzing workflows)",
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
🚀 Execute a security testing workflow
|
🚀 Execute a security testing workflow
|
||||||
@@ -221,9 +234,10 @@ def run_workflow(
|
|||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
interactive=interactive,
|
interactive=interactive,
|
||||||
wait=wait,
|
wait=wait,
|
||||||
live=live
|
live=live,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@workflow_app.callback()
|
@workflow_app.callback()
|
||||||
def workflow_main():
|
def workflow_main():
|
||||||
"""
|
"""
|
||||||
@@ -239,17 +253,18 @@ def workflow_main():
|
|||||||
|
|
||||||
# === Finding commands (singular) ===
|
# === Finding commands (singular) ===
|
||||||
|
|
||||||
|
|
||||||
@finding_app.command("export")
|
@finding_app.command("export")
|
||||||
def export_finding(
|
def export_finding(
|
||||||
execution_id: Optional[str] = typer.Argument(None, help="Execution ID (defaults to latest)"),
|
execution_id: Optional[str] = typer.Argument(
|
||||||
|
None, help="Execution ID (defaults to latest)"
|
||||||
|
),
|
||||||
format: str = typer.Option(
|
format: str = typer.Option(
|
||||||
"sarif", "--format", "-f",
|
"sarif", "--format", "-f", help="Export format: sarif, json, csv"
|
||||||
help="Export format: sarif, json, csv"
|
|
||||||
),
|
),
|
||||||
output: Optional[str] = typer.Option(
|
output: Optional[str] = typer.Option(
|
||||||
None, "--output", "-o",
|
None, "--output", "-o", help="Output file (defaults to stdout)"
|
||||||
help="Output file (defaults to stdout)"
|
),
|
||||||
)
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
📤 Export findings to file
|
📤 Export findings to file
|
||||||
@@ -270,7 +285,9 @@ def export_finding(
|
|||||||
execution_id = recent_runs[0].run_id
|
execution_id = recent_runs[0].run_id
|
||||||
console.print(f"🔍 Using most recent execution: {execution_id}")
|
console.print(f"🔍 Using most recent execution: {execution_id}")
|
||||||
else:
|
else:
|
||||||
console.print("⚠️ No findings found in project database", style="yellow")
|
console.print(
|
||||||
|
"⚠️ No findings found in project database", style="yellow"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
console.print("❌ No project database found", style="red")
|
console.print("❌ No project database found", style="red")
|
||||||
@@ -283,14 +300,16 @@ def export_finding(
|
|||||||
|
|
||||||
@finding_app.command("analyze")
|
@finding_app.command("analyze")
|
||||||
def analyze_finding(
|
def analyze_finding(
|
||||||
finding_id: Optional[str] = typer.Argument(None, help="Finding ID to analyze")
|
finding_id: Optional[str] = typer.Argument(None, help="Finding ID to analyze"),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
🤖 AI analysis of a finding
|
🤖 AI analysis of a finding
|
||||||
"""
|
"""
|
||||||
from .commands.ai import analyze_finding as ai_analyze
|
from .commands.ai import analyze_finding as ai_analyze
|
||||||
|
|
||||||
ai_analyze(finding_id)
|
ai_analyze(finding_id)
|
||||||
|
|
||||||
|
|
||||||
@finding_app.callback(invoke_without_command=True)
|
@finding_app.callback(invoke_without_command=True)
|
||||||
def finding_main(
|
def finding_main(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
@@ -309,7 +328,7 @@ def finding_main(
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Get remaining arguments for direct viewing
|
# Get remaining arguments for direct viewing
|
||||||
args = ctx.args if hasattr(ctx, 'args') else []
|
args = ctx.args if hasattr(ctx, "args") else []
|
||||||
finding_id = args[0] if args else None
|
finding_id = args[0] if args else None
|
||||||
|
|
||||||
# Direct viewing: fuzzforge finding [id]
|
# Direct viewing: fuzzforge finding [id]
|
||||||
@@ -329,7 +348,9 @@ def finding_main(
|
|||||||
finding_id = recent_runs[0].run_id
|
finding_id = recent_runs[0].run_id
|
||||||
console.print(f"🔍 Using most recent execution: {finding_id}")
|
console.print(f"🔍 Using most recent execution: {finding_id}")
|
||||||
else:
|
else:
|
||||||
console.print("⚠️ No findings found in project database", style="yellow")
|
console.print(
|
||||||
|
"⚠️ No findings found in project database", style="yellow"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
console.print("❌ No project database found", style="red")
|
console.print("❌ No project database found", style="red")
|
||||||
@@ -355,6 +376,7 @@ app.add_typer(monitor.app, name="monitor", help="📊 Real-time monitoring")
|
|||||||
app.add_typer(ai.app, name="ai", help="🤖 AI integration features")
|
app.add_typer(ai.app, name="ai", help="🤖 AI integration features")
|
||||||
app.add_typer(ingest.app, name="ingest", help="🧠 Ingest knowledge into AI")
|
app.add_typer(ingest.app, name="ingest", help="🧠 Ingest knowledge into AI")
|
||||||
|
|
||||||
|
|
||||||
# Help and utility commands
|
# Help and utility commands
|
||||||
@app.command()
|
@app.command()
|
||||||
def examples():
|
def examples():
|
||||||
@@ -372,7 +394,6 @@ def examples():
|
|||||||
[bold]Execute Workflows:[/bold]
|
[bold]Execute Workflows:[/bold]
|
||||||
ff workflow afl-fuzzing ./target # Run fuzzing on target
|
ff workflow afl-fuzzing ./target # Run fuzzing on target
|
||||||
ff workflow afl-fuzzing . --live # Run with live monitoring
|
ff workflow afl-fuzzing . --live # Run with live monitoring
|
||||||
ff workflow scan-c ./src timeout=300 threads=4 # With parameters
|
|
||||||
|
|
||||||
[bold]Monitor Execution:[/bold]
|
[bold]Monitor Execution:[/bold]
|
||||||
ff status # Check latest execution
|
ff status # Check latest execution
|
||||||
@@ -399,16 +420,16 @@ def version():
|
|||||||
📦 Show version information
|
📦 Show version information
|
||||||
"""
|
"""
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
console.print(f"FuzzForge CLI v{__version__}")
|
console.print(f"FuzzForge CLI v{__version__}")
|
||||||
console.print(f"Short command: ff")
|
console.print("Short command: ff")
|
||||||
|
|
||||||
|
|
||||||
@app.callback()
|
@app.callback()
|
||||||
def main_callback(
|
def main_callback(
|
||||||
ctx: typer.Context,
|
ctx: typer.Context,
|
||||||
version: Optional[bool] = typer.Option(
|
version: Optional[bool] = typer.Option(
|
||||||
None, "--version", "-v",
|
None, "--version", "-v", help="Show version information"
|
||||||
help="Show version information"
|
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -422,6 +443,7 @@ def main_callback(
|
|||||||
"""
|
"""
|
||||||
if version:
|
if version:
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
console.print(f"FuzzForge CLI v{__version__}")
|
console.print(f"FuzzForge CLI v{__version__}")
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
@@ -432,12 +454,11 @@ def main():
|
|||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
|
||||||
# Handle finding command with pattern recognition
|
# Handle finding command with pattern recognition
|
||||||
if len(args) >= 2 and args[0] == 'finding':
|
if len(args) >= 2 and args[0] == "finding":
|
||||||
finding_subcommands = ['export', 'analyze']
|
finding_subcommands = ["export", "analyze"]
|
||||||
# Skip custom dispatching if help flags are present
|
# Skip custom dispatching if help flags are present
|
||||||
if not any(arg in ['--help', '-h', '--version', '-v'] for arg in args):
|
if not any(arg in ["--help", "-h", "--version", "-v"] for arg in args):
|
||||||
if args[1] not in finding_subcommands:
|
if args[1] not in finding_subcommands:
|
||||||
# Direct finding display: ff finding <id>
|
# Direct finding display: ff finding <id>
|
||||||
from .commands.findings import get_findings
|
from .commands.findings import get_findings
|
||||||
@@ -457,18 +478,26 @@ def main():
|
|||||||
app()
|
app()
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
# Enhanced error handling for command not found
|
# Enhanced error handling for command not found
|
||||||
if hasattr(e, 'code') and e.code != 0 and len(sys.argv) > 1:
|
if hasattr(e, "code") and e.code != 0 and len(sys.argv) > 1:
|
||||||
command_parts = sys.argv[1:]
|
command_parts = sys.argv[1:]
|
||||||
clean_parts = [part for part in command_parts if not part.startswith('-')]
|
clean_parts = [part for part in command_parts if not part.startswith("-")]
|
||||||
|
|
||||||
if clean_parts:
|
if clean_parts:
|
||||||
main_cmd = clean_parts[0]
|
main_cmd = clean_parts[0]
|
||||||
valid_commands = [
|
valid_commands = [
|
||||||
'init', 'status', 'config', 'clean',
|
"init",
|
||||||
'workflows', 'workflow',
|
"status",
|
||||||
'findings', 'finding',
|
"config",
|
||||||
'monitor', 'ai', 'ingest',
|
"clean",
|
||||||
'examples', 'version'
|
"workflows",
|
||||||
|
"workflow",
|
||||||
|
"findings",
|
||||||
|
"finding",
|
||||||
|
"monitor",
|
||||||
|
"ai",
|
||||||
|
"ingest",
|
||||||
|
"examples",
|
||||||
|
"version",
|
||||||
]
|
]
|
||||||
|
|
||||||
if main_cmd not in valid_commands:
|
if main_cmd not in valid_commands:
|
||||||
|
|||||||
Reference in New Issue
Block a user