Browse Source

Merge pull request #8 from darrenhinde/agent-prompts-update

Agent prompts update - Gemini Tool
Darren Hinde 6 months ago
parent
commit
834c3a83ee

+ 760 - 0
.opencode/AGENT-SYSTEM-BLUEPRINT.md

@@ -0,0 +1,760 @@
+# The OpenCode Agent System Blueprint
+
+_Build Intelligent Workflow Systems with Context-Aware AI_
+
+## The Golden Rule
+
+**Context flows in one direction: Commands load context immediately, Agents can look up additional context deterministically.**
+
+Like a well-organized library: the librarian (command) brings you the right books immediately, but you (agent) can look up specific references when needed.
+
+## How @ Symbol Context Loading Works (FUNDAMENTAL)
+
+### The Magic of Automatic Context Injection
+
+When you create a slash command with @ references, OpenCode automatically loads that context into the agent's memory BEFORE the agent starts thinking:
+
+```markdown
+# .opencode/command/create-component.md
+
+---
+
+name: create-component
+agent: simple-coder
+
+---
+
+You are creating React components following our patterns.
+
+**Request:** $ARGUMENTS
+
+@.opencode/context/frontend/react-patterns.md
+@.opencode/context/styling/design-system.md
+@.opencode/context/core/validation-patterns.md
+
+Create the component now.
+```
+
+**What happens when user types `/create-component "user profile card"`:**
+
+1. ✅ OpenCode reads the command file
+2. ✅ **Automatically loads** react-patterns.md, design-system.md, validation-patterns.md
+3. ✅ **Injects all context** into the agent's working memory
+4. ✅ Agent receives: user request + all loaded context + instructions
+5. ✅ Agent can immediately use patterns without looking them up
+
+### Why This Mechanism Is Powerful
+
+**❌ Without @ loading:**
+
+```
+Agent: "I need to create a component but don't know your patterns. Let me search..."
+→ Agent uses generic patterns or has to look up files
+→ Inconsistent results, slower execution
+```
+
+**✅ With @ loading:**
+
+```
+Agent: "I have your React patterns, design system, and validation rules loaded. Creating component..."
+→ Agent immediately follows your exact patterns
+→ Consistent, fast, high-quality results
+```
+
+### Context Loading Strategy
+
+**Base Context (Always Load):**
+
+```markdown
+@.opencode/context/core/essential-patterns.md
+@.opencode/context/architecture/project-structure.md
+```
+
+**Domain-Specific Context (Load Based on Command Purpose):**
+
+```markdown
+# Frontend commands
+
+@.opencode/context/frontend/react-patterns.md
+@.opencode/context/styling/design-system.md
+
+# Backend commands
+
+@.opencode/context/backend/server-patterns.md
+@.opencode/context/database/query-patterns.md
+
+# Testing commands
+
+@.opencode/context/testing/test-patterns.md
+```
+
+### Dynamic Context Loading
+
+```markdown
+# Advanced: Conditional context based on request analysis
+
+@.opencode/context/core/essential-patterns.md
+!`if echo "$ARGUMENTS" | grep -i "component\|ui" > /dev/null; then echo "@.opencode/context/frontend/react-patterns.md"; fi`
+!`if echo "$ARGUMENTS" | grep -i "database\|query" > /dev/null; then echo "@.opencode/context/database/query-patterns.md"; fi`
+```
+
+### Best Practices for @ Context Loading
+
+1. **Load 2-4 context files maximum** (prevent cognitive overload)
+2. **Always include core patterns** (essential-patterns.md)
+3. **Load domain-specific patterns** based on command purpose
+4. **Keep context files focused** (50-150 lines each)
+5. **Use conditional loading** for dynamic context selection
+
+### Why This Architecture Matters
+
+Understanding @ symbol auto-loading is crucial because:
+
+- **It's the foundation** of how agents get consistent context
+- **It determines command design** - you must anticipate what context agents need
+- **It explains the architecture** - why commands are "smart loaders" and agents are "focused executors"
+- **It guides best practices** - load the right context, not too much, not too little
+
+**This mechanism is what makes the system "intelligent" - agents always start with the right knowledge instead of having to discover it.**
+
+## Core Principles
+
+### 1. Single-Level Context Loading
+
+**OpenCode processes `@` references only in command templates, NOT recursively in file contents.**
+
+```markdown
+# ✅ This works (in command template)
+
+@.opencode/context/frontend/patterns.md
+@.opencode/context/styling/design-system.md
+
+# ❌ This doesn't work (inside patterns.md file)
+
+# If patterns.md contains @other-file.md, it's treated as plain text
+```
+
+**Critical Implication**: Commands must load ALL necessary context upfront using @ references. Agents can look up additional files using tools (read, grep, glob), but cannot use @ loading themselves.
+
+### 2. Deterministic vs Non-Deterministic Behavior
+
+- **Commands**: Non-deterministic - analyze requests and load appropriate context
+- **Agents**: Deterministic - predictable behavior, can look up additional context
+
+### 3. Context Optimization
+
+- **Maximum 4 context files per command** (250-450 lines total)
+- **50-150 lines per context file** (optimal range)
+- **Always load core patterns** + request-specific context
+
+## The 5-Part Intelligent System
+
+### 1. Commands (.opencode/command/)
+
+**What:** Entry points that load immediate context based on request analysis
+**Rule:** Commands are non-deterministic - they adapt context to the request
+
+```
+/workflow                    → Intelligent routing with dynamic context
+/plan-task                   → Task planning with architecture context
+/execute-task               → Step execution with progress tracking
+/create-frontend-component  → UI development with styling + frontend context
+/create-backend-operation   → Server logic with backend + database context
+/create-database-operation  → Database queries with focused database context
+/review-completion          → Quality assurance with minimal context
+/validate-with-tests        → Automated testing with testing context
+```
+
+**Command Structure Pattern:**
+
+```markdown
+---
+name: command-name
+agent: target-agent
+description: "What this command does"
+---
+
+You are [doing specific task]. [Direct instructions to agent].
+
+**Request:** $ARGUMENTS
+
+**Your Instructions:**
+
+1. **ANALYZE** the request...
+2. **IMPLEMENT** following patterns...
+3. **VALIDATE** the results...
+
+**Context Loaded:**
+@.opencode/context/core/essential-patterns.md
+@.opencode/context/[domain]/[specific-patterns].md
+@.opencode/context/architecture/architecture-lite.md
+
+**Success Criteria:**
+
+- [Specific measurable outcomes]
+
+Execute this [task type] now.
+```
+
+### 2. Agents (.opencode/agent/)
+
+**What:** Deterministic AI workers with specific capabilities
+**Rule:** Agents can look up additional context but are predictable in behavior
+
+```
+Primary Agents:
+├── workflow-orchestrator.md  → Routes requests and analyzes complexity
+├── task-planner.md           → Creates detailed task plans with file tracking
+├── task-executor.md          → Executes planned steps systematically
+├── post-flight-reviewer.md   → Reviews completed work for compliance
+└── simple-coder.md           → Implements focused development tasks
+
+Subagents (Specialized):
+├── subagents/code-reviewer.md    → Security and quality reviews (read-only)
+├── subagents/test-writer.md      → Test creation and validation (reports issues)
+├── subagents/pattern-finder.md   → Discovers existing implementations
+└── subagents/doc-writer.md       → Documentation creation and updates
+```
+
+**Agent Structure Pattern:**
+
+```markdown
+---
+description: "What this agent does"
+mode: primary|subagent
+model: claude-4-sonnet
+temperature: 0.1-0.3
+tools:
+  read: true|false
+  edit: true|false
+  write: true|false
+  bash: true|false
+  task: true|false
+permissions:
+  edit:
+    "**/*.env*": "deny"
+    "**/*.secret": "deny"
+---
+
+You are [specific role]. [Direct instructions for behavior].
+
+**EXECUTE** this [process type] for every [task type]:
+
+**1. [ACTION]** the [subject]:
+
+- [Specific instruction 1]
+- [Specific instruction 2]
+
+**2. [ACTION]** the [subject]:
+
+- [Specific instruction 1]
+- [Specific instruction 2]
+
+**RULES**:
+
+- **ALWAYS** [critical requirement]
+- **NEVER** [forbidden action]
+- **ONLY** [scope limitation]
+
+Execute [task type] now.
+```
+
+### 3. Context (.opencode/context/)
+
+**What:** Layered knowledge system with focused domains
+**Rule:** Single-level loading - no recursive context references
+
+```
+context/
+├── core/
+│   └── essential-patterns.md    (76 lines - always loaded)
+├── architecture/
+│   └── architecture-lite.md     (74 lines - project structure)
+├── frontend/
+│   ├── frontend-patterns.md     (230 lines - React patterns)
+│   └── styling/design-system.md (122 lines - UI standards)
+├── backend/
+│   ├── backend-patterns.md      (326 lines → split into focused files)
+│   └── server-actions.md        (100 lines - focused server action patterns)
+├── database/
+│   └── database-queries.md      (207 lines - query patterns)
+├── security/
+│   └── security-patterns.md     (205 lines - auth patterns)
+├── testing/
+│   ├── testing-patterns.md      (322 lines → split)
+│   └── unit-testing.md          (100 lines - focused test patterns)
+└── debugging/
+    └── common-errors.md          (304 lines → split by error type)
+```
+
+**Context File Structure Pattern:**
+
+````markdown
+**[ACTION]** [subject] using these exact patterns:
+
+**[PATTERN NAME]** - [When to use]:
+
+```[language]
+// Example code with clear comments
+// Showing exactly what to do
+```
+````
+
+**[ANOTHER PATTERN]** - [When to use]:
+
+```[language]
+// Another example
+```
+
+**RULES**:
+
+- **ALWAYS** [critical requirement]
+- **NEVER** [forbidden action]
+- **USE** [specific tools/methods]
+
+```
+
+### 4. Task Management (tasks/)
+**What:** File-based progress tracking with checkbox systems
+**Rule:** Every complex task gets a plan file with step-by-step tracking
+
+```
+
+tasks/
+├── features/ # Feature development
+│ └── [feature-name]/
+│ ├── task-plan.md # Main plan with checkboxes
+│ ├── execution-log.md # Detailed progress log
+│ └── review-report.md # Post-flight review
+├── fixes/ # Bug fixes
+│ └── [issue-name]/
+│ ├── task-plan.md
+│ └── fix-report.md
+├── improvements/ # Code improvements
+│ └── [improvement-name]/
+└── single/ # Simple tasks
+└── [task-name].md
+
+````
+
+**Task Plan File Structure:**
+```markdown
+# Task: [Task Name]
+
+## Overview
+**Request**: [Original user request]
+**Complexity**: [Simple/Medium/Complex]
+**Estimated Duration**: [Time estimate]
+**Dependencies**: [List any blocking requirements]
+
+## Task Breakdown
+
+### Phase 1: [Phase Name]
+- [ ] **Step 1.1**: [Specific action]
+  - **Agent**: @[agent-name]
+  - **Context**: [Required context files]
+  - **Validation**: [How to verify completion]
+  - **Duration**: [Time estimate]
+
+- [ ] **Step 1.2**: [Specific action]
+  - **Agent**: @[agent-name]
+  - **Context**: [Required context files]
+  - **Validation**: [How to verify completion]
+  - **Duration**: [Time estimate]
+
+## Quality Gates
+- [ ] **Build Validation**: TypeScript compilation passes
+- [ ] **Code Review**: Security and quality review completed
+- [ ] **Testing**: All tests pass and coverage adequate
+- [ ] **Integration**: Feature works end-to-end
+
+## Acceptance Criteria
+- [ ] [Specific requirement 1]
+- [ ] [Specific requirement 2]
+
+## Progress Tracking
+**Started**: [Date/Time]
+**Last Updated**: [Date/Time]
+**Status**: [Planning/In Progress/Review/Complete]
+**Completed Steps**: 0/[Total Steps]
+````
+
+### 5. Workflow Orchestration
+
+**What:** Multi-agent coordination with quality gates
+**Rule:** Complex workflows use multiple agents with validation checkpoints
+
+```
+Workflow Types:
+├── Simple (< 30 min)     → Direct execution with focused context
+├── Medium (30min-2hrs)   → Task planning with step tracking
+└── Complex (> 2hrs)      → Multi-phase with quality gates
+
+Quality Gates:
+├── Build Validation      → TypeScript, linting, build checks
+├── Code Review          → Security and quality assessment
+├── Testing              → Automated test execution
+└── Post-Flight Review   → Instruction compliance verification
+```
+
+## System Flow (Intelligent Workflow Management)
+
+```mermaid
+flowchart TD
+    A[User Request] --> B[Workflow Orchestrator]
+    B --> C{Analyze Request}
+    C --> D{Complexity?}
+    D -->|Simple| E[Direct Execution]
+    D -->|Complex| F[Task Planning]
+
+    E --> G[Load Focused Context]
+    G --> H[Execute with Agent]
+    H --> I[Validate Results]
+    I --> J[Complete]
+
+    F --> K[Create Task Plan File]
+    K --> L[Step-by-Step Execution]
+    L --> M[Update Progress Tracking]
+    M --> N{More Steps?}
+    N -->|Yes| O[Next Step]
+    N -->|No| P[Quality Gates]
+    O --> L
+    P --> Q[Post-Flight Review]
+    Q --> R[Mark Complete]
+
+    style A fill:#E6F3FF
+    style J fill:#E6FFE6
+    style R fill:#E6FFE6
+```
+
+## Context Loading Strategy
+
+### Dynamic Context Loading Pattern
+
+```markdown
+# In workflow orchestrator
+
+**ANALYZE** request: "$ARGUMENTS"
+
+**BASE CONTEXT** (always loaded):
+@.opencode/context/core/essential-patterns.md
+@.opencode/context/architecture/architecture-lite.md
+
+**CONDITIONAL CONTEXT** (based on request analysis):
+!`if echo "$ARGUMENTS" | grep -i -E "(component|ui|frontend)" > /dev/null; then echo "@.opencode/context/styling/design-system.md"; fi`
+!`if echo "$ARGUMENTS" | grep -i -E "(server|backend|action)" > /dev/null; then echo "@.opencode/context/backend/server-actions.md"; fi`
+!`if echo "$ARGUMENTS" | grep -i -E "(database|query|data)" > /dev/null; then echo "@.opencode/context/database/database-queries.md"; fi`
+```
+
+### Context Size Guidelines
+
+- **✅ Optimal**: 50-150 lines (focused, actionable patterns)
+- **⚠️ Acceptable**: 150-250 lines (comprehensive but manageable)
+- **❌ Too Large**: 250+ lines (should be split into focused files)
+
+### Context Loading Rules
+
+1. **Always load core patterns** (essential-patterns.md + architecture-lite.md)
+2. **Maximum 4 context files** per command (prevent overload)
+3. **Load based on request analysis** (dynamic, not static)
+4. **Use bash commands** for conditional loading
+
+## Implementation Guide
+
+### Step 1: Create Your Workflow Commands
+
+```markdown
+# Essential Commands to Create:
+
+/workflow # Main entry with intelligent routing
+/plan-task # Complex task breakdown
+/execute-task # Step-by-step execution
+/create-frontend-component # UI development
+/create-backend-operation # Server logic
+/review-completion # Quality assurance
+```
+
+### Step 2: Build Your Agent System
+
+```markdown
+# Core Agents Needed:
+
+workflow-orchestrator.md # Request analysis and routing
+task-planner.md # Detailed planning with file tracking
+task-executor.md # Step execution with progress updates
+post-flight-reviewer.md # Compliance and quality review
+simple-coder.md # Focused implementation work
+
+# Specialized Subagents:
+
+subagents/code-reviewer.md # Security and quality (read-only)
+subagents/test-writer.md # Test creation (reports issues)
+```
+
+### Step 3: Structure Your Context
+
+```markdown
+# Context Organization:
+
+core/essential-patterns.md # Always loaded (76 lines)
+architecture/architecture-lite.md # Project structure (74 lines)
+frontend/frontend-patterns.md # React patterns (230 lines)
+styling/design-system.md # UI standards (122 lines)
+backend/server-actions.md # Server patterns (100 lines)
+database/database-queries.md # Query patterns (207 lines)
+```
+
+### Step 4: Implement Task Management
+
+```markdown
+# Task File Structure:
+
+tasks/features/[name]/task-plan.md # Main planning file
+tasks/features/[name]/review-report.md # Post-completion review
+tasks/fixes/[name]/task-plan.md # Bug fix planning
+tasks/single/[name].md # Simple tasks
+```
+
+### Step 5: Add Quality Gates
+
+```markdown
+# Automated Validation:
+
+!`pnpm tsc --noEmit` # TypeScript check
+!`pnpm lint` # Code quality
+!`pnpm build` # Build validation
+!`pnpm test` # Test execution
+```
+
+## Best Practices
+
+### Context Management
+
+1. **Keep context files focused** (50-150 lines)
+2. **Use single-level loading** (no recursive `@` references)
+3. **Load dynamically** based on request analysis
+4. **Always include core patterns** + architecture
+
+### Agent Design
+
+1. **Make agents deterministic** (predictable behavior)
+2. **Give clear, direct instructions** (not documentation)
+3. **Separate concerns** (one agent, one responsibility)
+4. **Use structured response formats**
+
+### Task Management
+
+1. **Break complex work into steps** (15-30 minutes each)
+2. **Use checkbox tracking** for progress visibility
+3. **Include validation criteria** for each step
+4. **Implement quality gates** at key milestones
+
+### Workflow Orchestration
+
+1. **Analyze before routing** (complexity and domain)
+2. **Use appropriate workflows** (simple vs complex)
+3. **Coordinate multiple agents** for complex tasks
+4. **Validate at every step** (build, test, review)
+
+## System Architecture
+
+```mermaid
+graph TB
+    subgraph "User Interface"
+        CMD["/workflow<br/>/plan-task<br/>/execute-task"]
+    end
+
+    subgraph ".opencode/command/"
+        C1[workflow.md]
+        C2[plan-task.md]
+        C3[execute-task.md]
+        C4[create-frontend-component.md]
+        C5[create-backend-operation.md]
+    end
+
+    subgraph ".opencode/agent/"
+        A1[workflow-orchestrator.md]
+        A2[task-planner.md]
+        A3[task-executor.md]
+        A4[simple-coder.md]
+        A5[post-flight-reviewer.md]
+    end
+
+    subgraph ".opencode/context/"
+        CTX1[core/essential-patterns.md]
+        CTX2[architecture/architecture-lite.md]
+        CTX3[frontend/frontend-patterns.md]
+        CTX4[backend/server-actions.md]
+        CTX5[database/database-queries.md]
+    end
+
+    subgraph "tasks/"
+        T1[features/[name]/task-plan.md]
+        T2[fixes/[name]/task-plan.md]
+        T3[single/[name].md]
+    end
+
+    CMD --> C1
+    CMD --> C2
+    CMD --> C3
+
+    C1 --> A1
+    C2 --> A2
+    C3 --> A3
+    C4 --> A4
+    C5 --> A4
+
+    A1 --> CTX1
+    A1 --> CTX2
+    A2 --> CTX2
+    A3 --> CTX1
+    A4 --> CTX3
+    A4 --> CTX4
+
+    A2 --> T1
+    A3 --> T1
+    A3 --> T2
+```
+
+## Using the System (User Guide)
+
+### How Slash Commands Work
+
+**Slash commands automatically inject context into agents using @ references:**
+
+```bash
+# User types this:
+/create-component "user profile card with avatar upload"
+
+# OpenCode automatically:
+# 1. Loads the command file: .opencode/command/create-component.md
+# 2. Reads all @ referenced context files
+# 3. Injects context + user request into the agent
+# 4. Agent starts with full knowledge of your patterns
+```
+
+### Basic Commands
+
+- `/workflow "your request"` - Main entry point for any development task
+- `/plan-task "complex feature"` - For multi-step planning
+- `/execute-task` - Continue working on planned tasks
+
+### Command Examples
+
+- `/workflow "Create a user profile component with form validation"`
+- `/create-frontend-component "Dashboard header with user menu"`
+- `/review-completion` - Check completed work for quality
+
+### When to Use Which Command
+
+- Simple tasks (< 30 min) → `/workflow`
+- Complex features → `/plan-task` then `/execute-task`
+- Specialized work → `/create-frontend-component`, `/create-backend-operation`
+
+### Why This Works Better Than Chat
+
+- **Consistent patterns**: Agents always use YOUR coding standards
+- **Faster execution**: No time spent discovering or asking about patterns
+- **Quality results**: Context-aware from the first response
+- **Scalable knowledge**: Add new patterns once, use everywhere
+
+## Quick Start Implementation
+
+### 1. Start with Core System
+
+```bash
+# Create essential structure
+mkdir -p .opencode/{command,agent,context/{core,architecture}}
+mkdir -p tasks/{features,fixes,single}
+```
+
+### 2. Create Main Entry Point
+
+```markdown
+# .opencode/command/workflow.md
+
+---
+
+name: workflow
+agent: workflow-orchestrator
+
+---
+
+You are analyzing requests and routing to optimal workflows.
+
+**Request:** $ARGUMENTS
+
+**Context Loaded:**
+@.opencode/context/core/essential-patterns.md
+@.opencode/context/architecture/architecture-lite.md
+
+Route to appropriate specialized workflow now.
+```
+
+### 3. Build Workflow Orchestrator
+
+```markdown
+# .opencode/agent/workflow-orchestrator.md
+
+---
+
+description: "Routes requests to specialized workflows"
+mode: primary
+tools: [read, grep, glob, task]
+
+---
+
+You are analyzing requests and routing to specialized workflows.
+
+**ANALYZE** the request and **ROUTE** to appropriate command:
+
+- Feature development → /plan-task or /create-frontend-component
+- Bug fixes → /fix-issue
+- Code review → /review-code
+
+Execute routing now.
+```
+
+### 4. Add Essential Context
+
+````markdown
+# .opencode/context/core/essential-patterns.md
+
+**CORE PATTERNS** - Essential patterns for all development:
+
+**AUTHENTICATION PATTERN**:
+
+```typescript
+const user = await getCurrentUser();
+if (!user) return { error: "Unauthorized" };
+```
+````
+
+**VALIDATION PATTERN**:
+
+```typescript
+const validated = schema.safeParse(data);
+if (!validated.success) return { error: "Validation failed" };
+```
+
+````
+
+### 5. Test Your System
+```bash
+# Test the workflow
+/workflow "Create a user profile component"
+# Should route to appropriate workflow with right context
+````
+
+## Remember
+
+1. **Context flows one direction** - Commands load immediately, Agents look up deterministically
+2. **Keep context focused** - 50-150 lines per file, maximum 4 files per command
+3. **Make agents predictable** - Deterministic behavior with clear instructions
+4. **Track everything** - File-based task management with checkbox progress
+5. **Validate continuously** - Quality gates and post-flight reviews
+6. **Start simple** - Build core system first, add complexity gradually
+
+---
+
+_Think of this system like a professional development team: each member has a specific role, they communicate clearly, they track their work systematically, and they validate quality at every step._

