create-plugin ยท diff
git:20260711.ec0d7ce to git:20260711.1ecdca5
3 added, 3 removed. Audit A to A.
---
name: create-plugin
- description: "Create an OpenCode plugin for iPolloWalk. Scaffolds the plugin file with the correct API shape, tool definitions, and hook registration. Use when the user asks to 'create a plugin', 'write a plugin', or 'make a plugin that does X'."
+ description: "Create an OpenCode plugin for iPolloWork. Scaffolds the plugin file with the correct API shape, tool definitions, and hook registration. Use when the user asks to 'create a plugin', 'write a plugin', or 'make a plugin that does X'."
---
# Skill: Create an OpenCode Plugin
- Scaffold a working OpenCode plugin for use in iPolloWalk.
+ Scaffold a working OpenCode plugin for use in iPolloWork.
## When to use
- User asks "create a plugin that does X"
- User asks "write a plugin" or "make a plugin"
- - User wants to extend iPolloWalk/OpenCode with custom tools
+ - User wants to extend iPolloWork/OpenCode with custom tools
## Plugin API
An OpenCode plugin is an **async factory function** that returns a **hooks object**.
### File location
Plugins can live in:
- **Project**: `.opencode/plugins/my-plugin.ts` (auto-discovered)
- **Global**: `~/.config/opencode/plugins/my-plugin.ts`
- **npm**: published as a package, referenced in `opencode.json` `plugin` array
- **URL**: `file:` or `https:` path in the `plugin` array
### Minimal shape
```typescript
import { z } from "zod";
export default async () => ({
tool: {
my_tool_name: {
description: "What this tool does.",
args: z.object({
input: z.string().describe("The input parameter."),
}).shape, // NOTE: .shape, not the ZodObject itself
async execute(args: { input: string }) {
// Your logic here. Can use fetch(), fs, child_process, etc.
return `Result: ${args.input}`;
},
},
},
});
```
### Key rules
1. **Export default an async function** that returns the hooks object.
2. **Tool args use `zodSchema.shape`** (a `ZodRawShape`), not the `ZodObject`.
3. **`execute` returns a `string`** or `{ output: string; metadata?: Record<string, unknown> }`.
4. **Tools are declared, not registered imperatively** โ return them in the hooks object.
5. **`fetch()` works** โ plugins run in-process inside the OpenCode runtime.
6. **`process.env` is accessible** โ use env vars for secrets/config.
### Available hooks
```typescript
{
// Modify the system prompt
"experimental.chat.system.transform": async (input, output: { system: string[] }) => {
output.system.push("Extra instruction for the agent.");
},
// Define tools the agent can call
tool: {
tool_name: { description, args, execute },
},
// Run code before/after a tool executes
"tool.execute.before": async ({ tool, args }) => { /* ... */ },
"tool.execute.after": async ({ tool, args, result }) => { /* ... */ },
// React to lifecycle events
event: async ({ event }) => { /* ... */ },
}
```
### Registering the plugin
Add to `opencode.json`:
```json
{
"plugin": [
".opencode/plugins/my-plugin.ts"
]
}
```
Or install from npm:
```json
{
"plugin": [
"my-published-plugin"
]
}
```
## Anthropic / Claude plugin compatibility
OpenCode plugins are **NOT** the same as Anthropic's plugin format. Key differences:
| Aspect | OpenCode Plugin | Anthropic Plugin |
|--------|----------------|-----------------|
| Entry point | Async factory function | Manifest JSON |
| Tool args | Zod schema `.shape` | JSON Schema |
| Runtime | In-process (Bun/Node) | Sandboxed container |
| Auth | `process.env` | OAuth/API key in manifest |
| Distribution | npm / file path / URL | Anthropic marketplace |
To adapt an Anthropic plugin for OpenCode:
1. Rewrite the tool definitions using Zod instead of JSON Schema.
2. Move any auth from the manifest to env vars or `opencode.json` provider config.
3. Export the async factory function instead of the manifest.
4. HTTP-based Anthropic plugins can often be wrapped as MCP servers instead.
## Example: plugin with multiple tools
```typescript
import { z } from "zod";
export default async () => ({
"experimental.chat.system.transform": async (_input: unknown, output: { system: string[] }) => {
output.system.push("You have access to a note-taking system. Use save_note and list_notes.");
},
tool: {
save_note: {
description: "Save a note with a title and body.",
args: z.object({
title: z.string().describe("Note title"),
body: z.string().describe("Note content"),
}).shape,
async execute(args: { title: string; body: string }) {
const fs = await import("node:fs/promises");
const path = `.opencode/notes/${args.title.replace(/[^a-zA-Z0-9-_]/g, "_")}.md`;
await fs.mkdir(".opencode/notes", { recursive: true });
await fs.writeFile(path, `# ${args.title}\n\n${args.body}\n`);
return `Saved note: ${path}`;
},
},
list_notes: {
description: "List all saved notes.",
args: {},
async execute() {
const fs = await import("node:fs/promises");
try {
const files = await fs.readdir(".opencode/notes");
return files.filter(f => f.endsWith(".md")).join("\n") || "No notes yet.";
} catch {
return "No notes yet.";
}
},
},
},
});
```
## Scaffold workflow
When the user describes what they want the plugin to do:
1. Create `.opencode/plugins/<name>.ts` with the plugin code.
2. Add it to `opencode.json` `plugin` array if not already present.
3. Tell the user to restart their session for the plugin to load.