diff --git a/src/aipass/api/apps/handlers/auth/env.py b/src/aipass/api/apps/handlers/auth/env.py index bdd36be7..256eb039 100644 --- a/src/aipass/api/apps/handlers/auth/env.py +++ b/src/aipass/api/apps/handlers/auth/env.py @@ -29,6 +29,9 @@ from typing import Optional, Dict, List # Logging from aipass.prax import logger +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # ============================================== # CONSTANTS @@ -86,6 +89,7 @@ def read_env_file(env_var: str, search_paths: Optional[List[Path]] = None) -> Op key, value = line.split('=', 1) if key.strip() == env_var: # Found env_var in env_file + json_handler.log_operation("env_loaded", {"variable": env_var, "source": str(env_file)}) return value.strip() except Exception as e: # Error reading env_file diff --git a/src/aipass/api/apps/handlers/auth/keys.py b/src/aipass/api/apps/handlers/auth/keys.py index bc4c6cea..5d5288c6 100644 --- a/src/aipass/api/apps/handlers/auth/keys.py +++ b/src/aipass/api/apps/handlers/auth/keys.py @@ -31,6 +31,9 @@ from typing import Optional, Dict, Any # Internal handlers from aipass.api.apps.handlers.auth.env import read_env_file +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # ============================================== # CONSTANTS @@ -86,23 +89,31 @@ def get_api_key(provider: str = "openrouter") -> Optional[str]: ... print(f"Got key: {key[:20]}...") """ try: + source = "" + # 1. Try config file key = get_key_from_config(provider) if key and validate_key(key, provider): # Using key from config - return key + source = "config" # 2. Try environment variable - key = get_key_from_env(provider) - if key and validate_key(key, provider): - # Using key from environment - return key + if not source: + key = get_key_from_env(provider) + if key and validate_key(key, provider): + # Using key from environment + source = "env" # 3. Try .env file - env_var = f"{provider.upper()}_API_KEY" - key = read_env_file(env_var) - if key and validate_key(key, provider): - # Using key from .env file + if not source: + env_var = f"{provider.upper()}_API_KEY" + key = read_env_file(env_var) + if key and validate_key(key, provider): + # Using key from .env file + source = "dotenv" + + if source: + json_handler.log_operation("key_retrieved", {"provider": provider, "source": source}) return key # No valid key found diff --git a/src/aipass/api/apps/handlers/config/provider.py b/src/aipass/api/apps/handlers/config/provider.py index bbc42cf2..6788def4 100644 --- a/src/aipass/api/apps/handlers/config/provider.py +++ b/src/aipass/api/apps/handlers/config/provider.py @@ -31,6 +31,9 @@ from typing import Dict, Any, Optional # Internal handlers from aipass.api.apps.handlers.json.json_handler import load_json, save_json +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # Logging from aipass.prax import logger @@ -115,6 +118,7 @@ def load_provider_config(provider: str = "openrouter") -> Optional[Dict[str, Any if provider_config: # Loaded config for provider + json_handler.log_operation("config_loaded", {"provider": provider}) return provider_config else: # Provider not found in config diff --git a/src/aipass/api/apps/handlers/google/auth.py b/src/aipass/api/apps/handlers/google/auth.py index c8995510..11035141 100644 --- a/src/aipass/api/apps/handlers/google/auth.py +++ b/src/aipass/api/apps/handlers/google/auth.py @@ -5,6 +5,7 @@ # Created: 2026-03-14 # Modified: 2026-03-14 # ============================================= +# pyright: reportMissingImports=false, reportInvalidTypeForm=false, reportOptionalMemberAccess=false, reportOptionalCall=false """ Google OAuth2 Authentication Handler @@ -22,6 +23,9 @@ Consumers get authenticated credentials, they decide what to do with them. from pathlib import Path from typing import Optional +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # ============================================= # CONSTANTS # ============================================= @@ -84,6 +88,7 @@ def load_credentials(scopes: Optional[list] = None) -> Optional["Credentials"]: try: creds = Credentials.from_authorized_user_file(str(CREDS_PATH), effective_scopes) + json_handler.log_operation("credentials_loaded", {"source": str(CREDS_PATH)}) return creds except Exception: return None diff --git a/src/aipass/api/apps/handlers/google/retry.py b/src/aipass/api/apps/handlers/google/retry.py index bdadbf2b..ad019152 100644 --- a/src/aipass/api/apps/handlers/google/retry.py +++ b/src/aipass/api/apps/handlers/google/retry.py @@ -29,6 +29,9 @@ import ssl import time from typing import Any, Callable, Optional +# JSON handler +from aipass.api.apps.handlers.json import json_handler + def is_ssl_error(exc: Exception) -> bool: """Check if an exception is a transient SSL/connection error. @@ -81,6 +84,7 @@ def api_call_with_retry( except Exception as e: if attempt < max_retries and is_ssl_error(e): wait = 2 ** attempt + json_handler.log_operation("api_retry_attempted", {"attempt": attempt + 1, "wait_seconds": wait}) time.sleep(wait) if rebuild_service_fn: rebuild_service_fn() diff --git a/src/aipass/api/apps/handlers/openrouter/caller.py b/src/aipass/api/apps/handlers/openrouter/caller.py index cd8df4f3..d486908d 100644 --- a/src/aipass/api/apps/handlers/openrouter/caller.py +++ b/src/aipass/api/apps/handlers/openrouter/caller.py @@ -27,6 +27,9 @@ from pathlib import Path import inspect from typing import Dict, Any, Optional, Tuple +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # ============================================= # CONFIGURATION # ============================================= @@ -61,11 +64,17 @@ def get_caller_info() -> Optional[Dict[str, Any]]: frame_path = Path(frame_info.filename) if "flow" in frame_path.parts: - return _detect_flow_caller(frame_path) + result = _detect_flow_caller(frame_path) + json_handler.log_operation("caller_detected", {"caller": result.get("caller_name"), "category": "flow"}) + return result elif "prax" in frame_path.parts: - return _detect_prax_caller(frame_path) + result = _detect_prax_caller(frame_path) + json_handler.log_operation("caller_detected", {"caller": result.get("caller_name"), "category": "prax"}) + return result elif any("skills" in part for part in frame_path.parts): - return _detect_skills_caller(frame_path) + result = _detect_skills_caller(frame_path) + json_handler.log_operation("caller_detected", {"caller": result.get("caller_name"), "category": "skills"}) + return result # logger.info(f"[{MODULE_NAME}] Could not detect caller from stack trace") return None diff --git a/src/aipass/api/apps/handlers/openrouter/client.py b/src/aipass/api/apps/handlers/openrouter/client.py index ca850cdb..c82c5d90 100644 --- a/src/aipass/api/apps/handlers/openrouter/client.py +++ b/src/aipass/api/apps/handlers/openrouter/client.py @@ -5,6 +5,7 @@ # Created: 2025-11-15 # Modified: 2025-11-15 # ============================================= +# pyright: reportInvalidTypeForm=false, reportOptionalCall=false """ OpenRouter Client Handler @@ -49,6 +50,7 @@ try: OPENAI_AVAILABLE = True except ImportError: # logger.error("OpenAI SDK not available. Install with: pip install openai") + OpenAI = None # type: ignore[assignment,misc] OPENAI_AVAILABLE = False # Handler imports @@ -56,6 +58,9 @@ from aipass.api.apps.handlers.auth.keys import get_api_key from aipass.api.apps.handlers.openrouter.caller import get_caller_info from aipass.api.apps.handlers.usage.tracking import track_usage +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # ============================================= # CONFIGURATION # ============================================= @@ -116,6 +121,7 @@ def create_client(api_key: str, base_url: str = OPENROUTER_BASE_URL, timeout: in ) # logger.info(f"Created OpenRouter client - base_url: {base_url}, timeout: {timeout}s") + json_handler.log_operation("client_initialized", {"base_url": base_url, "timeout": timeout}) return client except Exception as e: diff --git a/src/aipass/api/apps/handlers/openrouter/models.py b/src/aipass/api/apps/handlers/openrouter/models.py index eba74017..72305be9 100644 --- a/src/aipass/api/apps/handlers/openrouter/models.py +++ b/src/aipass/api/apps/handlers/openrouter/models.py @@ -34,6 +34,9 @@ import requests # Internal imports from aipass.api.apps.handlers.auth.keys import get_api_key +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # ============================================= # CONSTANTS @@ -80,6 +83,7 @@ def get_available_models(api_key: Optional[str] = None) -> List[Dict]: if models: # logger.info(f"[{MODULE_NAME}] Fetched {len(models)} models from OpenRouter") + json_handler.log_operation("models_listed", {"count": len(models)}) return models else: # logger.info(f"[{MODULE_NAME}] No models returned from API") diff --git a/src/aipass/api/apps/handlers/openrouter/provision.py b/src/aipass/api/apps/handlers/openrouter/provision.py index 73581bd3..3ad62896 100644 --- a/src/aipass/api/apps/handlers/openrouter/provision.py +++ b/src/aipass/api/apps/handlers/openrouter/provision.py @@ -36,6 +36,9 @@ from aipass.prax import logger from aipass.api.apps.handlers.openrouter.caller import detect_caller_from_stack +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # =========================================== # JSON UTILITIES @@ -223,6 +226,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]: logger.info(f"Auto-provisioned OpenRouter config for '{caller}'") logger.warning("Reload config and retry request") + json_handler.log_operation("provider_provisioned", {"caller": caller}) return config diff --git a/src/aipass/api/apps/handlers/usage/aggregation.py b/src/aipass/api/apps/handlers/usage/aggregation.py index 1c59378f..d1406b91 100644 --- a/src/aipass/api/apps/handlers/usage/aggregation.py +++ b/src/aipass/api/apps/handlers/usage/aggregation.py @@ -29,6 +29,9 @@ from typing import Dict, Any, List, Optional # Standard library for JSON operations import json +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # ============================================= # MODULE CONSTANTS @@ -222,6 +225,7 @@ def calculate_totals(usage_data: List[Dict]) -> Dict[str, float]: } # logger.info(f"[{MODULE_NAME}] Calculated totals: {result['total_requests']} requests, ${result['total_cost']:.6f}") + json_handler.log_operation("usage_aggregated", {"total_requests": result["total_requests"], "total_cost": result["total_cost"]}) return result except Exception as e: diff --git a/src/aipass/api/apps/handlers/usage/cleanup.py b/src/aipass/api/apps/handlers/usage/cleanup.py index 7f08cae3..b04e26fd 100644 --- a/src/aipass/api/apps/handlers/usage/cleanup.py +++ b/src/aipass/api/apps/handlers/usage/cleanup.py @@ -25,6 +25,9 @@ from typing import Optional, Dict, List # Logging from aipass.prax import logger +# JSON handler +from aipass.api.apps.handlers.json import json_handler + def _read_json(file_path: Path) -> Optional[Dict]: """Read JSON file with error handling.""" @@ -90,6 +93,7 @@ def cleanup_old_data(data_file_path: Path, retention_days: int = 30) -> int: _write_json(data_file_path, data) # logger.info(f"Cleaned up {len(old_generations)} generation entries") logger.info(f"Cleaned up {len(old_generations)} generation entries older than {retention_days} days") + json_handler.log_operation("usage_cleanup", {"generations_removed": len(old_generations), "retention_days": retention_days}) return len(old_generations) diff --git a/src/aipass/api/apps/handlers/usage/tracking.py b/src/aipass/api/apps/handlers/usage/tracking.py index a1e56bc5..a6c659a7 100644 --- a/src/aipass/api/apps/handlers/usage/tracking.py +++ b/src/aipass/api/apps/handlers/usage/tracking.py @@ -29,6 +29,9 @@ import time from datetime import datetime from typing import Dict, Any, Optional +# JSON handler +from aipass.api.apps.handlers.json import json_handler + # ============================================= # MODULE CONSTANTS # ============================================= @@ -99,6 +102,7 @@ def track_usage(generation_id: str, caller: str, model: str = "unknown", api_key # Store the usage data if store_usage_data(caller, model, generation_id, metrics): # Successfully tracked usage + json_handler.log_operation("usage_tracked", {"caller": caller, "model": model, "generation_id": generation_id}) return {"success": True, "metrics": metrics} else: # Failed to store usage data diff --git a/src/aipass/api/apps/modules/api_key.py b/src/aipass/api/apps/modules/api_key.py index 4207d530..d8ace3e7 100644 --- a/src/aipass/api/apps/modules/api_key.py +++ b/src/aipass/api/apps/modules/api_key.py @@ -65,17 +65,32 @@ def handle_command(command: str, args: List[str]) -> bool: if command not in ["get-key", "validate", "list-providers", "init"]: return False + # Help gate + if args and args[0] in ("--help", "-h", "help"): + print_help() + return True + # Log operation json_handler.log_operation(f"api_key_{command}", {"command": command}) + # Standalone commands — route before introspection gate + if command == "list-providers": + list_providers() + return True + if command == "init": + init_env() + return True + + # NO-ARGS GATE (seedgo standard) + if not args: + print_introspection() + return True + + # Arg-required commands if command == "get-key": get_key(args) elif command == "validate": validate_key(args) - elif command == "list-providers": - list_providers() - elif command == "init": - init_env() return True except Exception as e: diff --git a/src/aipass/api/apps/modules/google_client.py b/src/aipass/api/apps/modules/google_client.py index 22044426..98196fb4 100644 --- a/src/aipass/api/apps/modules/google_client.py +++ b/src/aipass/api/apps/modules/google_client.py @@ -124,8 +124,20 @@ def handle_command(command: str, args: List[str]) -> bool: Returns: True if command was handled, False to pass through. """ + # Help gate + if args and args[0] in ("--help", "-h", "help"): + print_help() + return True + + # NO-ARGS GATE (seedgo standard) + if not args: + if command == "google": + print_introspection() + return True + return False + # Only handle commands with "google" as the provider argument - if not args or args[0] != "google": + if args[0] != "google": return False if command == "validate": diff --git a/src/aipass/api/apps/modules/openrouter_client.py b/src/aipass/api/apps/modules/openrouter_client.py index fd8f8591..23334ae0 100644 --- a/src/aipass/api/apps/modules/openrouter_client.py +++ b/src/aipass/api/apps/modules/openrouter_client.py @@ -125,17 +125,33 @@ def handle_command(command: str, args: List[str]) -> bool: if command not in ["test", "call", "models", "status"]: return False + # Help gate + if args and args[0] in ("--help", "-h", "help"): + print_help() + return True + # Log operation json_handler.log_operation(f"openrouter_{command}", {"command": command}) + # Standalone commands — route before introspection gate if command == "test": test_connection() - elif command == "call": - make_call(args) - elif command == "models": + return True + if command == "models": list_models() - elif command == "status": + return True + if command == "status": check_status() + return True + + # NO-ARGS GATE (seedgo standard) + if not args: + print_introspection() + return True + + # Arg-required commands + if command == "call": + make_call(args) return True except Exception as e: diff --git a/src/aipass/api/apps/modules/usage_tracker.py b/src/aipass/api/apps/modules/usage_tracker.py index 217d4abe..72753d3e 100644 --- a/src/aipass/api/apps/modules/usage_tracker.py +++ b/src/aipass/api/apps/modules/usage_tracker.py @@ -134,15 +134,30 @@ def handle_command(command: str, args: List[str]) -> bool: if command not in ["track", "stats", "session", "caller-usage", "cleanup"]: return False + # Help gate + if args and args[0] in ("--help", "-h", "help"): + print_help() + return True + # Log operation json_handler.log_operation(f"usage_{command}", {"command": command}) + # Standalone commands — route before introspection gate + if command == "stats": + show_stats() + return True + if command == "session": + show_session() + return True + + # NO-ARGS GATE (seedgo standard) + if not args: + print_introspection() + return True + + # Arg-required commands if command == "track": track_usage(args) - elif command == "stats": - show_stats() - elif command == "session": - show_session() elif command == "caller-usage": show_caller_usage(args) elif command == "cleanup":