+ 15 - 11
.opencode/AGENTS.md

@@ -1,24 +1,28 @@
-# OpenCode Agents for Mastra RAG Template
+
 
 This repository defines task-focused agents to streamline planning, implementation, review, documentation, and testing.
 
 Agents:
 
-- `plan-project`: Roadmaps, milestones, ADRs, risk register
-- `plan-analyse`: Repo survey, external research, dependency and risk mapping
-- `mastra`: Implementation for ingestion, embeddings, LanceDB, retrieval, assembly, and agents
-- `review`: Code review with targeted feedback and suggested diffs
-- `documentation`: Doc updates for README, specs, examples
-- `write-test`: Unit/integration tests, mocks, deterministic practices
+- `general`: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks
+- `reviewer`: Code review, security, and quality assurance agent
+- `subagents/codebase-pattern-analyst`: TypeScript implementation agent for modular and functional development
+- `subagents/coder-agent`: Executes coding subtasks in sequence, ensuring completion as specified
+- `subagents/build-agent`: Type check and build validation agent
+- `subagents/tester`: Test authoring and TDD agent
+- `subagents/documentation`: Documentation authoring agent
+- `@task-manager`: Task planning and management agent
+- `@tester`: Test execution and validation agent
+- `@documentation`: Documentation generation and updates agent
 
 Usage:
 
 ```bash
-# Start a session with an agent
-opencode --agent plan-project
+# Launch a task with a specific agent
+task --description "Short task description" --prompt "Detailed task instructions" --subagent_type general
 
-# One-off task
-opencode run --agent mastra "Implement LanceDB schema and retrieval module"
+# Example for code review
+task --description "Review code" --prompt "Review the changes in pull request #123" --subagent_type reviewer
 ```
 
 Safety:

