diff --git a/CHANGELOG.md b/CHANGELOG.md index 20ca5fb1..5170f6df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,9 @@ PyPI version — not the changelog header. `print_help()`, +2 regression tests. Also rewrote `@ai_mail`'s `print_help()` to render hand-rolled Rich (the `--help` content was complete, just unstyled). A behavioral `--help` check (run it, assert not raw argparse) is noted as a - follow-up. (DPLAN-0217) + follow-up. (DPLAN-0217) On its first CI run the tightened checker immediately + surfaced the same pattern in 4 `@api` modules (`api_key`, `usage_tracker`, + `google_client`, `openrouter_client`) — migrated to Rich, `@api` back to 100%. - **seedgo `readme_check` ignored the `(disabled)` marker in self-scans** — its module-list and test-count scans now skip `foo(disabled).py`, matching the central audit collector. An in-place disabled module no longer trips a false diff --git a/src/aipass/api/apps/modules/api_key.py b/src/aipass/api/apps/modules/api_key.py index 2485d4a2..8c13aadf 100644 --- a/src/aipass/api/apps/modules/api_key.py +++ b/src/aipass/api/apps/modules/api_key.py @@ -250,55 +250,48 @@ def get_validation_rules(provider: str) -> dict: def print_help(): - """Print help output for API key management""" - import argparse - - parser = argparse.ArgumentParser( - prog="drone @api", - description="API Key Management Module - Manage API keys and credentials", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -COMMANDS: - get-key - Retrieve API key for a provider - get-secret - Read secret from provider store - validate - Validate API key - list-providers - List available providers - init - Initialize .env template - -USAGE: - drone @api [args] - drone @api --help - -EXAMPLES: - # Get key for provider - drone @api get-key openrouter - - # Check if a secret exists (masked summary, no raw value) - drone @api get-secret telegram/bot - - # Write secret to a protected file - drone @api get-secret telegram/bot --out /tmp/token.txt - - # Write secret as JSON to a protected file - drone @api get-secret telegram/bot --out /tmp/bot.json --json - - # List secrets for a provider - drone @api get-secret telegram --list - - # Programmatic access (in-process, no stdout): - # from aipass.api.apps.modules.secrets import get_secret - - # Validate key - drone @api validate openrouter - - # List providers - drone @api list-providers - - # Initialize environment - drone @api init - """, - ) - console.print(parser.format_help()) + """Print drone-compliant help output with Rich markup""" + console.print() + console.print("[bold cyan]API_KEY — Manage API keys and credentials[/bold cyan]") + console.print() + console.print("[yellow]COMMANDS:[/yellow]") + console.print(" [cyan]get-key[/cyan] [dim]Retrieve API key for a provider[/dim]") + console.print(" [cyan]get-secret[/cyan] [dim]Read secret from provider store[/dim]") + console.print(" [cyan]validate[/cyan] [dim]Validate API key[/dim]") + console.print(" [cyan]list-providers[/cyan] [dim]List available providers[/dim]") + console.print(" [cyan]init[/cyan] [dim]Initialize .env template[/dim]") + console.print() + console.print("[yellow]USAGE:[/yellow]") + console.print(" [cyan]drone @api[/cyan] [args]") + console.print(" [cyan]drone @api[/cyan] --help") + console.print() + console.print("[yellow]EXAMPLES:[/yellow]") + console.print(" [cyan]drone @api get-key openrouter[/cyan]") + console.print() + console.print(" [dim]# Check if a secret exists (masked summary, no raw value)[/dim]") + console.print(" [cyan]drone @api get-secret telegram/bot[/cyan]") + console.print() + console.print(" [dim]# Write secret to a protected file[/dim]") + console.print(" [cyan]drone @api get-secret telegram/bot --out /tmp/token.txt[/cyan]") + console.print() + console.print(" [dim]# Write secret as JSON to a protected file[/dim]") + console.print(" [cyan]drone @api get-secret telegram/bot --out /tmp/bot.json --json[/cyan]") + console.print() + console.print(" [dim]# List secrets for a provider[/dim]") + console.print(" [cyan]drone @api get-secret telegram --list[/cyan]") + console.print() + console.print(" [dim]# Programmatic access (in-process, no stdout):[/dim]") + console.print(" [dim]from aipass.api.apps.modules.secrets import get_secret[/dim]") + console.print() + console.print(" [dim]# Validate key[/dim]") + console.print(" [cyan]drone @api validate openrouter[/cyan]") + console.print() + console.print(" [dim]# List providers[/dim]") + console.print(" [cyan]drone @api list-providers[/cyan]") + console.print() + console.print(" [dim]# Initialize environment[/dim]") + console.print(" [cyan]drone @api init[/cyan]") + console.print() if __name__ == "__main__": diff --git a/src/aipass/api/apps/modules/google_client.py b/src/aipass/api/apps/modules/google_client.py index c4dbc4fe..f4023861 100644 --- a/src/aipass/api/apps/modules/google_client.py +++ b/src/aipass/api/apps/modules/google_client.py @@ -82,31 +82,25 @@ def print_introspection() -> None: def print_help() -> None: - """Print module help.""" - import argparse - - parser = argparse.ArgumentParser( - prog="drone @api", - description="Google Client - Google API authentication and service access", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -COMMANDS (via drone @api): - validate google - Check Google OAuth2 credentials - reauth google - Re-run OAuth2 flow for Google - -CROSS-BRANCH API: - from aipass.api.apps.modules.google_client import get_drive_service - service = get_drive_service() - -CREDENTIAL SETUP: - 1. Get OAuth client secret from Google Cloud Console - 2. Save as: ~/.secrets/aipass/google_client_secret.json - 3. Run: drone @api reauth google - 4. Complete OAuth consent in browser - 5. Credentials saved to: ~/.secrets/aipass/google_creds.json - """, - ) - console.print(parser.format_help()) + """Print drone-compliant help output with Rich markup""" + console.print() + console.print("[bold cyan]GOOGLE_CLIENT — Google API authentication and service access[/bold cyan]") + console.print() + console.print("[yellow]COMMANDS:[/yellow] [dim](via drone @api)[/dim]") + console.print(" [cyan]validate google[/cyan] [dim]Check Google OAuth2 credentials[/dim]") + console.print(" [cyan]reauth google[/cyan] [dim]Re-run OAuth2 flow for Google[/dim]") + console.print() + console.print("[yellow]CROSS-BRANCH API:[/yellow]") + console.print(" [dim]from aipass.api.apps.modules.google_client import get_drive_service[/dim]") + console.print(" [dim]service = get_drive_service()[/dim]") + console.print() + console.print("[yellow]CREDENTIAL SETUP:[/yellow]") + console.print(" [dim]1.[/dim] Get OAuth client secret from Google Cloud Console") + console.print(" [dim]2.[/dim] Save as: [cyan]~/.secrets/aipass/google_client_secret.json[/cyan]") + console.print(" [dim]3.[/dim] Run: [cyan]drone @api reauth google[/cyan]") + console.print(" [dim]4.[/dim] Complete OAuth consent in browser") + console.print(" [dim]5.[/dim] Credentials saved to: [cyan]~/.secrets/aipass/google_creds.json[/cyan]") + console.print() # ============================================= diff --git a/src/aipass/api/apps/modules/openrouter_client.py b/src/aipass/api/apps/modules/openrouter_client.py index b425da2f..5f05a406 100644 --- a/src/aipass/api/apps/modules/openrouter_client.py +++ b/src/aipass/api/apps/modules/openrouter_client.py @@ -52,62 +52,39 @@ def print_introspection(): def print_help(): - """Print module help with argparse""" - import argparse - - parser = argparse.ArgumentParser( - prog="drone @api", - description="OpenRouter Client - Manage LLM API connections", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -COMMANDS: - test - Test OpenRouter connection - call - Make API call to model - models - List available models - status - Check connection status - -USAGE: - drone @api test - drone @api call [--model MODEL] - drone @api models - drone @api status - -ARGUMENTS: - prompt - Prompt to send to the model - --model - Model to use (optional) - -EXAMPLES: - # Test OpenRouter connection - drone @api test - - # Make an API call - drone @api call "What is AI?" --model gpt-4 - - # List available models - drone @api models - - # Check connection status - drone @api status - """, - ) - - subparsers = parser.add_subparsers(dest="command", help="Available commands") - - # test command - subparsers.add_parser("test", help="Test OpenRouter connection") - - # call command - call_parser = subparsers.add_parser("call", help="Make API call to model") - call_parser.add_argument("prompt", help="Prompt to send") - call_parser.add_argument("--model", help="Model to use") - - # models command - subparsers.add_parser("models", help="List available models") - - # status command - subparsers.add_parser("status", help="Check connection status") - - console.print(parser.format_help()) + """Print drone-compliant help output with Rich markup""" + console.print() + console.print("[bold cyan]OPENROUTER_CLIENT — Manage LLM API connections[/bold cyan]") + console.print() + console.print("[yellow]COMMANDS:[/yellow]") + console.print(" [cyan]test[/cyan] [dim]Test OpenRouter connection[/dim]") + console.print(" [cyan]call[/cyan] [dim]Make API call to model[/dim]") + console.print(" [cyan]models[/cyan] [dim]List available models[/dim]") + console.print(" [cyan]status[/cyan] [dim]Check connection status[/dim]") + console.print() + console.print("[yellow]USAGE:[/yellow]") + console.print(" [cyan]drone @api test[/cyan]") + console.print(" [cyan]drone @api call[/cyan] [--model MODEL]") + console.print(" [cyan]drone @api models[/cyan]") + console.print(" [cyan]drone @api status[/cyan]") + console.print() + console.print("[yellow]ARGUMENTS:[/yellow]") + console.print(" [cyan]prompt[/cyan] [dim]Prompt to send to the model[/dim]") + console.print(" [cyan]--model[/cyan] [dim]Model to use (optional)[/dim]") + console.print() + console.print("[yellow]EXAMPLES:[/yellow]") + console.print(" [dim]# Test OpenRouter connection[/dim]") + console.print(" [cyan]drone @api test[/cyan]") + console.print() + console.print(" [dim]# Make an API call[/dim]") + console.print(' [cyan]drone @api call "What is AI?" --model gpt-4[/cyan]') + console.print() + console.print(" [dim]# List available models[/dim]") + console.print(" [cyan]drone @api models[/cyan]") + console.print() + console.print(" [dim]# Check connection status[/dim]") + console.print(" [cyan]drone @api status[/cyan]") + console.print() def handle_command(command: str, args: List[str]) -> bool: diff --git a/src/aipass/api/apps/modules/usage_tracker.py b/src/aipass/api/apps/modules/usage_tracker.py index 23d4e09b..78795c70 100644 --- a/src/aipass/api/apps/modules/usage_tracker.py +++ b/src/aipass/api/apps/modules/usage_tracker.py @@ -53,76 +53,44 @@ def print_introspection(): def print_help(): - """Print module help with argparse""" - import argparse - - parser = argparse.ArgumentParser( - prog="drone @api", - description="Usage Tracker - Monitor API usage and costs", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -COMMANDS: - track - Track API usage - stats - Show usage statistics - session - Show session data - caller-usage - Show usage by caller - cleanup - Clean up old usage data - -USAGE: - drone @api track - drone @api stats - drone @api session - drone @api caller-usage - drone @api cleanup [days] - -ARGUMENTS: - caller - Caller identifier - days - Number of days to retain (default: 30) - -EXAMPLES: - # Track usage for a caller - drone @api track my_application - - # Show usage statistics - drone @api stats - - # Show session data - drone @api session - - # Show usage for specific caller - drone @api caller-usage my_application - - # Cleanup data older than 60 days - drone @api cleanup 60 - """, - ) - - subparsers = parser.add_subparsers(dest="command", help="Available commands") - - # track command - track_parser = subparsers.add_parser("track", help="Track API usage") - track_parser.add_argument("caller", help="Caller identifier") - - # stats command - subparsers.add_parser("stats", help="Show usage statistics") - - # session command - subparsers.add_parser("session", help="Show session data") - - # caller-usage command - caller_parser = subparsers.add_parser("caller-usage", help="Show usage by caller") - caller_parser.add_argument("caller", help="Caller identifier") - - # cleanup command - cleanup_parser = subparsers.add_parser("cleanup", help="Clean up old usage data") - cleanup_parser.add_argument( - "days", - nargs="?", - default=str(DEFAULT_RETENTION_DAYS), - help=f"Days to retain (default: {DEFAULT_RETENTION_DAYS})", - ) - - console.print(parser.format_help()) + """Print drone-compliant help output with Rich markup""" + console.print() + console.print("[bold cyan]USAGE_TRACKER — Monitor API usage and costs[/bold cyan]") + console.print() + console.print("[yellow]COMMANDS:[/yellow]") + console.print(" [cyan]track[/cyan] [dim]Track API usage[/dim]") + console.print(" [cyan]stats[/cyan] [dim]Show usage statistics[/dim]") + console.print(" [cyan]session[/cyan] [dim]Show session data[/dim]") + console.print(" [cyan]caller-usage[/cyan] [dim]Show usage by caller[/dim]") + console.print(" [cyan]cleanup[/cyan] [dim]Clean up old usage data[/dim]") + console.print() + console.print("[yellow]USAGE:[/yellow]") + console.print(" [cyan]drone @api track[/cyan] ") + console.print(" [cyan]drone @api stats[/cyan]") + console.print(" [cyan]drone @api session[/cyan]") + console.print(" [cyan]drone @api caller-usage[/cyan] ") + console.print(" [cyan]drone @api cleanup[/cyan] [days]") + console.print() + console.print("[yellow]ARGUMENTS:[/yellow]") + console.print(" [cyan]caller[/cyan] [dim]Caller identifier[/dim]") + console.print(" [cyan]days[/cyan] [dim]Number of days to retain (default: 30)[/dim]") + console.print() + console.print("[yellow]EXAMPLES:[/yellow]") + console.print(" [dim]# Track usage for a caller[/dim]") + console.print(" [cyan]drone @api track my_application[/cyan]") + console.print() + console.print(" [dim]# Show usage statistics[/dim]") + console.print(" [cyan]drone @api stats[/cyan]") + console.print() + console.print(" [dim]# Show session data[/dim]") + console.print(" [cyan]drone @api session[/cyan]") + console.print() + console.print(" [dim]# Show usage for specific caller[/dim]") + console.print(" [cyan]drone @api caller-usage my_application[/cyan]") + console.print() + console.print(" [dim]# Cleanup data older than 60 days[/dim]") + console.print(" [cyan]drone @api cleanup 60[/cyan]") + console.print() def handle_command(command: str, args: List[str]) -> bool: @@ -194,9 +162,10 @@ def track_usage(args: List[str]): if result.get("success"): metrics = result.get("metrics", {}) - success( - f"Tracked: {metrics.get('tokens_prompt', 0)} prompt + {metrics.get('tokens_completion', 0)} completion tokens, ${metrics.get('total_cost', 0):.6f}" - ) + prompt_t = metrics.get("tokens_prompt", 0) + comp_t = metrics.get("tokens_completion", 0) + cost = metrics.get("total_cost", 0) + success(f"Tracked: {prompt_t} prompt + {comp_t} completion tokens, ${cost:.6f}") else: error(f"Tracking failed: {result.get('error', 'unknown')}")