mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-02-12 21:52:47 +00:00
Merge branch 'master' into dev for v0.7.0 release
Resolved conflicts: - Kept monitor.py (dev version - required for live monitoring) - Kept workflow_exec.py (dev version - includes worker management, --live, --fail-on, --export-sarif) - Kept main.py (dev version - includes new command structure) All conflicts resolved in favor of dev branch features for 0.7.0 release.
This commit is contained in:
70
.github/workflows/ci-python.yml
vendored
Normal file
70
.github/workflows/ci-python.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
name: Python CI
|
||||||
|
|
||||||
|
# This is a dumb Ci to ensure that the python client and backend builds correctly
|
||||||
|
# It could be optimized to run faster, building, testing and linting only changed code
|
||||||
|
# but for now it is good enough. It runs on every push and PR to any branch.
|
||||||
|
# It also runs on demand.
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "ai/**"
|
||||||
|
- "backend/**"
|
||||||
|
- "cli/**"
|
||||||
|
- "sdk/**"
|
||||||
|
- "src/**"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "ai/**"
|
||||||
|
- "backend/**"
|
||||||
|
- "cli/**"
|
||||||
|
- "sdk/**"
|
||||||
|
- "src/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
name: ci
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Setup uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
run: uv python install
|
||||||
|
|
||||||
|
# Validate no obvious issues
|
||||||
|
# Quick hack because CLI returns non-zero exit code when no args are provided
|
||||||
|
- name: Run base command
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
uv run ff
|
||||||
|
if [ $? -ne 2 ]; then
|
||||||
|
echo "Expected exit code 2 from 'uv run ff', got $?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build fuzzforge_ai package
|
||||||
|
run: uv build
|
||||||
|
|
||||||
|
- name: Build ai package
|
||||||
|
working-directory: ai
|
||||||
|
run: uv build
|
||||||
|
|
||||||
|
- name: Build cli package
|
||||||
|
working-directory: cli
|
||||||
|
run: uv build
|
||||||
|
|
||||||
|
- name: Build sdk package
|
||||||
|
working-directory: sdk
|
||||||
|
run: uv build
|
||||||
|
|
||||||
|
- name: Build backend package
|
||||||
|
working-directory: backend
|
||||||
|
run: uv build
|
||||||
5
.github/workflows/docs-deploy.yml
vendored
5
.github/workflows/docs-deploy.yml
vendored
@@ -1,10 +1,13 @@
|
|||||||
name: Deploy Docusaurus to GitHub Pages
|
name: Deploy Docusaurus to GitHub Pages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
workflow_dispatch:
|
paths:
|
||||||
|
- "docs/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
9
.github/workflows/docs-test-deploy.yml
vendored
9
.github/workflows/docs-test-deploy.yml
vendored
@@ -1,9 +1,14 @@
|
|||||||
name: Docusaurus test deployment
|
name: Docusaurus test deployment
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "docs/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
paths:
|
||||||
- master
|
- "docs/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-deploy:
|
test-deploy:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<p align="center"><strong>AI-powered workflow automation and AI Agents for AppSec, Fuzzing & Offensive Security</strong></p>
|
<p align="center"><strong>AI-powered workflow automation and AI Agents for AppSec, Fuzzing & Offensive Security</strong></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://discord.com/invite/acqv9FVG"><img src="https://img.shields.io/discord/1420767905255133267?logo=discord&label=Discord" alt="Discord"></a>
|
<a href="https://discord.gg/8XEX33UUwZ/"><img src="https://img.shields.io/discord/1420767905255133267?logo=discord&label=Discord" alt="Discord"></a>
|
||||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-BSL%20%2B%20Apache-orange" alt="License: BSL + Apache"></a>
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-BSL%20%2B%20Apache-orange" alt="License: BSL + Apache"></a>
|
||||||
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.11%2B-blue" alt="Python 3.11+"/></a>
|
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.11%2B-blue" alt="Python 3.11+"/></a>
|
||||||
<a href="https://fuzzforge.ai"><img src="https://img.shields.io/badge/Website-fuzzforge.ai-blue" alt="Website"/></a>
|
<a href="https://fuzzforge.ai"><img src="https://img.shields.io/badge/Website-fuzzforge.ai-blue" alt="Website"/></a>
|
||||||
@@ -165,7 +165,7 @@ _AI agents automatically analyzing code and providing security insights_
|
|||||||
|
|
||||||
- 🌐 [Website](https://fuzzforge.ai)
|
- 🌐 [Website](https://fuzzforge.ai)
|
||||||
- 📖 [Documentation](https://docs.fuzzforge.ai)
|
- 📖 [Documentation](https://docs.fuzzforge.ai)
|
||||||
- 💬 [Community Discord](https://discord.com/invite/acqv9FVG)
|
- 💬 [Community Discord](https://discord.gg/8XEX33UUwZ)
|
||||||
- 🎓 [FuzzingLabs Academy](https://academy.fuzzinglabs.com/?coupon=GITHUB_FUZZFORGE)
|
- 🎓 [FuzzingLabs Academy](https://academy.fuzzinglabs.com/?coupon=GITHUB_FUZZFORGE)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -194,7 +194,7 @@ Planned features and improvements:
|
|||||||
- ☁️ Multi-tenant SaaS platform with team collaboration
|
- ☁️ Multi-tenant SaaS platform with team collaboration
|
||||||
- 📊 Advanced reporting & analytics
|
- 📊 Advanced reporting & analytics
|
||||||
|
|
||||||
👉 Follow updates in the [GitHub issues](../../issues) and [Discord](https://discord.com/invite/acqv9FVG).
|
👉 Follow updates in the [GitHub issues](../../issues) and [Discord](https://discord.gg/8XEX33UUwZ)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -80,8 +80,6 @@ fuzzforge workflows info security_assessment
|
|||||||
# Submit a workflow for analysis
|
# Submit a workflow for analysis
|
||||||
fuzzforge workflow security_assessment /path/to/your/code
|
fuzzforge workflow security_assessment /path/to/your/code
|
||||||
|
|
||||||
# Monitor progress in real-time
|
|
||||||
fuzzforge monitor live <execution-id>
|
|
||||||
|
|
||||||
# View findings when complete
|
# View findings when complete
|
||||||
fuzzforge finding <execution-id>
|
fuzzforge finding <execution-id>
|
||||||
@@ -222,7 +220,6 @@ $ ff workflow security_assessment ./my-project
|
|||||||
- `--timeout, -t` - Execution timeout in seconds
|
- `--timeout, -t` - Execution timeout in seconds
|
||||||
- `--interactive/--no-interactive, -i/-n` - Interactive parameter input
|
- `--interactive/--no-interactive, -i/-n` - Interactive parameter input
|
||||||
- `--wait, -w` - Wait for execution to complete
|
- `--wait, -w` - Wait for execution to complete
|
||||||
- `--live, -l` - Show live monitoring during execution
|
|
||||||
|
|
||||||
**Worker Lifecycle Options (v0.7.0):**
|
**Worker Lifecycle Options (v0.7.0):**
|
||||||
- `--auto-start/--no-auto-start` - Auto-start required worker (default: from config)
|
- `--auto-start/--no-auto-start` - Auto-start required worker (default: from config)
|
||||||
@@ -320,39 +317,6 @@ fuzzforge finding export abc123def456 --format csv --output report.csv
|
|||||||
fuzzforge finding export --format html --output report.html
|
fuzzforge finding export --format html --output report.html
|
||||||
```
|
```
|
||||||
|
|
||||||
### Real-time Monitoring
|
|
||||||
|
|
||||||
#### `fuzzforge monitor stats <execution-id>`
|
|
||||||
Show current fuzzing statistics.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Show stats once
|
|
||||||
fuzzforge monitor stats abc123def456 --once
|
|
||||||
|
|
||||||
# Live updating stats (default)
|
|
||||||
fuzzforge monitor stats abc123def456 --refresh 5
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `fuzzforge monitor crashes <run-id>`
|
|
||||||
Display crash reports for a fuzzing run.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
fuzzforge monitor crashes abc123def456 --limit 50
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `fuzzforge monitor live <run-id>`
|
|
||||||
Real-time monitoring dashboard with live updates.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
fuzzforge monitor live abc123def456 --refresh 3
|
|
||||||
```
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Live updating statistics
|
|
||||||
- Progress indicators and bars
|
|
||||||
- Run status monitoring
|
|
||||||
- Automatic completion detection
|
|
||||||
|
|
||||||
### Configuration Management
|
### Configuration Management
|
||||||
|
|
||||||
#### `fuzzforge config show`
|
#### `fuzzforge config show`
|
||||||
@@ -560,7 +524,6 @@ cli/
|
|||||||
│ ├── workflows.py # Workflow management
|
│ ├── workflows.py # Workflow management
|
||||||
│ ├── runs.py # Run management
|
│ ├── runs.py # Run management
|
||||||
│ ├── findings.py # Findings management
|
│ ├── findings.py # Findings management
|
||||||
│ ├── monitor.py # Real-time monitoring
|
|
||||||
│ ├── config.py # Configuration commands
|
│ ├── config.py # Configuration commands
|
||||||
│ └── status.py # Status information
|
│ └── status.py # Status information
|
||||||
├── pyproject.toml # Project configuration
|
├── pyproject.toml # Project configuration
|
||||||
@@ -641,7 +604,6 @@ fuzzforge --help
|
|||||||
# Command-specific help
|
# Command-specific help
|
||||||
ff workflows --help
|
ff workflows --help
|
||||||
ff workflow run --help
|
ff workflow run --help
|
||||||
ff monitor live --help
|
|
||||||
|
|
||||||
# Show version
|
# Show version
|
||||||
fuzzforge --version
|
fuzzforge --version
|
||||||
@@ -683,4 +645,4 @@ Contributions are welcome! Please see the main FuzzForge repository for contribu
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**FuzzForge CLI** - Making security testing workflows accessible and efficient from the command line.
|
**FuzzForge CLI** - Making security testing workflows accessible and efficient from the command line.
|
||||||
|
|||||||
@@ -10,11 +10,10 @@
|
|||||||
#
|
#
|
||||||
# Additional attribution and requirements are provided in the NOTICE file.
|
# Additional attribution and requirements are provided in the NOTICE file.
|
||||||
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -32,17 +31,20 @@ app = typer.Typer()
|
|||||||
@app.command()
|
@app.command()
|
||||||
def project(
|
def project(
|
||||||
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 in the current directory.
|
📁 Initialize a new FuzzForge project in the current directory.
|
||||||
@@ -58,24 +60,20 @@ def project(
|
|||||||
# Check if project already exists
|
# Check if project already exists
|
||||||
if fuzzforge_dir.exists() and not force:
|
if fuzzforge_dir.exists() and not force:
|
||||||
if fuzzforge_dir.is_dir() and any(fuzzforge_dir.iterdir()):
|
if fuzzforge_dir.is_dir() and any(fuzzforge_dir.iterdir()):
|
||||||
console.print("❌ FuzzForge project already exists in this directory", style="red")
|
console.print(
|
||||||
|
"❌ FuzzForge project already exists in this directory", style="red"
|
||||||
|
)
|
||||||
console.print("Use --force to reinitialize", style="dim")
|
console.print("Use --force to reinitialize", style="dim")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
# Get project name
|
# Get project name
|
||||||
if not name:
|
if not name:
|
||||||
name = Prompt.ask(
|
name = Prompt.ask("Project name", default=current_dir.name, console=console)
|
||||||
"Project name",
|
|
||||||
default=current_dir.name,
|
|
||||||
console=console
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get API URL
|
# Get API URL
|
||||||
if not api_url:
|
if not api_url:
|
||||||
api_url = Prompt.ask(
|
api_url = Prompt.ask(
|
||||||
"FuzzForge API URL",
|
"FuzzForge API URL", default="http://localhost:8000", console=console
|
||||||
default="http://localhost:8000",
|
|
||||||
console=console
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Confirm initialization
|
# Confirm initialization
|
||||||
@@ -117,15 +115,15 @@ def project(
|
|||||||
]
|
]
|
||||||
|
|
||||||
if gitignore_path.exists():
|
if gitignore_path.exists():
|
||||||
with open(gitignore_path, 'r') as f:
|
with open(gitignore_path, "r") as f:
|
||||||
existing_content = f.read()
|
existing_content = f.read()
|
||||||
|
|
||||||
if "# FuzzForge CLI" not in existing_content:
|
if "# FuzzForge CLI" not in existing_content:
|
||||||
with open(gitignore_path, 'a') as f:
|
with open(gitignore_path, "a") as f:
|
||||||
f.write(f"\n{chr(10).join(gitignore_entries)}\n")
|
f.write(f"\n{chr(10).join(gitignore_entries)}\n")
|
||||||
console.print("📝 Updated .gitignore with FuzzForge entries")
|
console.print("📝 Updated .gitignore with FuzzForge entries")
|
||||||
else:
|
else:
|
||||||
with open(gitignore_path, 'w') as f:
|
with open(gitignore_path, "w") as f:
|
||||||
f.write(f"{chr(10).join(gitignore_entries)}\n")
|
f.write(f"{chr(10).join(gitignore_entries)}\n")
|
||||||
console.print("📝 Created .gitignore")
|
console.print("📝 Created .gitignore")
|
||||||
|
|
||||||
@@ -145,9 +143,6 @@ fuzzforge workflows
|
|||||||
# Submit a workflow for analysis
|
# Submit a workflow for analysis
|
||||||
fuzzforge workflow <workflow-name> /path/to/target
|
fuzzforge workflow <workflow-name> /path/to/target
|
||||||
|
|
||||||
# Monitor run progress
|
|
||||||
fuzzforge monitor live <run-id>
|
|
||||||
|
|
||||||
# View findings
|
# View findings
|
||||||
fuzzforge finding <run-id>
|
fuzzforge finding <run-id>
|
||||||
```
|
```
|
||||||
@@ -159,7 +154,7 @@ fuzzforge finding <run-id>
|
|||||||
- `.fuzzforge/findings.db` - Local database for runs and findings
|
- `.fuzzforge/findings.db` - Local database for runs and findings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with open(readme_path, 'w') as f:
|
with open(readme_path, "w") as f:
|
||||||
f.write(readme_content)
|
f.write(readme_content)
|
||||||
console.print("📚 Created README.md")
|
console.print("📚 Created README.md")
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ Provides "Did you mean...?" functionality and intelligent command/parameter sugg
|
|||||||
#
|
#
|
||||||
# Additional attribution and requirements are provided in the NOTICE file.
|
# Additional attribution and requirements are provided in the NOTICE file.
|
||||||
|
|
||||||
|
|
||||||
import difflib
|
import difflib
|
||||||
from typing import List, Optional, Dict, Any, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
@@ -34,10 +34,9 @@ class FuzzyMatcher:
|
|||||||
"workflows": ["list", "info"],
|
"workflows": ["list", "info"],
|
||||||
"runs": ["submit", "status", "list", "rerun"],
|
"runs": ["submit", "status", "list", "rerun"],
|
||||||
"findings": ["get", "list", "export", "all"],
|
"findings": ["get", "list", "export", "all"],
|
||||||
"monitor": ["stats", "crashes", "live"],
|
|
||||||
"config": ["set", "get", "list", "init"],
|
"config": ["set", "get", "list", "init"],
|
||||||
"ai": ["ask", "summarize", "explain"],
|
"ai": ["ask", "summarize", "explain"],
|
||||||
"ingest": ["project", "findings"]
|
"ingest": ["project", "findings"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Common workflow names
|
# Common workflow names
|
||||||
@@ -47,7 +46,7 @@ class FuzzyMatcher:
|
|||||||
"infrastructure_scan",
|
"infrastructure_scan",
|
||||||
"static_analysis_scan",
|
"static_analysis_scan",
|
||||||
"penetration_testing_scan",
|
"penetration_testing_scan",
|
||||||
"secret_detection_scan"
|
"secret_detection_scan",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Common parameter names
|
# Common parameter names
|
||||||
@@ -60,24 +59,25 @@ class FuzzyMatcher:
|
|||||||
"param-file",
|
"param-file",
|
||||||
"interactive",
|
"interactive",
|
||||||
"wait",
|
"wait",
|
||||||
"live",
|
|
||||||
"format",
|
"format",
|
||||||
"output",
|
"output",
|
||||||
"severity",
|
"severity",
|
||||||
"since",
|
"since",
|
||||||
"limit",
|
"limit",
|
||||||
"stats",
|
"stats",
|
||||||
"export"
|
"export",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Common values
|
# Common values
|
||||||
self.common_values = {
|
self.common_values = {
|
||||||
"volume_mode": ["ro", "rw"],
|
"volume_mode": ["ro", "rw"],
|
||||||
"format": ["json", "csv", "html", "sarif"],
|
"format": ["json", "csv", "html", "sarif"],
|
||||||
"severity": ["critical", "high", "medium", "low", "info"]
|
"severity": ["critical", "high", "medium", "low", "info"],
|
||||||
}
|
}
|
||||||
|
|
||||||
def find_closest_command(self, user_input: str, command_group: Optional[str] = None) -> Optional[Tuple[str, float]]:
|
def find_closest_command(
|
||||||
|
self, user_input: str, command_group: Optional[str] = None
|
||||||
|
) -> Optional[Tuple[str, float]]:
|
||||||
"""Find the closest matching command."""
|
"""Find the closest matching command."""
|
||||||
if command_group and command_group in self.commands:
|
if command_group and command_group in self.commands:
|
||||||
# Search within a specific command group
|
# Search within a specific command group
|
||||||
@@ -86,9 +86,7 @@ class FuzzyMatcher:
|
|||||||
# Search all main commands
|
# Search all main commands
|
||||||
candidates = list(self.commands.keys())
|
candidates = list(self.commands.keys())
|
||||||
|
|
||||||
matches = difflib.get_close_matches(
|
matches = difflib.get_close_matches(user_input, candidates, n=1, cutoff=0.6)
|
||||||
user_input, candidates, n=1, cutoff=0.6
|
|
||||||
)
|
|
||||||
|
|
||||||
if matches:
|
if matches:
|
||||||
match = matches[0]
|
match = matches[0]
|
||||||
@@ -114,7 +112,7 @@ class FuzzyMatcher:
|
|||||||
def find_closest_parameter(self, user_input: str) -> Optional[Tuple[str, float]]:
|
def find_closest_parameter(self, user_input: str) -> Optional[Tuple[str, float]]:
|
||||||
"""Find the closest matching parameter name."""
|
"""Find the closest matching parameter name."""
|
||||||
# Remove leading dashes
|
# Remove leading dashes
|
||||||
clean_input = user_input.lstrip('-')
|
clean_input = user_input.lstrip("-")
|
||||||
|
|
||||||
matches = difflib.get_close_matches(
|
matches = difflib.get_close_matches(
|
||||||
clean_input, self.parameter_names, n=1, cutoff=0.6
|
clean_input, self.parameter_names, n=1, cutoff=0.6
|
||||||
@@ -139,7 +137,9 @@ class FuzzyMatcher:
|
|||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_command_suggestions(self, user_command: List[str]) -> Optional[Dict[str, Any]]:
|
def get_command_suggestions(
|
||||||
|
self, user_command: List[str]
|
||||||
|
) -> Optional[Dict[str, Any]]:
|
||||||
"""Get suggestions for a user command that may have typos."""
|
"""Get suggestions for a user command that may have typos."""
|
||||||
if not user_command:
|
if not user_command:
|
||||||
return None
|
return None
|
||||||
@@ -153,11 +153,9 @@ class FuzzyMatcher:
|
|||||||
if closest:
|
if closest:
|
||||||
match, confidence = closest
|
match, confidence = closest
|
||||||
suggestions["type"] = "main_command"
|
suggestions["type"] = "main_command"
|
||||||
suggestions["suggestions"].append({
|
suggestions["suggestions"].append(
|
||||||
"text": match,
|
{"text": match, "confidence": confidence, "type": "command"}
|
||||||
"confidence": confidence,
|
)
|
||||||
"type": "command"
|
|
||||||
})
|
|
||||||
|
|
||||||
# Check subcommand if present
|
# Check subcommand if present
|
||||||
elif len(user_command) > 1:
|
elif len(user_command) > 1:
|
||||||
@@ -167,11 +165,13 @@ class FuzzyMatcher:
|
|||||||
if closest:
|
if closest:
|
||||||
match, confidence = closest
|
match, confidence = closest
|
||||||
suggestions["type"] = "subcommand"
|
suggestions["type"] = "subcommand"
|
||||||
suggestions["suggestions"].append({
|
suggestions["suggestions"].append(
|
||||||
"text": f"{main_cmd} {match}",
|
{
|
||||||
"confidence": confidence,
|
"text": f"{main_cmd} {match}",
|
||||||
"type": "subcommand"
|
"confidence": confidence,
|
||||||
})
|
"type": "subcommand",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return suggestions if suggestions["suggestions"] else None
|
return suggestions if suggestions["suggestions"] else None
|
||||||
|
|
||||||
@@ -210,17 +210,19 @@ def display_command_suggestion(suggestions: Dict[str, Any]):
|
|||||||
|
|
||||||
# Add helpful context
|
# Add helpful context
|
||||||
if suggestion_type == "main_command":
|
if suggestion_type == "main_command":
|
||||||
text.append("\n💡 Use 'fuzzforge --help' to see all available commands", style="dim")
|
text.append(
|
||||||
|
"\n💡 Use 'fuzzforge --help' to see all available commands", style="dim"
|
||||||
|
)
|
||||||
elif suggestion_type == "subcommand":
|
elif suggestion_type == "subcommand":
|
||||||
main_cmd = suggestions["original"][0]
|
main_cmd = suggestions["original"][0]
|
||||||
text.append(f"\n💡 Use 'fuzzforge {main_cmd} --help' to see available subcommands", style="dim")
|
text.append(
|
||||||
|
f"\n💡 Use 'fuzzforge {main_cmd} --help' to see available subcommands",
|
||||||
|
style="dim",
|
||||||
|
)
|
||||||
|
|
||||||
console.print(Panel(
|
console.print(
|
||||||
text,
|
Panel(text, title="🤔 Command Suggestion", border_style="yellow", expand=False)
|
||||||
title="🤔 Command Suggestion",
|
)
|
||||||
border_style="yellow",
|
|
||||||
expand=False
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def display_workflow_suggestion(original: str, suggestion: str):
|
def display_workflow_suggestion(original: str, suggestion: str):
|
||||||
@@ -234,14 +236,13 @@ def display_workflow_suggestion(original: str, suggestion: str):
|
|||||||
text.append(f"'{suggestion}'", style="bold green")
|
text.append(f"'{suggestion}'", style="bold green")
|
||||||
text.append("?\n\n")
|
text.append("?\n\n")
|
||||||
|
|
||||||
text.append("💡 Use 'fuzzforge workflows' to see all available workflows", style="dim")
|
text.append(
|
||||||
|
"💡 Use 'fuzzforge workflows' to see all available workflows", style="dim"
|
||||||
|
)
|
||||||
|
|
||||||
console.print(Panel(
|
console.print(
|
||||||
text,
|
Panel(text, title="🔧 Workflow Suggestion", border_style="yellow", expand=False)
|
||||||
title="🔧 Workflow Suggestion",
|
)
|
||||||
border_style="yellow",
|
|
||||||
expand=False
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def display_parameter_suggestion(original: str, suggestion: str):
|
def display_parameter_suggestion(original: str, suggestion: str):
|
||||||
@@ -257,12 +258,9 @@ def display_parameter_suggestion(original: str, suggestion: str):
|
|||||||
|
|
||||||
text.append("💡 Use '--help' to see all available parameters", style="dim")
|
text.append("💡 Use '--help' to see all available parameters", style="dim")
|
||||||
|
|
||||||
console.print(Panel(
|
console.print(
|
||||||
text,
|
Panel(text, title="⚙️ Parameter Suggestion", border_style="yellow", expand=False)
|
||||||
title="⚙️ Parameter Suggestion",
|
)
|
||||||
border_style="yellow",
|
|
||||||
expand=False
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def enhanced_command_not_found_handler(command_parts: List[str]):
|
def enhanced_command_not_found_handler(command_parts: List[str]):
|
||||||
@@ -306,4 +304,4 @@ def enhanced_parameter_not_found_handler(parameter_name: str):
|
|||||||
|
|
||||||
|
|
||||||
# Global fuzzy matcher instance
|
# Global fuzzy matcher instance
|
||||||
fuzzy_matcher = FuzzyMatcher()
|
fuzzy_matcher = FuzzyMatcher()
|
||||||
|
|||||||
Reference in New Issue
Block a user