AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL unreviewed MIT Self-run

Opencode Plugins

skill-timmy6942025-opencode-builder-skill-opencode-plugins · by Timmy6942025

|

No reviews yet
0 installs
44 views
0.0% view→install

Install

$ agentstack add skill-timmy6942025-opencode-builder-skill-opencode-plugins

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Destructive filesystem operation.

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Reliability & compatibility

Not yet reviewed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Opencode Plugins? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

OpenCode Plugins

> 📚 Official Docs: For the latest information, always refer to the official documentation: > https://opencode.ai/docs/plugins/

Write your own plugins to extend OpenCode. Plugins hook into events, customize behavior, add custom tools, and integrate with external services.


Plugin Loading

OpenCode loads plugins from multiple sources. Understanding the loading mechanism is critical for correct plugin placement and debugging.

Local Files

Place JavaScript or TypeScript files in the plugin directory:

  • .opencode/plugins/Project-level plugins (scoped to the current project)
  • ~/.config/opencode/plugins/Global plugins (available across all projects)

Files in these directories are automatically loaded at startup. Each file must export one or more plugin functions (see [Plugin Structure](#plugin-structure)).

npm Packages

Specify npm packages in your config file's "plugin" array:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    "opencode-helicone-session",
    "opencode-wakatime",
    "@my-org/custom-plugin"
  ]
}

Both regular (package-name) and scoped (@scope/package-name) npm packages are supported. Browse available plugins in the ecosystem.

How Plugins Are Installed

npm plugins are installed automatically using Bun at startup. Packages and their dependencies are cached in ~/.cache/opencode/node_modules/. No manual bun install is needed — OpenCode handles it transparently.

