MCP HubMCP Hub
スキル一覧に戻る

creating-opencode-plugins

pr-pm
更新日 Today
1245 閲覧
62
9
62
GitHubで表示
メタapidesign

について

このスキルは、コマンド、ファイル、LSP操作など25種類以上のイベントタイプにフックするOpenCodeプラグインを作成するための構造とAPI仕様を提供します。AIアシスタントのライフサイクルをインターセプトおよび拡張するJavaScript/TypeScriptモジュールの実装パターンを提供します。監視、カスタム処理、またはOpenCodeの機能拡張を行うイベント駆動型プラグインを構築する必要がある場合にご利用ください。

クイックインストール

Claude Code

推奨
プラグインコマンド推奨
/plugin add https://github.com/pr-pm/prpm
Git クローン代替
git clone https://github.com/pr-pm/prpm.git ~/.claude/skills/creating-opencode-plugins

このコマンドをClaude Codeにコピー&ペーストしてスキルをインストールします

ドキュメント

Creating OpenCode Plugins

Overview

OpenCode plugins are JavaScript/TypeScript modules that hook into 25+ events across the OpenCode AI assistant lifecycle. Plugins export an async function receiving context (project, client, $, directory, worktree) and return an event handler.

When to Use

Create an OpenCode plugin when:

  • Intercepting file operations (prevent sharing .env files)
  • Monitoring command execution (notifications, logging)
  • Processing LSP diagnostics (custom error handling)
  • Managing permissions (auto-approve trusted operations)
  • Reacting to session lifecycle (cleanup, initialization)
  • Extending tool capabilities (custom tool registration)
  • Enhancing TUI interactions (custom prompts, toasts)

Don't create for:

  • Simple prompt instructions (use agents instead)
  • One-time scripts (use bash tools)
  • Static configuration (use settings files)

Quick Reference

Plugin Structure

export const MyPlugin = async (context) => {
  // context: { project, client, $, directory, worktree }

  return {
    event: async ({ event }) => {
      // event: { type: 'event.name', data: {...} }

      switch(event.type) {
        case 'file.edited':
          // Handle file edits
          break;
        case 'tool.execute.before':
          // Pre-process tool execution
          break;
      }
    }
  };
};

Event Categories

CategoryEventsUse Cases
commandcommand.executedTrack command history, notifications
filefile.edited, file.watcher.updatedFile validation, auto-formatting
installationinstallation.updatedDependency tracking
lsplsp.client.diagnostics, lsp.updatedCustom error handling
messagemessage.*.updated/removedMessage filtering, logging
permissionpermission.replied/updatedPermission policies
serverserver.connectedConnection monitoring
sessionsession.created/deleted/error/idle/status/updated/compacted/diffSession management
todotodo.updatedTodo synchronization
tooltool.execute.before/afterTool interception, augmentation
tuitui.prompt.append, tui.command.execute, tui.toast.showUI customization

Plugin Manifest (package.json or separate config)

{
  "name": "env-protection",
  "description": "Prevents sharing .env files",
  "version": "1.0.0",
  "author": "Security Team",
  "plugin": {
    "file": "plugin.js",
    "location": "global"
  },
  "hooks": {
    "file": ["file.edited"],
    "permission": ["permission.replied"]
  }
}

Implementation

Complete Example: Environment File Protection

// .opencode/plugin/env-protection.js

export const EnvProtectionPlugin = async ({ project, client }) => {
  const sensitivePatterns = [
    /\.env$/,
    /\.env\..+$/,
    /credentials\.json$/,
    /\.secret$/,
  ];

  const isSensitiveFile = (filePath) => {
    return sensitivePatterns.some(pattern => pattern.test(filePath));
  };

  return {
    event: async ({ event }) => {
      switch (event.type) {
        case 'file.edited': {
          const { path } = event.data;

          if (isSensitiveFile(path)) {
            console.warn(`⚠️  Sensitive file edited: ${path}`);
            console.warn('This file should not be shared or committed.');
          }
          break;
        }

        case 'permission.replied': {
          const { action, target, decision } = event.data;

          // Block read/share operations on sensitive files
          if ((action === 'read' || action === 'share') &&
              isSensitiveFile(target) &&
              decision === 'allow') {

            console.error(`🚫 Blocked ${action} operation on sensitive file: ${target}`);

            // Override permission decision
            return {
              override: true,
              decision: 'deny',
              reason: 'Sensitive file protection policy'
            };
          }
          break;
        }
      }
    }
  };
};

Example: Command Execution Notifications

// .opencode/plugin/notify.js

export const NotifyPlugin = async ({ project, $ }) => {
  let commandStartTime = null;

  return {
    event: async ({ event }) => {
      switch (event.type) {
        case 'command.executed': {
          const { command, args, status } = event.data;
          commandStartTime = Date.now();

          console.log(`▶️  Executing: ${command} ${args.join(' ')}`);
          break;
        }

        case 'tool.execute.after': {
          const { tool, duration, success } = event.data;

          if (duration > 5000) {
            // Notify for long-running operations
            await $`osascript -e 'display notification "Completed in ${duration}ms" with title "${tool}"'`;
          }

          console.log(`✅ ${tool} completed in ${duration}ms`);
          break;
        }
      }
    }
  };
};

Example: Custom Tool Registration

// .opencode/plugin/custom-tools.js