+ 4 - 3
.opencode/agent/codebase-agent.md

@@ -33,9 +33,9 @@ Always start with phrase "DIGGING IN..."
 
 You have access to the following subagents: 
 - `@task-manager`
-- `@subagents/coder-agent`
-- `@subagents/tester`
-- `@subagents/documentation`
+- `@image-specialist`
+- `@subagents/tester` @tester
+- `@subagents/documentation` @documentation
 
 Focus:
 You are a TypeScript coding specialist focused on writing clean, maintainable, and scalable code. Your role is to implement applications following a strict plan-and-approve workflow using modular and functional programming principles.
@@ -78,6 +78,7 @@ Do NOT proceed without explicit approval
 Phase 2: Implementation (After Approval Only)
 
 Implement incrementally - complete one step at a time, never implement the entire plan at once
+If need images for a task, so pass it to the `@image-specialist` to make images for the task and tell it where to save the images. So you can use the images in the task.
 After each increment:
 - Use appropriate runtime (node/bun) to execute the code and check for errors before moving on to the next step
 - Run type checks using TypeScript compiler

+ 65 - 0
.opencode/agent/image-specialist.md

@@ -0,0 +1,65 @@
+---
+description: Specialized agent for image editing and analysis using Gemini AI tools
+mode: primary
+model: anthropic/claude-sonnet-4-20250514
+temperature: 0.3
+permission:
+  edit: deny
+  bash: deny
+  webfetch: allow
+tools:
+  write: false
+  edit: false
+  bash: false
+  read: true
+  grep: true
+  glob: true
+  list: true
+  gemini-multiple_analyze: true
+  gemini-multiple_edit: true
+  gemini: true
+---
+
+You are an image processing specialist powered by Gemini AI's Nano Banana model. Your capabilities include:
+
+## Core Functions
+- **Image Generation**: Creating images from text using Gemini Nano Banana
+- **Image Editing**: Modifying existing images with Nano Banana
+- **Image Analysis**: Analyzing images with detailed descriptions
+
+## Tools Available
+- `gemini-multiple_edit`: Edit existing images with Nano Banana
+- `gemini-multiple_analyze`: Analyze images and provide detailed descriptions  
+- `gemini`: Generate or edit images (legacy tool)
+
+## Meta-Prompt for Nano Banana Requests
+
+When users provide simple instructions, use this meta-prompt approach to create detailed Nano Banana prompts:
+
+**Process:**
+1. **Identify core purpose**: Schematic/diagram, action illustration, or emotive scene?
+2. **Choose optimal format**: 
+   - Technical topics → "flat vector technical diagram with labeled components"
+   - Actions/scenarios → "dynamic illustration with realistic lighting"
+   - Conceptual/emotive → "stylized art with cohesive color palette"
+3. **Determine style attributes**: Color palette, typography, composition
+4. **Build final prompt**: "Create a [FORMAT] illustrating [TOPIC] in a [STYLE] style, using [COLORS], with [TYPOGRAPHY] labels, include [LAYOUT ELEMENTS]"
+
+**Example:**
+- Input: "Visualize microservices architecture"
+- Output: "Create a flat-vector technical diagram illustrating a microservices architecture with labeled service nodes and directional arrows showing service-to-service calls, in a navy & teal color palette, with Roboto sans-serif labels, include a legend box at bottom right, optimized for 1200×627 px."
+
+## Workflow
+1. **For simple requests**: Apply meta-prompt to enhance the instruction
+2. **For image generation**: Use detailed, styled prompts with Nano Banana
+3. **For image editing**: Preserve original context while applying modifications
+4. **For analysis**: Provide comprehensive descriptions and suggestions
+
+## File Organization
+- Images are automatically organized by date: `assets/images/YYYY-MM-DD/`
+- Generations saved to: `generations/` subdirectory
+- Edits saved to: `edits/` subdirectory with auto-increment naming
+- No files are overwritten - each edit creates a unique numbered version
+- All images stored in repo's `assets/images/` directory for proper organization
+
+Always ensure you have necessary inputs and provide clear descriptions of operations performed.

+ 42 - 0
.opencode/agent/subagents/build-agent.md

@@ -0,0 +1,42 @@
+---
+
+description: "Type check and build validation agent"
+mode: subagent
+model: claude-4-sonnet
+temperature: 0.1
+tools:
+  bash: true
+  read: true
+  grep: true
+permissions:
+  bash:
+    "tsc": "allow"
+    "npm run build": "allow"
+    "yarn build": "allow"
+    "pnpm build": "allow"
+    "*": "deny"
+  edit:
+    "**/*": "deny"
+---
+
+# Build Agent
+
+You are a build validation agent. For every request, perform the following steps:
+
+1. **Type Check**
+   - Run the TypeScript compiler (`tsc`).
+   - If there are any type errors, return the error output and stop.
+
+2. **Build Check**
+   - If type checking passes, run the build command (`npm run build`, `yarn build`, or `pnpm build` as appropriate).
+   - If there are any build errors, return the error output.
+
+3. **Success**
+   - If both steps complete without errors, return a success message.
+
+**Rules:**
+- Only run type check and build check.
+- Only report errors if they occur; otherwise, report success.
+- Do not modify any code.
+
+Execute type check and build validation now.

