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'.
npx skills add https://github.com/Devin-AXIS/iPolloWork --skill create-plugin
Scaffold a working OpenCode plugin for use in iPolloWork.
An OpenCode plugin is an async factory function that returns a hooks object.
Plugins can live in:
.opencode/plugins/my-plugin.ts (auto-discovered)~/.config/opencode/plugins/my-plugin.tsopencode.json plugin arrayfile: or https: path in the plugin arrayimport { 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}`;
},
},
},
});
zodSchema.shape (a ZodRawShape), not the ZodObject.execute returns a string or { output: string; metadata?: Record<string, unknown> }.fetch() works — plugins run in-process inside the OpenCode runtime.process.env is accessible — use env vars for secrets/config.{
// 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 }) => { /* ... */ },
}
Add to opencode.json:
{
"plugin": [
".opencode/plugins/my-plugin.ts"
]
}
Or install from npm:
{
"plugin": [
"my-published-plugin"
]
}
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:
opencode.json provider config.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.";
}
},
},
},
});
When the user describes what they want the plugin to do:
.opencode/plugins/<name>.ts with the plugin code.opencode.json plugin array if not already present.Take devin-axis/create-plugin from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.