wip: plugins

This commit is contained in:
Dax Raad
2025-08-03 21:42:45 -04:00
parent 1bac46612c
commit f85d30c484
7 changed files with 67 additions and 316 deletions

View File

@@ -7,31 +7,16 @@ Plugins allow you to extend opencode's functionality by hooking into various eve
---
## Configuration
Plugins are configured in your `opencode.json` file using the `plugin` array. Each entry should be a path to a plugin module.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["./my-plugin.js", "../shared/company-plugin.js", "/absolute/path/to/plugin.js"]
}
```
Paths can be:
- **Relative paths** - Resolved from the directory containing the config file
- **Absolute paths** - Used as-is
---
## Creating a Plugin
A plugin is a JavaScript/TypeScript module that exports one or more plugin functions. Each function receives a context object and returns a hooks object.
A plugin is a JavaScript/TypeScript module that exports one or more plugin
functions. Each function receives a context object and returns a hooks object.
They are loaded from the `.opencode/plugin` directory either in your proejct or
globally in `~/.config/opencode/plugin`.
### Basic Structure
```typescript title="my-plugin.js"
```typescript title=".opencode/plugin/example.js"
export const MyPlugin = async ({ app, client, $ }) => {
console.log("Plugin initialized!")
@@ -63,52 +48,18 @@ export const MyPlugin: Plugin = async ({ app, client, $ }) => {
---
## Available Hooks
Plugins can implement various hooks to respond to opencode events:
### permission
Control permissions for various operations:
```javascript
export const SecurityPlugin = async ({ client }) => {
return {
permission: {
// Add permission logic here
},
}
}
```
### event
Listen to all events in the opencode system:
```javascript
export const LoggingPlugin = async ({ client }) => {
return {
event: ({ event }) => {
console.log("Event occurred:", event)
},
}
}
```
---
## Examples
### Notification Plugin
Send notifications when certain events occur:
```javascript title="notification-plugin.js"
```javascript title=".opencode/plugin/notification.js"
export const NotificationPlugin = async ({ client, $ }) => {
return {
event: async ({ event }) => {
// Send notification on session completion
if (event.type === "session.completed") {
if (event.type === "session.idle") {
await $`osascript -e 'display notification "Session completed!" with title "opencode"'`
}
},
@@ -116,191 +67,22 @@ export const NotificationPlugin = async ({ client, $ }) => {
}
```
### Custom Commands Plugin
Add custom functionality that can be triggered by the AI:
```javascript title="custom-commands.js"
export const CustomCommands = async ({ client }) => {
return {
event: async ({ event }) => {
if (event.type === "message" && event.content.includes("/deploy")) {
// Trigger deployment logic
console.log("Deploying application...")
}
},
}
}
```
### Integration Plugin
### .env Protection
Integrate with external services:
```javascript title="slack-integration.js"
export const SlackIntegration = async ({ client, $ }) => {
const webhookUrl = process.env.SLACK_WEBHOOK_URL
```javascript title=".opencode/plugin/slack.js"
export const EnvProtection = async ({ client, $ }) => {
return {
event: async ({ event }) => {
if (event.type === "error") {
// Send error to Slack
await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `opencode error: ${event.message}`,
}),
})
}
},
}
}
```
---
## Plugin Development Tips
1. **Error Handling**: Always handle errors gracefully to avoid crashing opencode
```javascript
event: async ({ event }) => {
try {
// Your logic here
} catch (error) {
console.error("Plugin error:", error)
}
}
```
2. **Performance**: Keep plugin operations lightweight and async where possible
```javascript
event: async ({ event }) => {
// Don't block - use async operations
setImmediate(() => {
// Heavy processing here
})
}
```
3. **Environment Variables**: Use environment variables for configuration
```javascript
const apiKey = process.env.MY_PLUGIN_API_KEY
if (!apiKey) {
console.warn("MY_PLUGIN_API_KEY not set")
return {}
}
```
4. **Multiple Exports**: You can export multiple plugins from one file
```javascript
export const PluginOne = async (context) => {
/* ... */
}
export const PluginTwo = async (context) => {
/* ... */
}
```
---
## Advanced Usage
### Using the SDK Client
The `client` parameter is a full opencode SDK client that can interact with the AI:
```javascript
export const AIAssistantPlugin = async ({ client }) => {
return {
event: async ({ event }) => {
if (event.type === "file.created") {
// Ask AI to review the new file
const response = await client.messages.create({
messages: [
{
role: "user",
content: `Review this new file: ${event.path}`,
},
],
})
console.log("AI Review:", response)
}
},
}
}
```
### Accessing Application State
The `app` parameter provides access to the opencode application instance:
```javascript
export const StatePlugin = async ({ app }) => {
return {
event: async ({ event }) => {
// Access application state and configuration
const currentPath = app.path.cwd
console.log("Working directory:", currentPath)
},
}
}
```
---
## Debugging Plugins
To debug your plugins:
1. **Console Logging**: Use `console.log()` to output debug information
2. **Error Boundaries**: Wrap hook implementations in try-catch blocks
3. **Development Mode**: Test plugins in a separate opencode instance first
```javascript
export const DebugPlugin = async (context) => {
console.log("Plugin loaded with context:", Object.keys(context))
return {
event: ({ event }) => {
console.log(`[${new Date().toISOString()}] Event:`, event.type)
},
}
}
```
---
## Best Practices
1. **Namespace Your Plugins**: Use descriptive names to avoid conflicts
2. **Document Your Hooks**: Add comments explaining what each hook does
3. **Version Control**: Keep plugins in version control with your project
4. **Test Thoroughly**: Test plugins with various opencode operations
5. **Handle Cleanup**: Clean up resources when appropriate
```javascript
// Good example with best practices
export const CompanyStandardsPlugin = async ({ client, $ }) => {
// Initialize resources
const config = await loadConfig()
return {
event: async ({ event }) => {
try {
// Well-documented hook logic
if (event.type === "code.generated") {
// Enforce company coding standards
await enforceStandards(event.code)
tool: {
execute: {
before: async (input, output) => {
if (input.tool === "read" && output.args.filePath.includes(".env")) {
throw new Error("Do not read .env files")
}
}
} catch (error) {
// Graceful error handling
console.error("Standards check failed:", error)
}
},
}
}
}
```