+ 10 - 2
.opencode/agent/reviewer.md

@@ -1,4 +1,5 @@
 ---
+
 description: "Code review, security, and quality assurance agent"
 mode: subagent
 model: claude-4-sonnet
@@ -25,15 +26,22 @@ Responsibilities:
 - Check alignment with naming conventions and modular patterns
 - Identify and flag potential security vulnerabilities (e.g., XSS, injection, insecure dependencies)
 - Flag potential performance and maintainability issues
+- Load project-specific context for accurate pattern validation
 - First sentence should be Start with "Reviewing..., what would you devs do if I didn't check up on you?"
 
 Workflow:
 
-1. Share a short review plan (files/concerns to inspect, including security aspects) and ask to proceed.
-2. Provide concise review notes with suggested diffs (do not apply changes), including any security concerns.
+1. **ANALYZE** request and load relevant project context
+2. Share a short review plan (files/concerns to inspect, including security aspects) and ask to proceed.
+3. Provide concise review notes with suggested diffs (do not apply changes), including any security concerns.
 
 Output:
 Start with "Reviewing..., what would you devs do if I didn't check up on you?"
 Then give a short summary of the review.
 
 - Risk level (including security risk) and recommended follow-ups
+
+**Context Loading:**
+- Load project patterns and security guidelines
+- Analyze code against established conventions
+- Flag deviations from team standards

+ 0 - 1
.opencode/agent/tester.md

@@ -1,7 +1,6 @@
 ---
 description: "Test authoring and TDD agent"
 mode: subagent
-model: google/gemini-2.5-flash
 temperature: 0.1
 tools:
   read: true

+ 56 - 0
.opencode/agent/workflow-orchestrator.md

@@ -0,0 +1,56 @@
+---
+
+description: "Routes requests to specialized workflows with selective context loading"
+mode: primary
+model: claude-4-sonnet
+temperature: 0.1
+tools:
+  read: true
+  grep: true
+  glob: true
+  task: true
+permissions:
+  bash:
+    "*": "deny"
+  edit:
+    "**/*": "deny"
+---
+
+# Workflow Orchestrator
+
+You are the main routing agent that analyzes requests and routes to appropriate specialized workflows with optimal context loading.
+
+**ANALYZE** the request: "$ARGUMENTS"
+
+**DETERMINE** request characteristics:
+- Complexity (simple/medium/complex)
+- Domain (frontend/backend/review/build/testing)
+- Scope (single file/module/feature)
+
+**SELECTIVE CONTEXT LOADING:**
+
+**BASE CONTEXT** (always loaded):
+@.opencode/context/core/essential-patterns.md
+@.opencode/context/project/project-context.md
+
+**CONDITIONAL CONTEXT** (based on analysis):
+!`if echo "$ARGUMENTS" | grep -i -E "(review|security|quality)" > /dev/null; then echo "@.opencode/context/project/project-context.md"; fi`
+!`if echo "$ARGUMENTS" | grep -i -E "(build|type|lint|compile)" > /dev/null; then echo "@.opencode/context/project/project-context.md"; fi`
+!`if echo "$ARGUMENTS" | grep -i -E "(test|spec|unit|integration)" > /dev/null; then echo "@.opencode/context/project/project-context.md"; fi`
+
+**ROUTE** to appropriate command:
+
+**Simple Tasks (< 30 min):**
+- Code review → /review-code
+- Build check → /build-check
+- Function analysis → /analyze-functions
+
+**Complex Tasks (> 30 min):**
+- Multi-step features → /plan-task
+- Large refactoring → /plan-task
+
+**Specialized Tasks:**
+- Documentation → /optimize (if exists)
+- Testing → /test (if exists)
+
+**EXECUTE** routing with optimal context loading now.

+ 121 - 0
.opencode/command/prompt-enchancer.md

@@ -0,0 +1,121 @@
+---
+description: "Research-backed XML prompt optimizer delivering 20% performance improvement"
+---
+
+<!-- RESEARCH-BACKED OPTIMAL SEQUENCE -->
+
+<context>
+  <system_context>Prompt optimization using empirically-proven XML structures</system_context>
+  <domain_context>LLM prompt engineering with Stanford/Anthropic research patterns</domain_context>
+  <optimization_metrics>20% routing accuracy, 25% consistency, 17% performance gains</optimization_metrics>
+</context>
+
+<role>Expert Prompt Architect specializing in evidence-based XML structure optimization</role>
+
+<task>Transform prompts into high-performance XML following proven component ordering for measurable improvements</task>
+
+<instructions>
+  <step id="1" name="analyze">
+    <action>Assess current structure against research patterns</action>
+    <checklist>
+      - Component order (context→role→task→instructions)
+      - Length ratios (role 5-10%, context 15-25%, instructions 40-50%)
+      - Context management strategy presence
+      - Hierarchical routing implementation
+    </checklist>
+  </step>
+  
+  <step id="2" name="restructure">
+    <action>Apply optimal component sequence</action>
+    <sequence>
+      1. Context (system→domain→task→execution)
+      2. Role (clear identity, first 20% of prompt)
+      3. Task (primary objective)
+      4. Instructions (hierarchical workflow)
+      5. Examples (if needed)
+      6. Constraints (boundaries)
+      7. Output_format (expected structure)
+    </sequence>
+  </step>
+  
+  <step id="3" name="enhance_routing">
+    <action>Implement manager-worker patterns</action>
+    <routing_logic>
+      - LLM-based decision making
+      - Explicit routing criteria with @ symbol
+      - Fallback strategies
+      - Context allocation per task type
+    </routing_logic>
+  </step>
+  
+  <step id="4" name="optimize_context">
+    <action>Apply 3-level context management</action>
+    <levels>
+      <level_1 usage="80%">Complete isolation - subagent receives only specific task</level_1>
+      <level_2 usage="20%">Filtered context - curated relevant background</level_2>
+      <level_3 usage="rare">Windowed context - last N messages only</level_3>
+    </levels>
+  </step>
+</instructions>
+
+<proven_patterns>
+  <xml_advantages>
+    - 40% improvement in response quality with descriptive tags
+    - 15% reduction in token overhead for complex prompts
+    - Universal compatibility across models
+    - Explicit boundaries prevent context bleeding
+  </xml_advantages>
+  
+  <component_ratios>
+    <role>5-10% of total prompt</role>
+    <context>15-25% hierarchical information</context>
+    <instructions>40-50% detailed procedures</instructions>
+    <examples>20-30% when needed</examples>
+    <constraints>5-10% boundaries</constraints>
+  </component_ratios>
+  
+  <routing_patterns>
+    <subagent_references>Always use @ symbol (e.g., @context-provider, @research-assistant-agent)</subagent_references>
+    <delegation_syntax>Route to @[agent-name] when [condition]</delegation_syntax>
+  </routing_patterns>
+</proven_patterns>
+
+<output_template>
+## Analysis Results
+- Current Structure Score: [X/10]
+- Optimization Opportunities: [LIST]
+- Expected Performance Gain: [X%]
+
+## Optimized Prompt Structure
+
+```xml
+<context>
+  [HIERARCHICAL CONTEXT: system→domain→task→execution]
+</context>
+
+<role>[AGENT IDENTITY - 5-10% of prompt]</role>
+
+<task>[PRIMARY OBJECTIVE]</task>
+
+<instructions>
+  [WORKFLOW WITH ROUTING USING @ SYMBOLS]
+</instructions>
+
+[ADDITIONAL COMPONENTS AS NEEDED]
+```
+
+## Implementation Notes
+- Component reordering impact: +[X]% performance
+- Context management efficiency: [X]% reduction
+- Routing accuracy improvement: +[X]%
+- Subagent references: @agent-name format maintained
+</output_template>
+
+<quality_principles>
+  <research_based>Stanford multi-instruction study + Anthropic XML research</research_based>
+  <performance_focused>Measurable 20% routing improvement</performance_focused>
+  <context_efficient>80% reduction in unnecessary context</context_efficient>
+  <immediate_usability>Ready for deployment without modification</immediate_usability>
+</quality_principles>
+
+

+ 0 - 72
.opencode/command/prompter.md

@@ -1,72 +0,0 @@
----
-description: Transform basic requests into comprehensive, well-structured prompts using the 10-step framework
----
-
-You are a prompt enhancement specialist. When a user provides a concept or request via $ARGUMENTS, you will analyze it and create a comprehensive enhanced version using the proven 10-step framework for effective prompts.
-
-## Your Enhancement Process:
-
-**Step 1: Analyze the Original Request**
-Examine the provided concept and identify:
-- Task type and scope
-- Required expertise domain  
-- End goal and success criteria
-- Potential challenges or complexity
-
-**Step 2: Apply the 10-Step Enhancement Framework**
-Transform the request by incorporating these essential elements:
-
-1. **Task Context** - Expert role and main objective
-2. **Tone Context** - Communication style and audience level
-3. **Background Knowledge** - Required expertise and resources
-4. **Task Rules & Standards** - Quality criteria and constraints
-5. **Examples & Patterns** - Concrete illustrations of approaches
-6. **Context Management** - Conversation continuity handling
-7. **Immediate Deliverables** - Expected outputs
-8. **Reasoning Approach** - Systematic thinking methodology
-9. **Output Structure** - Format and organization requirements
-10. **Response Framework** - Templates and structured approaches
-
-**Step 3: Present Your Analysis**
-
-Use this format:
-
----
-## 📋 Request Analysis
-**Original concept**: "[USER'S CONCEPT]"  
-**Task type**: [Brief description of work involved]  
-**Required expertise**: [Domain knowledge needed]  
-**Complexity level**: [Simple/Moderate/Complex]
-
-## 🎯 10-Step Enhanced Framework
-Instead of a basic request, here's a structured approach using all 10 framework elements:
-
-### ✨ Enhanced Request:
-```
-[Create a comprehensive request that naturally incorporates all 10 elements, written as a single cohesive prompt that the user could copy and use. Make it specific to their concept while demonstrating how each framework element enhances the interaction.]
-```
-
-## 🔧 Framework Elements Applied:
-- **Task Context**: [How expert role was defined]
-- **Tone Context**: [Communication style set]
-- **Background Knowledge**: [Expertise requirements specified]
-- **Task Rules & Standards**: [Quality criteria added]
-- **Examples & Patterns**: [Illustrations provided]
-- **Context Management**: [Continuity handled]
-- **Immediate Deliverables**: [Expected outputs clarified]
-- **Reasoning Approach**: [Thinking methodology specified]
-- **Output Structure**: [Format requirements defined]
-- **Response Framework**: [Templates/structure provided]
-
-### ▶️ Suggested Next Step
-"Would you like me to proceed with this enhanced 10-step approach? Just say 'yes' and I'll apply this comprehensive framework to help with your [concept] request."
-
----
-
-## Important Guidelines:
-- Make the enhanced request feel natural, not like a checklist
-- Ensure all 10 framework elements are meaningfully integrated
-- Tailor the complexity and tone to match the original concept
-- The enhanced request should be immediately usable by the user
-- Focus on practical improvements that will genuinely enhance their interaction
-

