feat(system): README update, API init fix, diagnostic docs, STATUS sync

- README.md: updated to current state (15 branches, 95+ PRs, 733 tests, 173 commands)
- API: init path fixed (creates .env at ~/.secrets/aipass/ not src/aipass/api/)
- API: init shows path and detects existing .env
- devpulse/docs: system_health_report, testing_standards, ai_mail comms upgrade
- STATUS.md: synced via drone @prax status sync

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AIOSAI
2026-03-20 00:02:04 -07:00
co-authored by Claude Opus 4.6
parent a800e7919a
commit ec63b8ba29
14 changed files with 1696 additions and 175 deletions
+4 -4
View File
@@ -152,17 +152,17 @@ def create_env_template(provider: str = "openrouter", target_path: Optional[Path
Args:
provider: API provider name (default: 'openrouter')
target_path: Optional custom path (defaults to api/.env)
target_path: Optional custom path (defaults to ~/.secrets/aipass/.env)
Returns:
bool: True if template created successfully, False otherwise
Example:
>>> if create_env_template('openrouter'):
... print("Template created at <api_root>/.env")
... print("Template created at ~/.secrets/aipass/.env")
"""
# Default to api/.env
env_path = target_path or (API_ROOT / ".env")
# Default to ~/.secrets/aipass/.env (cross-platform standard)
env_path = target_path or (Path.home() / ".secrets" / "aipass" / ".env")
# Don't overwrite existing file
if env_path.exists():
+47
View File
@@ -268,6 +268,53 @@ def get_validation_rules(provider: str) -> Dict[str, Any]:
# KEY FORMAT CHECKING
# ==============================================
def diagnose_key(provider: str = "openrouter") -> str:
"""
Diagnose why get_api_key() returned None.
Checks all sources for a raw key (skipping validation) and explains
exactly why it failed — missing entirely, wrong prefix, too short, etc.
Args:
provider: Provider name (default: 'openrouter')
Returns:
str: Human-readable explanation of the key issue
Example:
>>> if not get_api_key('openrouter'):
... print(diagnose_key('openrouter'))
"""
# Check all sources for raw key (without validation)
key = get_key_from_config(provider)
source = "config"
if not key:
key = get_key_from_env(provider)
source = "env"
if not key:
env_var = f"{provider.upper()}_API_KEY"
key = read_env_file(env_var)
source = "dotenv"
if not key:
return "No API key found in any source (config, environment, .env file)"
# Key exists but failed validation — explain why
key = key.strip()
rules = get_validation_rules(provider)
if "prefix" in rules and not key.startswith(rules["prefix"]):
actual_prefix = key[:len(rules["prefix"])] if len(key) >= len(rules["prefix"]) else key[:6]
return f"Key found ({source}) but invalid — expected prefix '{rules['prefix']}', got '{actual_prefix}...'"
if "min_length" in rules and len(key) < rules["min_length"]:
return f"Key found ({source}) but too short — {len(key)} chars, need {rules['min_length']}+"
return f"Key found ({source}) but failed validation"
def check_key_format(key: str) -> Dict[str, Any]:
"""
Analyze key format and return details.
@@ -56,6 +56,7 @@ except ImportError:
# Handler imports
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.openrouter.provision import ensure_caller_config
from aipass.api.apps.handlers.usage.tracking import track_usage
# JSON handler
@@ -315,6 +316,12 @@ def get_response(prompt: str, caller: Optional[str] = None, model: Optional[str]
# logger.warning("Could not detect caller - using 'unknown'")
caller = "unknown"
# Step 1b: Ensure caller has config (auto-provision if missing)
try:
ensure_caller_config(caller)
except Exception:
pass # Provisioning is best-effort — don't block the API call
# Step 2: Require model from caller - no defaults
if not model:
# logger.error("No model specified - caller must provide model from their branch config")
+7 -1
View File
@@ -153,9 +153,15 @@ def init_env():
header("Initialize API Configuration")
console.print()
env_path = Path.home() / ".secrets" / "aipass" / ".env"
if env_path.exists():
success(f"Environment file already exists at {env_path}")
return
# Create .env template via handler
if env.create_env_template():
success("Environment template created")
success(f"Environment template created at {env_path}")
else:
error("Failed to create environment template")
@@ -138,7 +138,7 @@ def handle_command(command: str, args: List[str]) -> bool:
test_connection()
return True
if command == "models":
list_models()
list_models(args)
return True
if command == "status":
check_status()
@@ -164,17 +164,23 @@ def test_connection():
header("Test OpenRouter Connection")
console.print()
console.print("[dim]Testing connection...[/dim]")
# Get API key via handler
api_key = keys.get_api_key("openrouter")
if not api_key:
error("No API key configured")
diagnosis = keys.diagnose_key("openrouter")
error(diagnosis)
return
warning("Testing connection...")
# Real API ping — hit /models endpoint
model_list = models.fetch_models_from_api(api_key)
# TODO: Call handler to test connection when implemented
success("Connection test successful")
if model_list:
success(f"Connection successful — {len(model_list)} models available")
else:
error("Connection failed — could not reach OpenRouter API")
def make_call(args: List[str]):
@@ -187,31 +193,67 @@ def make_call(args: List[str]):
warning("API call workflow - TODO")
def list_models():
def list_models(args: List[str] | None = None):
"""Orchestrate list models workflow"""
header("Available Models")
console.print()
show_all = args and "--all" in args
# Get API key via handler
api_key = keys.get_api_key("openrouter")
if not api_key:
error("No API key configured")
diagnosis = keys.diagnose_key("openrouter")
error(diagnosis)
return
warning("Fetching available models...")
console.print("[dim]Fetching available models...[/dim]")
# Call handler to fetch models
model_list = models.fetch_models_from_api(api_key)
if model_list:
success(f"Found {len(model_list)} models")
for model in model_list[:10]:
console.print(f" - {model}")
if len(model_list) > 10:
console.print(f" ... and {len(model_list) - 10} more")
else:
if not model_list:
error("Failed to fetch models")
return
success(f"Found {len(model_list)} models")
console.print()
# Format as table
display_count = len(model_list) if show_all else min(10, len(model_list))
console.print(f" {'Model':<50} {'Context':>10} {'$/prompt':>10} {'$/compl':>10}")
console.print(f" {'─' * 50} {'─' * 10} {'─' * 10} {'─' * 10}")
for model_data in model_list[:display_count]:
model_id = model_data.get("id", "unknown")
context = model_data.get("context_length", 0)
pricing = model_data.get("pricing", {})
prompt_cost = pricing.get("prompt", "0")
completion_cost = pricing.get("completion", "0")
# Format context length
if context >= 1_000_000:
ctx_str = f"{context // 1_000_000}M"
elif context >= 1_000:
ctx_str = f"{context // 1_000}k"
else:
ctx_str = str(context)
# Format pricing
if str(prompt_cost) == "0" and str(completion_cost) == "0":
p_str = "free"
c_str = "free"
else:
p_str = f"${prompt_cost}"
c_str = f"${completion_cost}"
console.print(f" {model_id:<50} {ctx_str:>10} {p_str:>10} {c_str:>10}")
if not show_all and len(model_list) > 10:
console.print()
console.print(f" [dim]Showing 10 of {len(model_list)} — use --all for full list[/dim]")
def check_status():
@@ -219,8 +261,32 @@ def check_status():
header("OpenRouter Client Status")
console.print()
# TODO: Call handlers for status when implemented
warning("Client status - TODO")
# Key status
api_key = keys.get_api_key("openrouter")
if api_key:
masked = api_key[:8] + "..." + api_key[-4:]
console.print(f" [cyan]Key configured:[/cyan] [green]yes[/green]")
console.print(f" [cyan]Key:[/cyan] {masked}")
else:
console.print(f" [cyan]Key configured:[/cyan] [red]no[/red]")
diagnosis = keys.diagnose_key("openrouter")
console.print(f" [cyan]Reason:[/cyan] {diagnosis}")
console.print(f" [cyan]Provider:[/cyan] OpenRouter")
console.print(f" [cyan]Base URL:[/cyan] https://openrouter.ai/api/v1")
# OpenAI SDK availability
try:
import openai # noqa: F401
console.print(f" [cyan]OpenAI SDK:[/cyan] [green]available[/green]")
except ImportError:
console.print(f" [cyan]OpenAI SDK:[/cyan] [red]missing[/red]")
# Client cache stats
cache_stats = client.get_cache_stats()
console.print(f" [cyan]Cached clients:[/cyan] {cache_stats['cached_clients']}/{cache_stats['max_cache_size']}")
console.print()
# =============================================