Add interactive tool selection with gum multi-select

Implemented comprehensive gum-based interactive tool selection for Level 3 menu.

Features:
- Added gum_multi_select() function to utils.py with proper type hints
- Implemented install_category_selected() in toolbelt.py
- Interactive multi-select for APT, Go, /opt, Python, and Docker categories
- Graceful fallback when gum is not installed
- Confirmation prompt before installation
- Updated README with gum usage examples
- Added PYTHON_STANDARDS.md for type hint requirements
- Created TODO.md with v2.1+ enhancement roadmap
- Added project branding images

User can now:
1. Browse categories and see tool counts
2. Use SPACE to toggle tools, ENTER when done
3. Review selected tools before installing
4. Get helpful prompts if gum is missing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
rpriven 2025-10-31 23:47:33 -06:00
parent fd0659d70c
commit 52f654dd32
Signed by: djedi
GPG key ID: D04DED574622EF45
25 changed files with 649 additions and 4 deletions

143
PYTHON_STANDARDS.md Normal file
View file

@ -0,0 +1,143 @@
# Python Coding Standards
**Note:** We prefer TypeScript/Bash, but when Python is necessary, follow these standards.
## Type Hints (Required)
Always use type hints for function parameters and return values.
### Function Signatures
```python
from typing import List, Dict, Optional, Tuple
def install_tools(
tools: Optional[List[str]] = None,
distro_type: str = 'unknown',
logger: Optional[logging.Logger] = None
) -> bool:
"""
Install tools with type-safe parameters
Args:
tools: List of package names (None for all)
distro_type: Distro type for filtering
logger: Logger instance
Returns:
True if successful
"""
pass
```
### Variable Type Hints
```python
# Simple types
count: int = 0
name: str = "toolbelt"
is_installed: bool = True
# Collections
tools: List[str] = ["nmap", "masscan"]
config: Dict[str, str] = {"key": "value"}
result: Tuple[str, int] = ("success", 200)
# Optional values
logger: Optional[logging.Logger] = None
```
### Common Types
```python
from typing import List, Dict, Set, Tuple, Optional, Union, Any
# Lists
packages: List[str] = []
# Dictionaries
tool_config: Dict[str, Any] = {}
# Optional (can be None)
logger: Optional[logging.Logger] = None
# Union (multiple types)
result: Union[str, int] = "success"
# Tuples with specific types
coordinates: Tuple[int, int] = (10, 20)
```
## Why Type Hints Matter
1. **Catch bugs early** - Type checkers find errors before runtime
2. **Better IDE support** - Autocomplete knows what methods exist
3. **Documentation** - Function signatures show what to pass
4. **Refactoring safety** - Change types, find all affected code
## Type Checking
Run type checker before committing:
```bash
# Install mypy
pip3 install mypy
# Check types
mypy toolbelt.py utils.py config.py installer.py
```
## When to Skip Type Hints
- Quick throwaway scripts (< 50 lines)
- Interactive REPL exploration
- Never skip in production code
## Additional Standards
### Imports
```python
# Standard library first
import os
import sys
from pathlib import Path
# Third party
import requests
# Local modules
from utils import setup_logging
import config
```
### Error Handling
```python
# Specific exceptions, not bare except
try:
result = risky_operation()
except FileNotFoundError as e:
logger.error(f"File not found: {e}")
except PermissionError as e:
logger.error(f"Permission denied: {e}")
except Exception as e:
logger.error(f"Unexpected error: {e}")
raise
```
### Logging
```python
# Use logging, not print (for production code)
import logging
logger = logging.getLogger(__name__)
logger.info("Operation started")
logger.warning("Potential issue")
logger.error("Operation failed")
```
## Bottom Line
**If we must use Python, we do it right. Type hints are non-negotiable for production code.**

View file

@ -144,6 +144,7 @@ wfuzz, arjun, scrapy, tld, requests, fuzzywuzzy
- **Python:** 3.6+ - **Python:** 3.6+
- **Package Manager:** apt - **Package Manager:** apt
- **Privileges:** sudo access (script runs as user, not root) - **Privileges:** sudo access (script runs as user, not root)
- **Optional:** gum (for interactive multi-select) - `go install github.com/charmbracelet/gum@latest`
--- ---
@ -163,9 +164,25 @@ python3 toolbelt.py
python3 toolbelt.py python3 toolbelt.py
# Select: 2) Browse & Select Categories # Select: 2) Browse & Select Categories
# Choose category (e.g., Go Tools) # Choose category (e.g., Go Tools)
# Install all or select specific tools # Option 1: Install all tools
# Option 2: Select specific tools (interactive multi-select with gum)
``` ```
### Interactive Tool Selection (gum)
With gum installed, you get beautiful interactive multi-select:
```bash
python3 toolbelt.py
# Select: 2) Browse & Select Categories
# Choose: Go Tools
# Select: 2) Select Specific Tools
# Use SPACE to toggle, ENTER when done
# Confirm selection and install
```
**Without gum:** The script will prompt you to install it or fall back to "Install All" option.
### Check Installed Tools ### Check Installed Tools
```bash ```bash

