Files
2025-11-30 08:48:52 +08:00

301 lines
9.3 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Common API Key Detection Helper for Gemini Skills
Supports both Google AI Studio and Vertex AI endpoints.
API Key Detection Order:
1. Process environment variable
2. Project root .env file
3. ./.claude/.env
4. ./.claude/skills/.env
5. Skill directory .env file
Vertex AI Configuration:
- GEMINI_USE_VERTEX: Set to "true" to use Vertex AI endpoint
- VERTEX_PROJECT_ID: GCP project ID (required for Vertex AI)
- VERTEX_LOCATION: GCP region (default: us-central1)
"""
import os
import sys
from pathlib import Path
from typing import Optional, Dict, Any
def find_api_key(skill_dir: Optional[Path] = None) -> Optional[str]:
"""
Find GEMINI_API_KEY using 5-step lookup:
1. Process environment
2. Project root .env
3. ./.claude/.env
4. ./.claude/skills/.env
5. Skill directory .env
Args:
skill_dir: Path to skill directory (optional, auto-detected if None)
Returns:
API key string or None if not found
"""
# Step 1: Check process environment
api_key = os.getenv('GEMINI_API_KEY')
if api_key:
print("✓ Using API key from environment variable", file=sys.stderr)
return api_key
# Determine paths
if skill_dir is None:
skill_dir = Path(__file__).parent.parent
project_dir = skill_dir.parent.parent.parent # 3 levels up from skill dir
# Step 2: Check project root .env
project_env = project_dir / '.env'
if project_env.exists():
api_key = load_env_file(project_env)
if api_key:
print(f"✓ Using API key from {project_env}", file=sys.stderr)
return api_key
# Step 3: Check ./.claude/.env
claude_env = project_dir / '.claude' / '.env'
if claude_env.exists():
api_key = load_env_file(claude_env)
if api_key:
print(f"✓ Using API key from {claude_env}", file=sys.stderr)
return api_key
# Step 4: Check ./.claude/skills/.env
claude_skills_env = project_dir / '.claude' / 'skills' / '.env'
if claude_skills_env.exists():
api_key = load_env_file(claude_skills_env)
if api_key:
print(f"✓ Using API key from {claude_skills_env}", file=sys.stderr)
return api_key
# Step 5: Check skill directory .env
skill_env = skill_dir / '.env'
if skill_env.exists():
api_key = load_env_file(skill_env)
if api_key:
print(f"✓ Using API key from {skill_env}", file=sys.stderr)
return api_key
return None
def load_env_file(env_path: Path) -> Optional[str]:
"""
Load GEMINI_API_KEY from .env file
Args:
env_path: Path to .env file
Returns:
API key or None
"""
try:
with open(env_path, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('GEMINI_API_KEY='):
# Extract value, removing quotes if present
value = line.split('=', 1)[1].strip()
value = value.strip('"').strip("'")
if value:
return value
except Exception as e:
print(f"Warning: Error reading {env_path}: {e}", file=sys.stderr)
return None
def load_env_var(env_path: Path, var_name: str) -> Optional[str]:
"""
Load a specific environment variable from .env file
Args:
env_path: Path to .env file
var_name: Name of the environment variable
Returns:
Variable value or None
"""
try:
with open(env_path, 'r') as f:
for line in f:
line = line.strip()
if line.startswith(f'{var_name}='):
value = line.split('=', 1)[1].strip()
value = value.strip('"').strip("'")
if value:
return value
except Exception as e:
print(f"Warning: Error reading {env_path}: {e}", file=sys.stderr)
return None
def find_env_var(var_name: str, skill_dir: Optional[Path] = None) -> Optional[str]:
"""
Find environment variable using 5-step lookup (same as API key)
Args:
var_name: Name of environment variable
skill_dir: Path to skill directory (optional)
Returns:
Variable value or None
"""
# Step 1: Check process environment
value = os.getenv(var_name)
if value:
return value
# Determine paths
if skill_dir is None:
skill_dir = Path(__file__).parent.parent
project_dir = skill_dir.parent.parent.parent
# Step 2-5: Check .env files in order
env_files = [
project_dir / '.env',
project_dir / '.claude' / '.env',
project_dir / '.claude' / 'skills' / '.env',
skill_dir / '.env'
]
for env_path in env_files:
if env_path.exists():
value = load_env_var(env_path, var_name)
if value:
return value
return None
def get_vertex_config(skill_dir: Optional[Path] = None) -> Dict[str, Any]:
"""
Get Vertex AI configuration from environment variables
Args:
skill_dir: Path to skill directory (optional)
Returns:
Dictionary with Vertex AI configuration:
{
'use_vertex': bool,
'project_id': str or None,
'location': str (default: 'us-central1')
}
"""
use_vertex_str = find_env_var('GEMINI_USE_VERTEX', skill_dir)
use_vertex = use_vertex_str and use_vertex_str.lower() in ('true', '1', 'yes')
config = {
'use_vertex': use_vertex,
'project_id': find_env_var('VERTEX_PROJECT_ID', skill_dir) if use_vertex else None,
'location': find_env_var('VERTEX_LOCATION', skill_dir) or 'us-central1'
}
return config
def get_api_key_or_exit(skill_dir: Optional[Path] = None) -> str:
"""
Get API key or exit with helpful error message
Args:
skill_dir: Path to skill directory (optional, auto-detected if None)
Returns:
API key string
"""
api_key = find_api_key(skill_dir)
if not api_key:
print("\n❌ Error: GEMINI_API_KEY not found!", file=sys.stderr)
print("\n📋 Please set your API key using one of these methods (in priority order):", file=sys.stderr)
if skill_dir is None:
skill_dir = Path(__file__).parent.parent
project_dir = skill_dir.parent.parent.parent
print("\n1⃣ Environment variable (recommended for development):", file=sys.stderr)
print(" export GEMINI_API_KEY='your-api-key'", file=sys.stderr)
print("\n2⃣ Project root .env file:", file=sys.stderr)
print(f" echo 'GEMINI_API_KEY=your-api-key' > {project_dir}/.env", file=sys.stderr)
print("\n3⃣ .claude/.env file:", file=sys.stderr)
print(f" echo 'GEMINI_API_KEY=your-api-key' > {project_dir}/.claude/.env", file=sys.stderr)
print("\n4⃣ .claude/skills/.env file (shared across all Gemini skills):", file=sys.stderr)
print(f" echo 'GEMINI_API_KEY=your-api-key' > {project_dir}/.claude/skills/.env", file=sys.stderr)
print("\n5⃣ Skill directory .env file:", file=sys.stderr)
print(f" echo 'GEMINI_API_KEY=your-api-key' > {skill_dir}/.env", file=sys.stderr)
print("\n🔑 Get your API key at: https://aistudio.google.com/apikey", file=sys.stderr)
print("\n💡 Tip: Add .env files to .gitignore to avoid committing API keys", file=sys.stderr)
sys.exit(1)
return api_key
def get_client(skill_dir: Optional[Path] = None):
"""
Get appropriate Gemini client (AI Studio or Vertex AI)
Args:
skill_dir: Path to skill directory (optional)
Returns:
genai.Client or vertexai client
"""
vertex_config = get_vertex_config(skill_dir)
if vertex_config['use_vertex']:
# Use Vertex AI
import vertexai
from vertexai.generative_models import GenerativeModel
if not vertex_config['project_id']:
print("\n❌ Error: VERTEX_PROJECT_ID required when GEMINI_USE_VERTEX=true!", file=sys.stderr)
print("\n📋 Set your GCP project ID:", file=sys.stderr)
print(" export VERTEX_PROJECT_ID='your-project-id'", file=sys.stderr)
print(" Or add to .env file: VERTEX_PROJECT_ID=your-project-id", file=sys.stderr)
sys.exit(1)
print(f"✓ Using Vertex AI endpoint", file=sys.stderr)
print(f" Project: {vertex_config['project_id']}", file=sys.stderr)
print(f" Location: {vertex_config['location']}", file=sys.stderr)
vertexai.init(
project=vertex_config['project_id'],
location=vertex_config['location']
)
return {'type': 'vertex', 'config': vertex_config}
else:
# Use AI Studio
from google import genai
api_key = get_api_key_or_exit(skill_dir)
client = genai.Client(api_key=api_key)
return {'type': 'aistudio', 'client': client}
if __name__ == '__main__':
# Test the API key detection
api_key = get_api_key_or_exit()
print(f"✓ Found API key: {api_key[:8]}..." + "*" * (len(api_key) - 8))
# Test Vertex AI config
vertex_config = get_vertex_config()
if vertex_config['use_vertex']:
print(f"\n✓ Vertex AI enabled:")
print(f" Project: {vertex_config['project_id']}")
print(f" Location: {vertex_config['location']}")