This commit includes two major improvements:
## 1. Unified Create Command (v3.0.0 feature)
- Auto-detects source type (web, GitHub, local, PDF, config)
- Three-tier argument organization (universal, source-specific, advanced)
- Routes to existing scrapers (100% backward compatible)
- Progressive disclosure: 15 universal flags in default help
**New files:**
- src/skill_seekers/cli/source_detector.py - Auto-detection logic
- src/skill_seekers/cli/arguments/create.py - Argument definitions
- src/skill_seekers/cli/create_command.py - Main orchestrator
- src/skill_seekers/cli/parsers/create_parser.py - Parser integration
**Tests:**
- tests/test_source_detector.py (35 tests)
- tests/test_create_arguments.py (30 tests)
- tests/test_create_integration_basic.py (10 tests)
## 2. Enhanced Flag Consolidation (Phase 1)
- Consolidated 3 flags (--enhance, --enhance-local, --enhance-level) → 1 flag
- --enhance-level 0-3 with auto-detection of API vs LOCAL mode
- Default: --enhance-level 2 (balanced enhancement)
**Modified files:**
- arguments/{common,create,scrape,github,analyze}.py - Added enhance_level
- {doc_scraper,github_scraper,config_extractor,main}.py - Updated logic
- create_command.py - Uses consolidated flag
**Auto-detection:**
- If ANTHROPIC_API_KEY set → API mode
- Else → LOCAL mode (Claude Code)
## 3. PresetManager Bug Fix
- Fixed module naming conflict (presets.py vs presets/ directory)
- Moved presets.py → presets/manager.py
- Updated __init__.py exports
**Test Results:**
- All 160+ tests passing
- Zero regressions
- 100% backward compatible
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
618 lines
17 KiB
Markdown
618 lines
17 KiB
Markdown
# UI Integration Guide
|
|
## How the CLI Refactor Enables Future UI Development
|
|
|
|
**Date:** 2026-02-14
|
|
**Status:** Planning Document
|
|
**Related:** CLI_REFACTOR_PROPOSAL.md
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
The "Pure Explicit" architecture proposed for fixing #285 is **ideal** for UI development because:
|
|
|
|
1. ✅ **Single source of truth** for all command options
|
|
2. ✅ **Self-documenting** argument definitions
|
|
3. ✅ **Easy to introspect** for dynamic form generation
|
|
4. ✅ **Consistent validation** between CLI and UI
|
|
|
|
**Recommendation:** Proceed with the refactor. It actively enables future UI work.
|
|
|
|
---
|
|
|
|
## Why This Architecture is UI-Friendly
|
|
|
|
### Current Problem (Without Refactor)
|
|
|
|
```python
|
|
# BEFORE: Arguments scattered in multiple files
|
|
# doc_scraper.py
|
|
def create_argument_parser():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--name", help="Skill name") # ← Here
|
|
parser.add_argument("--max-pages", type=int) # ← Here
|
|
return parser
|
|
|
|
# parsers/scrape_parser.py
|
|
class ScrapeParser:
|
|
def add_arguments(self, parser):
|
|
parser.add_argument("--name", help="Skill name") # ← Duplicate!
|
|
# max-pages forgotten!
|
|
```
|
|
|
|
**UI Problem:** Which arguments exist? What's the full schema? Hard to discover.
|
|
|
|
### After Refactor (UI-Friendly)
|
|
|
|
```python
|
|
# AFTER: Centralized, structured definitions
|
|
# arguments/scrape.py
|
|
|
|
SCRAPER_ARGUMENTS = {
|
|
"name": {
|
|
"type": str,
|
|
"help": "Skill name",
|
|
"ui_label": "Skill Name",
|
|
"ui_section": "Basic",
|
|
"placeholder": "e.g., React"
|
|
},
|
|
"max_pages": {
|
|
"type": int,
|
|
"help": "Maximum pages to scrape",
|
|
"ui_label": "Max Pages",
|
|
"ui_section": "Limits",
|
|
"min": 1,
|
|
"max": 1000,
|
|
"default": 100
|
|
},
|
|
"async_mode": {
|
|
"type": bool,
|
|
"help": "Use async scraping",
|
|
"ui_label": "Async Mode",
|
|
"ui_section": "Performance",
|
|
"ui_widget": "checkbox"
|
|
}
|
|
}
|
|
|
|
def add_scrape_arguments(parser):
|
|
for name, config in SCRAPER_ARGUMENTS.items():
|
|
parser.add_argument(f"--{name}", **config)
|
|
```
|
|
|
|
**UI Benefit:** Arguments are data! Easy to iterate and build forms.
|
|
|
|
---
|
|
|
|
## UI Architecture Options
|
|
|
|
### Option 1: Console UI (TUI) - Recommended First Step
|
|
|
|
**Libraries:** `rich`, `textual`, `inquirer`, `questionary`
|
|
|
|
```python
|
|
# Example: TUI using the shared argument definitions
|
|
# src/skill_seekers/ui/console/scrape_wizard.py
|
|
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.prompt import Prompt, IntPrompt, Confirm
|
|
|
|
from skill_seekers.cli.arguments.scrape import SCRAPER_ARGUMENTS
|
|
from skill_seekers.cli.presets.scrape_presets import PRESETS
|
|
|
|
|
|
class ScrapeWizard:
|
|
"""Interactive TUI for scrape command."""
|
|
|
|
def __init__(self):
|
|
self.console = Console()
|
|
self.results = {}
|
|
|
|
def run(self):
|
|
"""Run the wizard."""
|
|
self.console.print(Panel.fit(
|
|
"[bold blue]Skill Seekers - Scrape Wizard[/bold blue]",
|
|
border_style="blue"
|
|
))
|
|
|
|
# Step 1: Choose preset (simplified) or custom
|
|
use_preset = Confirm.ask("Use a preset configuration?")
|
|
|
|
if use_preset:
|
|
self._select_preset()
|
|
else:
|
|
self._custom_configuration()
|
|
|
|
# Execute
|
|
self._execute()
|
|
|
|
def _select_preset(self):
|
|
"""Let user pick a preset."""
|
|
from rich.table import Table
|
|
|
|
table = Table(title="Available Presets")
|
|
table.add_column("Preset", style="cyan")
|
|
table.add_column("Description")
|
|
table.add_column("Time")
|
|
|
|
for name, preset in PRESETS.items():
|
|
table.add_row(name, preset.description, preset.estimated_time)
|
|
|
|
self.console.print(table)
|
|
|
|
choice = Prompt.ask(
|
|
"Select preset",
|
|
choices=list(PRESETS.keys()),
|
|
default="standard"
|
|
)
|
|
|
|
self.results["preset"] = choice
|
|
|
|
def _custom_configuration(self):
|
|
"""Interactive form based on argument definitions."""
|
|
|
|
# Group by UI section
|
|
sections = {}
|
|
for name, config in SCRAPER_ARGUMENTS.items():
|
|
section = config.get("ui_section", "General")
|
|
if section not in sections:
|
|
sections[section] = []
|
|
sections[section].append((name, config))
|
|
|
|
# Render each section
|
|
for section_name, fields in sections.items():
|
|
self.console.print(f"\n[bold]{section_name}[/bold]")
|
|
|
|
for name, config in fields:
|
|
value = self._prompt_for_field(name, config)
|
|
self.results[name] = value
|
|
|
|
def _prompt_for_field(self, name: str, config: dict):
|
|
"""Generate appropriate prompt based on argument type."""
|
|
|
|
label = config.get("ui_label", name)
|
|
help_text = config.get("help", "")
|
|
|
|
if config.get("type") == bool:
|
|
return Confirm.ask(f"{label}?", default=config.get("default", False))
|
|
|
|
elif config.get("type") == int:
|
|
return IntPrompt.ask(
|
|
f"{label}",
|
|
default=config.get("default")
|
|
)
|
|
|
|
else:
|
|
return Prompt.ask(
|
|
f"{label}",
|
|
default=config.get("default", ""),
|
|
show_default=True
|
|
)
|
|
```
|
|
|
|
**Benefits:**
|
|
- ✅ Reuses all validation and help text
|
|
- ✅ Consistent with CLI behavior
|
|
- ✅ Can run in any terminal
|
|
- ✅ No web server needed
|
|
|
|
---
|
|
|
|
### Option 2: Web UI (Gradio/Streamlit)
|
|
|
|
**Libraries:** `gradio`, `streamlit`, `fastapi + htmx`
|
|
|
|
```python
|
|
# Example: Web UI using Gradio
|
|
# src/skill_seekers/ui/web/app.py
|
|
|
|
import gradio as gr
|
|
from skill_seekers.cli.arguments.scrape import SCRAPER_ARGUMENTS
|
|
|
|
|
|
def create_scrape_interface():
|
|
"""Create Gradio interface for scrape command."""
|
|
|
|
# Generate inputs from argument definitions
|
|
inputs = []
|
|
|
|
for name, config in SCRAPER_ARGUMENTS.items():
|
|
arg_type = config.get("type")
|
|
label = config.get("ui_label", name)
|
|
help_text = config.get("help", "")
|
|
|
|
if arg_type == bool:
|
|
inputs.append(gr.Checkbox(
|
|
label=label,
|
|
info=help_text,
|
|
value=config.get("default", False)
|
|
))
|
|
|
|
elif arg_type == int:
|
|
inputs.append(gr.Number(
|
|
label=label,
|
|
info=help_text,
|
|
value=config.get("default"),
|
|
minimum=config.get("min"),
|
|
maximum=config.get("max")
|
|
))
|
|
|
|
else:
|
|
inputs.append(gr.Textbox(
|
|
label=label,
|
|
info=help_text,
|
|
placeholder=config.get("placeholder", ""),
|
|
value=config.get("default", "")
|
|
))
|
|
|
|
return gr.Interface(
|
|
fn=run_scrape,
|
|
inputs=inputs,
|
|
outputs="text",
|
|
title="Skill Seekers - Scrape Documentation",
|
|
description="Convert documentation to AI-ready skills"
|
|
)
|
|
```
|
|
|
|
**Benefits:**
|
|
- ✅ Automatic form generation from argument definitions
|
|
- ✅ Runs in browser
|
|
- ✅ Can be deployed as web service
|
|
- ✅ Great for non-technical users
|
|
|
|
---
|
|
|
|
### Option 3: Desktop GUI (Tkinter/PyQt)
|
|
|
|
```python
|
|
# Example: Tkinter GUI
|
|
# src/skill_seekers/ui/desktop/app.py
|
|
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
from skill_seekers.cli.arguments.scrape import SCRAPER_ARGUMENTS
|
|
|
|
|
|
class SkillSeekersGUI:
|
|
"""Desktop GUI for Skill Seekers."""
|
|
|
|
def __init__(self, root):
|
|
self.root = root
|
|
self.root.title("Skill Seekers")
|
|
|
|
# Create notebook (tabs)
|
|
self.notebook = ttk.Notebook(root)
|
|
self.notebook.pack(fill='both', expand=True)
|
|
|
|
# Create tabs from command arguments
|
|
self._create_scrape_tab()
|
|
self._create_github_tab()
|
|
|
|
def _create_scrape_tab(self):
|
|
"""Create scrape tab from argument definitions."""
|
|
tab = ttk.Frame(self.notebook)
|
|
self.notebook.add(tab, text="Scrape")
|
|
|
|
# Group by section
|
|
sections = {}
|
|
for name, config in SCRAPER_ARGUMENTS.items():
|
|
section = config.get("ui_section", "General")
|
|
sections.setdefault(section, []).append((name, config))
|
|
|
|
# Create form fields
|
|
row = 0
|
|
for section_name, fields in sections.items():
|
|
# Section label
|
|
ttk.Label(tab, text=section_name, font=('Arial', 10, 'bold')).grid(
|
|
row=row, column=0, columnspan=2, pady=(10, 5), sticky='w'
|
|
)
|
|
row += 1
|
|
|
|
for name, config in fields:
|
|
# Label
|
|
label = ttk.Label(tab, text=config.get("ui_label", name))
|
|
label.grid(row=row, column=0, sticky='w', padx=5)
|
|
|
|
# Input widget
|
|
if config.get("type") == bool:
|
|
var = tk.BooleanVar(value=config.get("default", False))
|
|
widget = ttk.Checkbutton(tab, variable=var)
|
|
else:
|
|
var = tk.StringVar(value=str(config.get("default", "")))
|
|
widget = ttk.Entry(tab, textvariable=var, width=40)
|
|
|
|
widget.grid(row=row, column=1, sticky='ew', padx=5)
|
|
|
|
# Help tooltip (simplified)
|
|
if "help" in config:
|
|
label.bind("<Enter>", lambda e, h=config["help"]: self._show_tooltip(h))
|
|
|
|
row += 1
|
|
```
|
|
|
|
---
|
|
|
|
## Enhancing Arguments for UI
|
|
|
|
To make arguments even more UI-friendly, we can add optional UI metadata:
|
|
|
|
```python
|
|
# arguments/scrape.py - Enhanced with UI metadata
|
|
|
|
SCRAPER_ARGUMENTS = {
|
|
"url": {
|
|
"type": str,
|
|
"help": "Documentation URL to scrape",
|
|
|
|
# UI-specific metadata (optional)
|
|
"ui_label": "Documentation URL",
|
|
"ui_section": "Source", # Groups fields in UI
|
|
"ui_order": 1, # Display order
|
|
"placeholder": "https://docs.example.com",
|
|
"required": True,
|
|
"validate": "url", # Auto-validate as URL
|
|
},
|
|
|
|
"name": {
|
|
"type": str,
|
|
"help": "Name for the generated skill",
|
|
|
|
"ui_label": "Skill Name",
|
|
"ui_section": "Output",
|
|
"ui_order": 2,
|
|
"placeholder": "e.g., React, Python, Docker",
|
|
"validate": r"^[a-zA-Z0-9_-]+$", # Regex validation
|
|
},
|
|
|
|
"max_pages": {
|
|
"type": int,
|
|
"help": "Maximum pages to scrape",
|
|
"default": 100,
|
|
|
|
"ui_label": "Max Pages",
|
|
"ui_section": "Limits",
|
|
"ui_widget": "slider", # Use slider in GUI
|
|
"min": 1,
|
|
"max": 1000,
|
|
"step": 10,
|
|
},
|
|
|
|
"async_mode": {
|
|
"type": bool,
|
|
"help": "Enable async mode for faster scraping",
|
|
"default": False,
|
|
|
|
"ui_label": "Async Mode",
|
|
"ui_section": "Performance",
|
|
"ui_widget": "toggle", # Use toggle switch in GUI
|
|
"advanced": True, # Hide in simple mode
|
|
},
|
|
|
|
"api_key": {
|
|
"type": str,
|
|
"help": "API key for enhancement",
|
|
|
|
"ui_label": "API Key",
|
|
"ui_section": "Authentication",
|
|
"ui_widget": "password", # Mask input
|
|
"env_var": "ANTHROPIC_API_KEY", # Can read from env
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## UI Modes
|
|
|
|
With this architecture, we can support multiple UI modes:
|
|
|
|
```bash
|
|
# CLI mode (default)
|
|
skill-seekers scrape --url https://react.dev --name react
|
|
|
|
# TUI mode (interactive)
|
|
skill-seekers ui scrape
|
|
|
|
# Web mode
|
|
skill-seekers ui --web
|
|
|
|
# Desktop mode
|
|
skill-seekers ui --desktop
|
|
```
|
|
|
|
### Implementation
|
|
|
|
```python
|
|
# src/skill_seekers/cli/ui_command.py
|
|
|
|
import argparse
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("command", nargs="?", help="Command to run in UI")
|
|
parser.add_argument("--web", action="store_true", help="Launch web UI")
|
|
parser.add_argument("--desktop", action="store_true", help="Launch desktop UI")
|
|
parser.add_argument("--port", type=int, default=7860, help="Port for web UI")
|
|
args = parser.parse_args()
|
|
|
|
if args.web:
|
|
from skill_seekers.ui.web.app import launch_web_ui
|
|
launch_web_ui(port=args.port)
|
|
|
|
elif args.desktop:
|
|
from skill_seekers.ui.desktop.app import launch_desktop_ui
|
|
launch_desktop_ui()
|
|
|
|
else:
|
|
# Default to TUI
|
|
from skill_seekers.ui.console.app import launch_tui
|
|
launch_tui(command=args.command)
|
|
```
|
|
|
|
---
|
|
|
|
## Migration Path to UI
|
|
|
|
### Phase 1: Refactor (Current Proposal)
|
|
- Create `arguments/` module with structured definitions
|
|
- Keep CLI working exactly as before
|
|
- **Enables:** UI can introspect arguments
|
|
|
|
### Phase 2: Add TUI (Optional, ~1 week)
|
|
- Build console UI using `rich` or `textual`
|
|
- Reuses argument definitions
|
|
- **Benefit:** Better UX for terminal users
|
|
|
|
### Phase 3: Add Web UI (Optional, ~2 weeks)
|
|
- Build web UI using `gradio` or `streamlit`
|
|
- Same argument definitions
|
|
- **Benefit:** Accessible to non-technical users
|
|
|
|
### Phase 4: Add Desktop GUI (Optional, ~3 weeks)
|
|
- Build native desktop app using `tkinter` or `PyQt`
|
|
- **Benefit:** Standalone application experience
|
|
|
|
---
|
|
|
|
## Code Example: Complete UI Integration
|
|
|
|
Here's how a complete integration would look:
|
|
|
|
```python
|
|
# src/skill_seekers/arguments/base.py
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Optional, Any, Callable
|
|
|
|
|
|
@dataclass
|
|
class ArgumentDef:
|
|
"""Definition of a CLI argument with UI metadata."""
|
|
|
|
# Core argparse fields
|
|
name: str
|
|
type: type
|
|
help: str
|
|
default: Any = None
|
|
choices: Optional[list] = None
|
|
action: Optional[str] = None
|
|
|
|
# UI metadata (all optional)
|
|
ui_label: Optional[str] = None
|
|
ui_section: str = "General"
|
|
ui_order: int = 0
|
|
ui_widget: str = "auto" # auto, text, checkbox, slider, select, etc.
|
|
placeholder: Optional[str] = None
|
|
required: bool = False
|
|
advanced: bool = False # Hide in simple mode
|
|
|
|
# Validation
|
|
validate: Optional[str] = None # "url", "email", regex pattern
|
|
min: Optional[float] = None
|
|
max: Optional[float] = None
|
|
|
|
# Environment
|
|
env_var: Optional[str] = None # Read default from env
|
|
|
|
|
|
class ArgumentRegistry:
|
|
"""Registry of all command arguments."""
|
|
|
|
_commands = {}
|
|
|
|
@classmethod
|
|
def register(cls, command: str, arguments: list[ArgumentDef]):
|
|
"""Register arguments for a command."""
|
|
cls._commands[command] = arguments
|
|
|
|
@classmethod
|
|
def get_arguments(cls, command: str) -> list[ArgumentDef]:
|
|
"""Get all arguments for a command."""
|
|
return cls._commands.get(command, [])
|
|
|
|
@classmethod
|
|
def to_argparse(cls, command: str, parser):
|
|
"""Add registered arguments to argparse parser."""
|
|
for arg in cls._commands.get(command, []):
|
|
kwargs = {
|
|
"help": arg.help,
|
|
"default": arg.default,
|
|
}
|
|
if arg.type != bool:
|
|
kwargs["type"] = arg.type
|
|
if arg.action:
|
|
kwargs["action"] = arg.action
|
|
if arg.choices:
|
|
kwargs["choices"] = arg.choices
|
|
|
|
parser.add_argument(f"--{arg.name}", **kwargs)
|
|
|
|
@classmethod
|
|
def to_ui_form(cls, command: str) -> list[dict]:
|
|
"""Convert arguments to UI form schema."""
|
|
return [
|
|
{
|
|
"name": arg.name,
|
|
"label": arg.ui_label or arg.name,
|
|
"type": arg.ui_widget if arg.ui_widget != "auto" else cls._infer_widget(arg),
|
|
"section": arg.ui_section,
|
|
"order": arg.ui_order,
|
|
"required": arg.required,
|
|
"placeholder": arg.placeholder,
|
|
"validation": arg.validate,
|
|
"min": arg.min,
|
|
"max": arg.max,
|
|
}
|
|
for arg in cls._commands.get(command, [])
|
|
]
|
|
|
|
@staticmethod
|
|
def _infer_widget(arg: ArgumentDef) -> str:
|
|
"""Infer UI widget type from argument type."""
|
|
if arg.type == bool:
|
|
return "checkbox"
|
|
elif arg.choices:
|
|
return "select"
|
|
elif arg.type == int and arg.min is not None and arg.max is not None:
|
|
return "slider"
|
|
else:
|
|
return "text"
|
|
|
|
|
|
# Register all commands
|
|
from .scrape import SCRAPE_ARGUMENTS
|
|
from .github import GITHUB_ARGUMENTS
|
|
|
|
ArgumentRegistry.register("scrape", SCRAPE_ARGUMENTS)
|
|
ArgumentRegistry.register("github", GITHUB_ARGUMENTS)
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Question | Answer |
|
|
|----------|--------|
|
|
| **Is this refactor UI-friendly?** | ✅ Yes, actively enables UI development |
|
|
| **What UI types are supported?** | Console (TUI), Web, Desktop GUI |
|
|
| **How much extra work for UI?** | Minimal - reuse argument definitions |
|
|
| **Can we start with CLI only?** | ✅ Yes, UI is optional future work |
|
|
| **Should we add UI metadata now?** | Optional - can be added incrementally |
|
|
|
|
---
|
|
|
|
## Recommendation
|
|
|
|
1. **Proceed with the refactor** - It's the right foundation
|
|
2. **Start with CLI** - Get it working first
|
|
3. **Add basic UI metadata** - Just `ui_label` and `ui_section`
|
|
4. **Build TUI later** - When you want better terminal UX
|
|
5. **Consider Web UI** - If you need non-technical users
|
|
|
|
The refactor **doesn't commit you to a UI**, but makes it **easy to add one later**.
|
|
|
|
---
|
|
|
|
*End of Document*
|