338
TODO.md Normal file
View file

@ -0,0 +1,338 @@
# Toolbelt Enhancement TODO
## v2.1 Priority Features
### 1. Tool Update Detection ⭐⭐⭐
**Priority: HIGH**
Check which Go tools have updates available and provide interactive update menu.
```python
# Compare installed version vs @latest
# Show which tools need updates
# Bulk update option
```
**Implementation:**
- Query `go list -m` for installed versions
- Compare against `@latest` from go.dev
- Interactive menu to select which tools to update
---
### 2. Individual Tool Selection (Level 3) ⭐⭐⭐
**Priority: HIGH**
Replace "Coming Soon" with actual tool selection using gum.
**Using `gum choose --no-limit` for multi-select:**
```bash
# Install gum first
go install github.com/charmbracelet/gum@latest
# Then use in Python via subprocess
selected = subprocess.run(
['gum', 'choose', '--no-limit'] + tool_list,
capture_output=True,
text=True
).stdout.strip().split('\n')
```
**Implementation Plan:**
1. Add gum as optional dependency (install if not present)
2. In `tool_selection_menu()`, replace option 2 with gum multi-select
3. Pass selected tools to category installer
4. Fallback to sequential selection if gum not available
**Example:**
```python
def select_apt_tools(distro_type: str) -> List[str]:
"""Let user select specific APT tools with gum multi-select"""
all_tools = config.get_apt_tools_for_distro(distro_type)
if check_command_exists('gum'):
# Multi-select with gum
result = subprocess.run(
['gum', 'choose', '--no-limit', '--header', 'Select tools to install:'] + all_tools,
capture_output=True,
text=True
)
selected = [t for t in result.stdout.strip().split('\n') if t]
return selected
else:
# Fallback: show list, let user pick
print_warning("Install gum for multi-select: go install github.com/charmbracelet/gum@latest")
# ... manual selection loop
```
---
### 3. Wordlist Management ⭐⭐
**Priority: MEDIUM**
Download and organize common wordlists for pentesting.
**Wordlists to Include:**
- SecLists (already in APT tools, but organize it)
- rockyou.txt
- Daniel Miessler's lists
- Custom wordlists for subdomains, directories, passwords
**Directory Structure:**
```
~/wordlists/
├── passwords/
│ ├── rockyou.txt
│ └── common-passwords.txt
├── subdomains/
│ ├── subdomains-top1mil.txt
│ └── dns-bruteforce.txt
├── directories/
│ ├── common.txt
│ └── raft-large-directories.txt
└── usernames/
└── common-usernames.txt
```
**Implementation:**
- New category: "Wordlist Management"
- Download from GitHub releases
- Extract and organize
- Create symlinks to common locations
---
### 4. Resource Monitoring ⭐⭐
**Priority: MEDIUM**
Show disk space requirements before installation.
**Features:**
- Check available disk space
- Estimate download size per category
- Warn if insufficient space
- Show progress during large downloads
**Implementation:**
```python
def check_disk_space(required_gb: float) -> bool:
"""Check if enough disk space available"""
stat = os.statvfs(os.path.expanduser('~'))
available_gb = (stat.f_bavail * stat.f_frsize) / (1024**3)
if available_gb < required_gb:
print_error(f"Insufficient disk space!")
print_info(f"Required: {required_gb}GB, Available: {available_gb:.1f}GB")
return False
return True
```
---
### 5. Tool Usage Instructions (tldr) ⭐⭐
**Priority: MEDIUM**
Show basic usage examples after installing each tool.
**Using tldr pages:**
```bash
# Install tldr
pip3 install tldr
# Show usage
tldr nuclei
```
**Implementation:**
```python
def show_tool_usage(tool_name: str):
"""Display quick usage guide for tool"""
if check_command_exists('tldr'):
subprocess.run(['tldr', tool_name])
else:
# Fallback: show our own examples from config
if tool_name in TOOL_EXAMPLES:
print_info(f"\nQuick Start for {tool_name}:")
print(TOOL_EXAMPLES[tool_name])
```
**Add to config.py:**
```python
TOOL_EXAMPLES = {
"nuclei": "nuclei -t /path/to/templates -u https://target.com",
"httpx": "cat domains.txt | httpx -status-code -title",
"subfinder": "subfinder -d target.com -o subdomains.txt",
# ... etc
}
```
---
### 6. Export/Import Configuration ⭐
**Priority: LOW**
Save and restore custom tool selections.
**Format:**
```json
{
"name": "my-custom-setup",
"created": "2025-10-31",
"distro": "kali",
"tools": {
"apt": ["nmap", "masscan", "burpsuite"],
"go": ["nuclei", "httpx", "subfinder"],
"scripts": true
}
}
```
**Implementation:**
- Save to `~/.config/toolbelt/configs/`
- Load previous configs
- Share between systems
---
### 7. Workspace Setup ⭐
**Priority: LOW**
Create standard pentesting directory structure.
**Directory Tree:**
```
~/pentesting/
├── targets/
│ └── example.com/
│ ├── recon/
│ ├── scans/
│ └── loot/
├── wordlists/ (symlink to ~/wordlists)
├── tools/ (symlink to /opt)
└── reports/
```
**Implementation:**
- New menu option: "Setup Workspace"
- Creates directories
- Adds .gitignore templates
- Initializes git repos where appropriate
---
### 8. Tmux Integration (via tmux-recon) ⭐⭐⭐
**Priority: HIGH** (After tmux-recon.py is done)
**Menu Option:**
```
7) 🚀 Launch Pentesting Environment (tmux-recon)
Advanced shell environment with tmux automation
```
**Action:**
- Check if tmux-recon is installed
- If not, prompt to clone and install
- If yes, launch tmux-recon automation
**Implementation:**
```python
def launch_tmux_environment():
"""Launch tmux-recon pentesting environment"""
if not os.path.exists('/opt/tmux-recon'):
print_warning("tmux-recon not installed")
response = input("Clone and install tmux-recon? [y/N]: ")
if response.lower() == 'y':
# Clone and run tmux-recon
pass
else:
# Launch tmux-recon
subprocess.run(['/opt/tmux-recon/tmux-recon.py', '--auto'])
```
---
### 9. Health Check / Verify Installation ⭐
**Priority: LOW**
Verify all installed tools are working correctly.
**Checks:**
- Run `--version` or `--help` on each tool
- Verify can execute
- Check for broken symlinks
- Report missing dependencies
**Implementation:**
```python
def health_check():
"""Verify all installed tools work"""
broken_tools = []
for tool in installed_tools:
try:
subprocess.run([tool, '--version'],
capture_output=True,
timeout=5)
except Exception:
broken_tools.append(tool)
if broken_tools:
print_error(f"Broken tools: {', '.join(broken_tools)}")
```
---
### 10. Progress Bars for Large Downloads ⭐
**Priority: LOW**
Better visual feedback during installation.
**Using tqdm:**
```python
from tqdm import tqdm
def download_with_progress(url: str, output: str):
"""Download with progress bar"""
response = requests.get(url, stream=True)
total = int(response.headers.get('content-length', 0))
with open(output, 'wb') as f, tqdm(
total=total,
unit='B',
unit_scale=True,
desc=output
) as bar:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
bar.update(len(chunk))
```
---
## Implementation Priority
**v2.1 (Next Release):**
1. Individual Tool Selection (gum multi-select) ✅ **COMPLETED**
2. Tool Update Detection 🔜
3. Wordlist Management 🔜
**v2.2:**
4. Resource Monitoring
5. Usage Instructions (tldr)
6. Tmux-recon Integration
**v2.3:**
7. Export/Import Configs
8. Workspace Setup
9. Health Check
10. Progress Bars
---
## Notes
- All features should maintain modular architecture
- Add comprehensive logging for new features
- Update README with each release
- Keep Python type hints for all new code

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -16,6 +16,7 @@ from utils import (
check_apt, check_apt,
check_sudo, check_sudo,
check_command_exists, check_command_exists,
gum_multi_select,
print_banner, print_banner,
print_section, print_section,
print_success, print_success,
@ -259,8 +260,15 @@ def show_tool_selection_menu(category_id: str, distro_type: str):
print_info(cat_info['description']) print_info(cat_info['description'])
print() print()
# Check if gum is available
has_gum = check_command_exists('gum')
print(colorize("1)", 'green') + " Install All Tools in Category") print(colorize("1)", 'green') + " Install All Tools in Category")
print(colorize("2)", 'green') + " Select Specific Tools (Coming Soon)") if has_gum:
print(colorize("2)", 'green') + " Select Specific Tools (multi-select)")
else:
print(colorize("2)", 'green') + " Select Specific Tools (requires gum)")
print_info(" Install gum: go install github.com/charmbracelet/gum@latest")
print() print()
print(colorize("0)", 'red') + " Back") print(colorize("0)", 'red') + " Back")
print() print()
@ -277,8 +285,7 @@ def tool_selection_menu(category_id: str, distro_type: str, logger):
elif choice == '1': elif choice == '1':
install_category_all(category_id, distro_type, logger) install_category_all(category_id, distro_type, logger)
elif choice == '2': elif choice == '2':
print_warning("Individual tool selection coming in next update!") install_category_selected(category_id, distro_type, logger)
input("\nPress Enter to continue...")
else: else:
print_error("Invalid option. Please try again.") print_error("Invalid option. Please try again.")
input("\nPress Enter to continue...") input("\nPress Enter to continue...")
@ -313,6 +320,107 @@ def install_category_all(category_id: str, distro_type: str, logger):
input("\nPress Enter to continue...") input("\nPress Enter to continue...")
def install_category_selected(category_id: str, distro_type: str, logger):
"""Install selected tools from a category using gum multi-select"""
cat_info = config.CATEGORIES[category_id]
# Check if gum is available
if not check_command_exists('gum'):
print()
print_error("gum is not installed!")
print_info("gum is required for interactive multi-select")
print()
print_info("Install gum with:")
print(" go install github.com/charmbracelet/gum@latest")
print()
print_info("Make sure $GOPATH/bin is in your PATH")
print()
input("Press Enter to continue...")
return
print_section(f"Select {cat_info['name']}")
# Get tool list based on category
tools_list: List[str] = []
if category_id == 'apt':
tools_list = config.get_apt_tools_for_distro(distro_type)
elif category_id == 'go':
tools_list = list(config.GO_TOOLS.keys())
elif category_id == 'opt':
tools_list = list(config.get_opt_tools_for_distro(distro_type).keys())
elif category_id == 'python':
tools_list = list(config.PYTHON_TOOLS.keys())
elif category_id == 'docker':
tools_list = list(config.DOCKER_TOOLS.keys())
elif category_id == 'scripts':
print()
print_info("Scripts category downloads all scripts as a set")
print_info("Use option 1 to install all scripts")
print()
input("Press Enter to continue...")
return
if not tools_list:
print_error(f"No tools available for {cat_info['name']}")
input("\nPress Enter to continue...")
return
# Show count
print_info(f"Available tools: {len(tools_list)}")
print()
print(colorize("TIP: Use SPACE to select, ENTER when done, Ctrl+C to cancel", 'yellow'))
print()
# Use gum for multi-select
selected = gum_multi_select(
tools_list,
header=f"Select {cat_info['name']} to install:"
)
if not selected:
print()
print_warning("No tools selected")
input("\nPress Enter to continue...")
return
# Show what was selected
print()
print_section(f"Installing {len(selected)} Selected Tools")
for tool in selected:
print(colorize(f"{tool}", 'cyan'))
print()
# Confirm installation
response = input(colorize("Proceed with installation? [Y/n]: ", 'yellow')).strip().lower()
if response == 'n':
print_warning("Installation cancelled")
input("\nPress Enter to continue...")
return
# Install selected tools
success = False
if category_id == 'apt':
success = installer.install_apt_tools(tools=selected, distro_type=distro_type, logger=logger)
elif category_id == 'go':
success = installer.install_go_tools(tools=selected, logger=logger)
elif category_id == 'opt':
success = installer.install_opt_tools(tools=selected, distro_type=distro_type, logger=logger)
elif category_id == 'python':
success = installer.install_python_tools(tools=selected, logger=logger)
elif category_id == 'docker':
success = installer.install_docker_tools(tools=selected, logger=logger)
print()
if success:
print_success(f"Selected {cat_info['name']} installation complete!")
else:
print_warning(f"Selected {cat_info['name']} installation completed with some errors")
input("\nPress Enter to continue...")
# ============================================================================ # ============================================================================
# View Installed Tools # View Installed Tools
# ============================================================================ # ============================================================================

View file

@ -170,6 +170,45 @@ def check_command_exists(command: str) -> bool:
return False return False
def gum_multi_select(
options: List[str],
header: str = "Select items (space to select, enter when done):"
) -> List[str]:
"""
Use gum to let user multi-select from options
Args:
options: List of options to choose from
header: Header text to display
Returns:
List of selected options
"""
if not check_command_exists('gum'):
return []
try:
result = subprocess.run(
['gum', 'choose', '--no-limit', '--header', header] + options,
capture_output=True,
text=True,
timeout=300 # 5 minute timeout
)
if result.returncode == 0:
# Parse output - one item per line
selected = [item.strip() for item in result.stdout.split('\n') if item.strip()]
return selected
return []
except subprocess.TimeoutExpired:
print_warning("Selection timed out")
return []
except Exception as e:
logging.error(f"gum multi-select failed: {e}")
return []
def check_apt() -> bool: def check_apt() -> bool:
"""Check if apt package manager is available""" """Check if apt package manager is available"""
return check_command_exists('apt') return check_command_exists('apt')