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>
17 KiB
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:
- ✅ Single source of truth for all command options
- ✅ Self-documenting argument definitions
- ✅ Easy to introspect for dynamic form generation
- ✅ 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)
# 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)
# 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
# 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
# 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)
# 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:
# 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:
# 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
# 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
richortextual - Reuses argument definitions
- Benefit: Better UX for terminal users
Phase 3: Add Web UI (Optional, ~2 weeks)
- Build web UI using
gradioorstreamlit - Same argument definitions
- Benefit: Accessible to non-technical users
Phase 4: Add Desktop GUI (Optional, ~3 weeks)
- Build native desktop app using
tkinterorPyQt - Benefit: Standalone application experience
Code Example: Complete UI Integration
Here's how a complete integration would look:
# 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
- Proceed with the refactor - It's the right foundation
- Start with CLI - Get it working first
- Add basic UI metadata - Just
ui_labelandui_section - Build TUI later - When you want better terminal UX
- 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