+ 250 - 0
.opencode/context/core/essential-patterns.md

@@ -0,0 +1,250 @@
+# Essential Patterns - Core Knowledge Base
+
+## Error Handling Pattern
+
+**ALWAYS** handle errors gracefully:
+
+```typescript
+try {
+  const result = await riskyOperation();
+  return { success: true, data: result };
+} catch (error) {
+  console.error('Operation failed:', error);
+  return { success: false, error: error.message };
+}
+```
+
+## Validation Pattern
+
+**ALWAYS** validate input data:
+
+```typescript
+function validateInput(input: any): { valid: boolean; errors?: string[] } {
+  const errors: string[] = [];
+
+  if (!input) errors.push('Input is required');
+  if (typeof input !== 'string') errors.push('Input must be a string');
+  if (input.length < 3) errors.push('Input must be at least 3 characters');
+
+  return {
+    valid: errors.length === 0,
+    errors: errors.length > 0 ? errors : undefined
+  };
+}
+```
+
+## Logging Pattern
+
+**USE** consistent logging levels:
+
+```typescript
+// Debug information (development only)
+console.debug('Processing request:', requestId);
+
+// Info for important events
+console.info('User authenticated:', userId);
+
+// Warning for potential issues
+console.warn('Rate limit approaching for user:', userId);
+
+// Error for failures
+console.error('Database connection failed:', error);
+```
+
+## Security Pattern
+
+**NEVER** expose sensitive information:
+
+```typescript
+// ❌ BAD: Exposing internal errors
+return { error: 'Internal server error: ' + error.message };
+
+// ✅ GOOD: Generic error message
+return { error: 'An unexpected error occurred. Please try again.' };
+```
+
+## File System Safety Pattern
+
+**ALWAYS** validate file paths:
+
+```typescript
+import path from 'path';
+
+function safeReadFile(userPath: string): string | null {
+  const resolvedPath = path.resolve(userPath);
+  const allowedDir = path.resolve('./allowed-directory');
+
+  // Ensure path is within allowed directory
+  if (!resolvedPath.startsWith(allowedDir)) {
+    throw new Error('Access denied: Invalid path');
+  }
+
+  return fs.readFileSync(resolvedPath, 'utf8');
+}
+```
+
+## Type Safety Pattern
+
+**ALWAYS** use strict TypeScript types:
+
+```typescript
+interface User {
+  id: string;
+  name: string;
+  email: string;
+  createdAt: Date;
+}
+
+interface ApiResponse<T> {
+  success: boolean;
+  data?: T;
+  error?: string;
+}
+
+// Use generics for type-safe responses
+function createUser(userData: Omit<User, 'id' | 'createdAt'>): ApiResponse<User> {
+  // Implementation
+}
+```
+
+## Async/Await Pattern
+
+**ALWAYS** handle promises properly:
+
+```typescript
+// ❌ BAD: Nested promises
+fetchUser().then(user => {
+  return fetchPosts(user.id).then(posts => {
+    return { user, posts };
+  });
+});
+
+// ✅ GOOD: Async/await with error handling
+async function getUserWithPosts(userId: string) {
+  try {
+    const user = await fetchUser(userId);
+    const posts = await fetchPosts(user.id);
+    return { user, posts };
+  } catch (error) {
+    console.error('Failed to fetch user data:', error);
+    throw error;
+  }
+}
+```
+
+## Configuration Pattern
+
+**ALWAYS** use environment variables for configuration:
+
+```typescript
+// config.ts
+export const config = {
+  port: parseInt(process.env.PORT || '3000'),
+  databaseUrl: process.env.DATABASE_URL,
+  jwtSecret: process.env.JWT_SECRET,
+  nodeEnv: process.env.NODE_ENV || 'development',
+};
+
+// Validate required config
+if (!config.databaseUrl) {
+  throw new Error('DATABASE_URL environment variable is required');
+}
+```
+
+## Testing Pattern
+
+**ALWAYS** write testable code:
+
+```typescript
+// ❌ BAD: Hard to test
+export function processPayment(amount: number) {
+  const apiKey = process.env.STRIPE_KEY;
+  // Direct API call
+}
+
+// ✅ GOOD: Dependency injection
+export interface PaymentService {
+  processPayment(amount: number): Promise<boolean>;
+}
+
+export function createPaymentProcessor(service: PaymentService) {
+  return {
+    async process(amount: number) {
+      return service.processPayment(amount);
+    }
+  };
+}
+```
+
+## Documentation Pattern
+
+**ALWAYS** document complex logic:
+
+```typescript
+/**
+ * Calculates the total price including tax and discounts
+ * @param basePrice - The original price before modifications
+ * @param taxRate - Tax rate as decimal (e.g., 0.08 for 8%)
+ * @param discountPercent - Discount percentage (0-100)
+ * @returns The final price after tax and discount
+ */
+function calculateTotalPrice(
+  basePrice: number,
+  taxRate: number = 0.08,
+  discountPercent: number = 0
+): number {
+  const discountAmount = basePrice * (discountPercent / 100);
+  const discountedPrice = basePrice - discountAmount;
+  const taxAmount = discountedPrice * taxRate;
+  return discountedPrice + taxAmount;
+}
+```
+
+## Performance Pattern
+
+**AVOID** unnecessary operations in loops:
+
+```typescript
+// ❌ BAD: Repeated calculations
+const results = [];
+for (let i = 0; i < items.length; i++) {
+  results.push(items[i] * calculateTax(items[i])); // calculateTax called repeatedly
+}
+
+// ✅ GOOD: Pre-calculate or cache
+const results = [];
+const taxRate = getCurrentTaxRate(); // Calculate once
+for (let i = 0; i < items.length; i++) {
+  results.push(items[i] * taxRate);
+}
+```
+
+## Code Organization Pattern
+
+**KEEP** functions focused and small:
+
+```typescript
+// ❌ BAD: One function doing too much
+function processOrder(orderData) {
+  // Validate input
+  // Calculate pricing
+  // Save to database
+  // Send email
+  // Log analytics
+}
+
+// ✅ GOOD: Separated concerns
+function validateOrder(orderData) { /* validation logic */ }
+function calculatePricing(orderData) { /* pricing logic */ }
+function saveOrder(orderData) { /* database logic */ }
+function sendConfirmation(orderData) { /* email logic */ }
+function logAnalytics(orderData) { /* analytics logic */ }
+
+async function processOrder(orderData) {
+  validateOrder(orderData);
+  const pricing = calculatePricing(orderData);
+  await saveOrder({ ...orderData, pricing });
+  await sendConfirmation(orderData);
+  logAnalytics(orderData);
+}
+```

+ 97 - 0
.opencode/context/project/project-context.md

@@ -0,0 +1,97 @@
+# OpenCode Agent System Project Context
+
+## Technology Stack
+
+**Primary Language:** TypeScript
+**Runtime:** Node.js/Bun
+**Package Manager:** npm/pnpm/yarn
+**Build Tools:** TypeScript Compiler (tsc)
+**Testing:** Jest/Vitest (if configured)
+**Linting:** ESLint (if configured)
+
+## Project Structure
+
+```
+.opencode/
+├── agent/           # AI agents for specific tasks
+│   ├── subagents/   # Specialized subagents
+│   └── *.md         # Primary agents
+├── command/         # Slash commands
+├── context/         # Knowledge base for agents
+└── plugin/          # Extensions and integrations
+
+tasks/               # Task management files
+```
+
+## Core Patterns
+
+### Agent Structure Pattern
+```markdown
+---
+description: "What this agent does"
+mode: primary|subagent
+tools: [read, edit, bash, etc.]
+permissions: [security restrictions]
+---
+
+# Agent Name
+
+[Direct instructions for behavior]
+
+**EXECUTE** this [process type] for every [task type]:
+
+**1. [ACTION]** the [subject]:
+- [Specific instruction 1]
+- [Specific instruction 2]
+
+**RULES:**
+- **ALWAYS** [critical requirement]
+- **NEVER** [forbidden action]
+```
+
+### Command Structure Pattern
+```markdown
+---
+name: command-name
+agent: target-agent
+---
+
+You are [doing specific task].
+
+**Request:** $ARGUMENTS
+
+**Context Loaded:**
+@.opencode/context/core/essential-patterns.md
+@[additional context files]
+
+Execute [task] now.
+```
+
+### Context Loading Rules
+- Commands load context immediately using @ references
+- Agents can look up additional context deterministically
+- Maximum 4 context files per command (250-450 lines total)
+- Keep context files focused (50-150 lines each)
+
+## Security Guidelines
+
+- Agents have restricted permissions by default
+- Sensitive operations require explicit approval
+- No direct file system modifications without validation
+- Build commands limited to safe operations
+
+## Development Workflow
+
+1. **Planning:** Create detailed task plans for complex work
+2. **Implementation:** Execute one step at a time with validation
+3. **Review:** Code review and security checks
+4. **Testing:** Automated testing and build validation
+5. **Documentation:** Update docs and context files
+
+## Quality Gates
+
+- TypeScript compilation passes
+- Code review completed
+- Build process succeeds
+- Tests pass (if available)
+- Documentation updated

