feat(cli,drone): CLI front door — seedgo-compliant discovery + drone internal module
CLI entry point rewritten to follow seedgo auto-discovery pattern: - discover_modules() scans for handle_command() instead of hardcoded list - Introspection shows routable modules vs import-only services - init_project.py now owns the 'aipass' command with subcommand routing - Rich colors fixed (force_terminal=True) for drone subprocess pipes CLI registered as drone internal module (drone_adapter.py): - drone @cli accessible from any directory, not just inside AIPass repo - drone @cli aipass init works from external projects - Drone introspection now shows 3 internal modules: cli, drone, seedgo Drone: CLI added to interactive allow list for terminal passthrough. Co-Authored-By: @devpulse <devpulse@aipass>
This commit is contained in:
+163
-268
@@ -1,16 +1,18 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: cli.py
|
||||
# Description: Entry point CLI for drone @cli — display service and Rich formatting
|
||||
# Version: 1.0.0
|
||||
# Description: Entry point for drone @cli — seedgo-compliant module discovery and routing
|
||||
# Version: 2.0.0
|
||||
# Created: 2026-03-08
|
||||
# Modified: 2026-03-08
|
||||
# Modified: 2026-03-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
CLI Branch - Universal Display/Output Service Provider
|
||||
|
||||
PURPOSE: Provides consistent CLI display across all AIPass branches.
|
||||
Similar to Prax (logging service), CLI is a service provider for output.
|
||||
Routes commands to auto-discovered modules via seedgo pattern.
|
||||
- 'aipass init /path' -> init_project module (handle_command)
|
||||
- Modules auto-discovered from modules/ directory
|
||||
- Service modules (display, templates) also discoverable via handle_command()
|
||||
|
||||
SERVICES PROVIDED:
|
||||
- Display: headers, success/error/warning messages, sections
|
||||
@@ -23,9 +25,9 @@ ARCHITECTURE:
|
||||
"""
|
||||
|
||||
import sys
|
||||
import importlib
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from typing import List, Any
|
||||
|
||||
# Prax logger
|
||||
from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
@@ -34,96 +36,161 @@ from aipass.prax.apps.modules.logger import system_logger as logger
|
||||
from rich.table import Table
|
||||
from rich.columns import Columns
|
||||
from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
from rich.rule import Rule
|
||||
from rich import box
|
||||
|
||||
# CLI modules (showcasing our own services!)
|
||||
from aipass.cli.apps.modules.display import console as CONSOLE, header, success, error, warning, section
|
||||
from aipass.cli.apps.modules.templates import operation_start, operation_complete
|
||||
|
||||
VERSION = "2.0.0"
|
||||
CLI_ROOT = Path(__file__).parent
|
||||
MODULES_DIR = CLI_ROOT / "modules"
|
||||
|
||||
# Service modules — import-only, listed separately from command modules
|
||||
SERVICE_MODULES = {"display", "templates"}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# INTROSPECTION DISPLAY
|
||||
# MODULE DISCOVERY
|
||||
# =============================================================================
|
||||
|
||||
def get_handler_exports(handler_package_name: str) -> List[str]:
|
||||
"""Get exported functions from handler package"""
|
||||
try:
|
||||
handler_module = importlib.import_module(f"aipass.cli.apps.handlers.{handler_package_name}")
|
||||
return getattr(handler_module, '__all__', [])
|
||||
except Exception:
|
||||
return []
|
||||
def discover_modules() -> List[Any]:
|
||||
"""Auto-discover CLI modules in modules/ directory.
|
||||
|
||||
|
||||
def print_introspection():
|
||||
Scans modules/*.py for files with handle_command().
|
||||
Excludes __init__.py and private files.
|
||||
Follows seedgo discovery pattern exactly.
|
||||
"""
|
||||
Display discovered modules and handlers - Quick reference
|
||||
modules = []
|
||||
|
||||
if not MODULES_DIR.exists():
|
||||
return modules
|
||||
|
||||
for file_path in sorted(MODULES_DIR.glob("*.py")):
|
||||
if file_path.name.startswith("_"):
|
||||
continue
|
||||
|
||||
try:
|
||||
spec = importlib.util.spec_from_file_location(file_path.stem, file_path)
|
||||
if spec is None or spec.loader is None:
|
||||
continue
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
if hasattr(module, "handle_command"):
|
||||
modules.append(module)
|
||||
except Exception as e:
|
||||
logger.error(f"[CLI] Failed to load module {file_path.stem}: {e}")
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
def route_command(command: str, args: List[str], modules: List[Any]) -> bool:
|
||||
"""Route command to appropriate module."""
|
||||
for module in modules:
|
||||
try:
|
||||
if module.handle_command(command, args):
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"[CLI] Module error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DISPLAY
|
||||
# =============================================================================
|
||||
|
||||
def print_introspection() -> None:
|
||||
"""Display auto-discovered modules — seedgo-compliant introspection.
|
||||
|
||||
Called when cli.py runs with no arguments.
|
||||
Shows what's connected to CLI entry point (modules and handler domains).
|
||||
Shows command modules (with handle_command) and service modules separately.
|
||||
"""
|
||||
modules = discover_modules()
|
||||
|
||||
# Separate command modules from service modules
|
||||
command_modules = [m for m in modules
|
||||
if getattr(m, "__name__", "").split(".")[-1] not in SERVICE_MODULES]
|
||||
service_modules = [m for m in modules
|
||||
if getattr(m, "__name__", "").split(".")[-1] in SERVICE_MODULES]
|
||||
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("[bold cyan]CLI - Command Line Interface Branch[/bold cyan]")
|
||||
CONSOLE.print(f" Version: {VERSION}")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("[dim]Universal Display & Output Service Provider[/dim]")
|
||||
CONSOLE.print()
|
||||
|
||||
# Discover modules
|
||||
CONSOLE.print("[yellow]Discovered Modules:[/yellow] 3")
|
||||
# Discovered command modules
|
||||
CONSOLE.print(f"[yellow]Discovered Modules:[/yellow] {len(command_modules)}")
|
||||
for module in command_modules:
|
||||
name = getattr(module, "__name__", "unknown").split(".")[-1]
|
||||
desc = (module.__doc__ or "").strip().split("\n")[0] if module.__doc__ else "No description"
|
||||
CONSOLE.print(f" [cyan]\u2022[/cyan] {name} \u2014 {desc}")
|
||||
if not command_modules:
|
||||
CONSOLE.print(" [dim]No command modules discovered[/dim]")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print(" [cyan]•[/cyan] display")
|
||||
CONSOLE.print(" [cyan]•[/cyan] templates")
|
||||
CONSOLE.print(" [cyan]•[/cyan] console (Rich library wrapper)")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("[dim]Run 'python3 cli.py --help' for usage information[/dim]")
|
||||
|
||||
# Service modules (import-only, but with utility commands)
|
||||
if service_modules:
|
||||
CONSOLE.print(f"[yellow]Services:[/yellow] {len(service_modules)}")
|
||||
for module in service_modules:
|
||||
name = getattr(module, "__name__", "unknown").split(".")[-1]
|
||||
desc = (module.__doc__ or "").strip().split("\n")[0] if module.__doc__ else "No description"
|
||||
CONSOLE.print(f" [cyan]\u2022[/cyan] {name} \u2014 {desc}")
|
||||
CONSOLE.print()
|
||||
|
||||
CONSOLE.print("[yellow]Next:[/yellow] Explore a module")
|
||||
CONSOLE.print(" [green]drone @cli aipass[/green] [dim]# Project commands[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli aipass init --help[/green] [dim]# Bootstrap a project[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli --help[/green] [dim]# Full usage guide[/dim]")
|
||||
CONSOLE.print()
|
||||
|
||||
|
||||
def print_help():
|
||||
def print_help() -> None:
|
||||
"""Display Rich-formatted help - CLI services showcase!
|
||||
|
||||
Demonstrates:
|
||||
- How to import and use CLI modules
|
||||
- Rich formatting patterns (headers, tables, panels, columns)
|
||||
- Drone compliance with Commands line
|
||||
- Beautiful terminal presentation
|
||||
Shows COMMANDS, EXAMPLES, full reference.
|
||||
Follows seedgo help pattern.
|
||||
"""
|
||||
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 1: MAIN HEADER
|
||||
# Demonstrates: header() function from cli.apps.modules.display
|
||||
# =============================================================================
|
||||
header("CLI - Display & Templates Service Provider")
|
||||
|
||||
CONSOLE.print("[dim]Universal display and output formatting for all AIPass branches[/dim]")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print("\u2500" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 2: WHAT IS CLI?
|
||||
# Demonstrates: Rich text styling with [bold], [cyan], [green], etc.
|
||||
# =============================================================================
|
||||
# What is CLI
|
||||
CONSOLE.print("[bold cyan]WHAT IS CLI?[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("CLI is the [bold]Display & Templates Service[/bold] - like Prax for logging:")
|
||||
CONSOLE.print(" [green]✓[/green] Centralized display formatting (headers, tables, panels)")
|
||||
CONSOLE.print(" [green]✓[/green] Reusable templates for common operations")
|
||||
CONSOLE.print(" [green]✓[/green] Rich library integration for beautiful output")
|
||||
CONSOLE.print(" [green]✓[/green] Consistent styling across all AIPass branches")
|
||||
CONSOLE.print(" [green]\u2713[/green] Centralized display formatting (headers, tables, panels)")
|
||||
CONSOLE.print(" [green]\u2713[/green] Reusable templates for common operations")
|
||||
CONSOLE.print(" [green]\u2713[/green] Rich library integration for beautiful output")
|
||||
CONSOLE.print(" [green]\u2713[/green] Consistent styling across all AIPass branches")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("Update CLI once → All branches instantly benefit from improvements")
|
||||
CONSOLE.print("Update CLI once \u2192 All branches instantly benefit from improvements")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print("\u2500" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 3: PUBLIC SERVICES
|
||||
# Demonstrates: Table() from rich.table with columns, styling
|
||||
# =============================================================================
|
||||
# Commands
|
||||
CONSOLE.print("[bold cyan]COMMANDS:[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print(" [green]drone @cli[/green] [dim]# Show discovered modules[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli aipass[/green] [dim]# Project commands[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli aipass init[/green] [dim]# Bootstrap a project[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli aipass init /path MyProj[/green] [dim]# Bootstrap with name[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli display[/green] [dim]# Display module info[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli display demo[/green] [dim]# Run display demo[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli templates[/green] [dim]# Templates module info[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli templates demo[/green] [dim]# Run templates demo[/dim]")
|
||||
CONSOLE.print(" [green]drone @cli --help[/green] [dim]# This help message[/dim]")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("\u2500" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# Public services
|
||||
CONSOLE.print("[bold cyan]PUBLIC SERVICES (apps/modules/):[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
|
||||
@@ -145,13 +212,10 @@ def print_help():
|
||||
|
||||
CONSOLE.print(services_table)
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print("\u2500" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 4: IMPORT EXAMPLES
|
||||
# Demonstrates: Code examples with [dim] styling and [yellow] for labels
|
||||
# =============================================================================
|
||||
# Import examples
|
||||
CONSOLE.print("[bold cyan]HOW TO IMPORT CLI SERVICES:[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
|
||||
@@ -164,257 +228,88 @@ def print_help():
|
||||
CONSOLE.print()
|
||||
|
||||
CONSOLE.print("[yellow]Rich console:[/yellow]")
|
||||
CONSOLE.print("[dim] from rich.console import Console[/dim]")
|
||||
CONSOLE.print("[dim] CONSOLE = Console() # Access Rich library directly[/dim]")
|
||||
CONSOLE.print("[dim] from aipass.cli.apps.modules.display import console[/dim]")
|
||||
CONSOLE.print("[dim] console.print('[bold]Hello[/bold]') # Rich formatted output[/dim]")
|
||||
CONSOLE.print()
|
||||
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print("\u2500" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 5: USAGE EXAMPLES
|
||||
# Demonstrates: Columns() for side-by-side layout
|
||||
# =============================================================================
|
||||
CONSOLE.print("[bold cyan]USAGE EXAMPLES:[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
|
||||
example_cols = [
|
||||
"[yellow]Display header:[/yellow]\n[dim]header('Create Branch',\n {'Name': 'feature',\n 'Type': 'module'})[/dim]",
|
||||
"[yellow]Show success:[/yellow]\n[dim]success('Files created',\n items=12,\n time='2.3s')[/dim]",
|
||||
"[yellow]Show error:[/yellow]\n[dim]error('Path not found',\n suggestion='Check spelling')[/dim]"
|
||||
]
|
||||
|
||||
CONSOLE.print(Columns(example_cols, equal=True, expand=True))
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 6: ARCHITECTURE
|
||||
# Demonstrates: Panel() for highlighted content blocks
|
||||
# =============================================================================
|
||||
# Architecture
|
||||
CONSOLE.print("[bold cyan]ARCHITECTURE:[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
|
||||
arch_text = """[bold]CLI Branch Structure:[/bold]
|
||||
|
||||
[green]✓[/green] apps/modules/ = PUBLIC API (what branches import)
|
||||
[green]\u2713[/green] apps/modules/ = PUBLIC API (what branches import)
|
||||
- display.py Display functions (header, success, error, etc.)
|
||||
- templates.py Standard operation patterns
|
||||
- init_project.py Project bootstrap (aipass init)
|
||||
|
||||
[green]✓[/green] apps/handlers/ = PRIVATE (internal implementation)
|
||||
[green]\u2713[/green] apps/handlers/ = PRIVATE (internal implementation)
|
||||
- display/ Header, message formatters
|
||||
- templates/ Operation patterns
|
||||
- init/ Bootstrap logic
|
||||
|
||||
[green]✓[/green] Rich library = Underlying formatting engine
|
||||
[green]\u2713[/green] Rich library = Underlying formatting engine
|
||||
- Console, Table, Panel, Columns, Text styling"""
|
||||
|
||||
CONSOLE.print(Panel(arch_text, border_style="green", padding=(1, 2), box=box.ROUNDED))
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print("\u2500" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 8: FOR BRANCHES USING CLI
|
||||
# Demonstrates: Service provider pattern explanation
|
||||
# =============================================================================
|
||||
CONSOLE.print("[bold cyan]FOR BRANCHES USING CLI:[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
|
||||
workflow_text = """[bold]Replace custom display code with CLI imports:[/bold]
|
||||
|
||||
[green]✓[/green] Displaying headers? → [dim]from aipass.cli.apps.modules.display import header[/dim]
|
||||
[green]✓[/green] Showing success/errors? → [dim]from aipass.cli.apps.modules.display import success, error[/dim]
|
||||
[green]✓[/green] Operation templates? → [dim]from aipass.cli.apps.modules.templates import operation_*[/dim]
|
||||
[green]✓[/green] Rich formatting? → [dim]from rich.console import Console[/dim]
|
||||
|
||||
[bold]Benefits:[/bold]
|
||||
• No code duplication across branches
|
||||
• Consistent formatting system-wide
|
||||
• Update CLI once → affects all branches instantly
|
||||
• Rich library integration done once, used everywhere"""
|
||||
|
||||
CONSOLE.print(Panel(workflow_text, border_style="green", padding=(1, 2), box=box.ROUNDED))
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 9: QUICK REFERENCE
|
||||
# Demonstrates: Using both CONSOLE.print() and module functions
|
||||
# =============================================================================
|
||||
CONSOLE.print("[bold cyan]QUICK REFERENCE:[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
|
||||
quick_ref = Table(show_header=True, header_style="bold cyan", border_style="dim")
|
||||
quick_ref.add_column("Task", style="green")
|
||||
quick_ref.add_column("CLI Function", style="yellow")
|
||||
quick_ref.add_column("Example", style="dim")
|
||||
|
||||
quick_ref.add_row(
|
||||
"Display title",
|
||||
"header()",
|
||||
"header('Task Complete')"
|
||||
)
|
||||
quick_ref.add_row(
|
||||
"Success message",
|
||||
"success()",
|
||||
"success('Created', items=5)"
|
||||
)
|
||||
quick_ref.add_row(
|
||||
"Error message",
|
||||
"error()",
|
||||
"error('Failed', suggestion='Retry')"
|
||||
)
|
||||
quick_ref.add_row(
|
||||
"Warning message",
|
||||
"warning()",
|
||||
"warning('Be careful')"
|
||||
)
|
||||
quick_ref.add_row(
|
||||
"Section break",
|
||||
"section()",
|
||||
"section('Results')"
|
||||
)
|
||||
quick_ref.add_row(
|
||||
"Operation start",
|
||||
"operation_start()",
|
||||
"operation_start('Process', count=10)"
|
||||
)
|
||||
quick_ref.add_row(
|
||||
"Operation end",
|
||||
"operation_complete()",
|
||||
"operation_complete(created=5, failed=0)"
|
||||
)
|
||||
|
||||
CONSOLE.print(quick_ref)
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 10: FULL DOCUMENTATION
|
||||
# =============================================================================
|
||||
CONSOLE.print("[bold cyan]FULL DOCUMENTATION:[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
_cli_root = Path(__file__).resolve().parents[1] # cli.py -> apps -> cli
|
||||
CONSOLE.print(f" [yellow]Source code:[/yellow] [dim]{_cli_root / 'apps'}[/dim]")
|
||||
CONSOLE.print(f" [yellow]Public API:[/yellow] [dim]{_cli_root / 'apps' / 'modules'}[/dim]")
|
||||
CONSOLE.print(f" [yellow]Implementation:[/yellow] [dim]{_cli_root / 'apps' / 'handlers'}[/dim]")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 11: RICH FORMATTING TIPS
|
||||
# This section itself demonstrates Rich formatting!
|
||||
# =============================================================================
|
||||
CONSOLE.print("[bold cyan]RICH FORMATTING TIPS:[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print(" [yellow]Text styles:[/yellow] [bold]bold[/bold], [dim]dim[/dim], [italic]italic[/italic], [underline]underline[/underline]")
|
||||
CONSOLE.print(" [yellow]Colors:[/yellow] [red]red[/red], [green]green[/green], [yellow]yellow[/yellow], [blue]blue[/blue], [cyan]cyan[/cyan]")
|
||||
CONSOLE.print(" [yellow]Icons:[/yellow] ✓ ✅ ❌ ⚠️ ⚙️ → • — ─")
|
||||
CONSOLE.print(" [yellow]Structures:[/yellow] Table, Panel, Columns, Text, Progress")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 70)
|
||||
CONSOLE.print()
|
||||
|
||||
# =============================================================================
|
||||
# SECTION 12: DRONE COMPLIANCE
|
||||
# CRITICAL: Commands line must be present for drone discovery
|
||||
# =============================================================================
|
||||
CONSOLE.print("[dim]Commands: help, --help, -h[/dim]")
|
||||
# Drone compliance — commands line
|
||||
CONSOLE.print("[dim]Commands: aipass, display, templates, demo, --help[/dim]")
|
||||
CONSOLE.print()
|
||||
|
||||
|
||||
def show_version():
|
||||
"""Print version from META DATA HEADER."""
|
||||
CONSOLE.print("CLI v0.2.0")
|
||||
"""Print version."""
|
||||
CONSOLE.print(f"CLI v{VERSION}")
|
||||
|
||||
|
||||
def _handle_aipass(args):
|
||||
"""Route aipass subcommands."""
|
||||
if not args or (args[0] in ("--help", "-h", "help")):
|
||||
_print_aipass_introspection()
|
||||
return
|
||||
# =============================================================================
|
||||
# MAIN ENTRY POINT
|
||||
# =============================================================================
|
||||
|
||||
subcmd = args[0]
|
||||
sub_args = args[1:]
|
||||
def main() -> int:
|
||||
"""Main entry point - routes to modules."""
|
||||
modules = discover_modules()
|
||||
args = sys.argv[1:]
|
||||
|
||||
from aipass.cli.apps.modules.init_project import handle_command
|
||||
if handle_command(subcmd, sub_args):
|
||||
return
|
||||
|
||||
error(f"Unknown aipass subcommand: {subcmd}", suggestion="drone @cli aipass --help")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _print_aipass_introspection():
|
||||
"""Show available aipass subcommands — discovery for new users."""
|
||||
from rich.table import Table
|
||||
|
||||
CONSOLE.print()
|
||||
header("aipass — Project Commands")
|
||||
CONSOLE.print("[dim]Manage AIPass projects from the command line[/dim]")
|
||||
CONSOLE.print()
|
||||
|
||||
table = Table(show_header=True, header_style="bold cyan", border_style="dim")
|
||||
table.add_column("Command", style="green")
|
||||
table.add_column("Description", style="white")
|
||||
table.add_column("Example", style="dim")
|
||||
|
||||
table.add_row(
|
||||
"init",
|
||||
"Bootstrap a new AIPass project",
|
||||
"drone @cli aipass init /path MyProject",
|
||||
)
|
||||
|
||||
CONSOLE.print(table)
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("[dim]Run [bold]drone @cli aipass init --help[/bold] for detailed usage[/dim]")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("[dim]Commands: init, --help[/dim]")
|
||||
CONSOLE.print()
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI branch entry point - shows available services"""
|
||||
|
||||
# Show introspection when run without arguments
|
||||
if len(sys.argv) == 1:
|
||||
# No args -> introspection (discovery mode)
|
||||
if not args:
|
||||
print_introspection()
|
||||
return
|
||||
return 0
|
||||
|
||||
# Handle version flag
|
||||
if len(sys.argv) > 1 and sys.argv[1] in ['--version', '-V']:
|
||||
show_version()
|
||||
return
|
||||
|
||||
# Handle help flags
|
||||
if len(sys.argv) > 1 and sys.argv[1] in ['--help', '-h', 'help']:
|
||||
# Help flag -> full help with usage
|
||||
if args[0] in ["--help", "-h", "help"]:
|
||||
print_help()
|
||||
return
|
||||
return 0
|
||||
|
||||
# Route subcommands
|
||||
command = sys.argv[1]
|
||||
cmd_args = sys.argv[2:] if len(sys.argv) > 2 else []
|
||||
if args[0] in ["--version", "-V"]:
|
||||
show_version()
|
||||
return 0
|
||||
|
||||
if command == "aipass":
|
||||
_handle_aipass(cmd_args)
|
||||
return
|
||||
command = args[0]
|
||||
remaining = args[1:] if len(args) > 1 else []
|
||||
|
||||
# Route to modules
|
||||
if route_command(command, remaining, modules):
|
||||
return 0
|
||||
|
||||
# Unknown command
|
||||
error(f"Unknown command: {command}", suggestion="Run 'drone @cli --help' for usage")
|
||||
sys.exit(1)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
sys.exit(main())
|
||||
except KeyboardInterrupt:
|
||||
CONSOLE.print("\n[yellow]Operation cancelled[/yellow]")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error(f"CLI error: {e}", exc_info=True)
|
||||
CONSOLE.print(f"\n[red]❌ Error: {e}[/red]")
|
||||
CONSOLE.print(f"\n[red]Error: {e}[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -33,9 +33,9 @@ from rich.columns import Columns
|
||||
# from aipass.prax import logger
|
||||
|
||||
# Initialize Rich console (lowercase follows service instance pattern)
|
||||
CONSOLE = Console() # Internal constant
|
||||
CONSOLE = Console(force_terminal=True) # Internal constant — force_terminal ensures ANSI colors even when piped
|
||||
console = CONSOLE # Primary export (lowercase service instance pattern)
|
||||
err_console = Console(stderr=True) # Stderr console for error/warning output
|
||||
err_console = Console(stderr=True, force_terminal=True) # Stderr console for error/warning output
|
||||
|
||||
# Trigger loaded lazily to avoid circular import
|
||||
_trigger = None
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: init_project.py
|
||||
# Description: Init Project Module — orchestration layer for aipass init
|
||||
# Version: 1.0.0
|
||||
# Description: AIPass Project Commands Module — owns the 'aipass' top-level command
|
||||
# Version: 2.0.0
|
||||
# Created: 2026-03-14
|
||||
# Modified: 2026-03-14
|
||||
# Modified: 2026-03-15
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Init Project Module - PUBLIC API
|
||||
AIPass Project Commands Module
|
||||
|
||||
Thin orchestration module for the `aipass init` command.
|
||||
Routes to the init bootstrap handler for business logic.
|
||||
Owns the 'aipass' top-level command and routes subcommands (init, etc.).
|
||||
Follows seedgo module interface: handle_command(), print_introspection(), print_help().
|
||||
|
||||
Usage:
|
||||
drone @cli aipass init [target_dir] [project_name]
|
||||
Run: drone @cli aipass
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -30,25 +29,201 @@ from aipass.cli.apps.modules.display import console, success, error, header
|
||||
# =============================================================================
|
||||
|
||||
def print_introspection():
|
||||
"""Display module info and connected handlers."""
|
||||
"""Display aipass command info — available subcommands and connected handlers."""
|
||||
from rich.table import Table
|
||||
|
||||
console.print()
|
||||
console.print("[bold cyan]Init Project Module[/bold cyan]")
|
||||
console.print("[bold cyan]aipass — Project Commands[/bold cyan]")
|
||||
console.print("[dim]Manage AIPass projects from the command line[/dim]")
|
||||
console.print()
|
||||
console.print("[dim]Bootstrap an AIPass project in any directory[/dim]")
|
||||
|
||||
# Available subcommands
|
||||
table = Table(show_header=True, header_style="bold cyan", border_style="dim")
|
||||
table.add_column("Command", style="green")
|
||||
table.add_column("Description", style="white")
|
||||
table.add_column("Example", style="dim")
|
||||
|
||||
table.add_row(
|
||||
"init",
|
||||
"Bootstrap a new AIPass project",
|
||||
"drone @cli aipass init /path MyProject",
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
console.print()
|
||||
|
||||
console.print("[yellow]Connected Handlers:[/yellow]")
|
||||
console.print()
|
||||
console.print(" [cyan]handlers/init/[/cyan]")
|
||||
console.print(" [dim]- bootstrap.py[/dim]")
|
||||
console.print()
|
||||
|
||||
console.print("[dim]Run 'drone @cli aipass init --help' for usage[/dim]")
|
||||
console.print("[yellow]Next:[/yellow] Run a subcommand")
|
||||
console.print(" [green]drone @cli aipass init[/green] [dim]# Bootstrap a project[/dim]")
|
||||
console.print(" [green]drone @cli aipass init --help[/green] [dim]# Detailed init usage[/dim]")
|
||||
console.print(" [green]drone @cli aipass --help[/green] [dim]# Full help[/dim]")
|
||||
console.print()
|
||||
|
||||
|
||||
def print_help():
|
||||
"""Display Rich-formatted help for the init command."""
|
||||
"""Display Rich-formatted help for the aipass command and all subcommands."""
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich import box
|
||||
|
||||
console.print()
|
||||
header("aipass — Project Commands")
|
||||
console.print("[dim]Manage AIPass projects from the command line[/dim]")
|
||||
console.print()
|
||||
console.print("─" * 70)
|
||||
console.print()
|
||||
|
||||
# Subcommands
|
||||
console.print("[bold cyan]COMMANDS:[/bold cyan]")
|
||||
console.print()
|
||||
console.print(" [green]drone @cli aipass[/green] [dim]Show available subcommands[/dim]")
|
||||
console.print(" [green]drone @cli aipass init[/green] [dim]Bootstrap in current directory[/dim]")
|
||||
console.print(" [green]drone @cli aipass init /path[/green] [dim]Bootstrap in target directory[/dim]")
|
||||
console.print(" [green]drone @cli aipass init /path MyProj[/green] [dim]Bootstrap with custom name[/dim]")
|
||||
console.print(" [green]drone @cli aipass --help[/green] [dim]This help message[/dim]")
|
||||
console.print()
|
||||
console.print("─" * 70)
|
||||
console.print()
|
||||
|
||||
# What init creates
|
||||
console.print("[bold cyan]WHAT INIT CREATES:[/bold cyan]")
|
||||
console.print()
|
||||
|
||||
files_text = """[bold]Project scaffold (6 files):[/bold]
|
||||
|
||||
[green]1.[/green] [yellow]{NAME}_REGISTRY.json[/yellow] Project registry with UUID
|
||||
[green]2.[/green] [yellow].trinity/passport.json[/yellow] Project identity (with registry_id)
|
||||
[green]3.[/green] [yellow].trinity/local.json[/yellow] Session history & learnings
|
||||
[green]4.[/green] [yellow].trinity/observations.json[/yellow] Collaboration patterns
|
||||
[green]5.[/green] [yellow].aipass/aipass_local_prompt.md[/yellow] Local prompt (injected every turn)
|
||||
[green]6.[/green] [yellow]AIPASS.md[/yellow] Project prompt (persists in context)"""
|
||||
|
||||
console.print(Panel(files_text, border_style="green", padding=(1, 2), box=box.ROUNDED))
|
||||
console.print()
|
||||
console.print("─" * 70)
|
||||
console.print()
|
||||
|
||||
# Arguments
|
||||
console.print("[bold cyan]ARGUMENTS (init):[/bold cyan]")
|
||||
console.print()
|
||||
|
||||
args_table = Table(show_header=True, header_style="bold cyan", border_style="dim")
|
||||
args_table.add_column("Argument", style="green")
|
||||
args_table.add_column("Required", style="yellow")
|
||||
args_table.add_column("Default", style="dim")
|
||||
args_table.add_column("Description", style="white")
|
||||
args_table.add_row("target_dir", "No", "Current directory", "Directory to initialize")
|
||||
args_table.add_row("project_name", "No", "Directory name", "Name for registry (auto-uppercased)")
|
||||
console.print(args_table)
|
||||
console.print()
|
||||
console.print("─" * 70)
|
||||
console.print()
|
||||
|
||||
console.print("[dim]Commands: aipass, aipass init, aipass --help[/dim]")
|
||||
console.print()
|
||||
|
||||
|
||||
def handle_command(command: str, args: List[str]) -> bool:
|
||||
"""Handle 'aipass' command with subcommand routing.
|
||||
|
||||
Args:
|
||||
command: The command string (e.g. "aipass")
|
||||
args: Remaining arguments after the command
|
||||
[] -> show introspection (available subcommands)
|
||||
["init"] -> run init
|
||||
["init", "--help"] -> show init help
|
||||
["--help"] -> show full help
|
||||
|
||||
Returns:
|
||||
True if handled, False if not this module's command
|
||||
"""
|
||||
if command != "aipass":
|
||||
return False
|
||||
|
||||
# No subcommand -> show introspection
|
||||
if not args:
|
||||
print_introspection()
|
||||
return True
|
||||
|
||||
# Help flag -> show full help
|
||||
if args[0] in ("--help", "-h", "help"):
|
||||
print_help()
|
||||
return True
|
||||
|
||||
subcmd = args[0]
|
||||
sub_args = args[1:]
|
||||
|
||||
# Route subcommands
|
||||
if subcmd == "init":
|
||||
return _handle_init(sub_args)
|
||||
|
||||
error(f"Unknown aipass subcommand: {subcmd}", suggestion="drone @cli aipass --help")
|
||||
return True
|
||||
|
||||
|
||||
def _handle_init(args: List[str]) -> bool:
|
||||
"""Handle the 'init' subcommand."""
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich import box
|
||||
|
||||
# Handle help flag
|
||||
if args and args[0] in ("--help", "-h", "help"):
|
||||
_print_init_help()
|
||||
return True
|
||||
|
||||
# Parse positional args: [target_dir] [project_name]
|
||||
caller_cwd = os.environ.get("AIPASS_CALLER_CWD", os.getcwd())
|
||||
target = Path(args[0]) if args else Path(caller_cwd)
|
||||
project_name = args[1] if len(args) > 1 else None
|
||||
|
||||
try:
|
||||
result = init_project(target, project_name)
|
||||
except ValueError as exc:
|
||||
error(str(exc), suggestion="Pass a project name explicitly")
|
||||
sys.exit(1)
|
||||
except FileExistsError as exc:
|
||||
error(str(exc), suggestion="Remove the existing file to re-initialize")
|
||||
sys.exit(1)
|
||||
except OSError as exc:
|
||||
error(f"Filesystem error: {exc}")
|
||||
sys.exit(1)
|
||||
|
||||
# Display results
|
||||
console.print()
|
||||
header("Project Initialized")
|
||||
|
||||
# Summary panel
|
||||
summary = (
|
||||
f"[bold]{result['project_name']}[/bold]\n"
|
||||
f"\n"
|
||||
f" [yellow]Registry:[/yellow] {result['registry_file']}\n"
|
||||
f" [yellow]ID:[/yellow] [dim]{result['registry_id'][:8]}...[/dim]\n"
|
||||
f" [yellow]Target:[/yellow] [dim]{result['target']}[/dim]"
|
||||
)
|
||||
console.print(Panel(summary, border_style="green", box=box.ROUNDED))
|
||||
|
||||
# Files table
|
||||
files_table = Table(show_header=True, header_style="bold cyan", border_style="dim")
|
||||
files_table.add_column("#", style="green", width=3)
|
||||
files_table.add_column("File", style="yellow")
|
||||
for i, f in enumerate(result["created_files"], 1):
|
||||
files_table.add_row(str(i), f)
|
||||
console.print(files_table)
|
||||
console.print()
|
||||
|
||||
success(f"Created {len(result['created_files'])} files")
|
||||
console.print()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _print_init_help():
|
||||
"""Display detailed help for the init subcommand."""
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich import box
|
||||
@@ -113,74 +288,6 @@ def print_help():
|
||||
console.print()
|
||||
|
||||
|
||||
def handle_command(command: str, args: List[str]) -> bool:
|
||||
"""Handle 'init' command.
|
||||
|
||||
Args:
|
||||
command: The subcommand string (e.g. "init")
|
||||
args: Remaining positional arguments after the subcommand
|
||||
|
||||
Returns:
|
||||
True if the command was handled, False otherwise
|
||||
"""
|
||||
if command != "init":
|
||||
return False
|
||||
|
||||
# Handle help flag
|
||||
if args and args[0] in ("--help", "-h", "help"):
|
||||
print_help()
|
||||
return True
|
||||
|
||||
# Parse positional args: [target_dir] [project_name]
|
||||
caller_cwd = os.environ.get("AIPASS_CALLER_CWD", os.getcwd())
|
||||
target = Path(args[0]) if args else Path(caller_cwd)
|
||||
project_name = args[1] if len(args) > 1 else None
|
||||
|
||||
try:
|
||||
result = init_project(target, project_name)
|
||||
except ValueError as exc:
|
||||
error(str(exc), suggestion="Pass a project name explicitly")
|
||||
sys.exit(1)
|
||||
except FileExistsError as exc:
|
||||
error(str(exc), suggestion="Remove the existing file to re-initialize")
|
||||
sys.exit(1)
|
||||
except OSError as exc:
|
||||
error(f"Filesystem error: {exc}")
|
||||
sys.exit(1)
|
||||
|
||||
# Display results
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich import box
|
||||
|
||||
console.print()
|
||||
header("Project Initialized")
|
||||
|
||||
# Summary panel
|
||||
summary = (
|
||||
f"[bold]{result['project_name']}[/bold]\n"
|
||||
f"\n"
|
||||
f" [yellow]Registry:[/yellow] {result['registry_file']}\n"
|
||||
f" [yellow]ID:[/yellow] [dim]{result['registry_id'][:8]}...[/dim]\n"
|
||||
f" [yellow]Target:[/yellow] [dim]{result['target']}[/dim]"
|
||||
)
|
||||
console.print(Panel(summary, border_style="green", box=box.ROUNDED))
|
||||
|
||||
# Files table
|
||||
files_table = Table(show_header=True, header_style="bold cyan", border_style="dim")
|
||||
files_table.add_column("#", style="green", width=3)
|
||||
files_table.add_column("File", style="yellow")
|
||||
for i, f in enumerate(result["created_files"], 1):
|
||||
files_table.add_row(str(i), f)
|
||||
console.print(files_table)
|
||||
console.print()
|
||||
|
||||
success(f"Created {len(result['created_files'])} files")
|
||||
console.print()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# MODULE EXPORTS
|
||||
# =============================================================================
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
CLI drone adapter — bridges drone routing to CLI commands.
|
||||
|
||||
Drone discovers this module via aipass.drone.modules._MODULE_REGISTRY
|
||||
and routes `drone @cli <command> [args]` here.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
DRONE_MODULE = {
|
||||
"name": "cli",
|
||||
"version": "2.0.0",
|
||||
"description": "Universal Display & Output Service Provider",
|
||||
}
|
||||
|
||||
|
||||
def handle_command(command: str, args: list[str] | None = None) -> dict:
|
||||
"""Route a drone command to CLI's entry point.
|
||||
|
||||
Captures stdout/stderr and returns as dict for drone CLI to print.
|
||||
"""
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
# Build argv as if `cli <command> [args]` was called
|
||||
original_argv = sys.argv
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
captured_out = StringIO()
|
||||
captured_err = StringIO()
|
||||
|
||||
try:
|
||||
sys.argv = ["cli", command] + args
|
||||
sys.stdout = captured_out
|
||||
sys.stderr = captured_err
|
||||
|
||||
# Import here to avoid circular imports at module level
|
||||
from aipass.cli.apps.cli import main
|
||||
exit_code = main()
|
||||
except SystemExit as e:
|
||||
exit_code = e.code if e.code is not None else 0
|
||||
except Exception as e:
|
||||
captured_err.write(str(e))
|
||||
exit_code = 1
|
||||
finally:
|
||||
sys.argv = original_argv
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
|
||||
return {
|
||||
"stdout": captured_out.getvalue(),
|
||||
"stderr": captured_err.getvalue(),
|
||||
"exit_code": exit_code if isinstance(exit_code, int) else 1,
|
||||
}
|
||||
|
||||
|
||||
def get_help(command: str | None = None) -> str:
|
||||
"""Return help text for CLI as Rich markup strings.
|
||||
|
||||
Returns Rich markup (not captured ANSI) so drone's console.print()
|
||||
renders it cleanly — same pattern as get_introspective().
|
||||
"""
|
||||
if command:
|
||||
result = handle_command(command, ["--help"])
|
||||
return result.get("stdout", "") or result.get("stderr", "")
|
||||
|
||||
# Build help as Rich markup strings (drone renders these)
|
||||
try:
|
||||
from aipass.cli.apps.cli import discover_modules, VERSION
|
||||
modules = discover_modules()
|
||||
except Exception:
|
||||
return "cli — Universal Display & Output Service Provider\nRun 'drone @cli --help' for usage\n"
|
||||
|
||||
lines = []
|
||||
lines.append("")
|
||||
lines.append("[bold cyan]CLI - Display & Templates Service Provider[/bold cyan]")
|
||||
lines.append(f" Version: {VERSION}")
|
||||
lines.append("")
|
||||
lines.append("[dim]Universal display and output formatting for all AIPass branches[/dim]")
|
||||
lines.append("")
|
||||
lines.append("\u2500" * 70)
|
||||
lines.append("")
|
||||
|
||||
# What is CLI
|
||||
lines.append("[bold cyan]WHAT IS CLI?[/bold cyan]")
|
||||
lines.append("")
|
||||
lines.append("CLI is the [bold]Display & Templates Service[/bold] \u2014 it:")
|
||||
lines.append(" [green]\u2713[/green] Provides [green]centralized display formatting[/green] (headers, tables, panels)")
|
||||
lines.append(" [green]\u2713[/green] Reusable templates for common operations")
|
||||
lines.append(" [green]\u2713[/green] Rich library integration for beautiful output")
|
||||
lines.append(" [green]\u2713[/green] Consistent styling across all AIPass branches")
|
||||
lines.append("")
|
||||
|
||||
# Discovered modules
|
||||
if modules:
|
||||
lines.append("[bold cyan]DISCOVERED MODULES:[/bold cyan]")
|
||||
lines.append("")
|
||||
for module in modules:
|
||||
name = getattr(module, "__name__", "unknown").split(".")[-1]
|
||||
desc = (module.__doc__ or "").strip().split("\n")[0] if module.__doc__ else "No description"
|
||||
lines.append(f" [cyan]\u2022[/cyan] {name} \u2014 {desc}")
|
||||
lines.append("")
|
||||
|
||||
lines.append("\u2500" * 70)
|
||||
lines.append("")
|
||||
|
||||
# Usage
|
||||
lines.append("[bold cyan]USAGE:[/bold cyan]")
|
||||
lines.append("")
|
||||
lines.append("[yellow]Commands:[/yellow]")
|
||||
lines.append(" [dim]drone @cli # Show discovered modules[/dim]")
|
||||
lines.append(" [dim]drone @cli aipass # Project commands[/dim]")
|
||||
lines.append(" [dim]drone @cli aipass init # Bootstrap a project[/dim]")
|
||||
lines.append(" [dim]drone @cli aipass init /path MyProj # Bootstrap with name[/dim]")
|
||||
lines.append(" [dim]drone @cli display # Display module info[/dim]")
|
||||
lines.append(" [dim]drone @cli display demo # Run display demo[/dim]")
|
||||
lines.append(" [dim]drone @cli --help # Full usage guide[/dim]")
|
||||
lines.append("")
|
||||
|
||||
lines.append("\u2500" * 70)
|
||||
lines.append("")
|
||||
|
||||
# Commands line for drone discovery
|
||||
lines.append("[dim]Commands: aipass, display, templates, demo, --help[/dim]")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def get_introspective() -> str:
|
||||
"""Discovery mode: show what CLI has connected."""
|
||||
try:
|
||||
from aipass.cli.apps.cli import discover_modules, VERSION, SERVICE_MODULES
|
||||
|
||||
modules = discover_modules()
|
||||
|
||||
# Separate command modules from service modules
|
||||
command_modules = [m for m in modules
|
||||
if getattr(m, "__name__", "").split(".")[-1] not in SERVICE_MODULES]
|
||||
service_modules = [m for m in modules
|
||||
if getattr(m, "__name__", "").split(".")[-1] in SERVICE_MODULES]
|
||||
|
||||
lines = []
|
||||
lines.append("[bold cyan]CLI - Display & Templates Service Provider[/bold cyan]")
|
||||
lines.append(f" Version: {VERSION}")
|
||||
lines.append("")
|
||||
|
||||
# Command modules
|
||||
lines.append(f"[yellow]Discovered Modules:[/yellow] {len(command_modules)}")
|
||||
for module in command_modules:
|
||||
mod_name = getattr(module, "__name__", "unknown").split(".")[-1]
|
||||
desc = (module.__doc__ or "").strip().split("\n")[0] if module.__doc__ else "No description"
|
||||
lines.append(f" [cyan]\u2022[/cyan] {mod_name} \u2014 {desc}")
|
||||
if not command_modules:
|
||||
lines.append(" [dim]No command modules discovered[/dim]")
|
||||
lines.append("")
|
||||
|
||||
# Service modules
|
||||
if service_modules:
|
||||
lines.append(f"[yellow]Services:[/yellow] {len(service_modules)}")
|
||||
for module in service_modules:
|
||||
mod_name = getattr(module, "__name__", "unknown").split(".")[-1]
|
||||
desc = (module.__doc__ or "").strip().split("\n")[0] if module.__doc__ else "No description"
|
||||
lines.append(f" [cyan]\u2022[/cyan] {mod_name} \u2014 {desc}")
|
||||
lines.append("")
|
||||
|
||||
lines.append("[dim]Run 'drone @cli --help' for usage[/dim]")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
except Exception:
|
||||
return "@cli \u2014 Universal Display & Output Service Provider (run 'drone @cli --help' for usage)\n"
|
||||
@@ -105,7 +105,7 @@ By default, drone captures subprocess output (`capture_output=True`) with a 30s
|
||||
|
||||
Commands in the interactive tuple bypass capture and inherit the terminal directly — enabling live Rich output, colors, and no timeout. Only add commands here when Patrick needs full terminal experience.
|
||||
|
||||
**Current interactive commands** (in `apps/drone.py`):
|
||||
**Per-command allowlist** (in `apps/drone.py`):
|
||||
|
||||
| Command | Reason |
|
||||
|--------------|---------------------------------------------|
|
||||
@@ -113,7 +113,13 @@ Commands in the interactive tuple bypass capture and inherit the terminal direct
|
||||
| `snapshot` | Backup snapshot (Rich progress bars) |
|
||||
| `versioned` | Backup versioned (Rich progress, long-running) |
|
||||
|
||||
To add a new command: edit the `interactive` tuple in `_handle_target()` in `apps/drone.py`.
|
||||
**Per-branch allowlist** — all commands from these branches get interactive mode:
|
||||
|
||||
| Branch | Reason |
|
||||
|--------|---------------------------------------------|
|
||||
| `cli` | User-facing CLI with Rich formatted output |
|
||||
|
||||
To add: edit `interactive_commands` or `interactive_branches` in `_handle_target()` in `apps/drone.py`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -209,8 +209,12 @@ def _handle_target(args: List[str]) -> int:
|
||||
command = rest[0]
|
||||
cmd_args = rest[1:]
|
||||
|
||||
# Long-running interactive commands bypass capture + timeout
|
||||
interactive = command in ("monitor", "snapshot", "versioned")
|
||||
# Interactive mode bypasses capture + timeout for human-facing output.
|
||||
# Per-command: specific commands that need live terminal (progress bars, TUI).
|
||||
# Per-branch: all commands from that branch get interactive mode (Rich CLI).
|
||||
interactive_commands = ("monitor", "snapshot", "versioned")
|
||||
interactive_branches = ("cli",)
|
||||
interactive = command in interactive_commands or module_name in interactive_branches
|
||||
|
||||
try:
|
||||
result = route_command(
|
||||
|
||||
@@ -23,6 +23,7 @@ from aipass.prax import logger
|
||||
|
||||
# Maps module name -> import path for its drone_adapter
|
||||
_MODULE_REGISTRY: dict[str, str] = {
|
||||
"cli": "aipass.cli.drone_adapter",
|
||||
"drone": "aipass.drone.drone_adapter",
|
||||
"seedgo": "aipass.seedgo.drone_adapter",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user