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>
143
PYTHON_STANDARDS.md
Normal 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.**
|
||||||
19
README.md
|
|
@ -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
|
|
@ -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
|
||||||
BIN
images/flux_jedi-toolbelt_seed_0_1.jpeg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
images/flux_jedi-toolbelt_seed_10_1.jpeg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
images/flux_jedi-toolbelt_seed_11_1.jpeg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
images/flux_jedi-toolbelt_seed_12_1.jpeg
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
images/flux_jedi-toolbelt_seed_13_1.jpeg
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
images/flux_jedi-toolbelt_seed_14_1.jpeg
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
images/flux_jedi-toolbelt_seed_15_1.jpeg
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
images/flux_jedi-toolbelt_seed_16_1.jpeg
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
images/flux_jedi-toolbelt_seed_17_1.jpeg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
images/flux_jedi-toolbelt_seed_18_1.jpeg
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
images/flux_jedi-toolbelt_seed_19_1.jpeg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
images/flux_jedi-toolbelt_seed_1_1.jpeg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
images/flux_jedi-toolbelt_seed_2_1.jpeg
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
images/flux_jedi-toolbelt_seed_3_1.jpeg
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
images/flux_jedi-toolbelt_seed_4_1.jpeg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
images/flux_jedi-toolbelt_seed_5_1.jpeg
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
images/flux_jedi-toolbelt_seed_6_1.jpeg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
images/flux_jedi-toolbelt_seed_7_1.jpeg
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
images/flux_jedi-toolbelt_seed_8_1.jpeg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
images/flux_jedi-toolbelt_seed_9_1.jpeg
Normal file
|
After Width: | Height: | Size: 64 KiB |
114
toolbelt.py
|
|
@ -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
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
|
||||||
39
utils.py
|
|
@ -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')
|
||||||
|
|
|
||||||