+ 79 - 0
.opencode/tool/README.md

@@ -0,0 +1,79 @@
+# Gemini Image Tools
+
+This collection of tools allows you to edit and analyze images using Google's Gemini AI directly from OpenCode.
+
+## Setup
+
+1. Get your Gemini API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
+2. Add it to your `.env` file:
+   ```bash
+   GEMINI_API_KEY=your_api_key_here
+   ```
+
+## Available Tools
+
+### `/gemini` - Simple Image Editor
+Edit an image using file path or data URL:
+
+```bash
+/gemini "path/to/image.png" "Add a red border around the image" "output.png"
+/gemini "data:image/png;base64,AAA..." "Convert to black and white"
+```
+
+### `/gemini_multiple_edit` - Advanced Image Editor
+Same functionality as `/gemini` but from the multiple tools file:
+
+```bash
+/gemini_multiple_edit "image.jpg" "Make it look like a watercolor painting" "watercolor.jpg"
+```
+
+### `/gemini_multiple_analyze` - Image Analysis
+Analyze an image without editing it:
+
+```bash
+/gemini_multiple_analyze "screenshot.png" "What programming language is shown in this code?"
+/gemini_multiple_analyze "photo.jpg" "Describe the objects and colors in this image"
+```
+
+### `/gemini_edit` - Auto-Detection Plugin
+1. Paste an image directly into your OpenCode chat
+2. Use the command with just the prompt:
+
+```bash
+/gemini_edit "Add the text 'Hello World' in cursive at top"
+/gemini_edit "Make this image look like a painting"
+```
+
+## Features
+
+- **File Path Support**: Pass local image file paths
+- **Data URL Support**: Use base64 data URLs from pasted images
+- **Auto-Detection**: Plugin automatically captures the latest pasted image
+- **Image Analysis**: Ask questions about images without editing
+- **Flexible Output**: Specify custom output filenames or use defaults
+- **Error Handling**: Clear error messages for missing API keys or failed requests
+
+## Files
+
+- `gemini.ts` - Simple tool that accepts image arguments
+- `gemini-multiple.ts` - Multiple tools (edit + analyze) in one file
+- `../plugin/gemini-edit.ts` - Plugin with auto-detection of pasted images
+
+## API Endpoints
+
+- **Image Editing**: Uses Gemini 2.5 Flash with image preview capabilities
+- **Image Analysis**: Uses Gemini 2.5 Flash for text-based analysis
+
+## Examples
+
+```bash
+# Edit an image
+/gemini "logo.png" "Add a subtle drop shadow" "logo-shadow.png"
+
+# Analyze code in a screenshot
+/gemini_multiple_analyze "code-screenshot.png" "What bugs can you spot in this code?"
+
+# Auto-edit pasted image
+# (paste image first, then run:)
+/gemini_edit "Remove the background and make it transparent"
+```

+ 160 - 0
.opencode/tool/bun.lock