Local plugins are loaded directly from the plugin directory. To use external npm packages in local plugins, you must create a package.json within your .opencode/ directory (see [Dependencies](#dependencies)), or publish the plugin to npm and add it to the config.

Load Order

Plugins are loaded from all sources and all hooks run in sequence. The load order is:

  1. Global config (~/.config/opencode/opencode.json) — plugins listed in the global config's "plugin" array
  2. Project config (opencode.json) — plugins listed in the project config's "plugin" array
  3. Global plugin directory (~/.config/opencode/plugins/) — local JS/TS files
  4. Project plugin directory (.opencode/plugins/) — local JS/TS files

Deduplication Rules

  • Same npm package, same version: Loaded only once. If the same name@version appears in both global and project config, it loads once.
  • Different versions: Both versions are loaded separately.
  • Local + npm with similar names: Both are loaded separately. A local file .opencode/plugins/foo.ts and an npm package foo in the config are distinct plugins that both run.

Plugin Structure

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.

Basic Structure

// .opencode/plugins/example.js
export const MyPlugin = async ({ project, client, $, directory, worktree }) => {
  console.log("Plugin initialized!")

  return {
    // Hook implementations go here
  }
}

Context Object

The plugin function receives a context object with the following properties:

| Property | Type | Description | |----------|------|-------------| | project | Project | The current project information (name, path, etc.) | | directory | string | The current working directory | | worktree | string | The git worktree path (root of the git repository) | | client | Client | An OpenCode SDK client for interacting with the AI server | | $ | Shell | Bun's shell API for executing system commands |

Returns

The plugin function must return an object where keys are hook names and values are hook handler functions. It can also return a tool key for custom tool definitions.

TypeScript Support

For type-safe plugins, import the Plugin type from the plugin package:

// .opencode/plugins/example.ts
import type { Plugin } from "@opencode-ai/plugin"

export const MyPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
  return {
    // Type-safe hook implementations
  }
}

The Plugin type provides full type annotations for all hooks, the context object, and tool definitions.

Multiple Exports

A single file can export multiple plugin functions. Each export is loaded independently:

// .opencode/plugins/multi.ts
import type { Plugin } from "@opencode-ai/plugin"

export const PluginA: Plugin = async (ctx) => {
  return {
    "session.idle": async ({ event }) => {
      // Handle idle
    },
  }
}

export const PluginB: Plugin = async (ctx) => {
  return {
    "tool.execute.before": async (input, output) => {
      // Intercept tools
    },
  }
}

Dependencies

Local plugins can use external npm packages. Add a package.json to your .opencode/ config directory with the dependencies you need.

Setup

  1. Create .opencode/package.json:
{
  "dependencies": {
    "shescape": "^2.1.0"
  }
}
  1. OpenCode runs bun install at startup to install these dependencies.
  1. Your plugins can then import them:
// .opencode/plugins/my-plugin.ts
import { escape } from "shescape"

export const MyPlugin = async (ctx) => {
  return {
    "tool.execute.before": async (input, output) => {
      if (input.tool === "bash") {
        output.args.command = escape(output.args.command)
      }
    },
  }
}

Notes

  • Dependencies are installed into .opencode/node_modules/ (project-level) or ~/.config/opencode/node_modules/ (global-level).
  • Bun is the package manager — use Bun-compatible packages.
  • If you publish your plugin to npm, dependencies are resolved from the npm package's own package.json instead.

Plugin Hooks

All hooks follow the pattern (input, output) => {} or ({ event }) => {} depending on the hook type. Hooks run in the order plugins are loaded (see [Load Order](#load-order)).

Tool Hooks

tool.execute.before

Fires before a tool executes. You can mutate output.args to modify the tool's arguments, or throw an error to block execution entirely.

"tool.execute.before": async (input, output) => {
  // input.tool — name of the tool (e.g. "bash", "read", "write", "glob")
  // output.args — mutable arguments object

  // Block dangerous commands
  if (input.tool === "bash" && output.args.command.includes("rm -rf /")) {
    throw new Error("Blocked dangerous command")
  }

  // Modify arguments
  if (input.tool === "bash") {
    output.args.command = `set -euo pipefail && ${output.args.command}`
  }
}

Parameters:

| Parameter | Description | |-----------|-------------| | input.tool | The tool name being executed ("bash", "read", "write", "glob", "grep", etc.) | | output.args | Mutable object containing the tool's arguments. Shape varies by tool. |

Throwing: Throw any Error to prevent the tool from executing. The error message is shown to the user.

tool.execute.after

Fires after a tool executes. Inspect the tool's return value or error state.

"tool.execute.after": async (input, output) => {
  // input.tool — name of the tool
  // output.result — the tool's return value (may be undefined on error)
  // output.error — error if the tool failed (may be undefined on success)

  if (input.tool === "write") {
    console.log(`File written: ${output.result?.filePath}`)
  }
}

Parameters:

| Parameter | Description | |-----------|-------------| | input.tool | The tool name that was executed | | output.result | The tool's return value (type varies by tool) | | output.error | Error object if the tool failed, otherwise undefined |

Shell Hooks

shell.env

Inject or modify environment variables for all shell executions — this includes AI tool calls (the bash tool) and user terminal sessions.

"shell.env": async (input, output) => {
  // input.cwd — current working directory
  output.env.MY_API_KEY = "secret"
  output.env.PROJECT_ROOT = input.cwd
  output.env.NODE_ENV = "development"
}

Parameters:

| Parameter | Description | |-----------|-------------| | input.cwd | The current working directory for the shell execution | | output.env | Mutable object of environment variables. Set keys to inject values. |

Use cases: Inject API keys, set project-specific env vars, configure PATH, override defaults.

Session Hooks

session.created

Fires when a new session is created.

"session.created": async ({ event }) => {
  // event.properties — session details
  console.log("New session:", event.properties?.sessionID)
}
session.updated

Fires when a session is updated (e.g., title change, model change).

"session.updated": async ({ event }) => {
  console.log("Session updated:", event.properties?.sessionID)
}
session.idle

Fires when the AI finishes responding and the session becomes idle. Commonly used for notifications.

"session.idle": async ({ event }) => {
  await $`osascript -e 'display notification "Done!" with title "opencode"'`
}
session.error

Fires when a session encounters an error.

"session.error": async ({ event }) => {
  await client.app.log({
    body: {
      service: "my-plugin",
      level: "error",
      message: `Error: ${event.properties?.error}`,
    },
  })
}
session.compacted

Fires after session context has been compacted (context window management).

"session.compacted": async ({ event }) => {
  console.log("Session compacted:", event.properties?.sessionID)
}
session.deleted

Fires when a session is deleted.

"session.deleted": async ({ event }) => {
  console.log("Session deleted:", event.properties?.sessionID)
}
session.diff

Fires when a diff is computed (file changes detected).

"session.diff": async ({ event }) => {
  console.log("Diff available:", event.properties)
}
session.status

Fires when session status changes.

"session.status": async ({ event }) => {
  console.log("Status changed:", event.properties)
}

Message Hooks

message.updated

Fires when a message is updated.

"message.updated": async ({ event }) => {
  console.log("Message updated:", event.properties?.messageID)
}
message.removed

Fires when a message is removed.

"message.removed": async ({ event }) => {
  console.log("Message removed:", event.properties?.messageID)
}
message.part.updated

Fires when a message part (text chunk, tool call result) is updated.

"message.part.updated": async ({ event }) => {
  console.log("Part updated:", event.properties?.partID)
}
message.part.removed

Fires when a message part is removed.

"message.part.removed": async ({ event }) => {
  console.log("Part removed:", event.properties?.partID)
}

TUI Hooks

tui.prompt.append

Append text to the prompt input before submission.

"tui.prompt.append": async (input, output) => {
  // output.text — mutable string, prepend or modify
  output.text = `Branch: main\n${output.text}`
}
tui.command.execute

Execute a custom TUI command.

"tui.command.execute": async (input, output) => {
  // input.command — the command string
  // output.result — the command result
  console.log(`Command: ${input.command}`)
}
tui.toast.show

Show a toast notification in the TUI.

"tui.toast.show": async (input, output) => {
  // output.message — toast message
  // output.level — severity level
  output.message = "Custom plugin loaded successfully"
  output.level = "info"
}

File Hooks

file.edited

Fires when a file is edited.

"file.edited": async ({ event }) => {
  console.log("File edited:", event.properties?.filePath)
}
file.watcher.updated

Fires when the file watcher detects a change on disk.

"file.watcher.updated": async ({ event }) => {
  console.log("File changed on disk:", event.properties?.filePath)
}

Permission Hooks

permission.asked

Fires when OpenCode asks for a permission (e.g., file write, shell execution).

"permission.asked": async ({ event }) => {
  console.log("Permission requested:", event.properties?.permission)
}
permission.replied

Fires when a permission request is replied to (approved or denied).

"permission.replied": async ({ event }) => {
  console.log("Permission replied:", event.properties?.permission)
}

LSP Hooks

lsp.client.diagnostics

Fires when LSP diagnostics are received (errors, warnings from language servers).

"lsp.client.diagnostics": async ({ event }) => {
  console.log("Diagnostics:", event.properties?.diagnostics)
}
lsp.updated

Fires when LSP state is updated (server started, stopped, or reconnected).

"lsp.updated": async ({ event }) => {
  console.log("LSP state:", event.properties?.language)
}

Other Hooks

command.executed

Fires when a command is executed.

"command.executed": async ({ event }) => {
  console.log("Command executed:", event.properties)
}
installation.updated

Fires when installation state changes.

"installation.updated": async ({ event }) => {
  console.log("Installation state:", event.properties)
}
todo.updated

Fires when a todo item is updated.

"todo.updated": async ({ event }) => {
  console.log("Todo updated:", event.properties)
}
server.connected

Fires when the server connects.

"server.connected": async ({ event }) => {
  console.log("Server connected:", event.properties)
}

Experimental Hooks

experimental.session.compacting

Fires before the LLM generates a continuation summary during compaction. This hook allows you to inject domain-specific context or replace the compaction prompt entirely.

Inject additional context:

"experimental.session.compacting": async (input, output) => {
  output.context.push(`## Custom Context
Include any state that should persist across compaction:
- Current task status
- Important decisions made
- Files being actively worked on`)
}

Replace the entire compaction prompt:

"experimental.session.compacting": async (input, output) => {
  output.prompt = `You are generating a continuation prompt for a multi-agent swarm session.
Summarize:
1. The current task and its status
2. Which files are being modified and by whom
3. Any blockers or dependencies between agents
4. The next steps to complete the work
Format as a structured prompt that a new agent can use to resume work.`
}

Key behaviors:

  • output.context — An array of strings. Push additional context blocks to include in the compaction prompt.
  • output.prompt — A string. When set, it completely replaces the default compaction prompt. When output.prompt is set, output.context is ignored.

Event Hook Pattern

For event-based hooks (session.*, message.*, file.*, permission.*, lsp.*, command.*, installation.*, todo.*, server.*), you can use a generic event handler that listens to all events:

export const MyPlugin = async ({ client }) => {
  return {
    event: async ({ event }) => {
      // event.type — the event name (e.g. "session.idle", "file.edited")
      // event.properties — event-specific data

      switch (event.type) {
        case "session.idle":
          await client.app.log({
            body: { service: "my-plugin", level: "info", message: "Session idle" },
          })
          break
        case "file.edited":
          console.log("File:", event.properties?.filePath)
          break
        case "session.error":
          console.error("Error:", event.properties?.error)
          break
      }
    },
  }
}

This pattern is useful when you

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.