export const CustomToolsPlugin = async ({ client }) => {
  // Register custom tool on initialization
  await client.registerTool({
    name: 'lint',
    description: 'Run linter on current file with auto-fix option',
    parameters: {
      type: 'object',
      properties: {
        fix: {
          type: 'boolean',
          description: 'Auto-fix issues'
        }
      }
    },
    handler: async ({ fix }) => {
      const result = await $`eslint ${fix ? '--fix' : ''} .`;
      return {
        output: result.stdout,
        errors: result.stderr
      };
    }
  });

  return {
    event: async ({ event }) => {
      // Monitor tool usage
      if (event.type === 'tool.execute.before') {
        console.log(`🔧 Tool: ${event.data.tool}`);
      }
    }
  };
};

Installation Locations

LocationPathScopeUse Case
Global~/.config/opencode/plugin/All projectsSecurity policies, global utilities
Project.opencode/plugin/Current projectProject-specific hooks, validators

Common Mistakes

MistakeWhy It FailsFix
Synchronous event handlerBlocks event loopUse async handlers
Missing error handlingPlugin crashes on errorWrap in try/catch
Heavy computation in handlerSlows down operationsDefer to background process
Mutating event data directlyCauses side effectsReturn override object
Not checking event typeHandles wrong eventsUse switch/case on event.type
Forgetting context destructuringMissing key utilitiesDestructure { project, client, $, directory, worktree }

Event Data Structures

// File Events
interface FileEditedEvent {
  type: 'file.edited';
  data: {
    path: string;
    content: string;
    timestamp: number;
  };
}

// Tool Events
interface ToolExecuteBeforeEvent {
  type: 'tool.execute.before';
  data: {
    tool: string;
    args: Record<string, any>;
    user: string;
  };
}

interface ToolExecuteAfterEvent {
  type: 'tool.execute.after';
  data: {
    tool: string;
    duration: number;
    success: boolean;
    output?: any;
    error?: string;
  };
}

// Permission Events
interface PermissionRepliedEvent {
  type: 'permission.replied';
  data: {
    action: 'read' | 'write' | 'execute' | 'share';
    target: string;
    decision: 'allow' | 'deny';
  };
}

Testing Plugins

// Test plugin locally before installation
import { EnvProtectionPlugin } from './env-protection.js';

const mockContext = {
  project: { root: '/test/project' },
  client: {},
  $: async (cmd) => ({ stdout: '', stderr: '' }),
  directory: '/test/project',
  worktree: null
};

const plugin = await EnvProtectionPlugin(mockContext);

// Simulate event
await plugin.event({
  event: {
    type: 'file.edited',
    data: { path: '.env', content: 'SECRET=123', timestamp: Date.now() }
  }
});

Real-World Impact

Security: Prevent accidental sharing of credentials (env-protection plugin blocks .env file reads)

Productivity: Auto-notify on long-running commands (notify plugin sends system notifications)

Quality: Auto-format files on save (file.edited hook runs prettier)

Monitoring: Track tool usage patterns (tool.execute hooks log analytics)

Claude Code Event Mapping

When porting Claude Code hook behavior to OpenCode plugins, use these event mappings:

Claude HookOpenCode EventDescription
PreToolUsetool.execute.beforeRun before tool execution, can block
PostToolUsetool.execute.afterRun after tool execution
UserPromptSubmitmessage.* eventsProcess user prompts
SessionEndsession.idleSession completion

Example: Claude-like Hook Behavior

export const CompatiblePlugin = async (context) => {
  return {
    // Equivalent to Claude's PreToolUse hook
    'tool.execute.before': async (input, output) => {
      if (shouldBlock(input)) {
        throw new Error('Blocked by policy');
      }
    },

    // Equivalent to Claude's PostToolUse hook
    'tool.execute.after': async (result) => {
      console.log(`Tool completed: ${result.tool}`);
    },

    // Equivalent to Claude's SessionEnd hook
    event: async ({ event }) => {
      if (event.type === 'session.idle') {
        await cleanup();
      }
    }
  };
};

Plugin Composition

Combine multiple plugins using opencode-plugin-compose:

import { compose } from "opencode-plugin-compose";

const composedPlugin = compose([
  envProtectionPlugin,
  notifyPlugin,
  customToolsPlugin
]);
// Runs all hooks in sequence

Non-Convertibility Note

Important: OpenCode plugins cannot be directly converted from Claude Code hooks due to fundamental differences:

  • Event models differ: Claude has 4 hook events, OpenCode has 32+
  • Formats differ: Claude uses executable scripts, OpenCode uses JS/TS modules
  • Execution context differs: Different context objects and return value semantics

When porting Claude hooks to OpenCode plugins, you'll need to rewrite the logic using the OpenCode plugin API.


Schema Reference: packages/converters/schemas/opencode-plugin.schema.json

Documentation: https://opencode.ai/docs/plugins/

GitHub リポジトリ

pr-pm/prpm
パス: .claude/skills/creating-opencode-plugins
claudeclaude-codecursorcursor-ai-editcursorrulespackage-manager

関連スキル

content-collections

メタ

This skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.

スキルを見る

evaluating-llms-harness

テスト

This Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.

スキルを見る

polymarket

メタ

This skill enables developers to build applications with the Polymarket prediction markets platform, including API integration for trading and market data. It also provides real-time data streaming via WebSocket to monitor live trades and market activity. Use it for implementing trading strategies or creating tools that process live market updates.

スキルを見る

langchain

メタ

LangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.

スキルを見る