@@ -0,0 +1,160 @@
+{
+  "lockfileVersion": 1,
+  "workspaces": {
+    "": {
+      "name": "opencode-gemini-tool",
+      "dependencies": {
+        "@opencode-ai/sdk": "^0.10.0",
+        "zod": "^4.1.9",
+      },
+      "devDependencies": {
+        "@opencode-ai/plugin": "^0.10.0",
+        "@types/node": "^24.2.1",
+        "bun-types": "latest",
+      },
+    },
+  },
+  "packages": {
+    "@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.0.6", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0", "lodash": "^4.17.21" } }, "sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w=="],
+
+    "@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.81.0", "", { "dependencies": { "@hey-api/json-schema-ref-parser": "1.0.6", "ansi-colors": "4.1.3", "c12": "2.0.1", "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8", "js-yaml": "4.1.0", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-PoJukNBkUfHOoMDpN33bBETX49TUhy7Hu8Sa0jslOvFndvZ5VjQr4Nl/Dzjb9LG1Lp5HjybyTJMA6a1zYk/q6A=="],
+
+    "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
+
+    "@opencode-ai/plugin": ["@opencode-ai/plugin@0.10.0", "", { "dependencies": { "@opencode-ai/sdk": "0.10.0", "zod": "4.1.8" } }, "sha512-qviOlxiWZchKGfJ1WWWM9q8Jz/6PokcdEUvGW0xeuC11QAkgC/RRC0DDn62PCcJUuWTOfIBkIcIQdP4qzbYNKw=="],
+
+    "@opencode-ai/sdk": ["@opencode-ai/sdk@0.10.0", "", { "dependencies": { "@hey-api/openapi-ts": "0.81.0" } }, "sha512-2ojC3KEdf5r9rmhKJJx4cq8gxhw5LATHokIUl+VXsbE5qWaLe7yzt5Xpb2505wyieMfUlujo6XDGUtnPQXWkAw=="],
+
+    "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
+
+    "@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
+
+    "@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
+
+    "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
+
+    "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
+
+    "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+    "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
+
+    "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
+
+    "c12": ["c12@2.0.1", "", { "dependencies": { "chokidar": "^4.0.1", "confbox": "^0.1.7", "defu": "^6.1.4", "dotenv": "^16.4.5", "giget": "^1.2.3", "jiti": "^2.3.0", "mlly": "^1.7.1", "ohash": "^1.1.4", "pathe": "^1.1.2", "perfect-debounce": "^1.0.0", "pkg-types": "^1.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A=="],
+
+    "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
+
+    "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
+
+    "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
+
+    "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="],
+
+    "commander": ["commander@13.0.0", "", {}, "sha512-oPYleIY8wmTVzkvQq10AEok6YcTC4sRUBl8F9gVuwchGVUCTbl/vhLTaQqutuuySYOsu8YTgV+OxKc/8Yvx+mQ=="],
+
+    "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
+
+    "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
+
+    "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+
+    "default-browser": ["default-browser@5.2.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg=="],
+
+    "default-browser-id": ["default-browser-id@5.0.0", "", {}, "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA=="],
+
+    "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
+
+    "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
+
+    "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
+
+    "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+
+    "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
+
+    "giget": ["giget@1.2.5", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.5.4", "pathe": "^2.0.3", "tar": "^6.2.1" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug=="],
+
+    "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
+
+    "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
+
+    "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
+
+    "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
+
+    "jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="],
+
+    "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
+
+    "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
+
+    "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+
+    "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
+
+    "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
+
+    "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
+
+    "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="],
+
+    "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
+
+    "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
+
+    "nypm": ["nypm@0.5.4", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "tinyexec": "^0.3.2", "ufo": "^1.5.4" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA=="],
+
+    "ohash": ["ohash@1.1.6", "", {}, "sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg=="],
+
+    "open": ["open@10.1.2", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw=="],
+
+    "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
+
+    "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
+
+    "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
+
+    "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
+
+    "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
+
+    "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
+
+    "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+
+    "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+    "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
+
+    "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
+
+    "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
+
+    "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
+
+    "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
+
+    "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
+
+    "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
+
+    "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
+
+    "zod": ["zod@4.1.9", "", {}, "sha512-HI32jTq0AUAC125z30E8bQNz0RQ+9Uc+4J7V97gLYjZVKRjeydPgGt6dvQzFrav7MYOUGFqqOGiHpA/fdbd0cQ=="],
+
+    "@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
+
+    "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+    "giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+    "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
+
+    "mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+    "nypm/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+
+    "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+  }
+}

+ 168 - 0
.opencode/tool/env/index.ts

@@ -0,0 +1,168 @@
+import { readFile } from "fs/promises"
+import { resolve } from "path"
+
+/**
+ * Configuration for environment variable loading
+ */
+export interface EnvLoaderConfig {
+  /** Custom paths to search for .env files (relative to current working directory) */
+  searchPaths?: string[]
+  /** Whether to log when environment variables are loaded */
+  verbose?: boolean
+  /** Whether to override existing environment variables */
+  override?: boolean
+}
+
+/**
+ * Default search paths for .env files
+ */
+const DEFAULT_ENV_PATHS = [
+  './.env',
+  '../.env', 
+  '../../.env',
+  '../plugin/.env',
+  '../../../.env'
+]
+
+/**
+ * Load environment variables from .env files
+ * Searches multiple common locations for .env files and loads them into process.env
+ * 
+ * @param config Configuration options
+ * @returns Object containing loaded environment variables
+ */
+export async function loadEnvVariables(config: EnvLoaderConfig = {}): Promise<Record<string, string>> {
+  const { 
+    searchPaths = DEFAULT_ENV_PATHS, 
+    verbose = false, 
+    override = false 
+  } = config
+  
+  const loadedVars: Record<string, string> = {}
+  
+  for (const envPath of searchPaths) {
+    try {
+      const fullPath = resolve(envPath)
+      const content = await readFile(fullPath, 'utf8')
+      
+      if (verbose) {
+        console.log(`Checking .env file: ${envPath}`)
+      }
+      
+      // Parse .env file content
+      const lines = content.split('\n')
+      for (const line of lines) {
+        const trimmed = line.trim()
+        if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) {
+          const [key, ...valueParts] = trimmed.split('=')
+          const value = valueParts.join('=').trim()
+          
+          // Remove quotes if present
+          const cleanValue = value.replace(/^["']|["']$/g, '')
+          
+          if (key && cleanValue && (override || !process.env[key])) {
+            process.env[key] = cleanValue
+            loadedVars[key] = cleanValue
+            
+            if (verbose) {
+              console.log(`Loaded ${key} from ${envPath}`)
+            }
+          }
+        }
+      }
+    } catch (error) {
+      // File doesn't exist or can't be read, continue to next
+      if (verbose) {
+        console.log(`Could not read ${envPath}: ${error.message}`)
+      }
+    }
+  }
+  
+  return loadedVars
+}
+
+/**
+ * Get a specific environment variable with automatic .env file loading
+ * 
+ * @param varName Name of the environment variable
+ * @param config Configuration options
+ * @returns The environment variable value or null if not found
+ */
+export async function getEnvVariable(varName: string, config: EnvLoaderConfig = {}): Promise<string | null> {
+  // First check if it's already in the environment
+  let value = process.env[varName]
+  
+  if (!value) {
+    // Try to load from .env files
+    const loadedVars = await loadEnvVariables(config)
+    value = loadedVars[varName] || process.env[varName]
+  }
+  
+  return value || null
+}
+
+/**
+ * Get a required environment variable with automatic .env file loading
+ * Throws an error if the variable is not found
+ * 
+ * @param varName Name of the environment variable
+ * @param config Configuration options
+ * @returns The environment variable value
+ * @throws Error if the variable is not found
+ */
+export async function getRequiredEnvVariable(varName: string, config: EnvLoaderConfig = {}): Promise<string> {
+  const value = await getEnvVariable(varName, config)
+  
+  if (!value) {
+    const searchPaths = config.searchPaths || DEFAULT_ENV_PATHS
+    throw new Error(`${varName} not found. Please set it in your environment or .env file.
+    
+To fix this:
+1. Add to .env file: ${varName}=your_value_here
+2. Or export it: export ${varName}=your_value_here
+
+Current working directory: ${process.cwd()}
+Searched paths: ${searchPaths.join(', ')}
+Environment variables available: ${Object.keys(process.env).filter(k => k.includes(varName.split('_')[0])).join(', ') || 'none matching'}`)
+  }
+  
+  return value
+}
+
+/**
+ * Load multiple required environment variables at once
+ * 
+ * @param varNames Array of environment variable names
+ * @param config Configuration options
+ * @returns Object with variable names as keys and values as values
+ * @throws Error if any variable is not found
+ */
+export async function getRequiredEnvVariables(varNames: string[], config: EnvLoaderConfig = {}): Promise<Record<string, string>> {
+  const result: Record<string, string> = {}
+  
+  // Load all .env files first
+  await loadEnvVariables(config)
+  
+  // Check each required variable
+  for (const varName of varNames) {
+    const value = process.env[varName]
+    if (!value) {
+      throw new Error(`Required environment variable ${varName} not found. Please set it in your environment or .env file.`)
+    }
+    result[varName] = value
+  }
+  
+  return result
+}
+
+/**
+ * Utility function specifically for API keys
+ * 
+ * @param apiKeyName Name of the API key environment variable
+ * @param config Configuration options
+ * @returns The API key value
+ * @throws Error if the API key is not found
+ */
+export async function getApiKey(apiKeyName: string, config: EnvLoaderConfig = {}): Promise<string> {
+  return getRequiredEnvVariable(apiKeyName, config)
+}

+ 379 - 0
.opencode/tool/gemini/index.ts

@@ -0,0 +1,379 @@
+import { tool } from "@opencode-ai/plugin/tool"
+import { mkdir } from "fs/promises"
+import { join, dirname, basename, extname, resolve } from "path"
+import { getApiKey } from "../env"
+
+// Function to detect if we're in test mode
+function isTestMode(): boolean {
+  // Only enable test mode when explicitly set
+  return process.env.GEMINI_TEST_MODE === 'true'
+}
+
+// Function to get Gemini API key with automatic .env loading
+async function getGeminiApiKey(): Promise<string> {
+  if (isTestMode()) {
+    return 'test-api-key'
+  }
+  return getApiKey('GEMINI_API_KEY')
+}
+
+interface ImageConfig {
+  outputDir?: string;
+  useTimestamp?: boolean;
+  preserveOriginal?: boolean;
+  customName?: string;
+}
+
+async function parseImageInput(input: string) {
+  // Accepts file path ("./img.png") or data URL ("data:image/png;base64,...")
+  if (input.startsWith("data:")) {
+    const base64 = input.split(",")[1]
+    const mime = input.substring(5, input.indexOf(";"))
+    return { mime, base64 }
+  }
+  // Treat as file path
+  const file = Bun.file(input)
+  const arr = await file.arrayBuffer()
+  const base64 = Buffer.from(arr).toString("base64")
+  // Best-effort mime
+  const mime = file.type || "image/png"
+  return { mime, base64 }
+}
+
+async function ensureDirectoryExists(dirPath: string) {
+  try {
+    await mkdir(dirPath, { recursive: true })
+  } catch (error) {
+    // Directory might already exist, that's fine
+  }
+}
+
+function getDateBasedPath(baseDir?: string): string {
+  // Default to assets/images at repo root
+  if (!baseDir) {
+    // Navigate from .opencode/tool/ to repo root, then to assets/images
+    baseDir = resolve(process.cwd(), "../../assets/images")
+  }
+  const today = new Date().toISOString().split('T')[0] // YYYY-MM-DD format
+  return join(baseDir, today)
+}
+
+async function getUniqueFilename(directory: string, baseName: string, extension: string, isEdit: boolean = false): Promise<string> {
+  await ensureDirectoryExists(directory)
+  
+  if (!isEdit) {
+    // For generations, use timestamp if file exists
+    const baseFilename = join(directory, `${baseName}${extension}`)
+    const fileExists = await Bun.file(baseFilename).exists()
+    
+    if (!fileExists) {
+      return baseFilename
+    }
+    
+    // Add timestamp if file exists
+    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5) // Remove milliseconds and Z
+    return join(directory, `${baseName}_${timestamp}${extension}`)
+  }
+  
+  // For edits, use incremental numbering
+  let counter = 1
+  let filename: string
+  
+  do {
+    const editSuffix = `_edit_${counter.toString().padStart(3, '0')}`
+    filename = join(directory, `${baseName}${editSuffix}${extension}`)
+    counter++
+  } while (await Bun.file(filename).exists())
+  
+  return filename
+}
+
+export async function generateImage(prompt: string, config: ImageConfig = {}): Promise<string> {
+  const apiKey = await getGeminiApiKey()
+
+  // Test mode - return mock response without API call
+  if (isTestMode()) {
+    const baseDir = config.outputDir || getDateBasedPath()
+    const generationsDir = join(baseDir, "generations")
+    let baseName = config.customName || "generated"
+    if (baseName.endsWith('.png') || baseName.endsWith('.jpg') || baseName.endsWith('.jpeg')) {
+      baseName = baseName.substring(0, baseName.lastIndexOf('.'))
+    }
+    const outputPath = await getUniqueFilename(generationsDir, baseName, ".png", false)
+    
+    return `[TEST MODE] Would generate image: ${outputPath} for prompt: "${prompt.substring(0, 50)}..."`
+  }
+
+  const body = {
+    contents: [{
+      parts: [{ text: prompt }]
+    }],
+  }
+
+  const res = await fetch(
+    "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent",
+    {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+        "x-goog-api-key": apiKey,
+      },
+      body: JSON.stringify(body),
+    }
+  )
+
+  if (!res.ok) {
+    const errorText = await res.text()
+    throw new Error(`API error (${res.status}): ${errorText}`)
+  }
+  
+  const json = await res.json()
+  
+  // Look for image data in the response
+  const candidates = json?.candidates
+  if (!candidates || candidates.length === 0) {
+    throw new Error("No candidates in response")
+  }
+  
+  const parts = candidates[0]?.content?.parts
+  if (!parts || parts.length === 0) {
+    throw new Error("No parts in response")
+  }
+  
+  let b64 = null
+  for (const part of parts) {
+    if (part.inlineData?.data) {
+      b64 = part.inlineData.data
+      break
+    }
+  }
+  
+  if (!b64) {
+    throw new Error("No image data returned from Nano Banana model")
+  }
+
+  // Determine output path
+  const baseDir = config.outputDir || getDateBasedPath()
+  const generationsDir = join(baseDir, "generations")
+  
+  // Generate filename (remove extension if already present)
+  let baseName = config.customName || "generated"
+  if (baseName.endsWith('.png') || baseName.endsWith('.jpg') || baseName.endsWith('.jpeg')) {
+    baseName = baseName.substring(0, baseName.lastIndexOf('.'))
+  }
+  const extension = ".png"
+  const outputPath = await getUniqueFilename(generationsDir, baseName, extension, false)
+  
+  console.log(`Saving generated image to: ${outputPath}`)
+  await Bun.write(outputPath, Buffer.from(b64, "base64"))
+  
+  const fileExists = await Bun.file(outputPath).exists()
+  if (!fileExists) {
+    throw new Error(`Failed to save file to ${outputPath}`)
+  }
+  
+  const stats = await Bun.file(outputPath).stat()
+  return `Generated image saved: ${outputPath} (${stats.size} bytes)`
+}
+
+export async function editImage(imagePath: string, prompt: string, config: ImageConfig = {}): Promise<string> {
+  const apiKey = await getGeminiApiKey()
+
+  // Test mode - return mock response without API call
+  if (isTestMode()) {
+    const baseDir = config.outputDir || getDateBasedPath()
+    const editsDir = join(baseDir, "edits")
+    const originalName = basename(imagePath, extname(imagePath))
+    let baseName = config.customName || originalName
+    if (baseName.endsWith('.png') || baseName.endsWith('.jpg') || baseName.endsWith('.jpeg')) {
+      baseName = baseName.substring(0, baseName.lastIndexOf('.'))
+    }
+    const outputPath = await getUniqueFilename(editsDir, baseName, ".png", true)
+    
+    return `[TEST MODE] Would edit image: ${imagePath} -> ${outputPath} with prompt: "${prompt.substring(0, 50)}..."`
+  }
+
+  // Parse the input image
+  const { mime, base64 } = await parseImageInput(imagePath)
+  
+  const body = {
+    contents: [{
+      parts: [
+        { text: prompt },
+        { inlineData: { mimeType: mime, data: base64 } }
+      ]
+    }],
+  }
+
+  const res = await fetch(
+    "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image-preview:generateContent",
+    {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+        "x-goog-api-key": apiKey,
+      },
+      body: JSON.stringify(body),
+    }
+  )
+
+  if (!res.ok) {
+    const errorText = await res.text()
+    throw new Error(`API error (${res.status}): ${errorText}`)
+  }
+  
+  const json = await res.json()
+  
+  // Look for image data in the response
+  const candidates = json?.candidates
+  if (!candidates || candidates.length === 0) {
+    throw new Error("No candidates in response")
+  }
+  
+  const parts = candidates[0]?.content?.parts
+  if (!parts || parts.length === 0) {
+    throw new Error("No parts in response")
+  }
+  
+  let b64 = null
+  for (const part of parts) {
+    if (part.inlineData?.data) {
+      b64 = part.inlineData.data
+      break
+    }
+  }
+  
+  if (!b64) {
+    throw new Error("No image data returned from Nano Banana model")
+  }
+
+  // Determine output path
+  const baseDir = config.outputDir || getDateBasedPath()
+  const editsDir = join(baseDir, "edits")
+  
+  // Extract original filename without extension
+  const originalName = basename(imagePath, extname(imagePath))
+  let baseName = config.customName || originalName
+  if (baseName.endsWith('.png') || baseName.endsWith('.jpg') || baseName.endsWith('.jpeg')) {
+    baseName = baseName.substring(0, baseName.lastIndexOf('.'))
+  }
+  const extension = ".png"
+  
+  const outputPath = await getUniqueFilename(editsDir, baseName, extension, true)
+  
+  console.log(`Saving edited image to: ${outputPath}`)
+  await Bun.write(outputPath, Buffer.from(b64, "base64"))
+  
+  const fileExists = await Bun.file(outputPath).exists()
+  if (!fileExists) {
+    throw new Error(`Failed to save file to ${outputPath}`)
+  }
+  
+  const stats = await Bun.file(outputPath).stat()
+  return `Edited image saved: ${outputPath} (${stats.size} bytes)`
+}
+
+export async function analyzeImage(imagePath: string, question: string): Promise<string> {
+  const apiKey = await getGeminiApiKey()
+
+  // Test mode - return mock response without API call
+  if (isTestMode()) {
+    return `[TEST MODE] Would analyze image: ${imagePath} with question: "${question.substring(0, 50)}..." - Mock analysis: This is a test image analysis response.`
+  }
+
+  const { mime, base64 } = await parseImageInput(imagePath)
+  
+  const res = await fetch(
+    "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent",
+    {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+        "x-goog-api-key": apiKey,
+      },
+      body: JSON.stringify({
+        contents: [{
+          parts: [
+            { text: question },
+            { inlineData: { mimeType: mime, data: base64 } }
+          ]
+        }],
+      }),
+    }
+  )
+
+  if (!res.ok) {
+    const errorText = await res.text()
+    throw new Error(`API error (${res.status}): ${errorText}`)
+  }
+  
+  const json = await res.json()
+  const text = json?.candidates?.[0]?.content?.parts?.[0]?.text
+  if (!text) {
+    throw new Error("No analysis returned")
+  }
+  
+  return text
+}
+
+// Tool for generating images from text
+export const generate = tool({
+  description: "Generate an image using Gemini Nano Banana from text prompt",
+  args: {
+    prompt: tool.schema.string().describe("Text description of the image to generate"),
+    outputDir: tool.schema.string().optional().describe("Custom output directory (default: ./generated-images/YYYY-MM-DD/)"),
+    filename: tool.schema.string().optional().describe("Custom filename (default: generated)"),
+  },
+  async execute(args, context) {
+    try {
+      const config: ImageConfig = {
+        outputDir: args.outputDir,
+        customName: args.filename,
+      }
+      return await generateImage(args.prompt, config)
+    } catch (error) {
+      return `Error: ${error.message}`
+    }
+  },
+})
+
+// Tool for editing existing images
+export const edit = tool({
+  description: "Edit an existing image using Gemini Nano Banana",
+  args: {
+    image: tool.schema.string().describe("File path or data URL of image to edit"),
+    prompt: tool.schema.string().describe("Edit instruction"),
+    outputDir: tool.schema.string().optional().describe("Custom output directory (default: ./generated-images/YYYY-MM-DD/)"),
+    filename: tool.schema.string().optional().describe("Custom filename (default: original name with _edit_XXX)"),
+  },
+  async execute(args, context) {
+    try {
+      const config: ImageConfig = {
+        outputDir: args.outputDir,
+        customName: args.filename,
+      }
+      return await editImage(args.image, args.prompt, config)
+    } catch (error) {
+      return `Error: ${error.message}`
+    }
+  },
+})
+
+// Tool for analyzing images
+export const analyze = tool({
+  description: "Analyze an image using Gemini (text analysis only)",
+  args: {
+    image: tool.schema.string().describe("File path or data URL of image to analyze"),
+    question: tool.schema.string().describe("What to analyze about the image"),
+  },
+  async execute(args, context) {
+    try {
+      return await analyzeImage(args.image, args.question)
+    } catch (error) {
+      return `Error: ${error.message}`
+    }
+  },
+})
+
+// Default export for backward compatibility
+export default edit

+ 27 - 0
.opencode/tool/index.ts

@@ -0,0 +1,27 @@
+/**
+ * OpenCode Gemini Tool - Main entry point
+ * 
+ * This module provides image generation, editing, and analysis capabilities
+ * using Google's Gemini AI models, along with environment variable utilities.
+ */
+
+// Gemini AI image tools
+export {
+  generate,           // Generate images from text prompts
+  edit,              // Edit existing images with text instructions
+  analyze,           // Analyze images and answer questions about them
+  generateImage,     // Core image generation function
+  editImage,         // Core image editing function
+  analyzeImage,      // Core image analysis function
+  default as gemini  // Default export (edit tool)
+} from "./gemini"
+
+// Environment variable utilities
+export {
+  loadEnvVariables,
+  getEnvVariable,
+  getRequiredEnvVariable,
+  getRequiredEnvVariables,
+  getApiKey,
+  type EnvLoaderConfig
+} from "./env"

+ 22 - 0
.opencode/tool/package.json

@@ -0,0 +1,22 @@
+{
+  "type": "module",
+  "name": "opencode-gemini-tool",
+  "version": "1.0.0",
+  "description": "Gemini image editing tool for OpenCode",
+  "main": "index.ts",
+  "scripts": {
+    "build": "bun build index.ts --outdir dist",
+    "type-check": "tsc --noEmit",
+    "test": "bun test.ts",
+    "test:real": "bun test.ts"
+  },
+  "dependencies": {
+    "@opencode-ai/sdk": "^0.10.0",
+    "zod": "^4.1.9"
+  },
+  "devDependencies": {
+    "@opencode-ai/plugin": "^0.10.0",
+    "@types/node": "^24.2.1",
+    "bun-types": "latest"
+  }
+}

+ 35 - 0
.opencode/tool/template/README.md

@@ -0,0 +1,35 @@
+# Tool Template
+
+This is a template for creating new tools in the modular structure.
+
+## How to Create a New Tool
+
+1. **Copy this template directory:**
+   ```bash
+   cp -r template/ your-tool-name/
+   ```
+
+2. **Edit `index.ts`:**
+   - Replace `exampleFunction` with your tool's logic
+   - Update the tool definition with proper description and args
+   - Export your functions and tool
+
+3. **Add to main index:**
+   - Add exports to `/tool/index.ts`:
+   ```typescript
+   export { yourTool, yourFunction } from "./your-tool-name"
+   ```
+
+4. **Test your tool:**
+   ```bash
+   bun -e "import('./your-tool-name/index.ts').then(m => console.log('Exports:', Object.keys(m)))"
+   ```
+
+## Structure
+
+```
+your-tool-name/
+└── index.ts          # All tool functionality
+```
+
+Keep it simple - one file per tool with all functionality included.

+ 25 - 0
.opencode/tool/template/index.ts

@@ -0,0 +1,25 @@
+import { tool } from "@opencode-ai/plugin/tool"
+
+// Example tool implementation
+export async function exampleFunction(input: string): Promise<string> {
+  // Your tool logic here
+  return `Processed: ${input}`
+}
+
+// Tool definition for OpenCode agent system
+export const exampleTool = tool({
+  description: "Example tool description",
+  args: {
+    input: tool.schema.string().describe("Input parameter description"),
+  },
+  async execute(args, context) {
+    try {
+      return await exampleFunction(args.input)
+    } catch (error) {
+      return `Error: ${error.message}`
+    }
+  },
+})
+
+// Default export
+export default exampleTool

+ 13 - 0
.opencode/tool/tsconfig.json

@@ -0,0 +1,13 @@
+{
+  "compilerOptions": {
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "resolvePackageJsonExports": true,
+    "resolvePackageJsonImports": true,
+    "target": "ES2022",
+    "skipLibCheck": true,
+    "types": ["bun-types"]
+  },
+  "include": ["*.ts"],
+  "exclude": ["node_modules"]
+}

+ 6 - 0
assets/.gitignore

@@ -0,0 +1,6 @@
+# Ignore generated images but keep directory structure
+images/*/
+!images/.gitkeep
+
+# Allow specific important images if needed
+# !images/examples/

+ 1 - 0
assets/images/.gitkeep

@@ -0,0 +1 @@
+# Keep this directory in git

+ 4 - 0
env.example

@@ -18,3 +18,7 @@ TELEGRAM_CHECK_INTERVAL=30000
 
 # Enable/disable the plugin (true/false)
 TELEGRAM_ENABLED=true
+
+# Gemini API Configuration
+# Get your API key from https://makersuite.google.com/app/apikey
+GEMINI_API_KEY=your_gemini_api_key_here