#!/usr/bin/env python3 """ Google Gemini Adaptor Implements platform-specific handling for Google Gemini skills. Uses Gemini Files API for grounding and Gemini 2.5 Flash for enhancement. """ import json import os import tarfile from pathlib import Path from typing import Any from .base import SkillAdaptor, SkillMetadata from skill_seekers.cli.arguments.common import DEFAULT_CHUNK_TOKENS, DEFAULT_CHUNK_OVERLAP_TOKENS class GeminiAdaptor(SkillAdaptor): """ Google Gemini platform adaptor. Handles: - Plain markdown format (no YAML frontmatter) - tar.gz packaging for Gemini Files API - Upload to Google AI Studio / Files API - AI enhancement using Gemini 2.5 Flash """ PLATFORM = "gemini" PLATFORM_NAME = "Google Gemini" DEFAULT_API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/files" def format_skill_md(self, skill_dir: Path, metadata: SkillMetadata) -> str: """ Format SKILL.md with plain markdown (no frontmatter). Gemini doesn't use YAML frontmatter - just clean markdown. Args: skill_dir: Path to skill directory metadata: Skill metadata Returns: Formatted SKILL.md content (plain markdown) """ # Read existing content (if any) existing_content = self._read_existing_content(skill_dir) # If existing content is substantial, use it if existing_content and len(existing_content) > 100: content_body = existing_content else: # Generate default content content_body = f"""# {metadata.name.title()} Documentation **Description:** {metadata.description} ## Quick Reference {self._extract_quick_reference(skill_dir)} ## Table of Contents {self._generate_toc(skill_dir)} ## Documentation Structure This skill contains comprehensive documentation organized into categorized reference files. ### Available References {self._generate_toc(skill_dir)} ## How to Use This Skill When asking questions about {metadata.name}: 1. Mention specific topics or features you need help with 2. Reference documentation sections will be automatically consulted 3. You'll receive detailed answers with code examples ## Navigation See the references directory for complete documentation with examples and best practices. """ # Return plain markdown (NO frontmatter) return content_body def package( self, skill_dir: Path, output_path: Path, enable_chunking: bool = False, chunk_max_tokens: int = DEFAULT_CHUNK_TOKENS, preserve_code_blocks: bool = True, chunk_overlap_tokens: int = DEFAULT_CHUNK_OVERLAP_TOKENS, ) -> Path: """ Package skill into tar.gz file for Gemini. Creates Gemini-compatible structure: - system_instructions.md (main SKILL.md) - references/*.md - gemini_metadata.json (skill metadata) Args: skill_dir: Path to skill directory output_path: Output path/filename for tar.gz Returns: Path to created tar.gz file """ skill_dir = Path(skill_dir) # Determine output filename if output_path.is_dir() or str(output_path).endswith("/"): output_path = Path(output_path) / f"{skill_dir.name}-gemini.tar.gz" elif not str(output_path).endswith(".tar.gz"): # Replace .zip with .tar.gz if needed output_str = str(output_path).replace(".zip", ".tar.gz") if not output_str.endswith(".tar.gz"): output_str += ".tar.gz" output_path = Path(output_str) output_path = Path(output_path) output_path.parent.mkdir(parents=True, exist_ok=True) # Create tar.gz file with tarfile.open(output_path, "w:gz") as tar: # Add SKILL.md as system_instructions.md skill_md = skill_dir / "SKILL.md" if skill_md.exists(): tar.add(skill_md, arcname="system_instructions.md") # Add references directory (if exists) refs_dir = skill_dir / "references" if refs_dir.exists(): for ref_file in refs_dir.rglob("*"): if ref_file.is_file() and not ref_file.name.startswith("."): arcname = ref_file.relative_to(skill_dir) tar.add(ref_file, arcname=str(arcname)) # Create and add metadata file metadata = { "platform": "gemini", "name": skill_dir.name, "version": "1.0.0", "created_with": "skill-seekers", } # Write metadata to temp file and add to archive import tempfile with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp: json.dump(metadata, tmp, indent=2) tmp_path = tmp.name try: tar.add(tmp_path, arcname="gemini_metadata.json") finally: os.unlink(tmp_path) return output_path def upload(self, package_path: Path, api_key: str, **_kwargs) -> dict[str, Any]: """ Upload skill tar.gz to Gemini Files API. Args: package_path: Path to skill tar.gz file api_key: Google API key **kwargs: Additional arguments Returns: Dictionary with upload result """ # Validate package file FIRST package_path = Path(package_path) if not package_path.exists(): return { "success": False, "skill_id": None, "url": None, "message": f"File not found: {package_path}", } if package_path.suffix != ".gz": return { "success": False, "skill_id": None, "url": None, "message": f"Not a tar.gz file: {package_path}", } # Check for google-generativeai library try: import google.generativeai as genai except ImportError: return { "success": False, "skill_id": None, "url": None, "message": "google-generativeai library not installed. Run: pip install google-generativeai", } # Configure Gemini try: genai.configure(api_key=api_key) # Extract tar.gz to temp directory import tempfile with tempfile.TemporaryDirectory() as temp_dir: # Extract archive with tarfile.open(package_path, "r:gz") as tar: tar.extractall(temp_dir) temp_path = Path(temp_dir) # Upload main file (system_instructions.md) main_file = temp_path / "system_instructions.md" if not main_file.exists(): return { "success": False, "skill_id": None, "url": None, "message": "Invalid package: system_instructions.md not found", } # Upload to Files API uploaded_file = genai.upload_file( path=str(main_file), display_name=f"{package_path.stem}_instructions" ) # Upload reference files (if any) refs_dir = temp_path / "references" uploaded_refs = [] if refs_dir.exists(): for ref_file in refs_dir.glob("*.md"): ref_uploaded = genai.upload_file( path=str(ref_file), display_name=f"{package_path.stem}_{ref_file.stem}" ) uploaded_refs.append(ref_uploaded.name) return { "success": True, "skill_id": uploaded_file.name, "url": f"https://aistudio.google.com/app/files/{uploaded_file.name}", "message": f"Skill uploaded to Google AI Studio ({len(uploaded_refs) + 1} files)", } except Exception as e: return { "success": False, "skill_id": None, "url": None, "message": f"Upload failed: {str(e)}", } def validate_api_key(self, api_key: str) -> bool: """ Validate Google API key format. Args: api_key: API key to validate Returns: True if key starts with 'AIza' """ return api_key.strip().startswith("AIza") def get_env_var_name(self) -> str: """ Get environment variable name for Google API key. Returns: 'GOOGLE_API_KEY' """ return "GOOGLE_API_KEY" def supports_enhancement(self) -> bool: """ Gemini supports AI enhancement via Gemini 2.5 Flash. Returns: True """ return True def enhance(self, skill_dir: Path, api_key: str) -> bool: """ Enhance SKILL.md using Gemini 2.5 Flash API. Args: skill_dir: Path to skill directory api_key: Google API key Returns: True if enhancement succeeded """ # Check for google-generativeai library try: import google.generativeai as genai except ImportError: print("āŒ Error: google-generativeai package not installed") print("Install with: pip install google-generativeai") return False skill_dir = Path(skill_dir) references_dir = skill_dir / "references" skill_md_path = skill_dir / "SKILL.md" # Read reference files print("šŸ“– Reading reference documentation...") references = self._read_reference_files(references_dir) if not references: print("āŒ No reference files found to analyze") return False print(f" āœ“ Read {len(references)} reference files") total_size = sum(len(c) for c in references.values()) print(f" āœ“ Total size: {total_size:,} characters\n") # Read current SKILL.md current_skill_md = None if skill_md_path.exists(): current_skill_md = skill_md_path.read_text(encoding="utf-8") print(f" ℹ Found existing SKILL.md ({len(current_skill_md)} chars)") else: print(" ℹ No existing SKILL.md, will create new one") # Build enhancement prompt prompt = self._build_enhancement_prompt(skill_dir.name, references, current_skill_md) print("\nšŸ¤– Asking Gemini to enhance SKILL.md...") print(f" Input: {len(prompt):,} characters") try: genai.configure(api_key=api_key) model = genai.GenerativeModel("gemini-2.5-flash") response = model.generate_content(prompt) enhanced_content = response.text print(f" āœ“ Generated enhanced SKILL.md ({len(enhanced_content)} chars)\n") # Backup original if skill_md_path.exists(): backup_path = skill_md_path.with_suffix(".md.backup") skill_md_path.rename(backup_path) print(f" šŸ’¾ Backed up original to: {backup_path.name}") # Save enhanced version skill_md_path.write_text(enhanced_content, encoding="utf-8") print(" āœ… Saved enhanced SKILL.md") return True except Exception as e: print(f"āŒ Error calling Gemini API: {e}") return False def _read_reference_files( self, references_dir: Path, max_chars: int = 200000 ) -> dict[str, str]: """ Read reference markdown files from skill directory. Args: references_dir: Path to references directory max_chars: Maximum total characters to read Returns: Dictionary mapping filename to content """ if not references_dir.exists(): return {} references = {} total_chars = 0 # Read all .md files for ref_file in sorted(references_dir.glob("*.md")): if total_chars >= max_chars: break try: content = ref_file.read_text(encoding="utf-8") # Limit individual file size if len(content) > 30000: content = content[:30000] + "\n\n...(truncated)" references[ref_file.name] = content total_chars += len(content) except Exception as e: print(f" āš ļø Could not read {ref_file.name}: {e}") return references def _build_enhancement_prompt( self, skill_name: str, references: dict[str, str], current_skill_md: str = None ) -> str: """ Build Gemini API prompt for enhancement. Args: skill_name: Name of the skill references: Dictionary of reference content current_skill_md: Existing SKILL.md content (optional) Returns: Enhancement prompt for Gemini """ prompt = f"""You are enhancing a skill's documentation file for use with Google Gemini. This skill is about: {skill_name} I've scraped documentation and organized it into reference files. Your job is to create an EXCELLENT markdown documentation file that will help Gemini use this documentation effectively. CURRENT DOCUMENTATION: {"```markdown" if current_skill_md else "(none - create from scratch)"} {current_skill_md or "No existing documentation"} {"```" if current_skill_md else ""} REFERENCE DOCUMENTATION: """ for filename, content in references.items(): prompt += f"\n\n## {filename}\n```markdown\n{content[:30000]}\n```\n" prompt += """ YOUR TASK: Create enhanced documentation that includes: 1. **Clear description** - What this skill covers and when to use it 2. **Excellent Quick Reference section** - Extract 5-10 of the BEST, most practical code examples from the reference docs - Choose SHORT, clear examples that demonstrate common tasks - Include both simple and intermediate examples - Annotate examples with clear descriptions - Use proper language tags (cpp, python, javascript, json, etc.) 3. **Table of Contents** - List all reference sections 4. **Practical usage guidance** - Help users navigate the documentation 5. **Key Concepts section** (if applicable) - Explain core concepts 6. **DO NOT use YAML frontmatter** - This is for Gemini, which uses plain markdown IMPORTANT: - Extract REAL examples from the reference docs, don't make them up - Prioritize SHORT, clear examples (5-20 lines max) - Make it actionable and practical - Don't be too verbose - be concise but useful - Use clean markdown formatting - Keep code examples properly formatted with language tags - NO YAML frontmatter (no --- blocks) OUTPUT: Return ONLY the complete markdown content, starting with the main title (#). """ return prompt