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

Agent prompts update - Gemini Tool
This commit is contained in:
Darren Hinde 2025-09-24 15:36:09 +01:00 committed by GitHub
commit 834c3a83ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 2339 additions and 89 deletions

View file

@ -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._

View file

@ -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:

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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

View file

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

View file

@ -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.

View file

@ -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>

View file

@ -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

View file

@ -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);
}
```

View file

@ -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
.opencode/tool/README.md Normal file
View file

@ -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
.opencode/tool/bun.lock Normal file
View file

@ -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
.opencode/tool/env/index.ts vendored Normal file
View file

@ -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)
}

View file

@ -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
.opencode/tool/index.ts Normal file
View file

@ -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"

View file

@ -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"
}
}

View file

@ -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.

View file

@ -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

View file

@ -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
assets/.gitignore vendored Normal file
View file

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

1
assets/images/.gitkeep Normal file
View file

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

View file

@ -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