@openpets/slack
v1.0.4
Published
Slack integration plugin for OpenCode - search channels, read messages, and post to Slack
Downloads
412
Maintainers
Readme
Factory-Based Plugin Template
A comprehensive template for creating OpenCode plugins using the factory pattern with multi-provider workflow management. This template demonstrates modern plugin architecture with provider abstraction, configuration management, and workflow orchestration.
Table of Contents
- Factory Architecture
- Multi-Provider Design
- Quick Start
- Configuration Management
- Workflow Tools
- Provider Integration
- Best Practices
- Migration Guide
- Testing
Factory Architecture
This template uses the factory pattern from src/core/plugin-factory.ts:
Key Components
- Factory Functions:
createPlugin()andcreateSingleTool() - Zod Schemas: Type-safe parameter validation
- Provider Abstraction: Dynamic provider loading and fallbacks
- Configuration Management: Environment-based multi-provider setup
Architecture Benefits
✅ Type Safety: Zod schemas ensure parameter validation
✅ Provider Agnostic: Support multiple providers per service type
✅ Graceful Fallbacks: Automatic provider switching on failures
✅ Configuration Driven: Environment-based provider selection
✅ Workflow Focused: High-level orchestration tools
Multi-Provider Design
Provider Types
interface PluginConfig {
providers: {
github?: { token: string; apiUrl?: string }
maps?: { provider: 'googlemaps' | 'mapbox'; apiKey: string }
ai?: { provider: 'fal'; apiKey: string }
}
preferences: Record<string, string>
workflow: { timeout: number; retries: number; logLevel: string }
}Provider Selection Logic
- Configuration Loading: Parse
PROVIDER_PREFERENCESJSON - Provider Initialization: Load available providers based on API keys
- Fallback Handling: Graceful degradation when providers fail
- Dynamic Loading: Import providers only when needed
Quick Start
1. Copy This Template
# Navigate to pets directory
cd /Users/andrewmaguire/LOCAL/Github/raggle-code/raggle-repo/core/pets/
# Copy template
cp -r _TEMPLATE_ my-workflow-manager
cd my-workflow-manager2. Install Dependencies
npm install3. Configure Environment
# Copy environment template
cp .env.example .env
# Edit with your API keys
nano .envExample .env:
# Primary providers
GITHUB_TOKEN=ghp_your_github_token
GOOGLE_MAPS_API_KEY=your_google_maps_key
FAL_KEY=your_fal_api_key
# Provider preferences
PROVIDER_PREFERENCES={"maps":"googlemaps","ai":"fal"}
# Workflow settings
DEFAULT_TIMEOUT=30000
MAX_RETRIES=3
LOG_LEVEL=info4. Test the Template
# Check provider status
opencode run "check provider status"
# Demo workflow
opencode run "demo workflow with geocode and enhance for 'New York'"Configuration Management
Environment Variables
| Variable | Description | Required |
|----------|-------------|-----------|
| GITHUB_TOKEN | GitHub personal access token | Optional |
| GOOGLE_MAPS_API_KEY | Google Maps API key | Optional |
| MAPBOX_ACCESS_TOKEN | Mapbox access token | Optional |
| FAL_KEY | FAL AI API key | Optional |
| PROVIDER_PREFERENCES | JSON string with provider preferences | Optional |
| DEFAULT_TIMEOUT | Operation timeout in milliseconds | Optional |
| MAX_RETRIES | Maximum retry attempts | Optional |
| LOG_LEVEL | Logging level (debug/info/warn/error) | Optional |
Provider Preferences
{
"maps": "googlemaps",
"ai": "fal",
"version_control": "github"
}Workflow Tools
1. workflow-demo
Demonstrates multi-provider workflow orchestration.
Actions:
geocode-and-enhance: Geocode location and enhance with AIcreate-and-tag: Create repository with location-based tagsai-process: Process text using AI provider
Examples:
opencode run "demo workflow with geocode and enhance for 'San Francisco'"
opencode run "demo workflow with create and tag for 'my-project'"
opencode run "demo workflow with ai process for 'Hello world'"2. manage-providers
Check provider status and availability.
Actions:
status: Show all provider statusestest: Test provider connectivitylist: List available and configured providers
Examples:
opencode run "check provider status"
opencode run "test all providers"
opencode run "list available providers"3. manage-config
Manage plugin configuration.
Actions:
get: Get configuration valuesset: Set configuration valuesvalidate: Validate configuration
Examples:
opencode run "get configuration"
opencode run "validate plugin configuration"
opencode run "set configuration key 'test.value' to 'demo'"Provider Integration
Adding New Providers
- Create Provider Utils (if not exists):
# Create new provider in src/utils/
mkdir src/utils/my-provider
cd src/utils/my-provider- Implement Provider Interface:
// src/utils/my-provider/index.ts
export interface MyProviderConfig {
apiKey: string
apiUrl?: string
}
export class MyProviderAPI {
constructor(private config: MyProviderConfig) {}
async processData(data: string) {
// Implementation
}
}- Update Template Configuration:
interface PluginConfig {
providers: {
// Add new provider
myprovider?: { apiKey: string; apiUrl?: string }
}
}- Add Provider Initialization:
const initializeProviders = async (config: PluginConfig) => {
const providers: any = {}
if (config.providers.myprovider) {
const { MyProviderAPI } = await import('../../src/utils/my-provider')
providers.myprovider = new MyProviderAPI(config.providers.myprovider)
}
return providers
}Best Practices
1. Factory Pattern Usage
// ✅ Good: Use factory for tool creation
const tools = [
{
name: "my-tool",
description: "Tool description",
schema: z.object({ /* ... */ }),
execute: async (args) => { /* ... */ }
}
]
return createPlugin(tools)
// ❌ Bad: Direct tool definition
return {
tool: {
"my-tool": tool({ /* ... */ })
}
}2. Provider Management
// ✅ Good: Lazy loading with error handling
const initializeProvider = async (config: any) => {
try {
const ProviderAPI = await import('../../src/utils/provider')
return new ProviderAPI(config)
} catch (error) {
console.warn(`Provider initialization failed:`, error)
return null
}
}
// ❌ Bad: Eager loading without error handling
const provider = new ProviderAPI(config) // Fails if import errors3. Configuration Validation
// ✅ Good: Comprehensive validation
const validateConfig = (config: PluginConfig) => {
const errors: string[] = []
if (!config.providers.github && !config.providers.maps) {
errors.push("At least one provider must be configured")
}
return { valid: errors.length === 0, errors }
}
// ❌ Bad: No validation
const providers = await initializeProviders(config) // May fail silently4. Error Handling
// ✅ Good: Structured error responses
return createResponse(false, undefined, [{
error: error.message,
provider: providerName,
action: args.action
}], { duration: Date.now() - startTime })
// ❌ Bad: Unstructured errors
throw new Error(`Operation failed: ${error.message}`)Migration Guide
From Legacy Plugin Structure
Old Structure:
export const LegacyPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
return {
tool: {
"tool-name": tool({
description: "...",
args: { /* ... */ },
execute: async (args) => { /* ... */ }
})
}
}
}New Factory Structure:
export const FactoryPlugin = async () => {
const config = loadConfiguration()
const providers = await initializeProviders(config)
const tools = createWorkflowTools(providers, config)
return createPlugin(tools)
}Migration Steps
- Remove Legacy Parameters: Plugin no longer receives
{ project, client, $, directory, worktree } - Add Configuration Loading: Implement
loadConfiguration()function - Add Provider Management: Implement
initializeProviders()function - Convert Tools to Factory: Use
createPlugin()instead of direct tool objects - Update Tool Schemas: Use Zod schemas instead of JSON schemas
- Add Error Handling: Implement structured error responses
Testing
Unit Testing
# Test individual tools
npm run test:queries
# Test workflow scenarios
npm run test:scenarios
# Test all functionality
npm run test:allProvider Testing
# Test provider connectivity
opencode run "test all providers"
# Test provider fallbacks
# Temporarily disable primary provider and test fallbacksConfiguration Testing
# Test with missing environment variables
unset GITHUB_TOKEN
opencode run "validate plugin configuration"
# Test with invalid configuration
PROVIDER_PREFERENCES='{"invalid":"provider"}' opencode run "check provider status"Advanced Patterns
1. Dynamic Provider Discovery
const discoverProviders = async () => {
const providerDir = '../../src/utils'
const providers = await fs.readdir(providerDir)
return Promise.all(
providers
.filter(name => !name.startsWith('.'))
.map(async name => {
try {
const module = await import(`${providerDir}/${name}`)
return { name, module: module.default || module }
} catch {
return { name, error: 'Failed to load' }
}
})
)
}2. Provider Health Monitoring
const monitorProviderHealth = async (providers: any) => {
const health = await Promise.allSettled(
Object.entries(providers).map(async ([name, provider]) => [
name,
{
status: await testProvider(provider),
lastCheck: new Date().toISOString(),
responseTime: await measureResponseTime(provider)
}
])
)
return Object.fromEntries(health)
}3. Workflow Composition
const composeWorkflow = (steps: WorkflowStep[]) => async (input: any) => {
let result = input
for (const step of steps) {
try {
result = await step.execute(result)
} catch (error) {
if (step.required) {
throw error
}
console.warn(`Optional step ${step.name} failed:`, error)
}
}
return result
}Directory Structure
my-workflow-manager/
├── index.ts # Factory-based plugin implementation
├── package.json # Dependencies and metadata
├── README.md # This documentation
├── .env.example # Environment variable template
├── .env # Your actual configuration (gitignored)
├── .gitignore # Git ignore file
└── opencode.json # OpenCode configurationNeed Help?
- Factory Pattern: See
src/core/plugin-factory.ts - Provider Examples: See
src/utils/directory - Documentation: See
prompts/pet-creator.mdfor LLM guidance
License
MIT - feel free to use this template for your own workflow managers!
Happy workflow building! 🚀
