Capitulo 15: Plugins

Por: Artiko
opencodeaipluginsextensionesecosistema

Capitulo 15: Plugins

< Volver al Indice del Tutorial

Que son los Plugins

Los plugins son extensiones que agregan funcionalidad a OpenCode más allá de lo que permiten MCP servers, skills o comandos custom. Mientras que MCP conecta con servicios externos y los skills definen instrucciones, los plugins pueden modificar el comportamiento interno de OpenCode: registrar hooks en el ciclo de vida, interceptar permisos, reaccionar a eventos de la sesión y más.

Son el mecanismo de extensión más profundo disponible. Un plugin puede ejecutar código cada vez que se completa una sesión, proteger archivos sensibles de edición, enviar notificaciones cuando el agente termina una tarea o integrar OpenCode con herramientas externas del equipo.

A diferencia de los MCP servers que exponen herramientas al agente, los plugins operan a nivel de la infraestructura de OpenCode. El agente no “usa” un plugin como usa una herramienta; el plugin intercepta y reacciona a lo que el agente hace.

Metodos de Carga

OpenCode soporta dos métodos para cargar plugins: archivos locales y paquetes npm.

Archivos Locales

Los plugins locales se colocan en directorios específicos que OpenCode reconoce automáticamente:

Plugins del proyecto:

tu-proyecto/
  .opencode/
    plugins/
      mi-plugin.ts
      otro-plugin.ts

Plugins globales:

~/.config/opencode/
  plugins/
    mi-plugin-global.ts
    notificaciones.ts

Los plugins locales del proyecto son específicos del repositorio y se comparten con el equipo. Los plugins globales aplican a todos tus proyectos y contienen extensiones personales.

Paquetes NPM

Los paquetes npm se configuran en opencode.json:

{
  "plugin": ["opencode-helicone-session", "@my-org/custom-plugin"]
}

OpenCode instala automáticamente los paquetes npm usando Bun. Los módulos se almacenan en ~/.cache/opencode/node_modules/, separados de las dependencias de tu proyecto para evitar conflictos. No necesitas ejecutar bun install manualmente; OpenCode lo hace al detectar plugins nuevos en la configuración.

Orden de Carga

OpenCode carga los plugins en un orden determinístico:

flowchart TD
    A["1. Config global"] --> B["2. Config del proyecto"]
    B --> C["3. Plugins globales"]
    C --> D["4. Plugins del proyecto"]
  1. Configuración global (~/.config/opencode/config.json): se procesa primero
  2. Configuración del proyecto (opencode.json): puede sobrescribir la global
  3. Plugins globales (~/.config/opencode/plugins/): se cargan en orden alfabético
  4. Plugins del proyecto (.opencode/plugins/): se cargan al final

Este orden importa porque los hooks se ejecutan en el mismo orden en que se registraron. Si un plugin global y uno del proyecto registran el mismo hook, el global se ejecuta primero.

Estructura de un Plugin

Un plugin es una función que recibe un PluginContext y retorna un objeto con hooks registrados:

import type { PluginContext } from "@opencode-ai/plugin"

export default function(context: PluginContext) {
  // context proporciona acceso a:
  // - context.project: información del proyecto
  // - context.client: cliente de OpenCode
  // - context.$: ejecutor de comandos shell (tagged template)
  // - context.directory: directorio del proyecto
  // - context.worktree: directorio de trabajo actual

  return {
    // hooks registrados aquí
  }
}

El PluginContext es el punto de acceso a todo lo que un plugin necesita. Proporciona información del proyecto, un cliente para interactuar con OpenCode, un ejecutor de comandos shell y las rutas relevantes del workspace.

El operador $ es especialmente útil: permite ejecutar comandos shell directamente desde el plugin usando tagged templates:

export default function(ctx: PluginContext) {
  return {
    session: {
      onComplete: async () => {
        await ctx.$`notify-send "OpenCode" "Sesion completada"`.run()
      }
    }
  }
}

Hooks Disponibles

Los hooks son el corazón del sistema de plugins. Cada hook se ejecuta en un momento específico del ciclo de vida de OpenCode. Los hooks disponibles cubren prácticamente todos los aspectos de la operación:

command: Ejecución de Comandos

Se activa cuando se ejecutan comandos en OpenCode. Permite interceptar, modificar o registrar la ejecución de comandos.

file: Edición de Archivos

Se activa cuando el agente edita archivos. Permite validar, aprobar o rechazar ediciones antes de que se apliquen.

install: Actualizaciones

Se activa durante actualizaciones de OpenCode o sus dependencias. Permite ejecutar tareas de migración o mantenimiento.

lsp: Diagnósticos

Se activa cuando el Language Server Protocol reporta diagnósticos. Permite filtrar, agregar o transformar diagnósticos antes de mostrarlos al agente.

message: Mensajes

Se activa con cada mensaje enviado o recibido. Permite interceptar mensajes para logging, analytics o transformación.

permission: Permisos

Se activa cuando OpenCode necesita evaluar un permiso. Permite implementar lógica de permisos personalizada.

session: Sesiones

Se activa al iniciar y completar sesiones. Permite ejecutar setup/teardown, enviar notificaciones o registrar métricas.

tools: Herramientas

Se activa cuando el agente usa herramientas. Permite interceptar llamadas a herramientas, agregar logging o modificar parámetros.

tui: Interacciones de Interfaz

Se activa con interacciones en la interfaz de terminal. Permite personalizar la experiencia de la TUI.

shell: Operaciones Shell

Se activa cuando se ejecutan operaciones en el shell. Permite interceptar o modificar comandos shell.

flowchart LR
    subgraph Hooks
        A[session] --> B[message]
        B --> C[tools]
        C --> D[file]
        C --> E[shell]
        D --> F[permission]
        E --> F
        F --> G[lsp]
    end
    subgraph Lifecycle
        H[Inicio sesión] --> I[Conversación]
        I --> J[Acciones]
        J --> K[Permisos]
        K --> L[Diagnósticos]
    end

Ejemplos Prácticos

Notificación al Completar Sesión

Uno de los usos más comunes es enviar una notificación cuando el agente termina una tarea larga:

export default function(ctx: PluginContext) {
  return {
    session: {
      onComplete: () => {
        ctx.$`notify-send "OpenCode" "Sesion completada"`.run()
      }
    }
  }
}

En Linux usa notify-send, en macOS podrías usar osascript -e 'display notification "Sesion completada"'. Este plugin es particularmente útil cuando lanzas tareas largas y cambias a otra ventana mientras el agente trabaja.

Proteger Archivos Sensibles

Un plugin que impide la edición de archivos con secretos:

export default function(ctx: PluginContext) {
  return {
    permission: {
      edit: (file: string) => {
        const protectedPatterns = ['.env', 'credentials', 'secrets', '.pem', '.key']
        const isProtected = protectedPatterns.some(p => file.includes(p))
        
        if (isProtected) return 'deny'
        return 'allow'
      }
    }
  }
}

Este plugin actúa como una capa de seguridad adicional. Aunque el agente intente editar un archivo .env o credentials.json, el plugin lo bloquea antes de que la edición se aplique. Es especialmente útil en equipos donde múltiples personas usan OpenCode y quieres garantizar que nadie exponga secretos accidentalmente.

Logging de Herramientas

Un plugin que registra cada vez que el agente usa una herramienta:

export default function(ctx: PluginContext) {
  return {
    tools: {
      onCall: (tool: string, params: unknown) => {
        ctx.client.app.log(`Tool: ${tool}, Params: ${JSON.stringify(params)}`)
      }
    }
  }
}

Validar Archivos Antes de Editar

Un plugin que ejecuta validaciones antes de permitir ediciones:

export default function(ctx: PluginContext) {
  return {
    file: {
      beforeEdit: async (filepath: string) => {
        // No permitir editar archivos generados
        if (filepath.includes('/generated/') || filepath.includes('/dist/')) {
          return 'deny'
        }
        
        // No permitir editar lockfiles
        if (filepath.endsWith('lock') || filepath.endsWith('.lock')) {
          return 'deny'
        }
        
        return 'allow'
      }
    }
  }
}

Métricas de Sesión

Un plugin que registra cuánto dura cada sesión y cuántas herramientas se usaron:

export default function(ctx: PluginContext) {
  let startTime: number
  let toolCount = 0

  return {
    session: {
      onStart: () => {
        startTime = Date.now()
        toolCount = 0
      },
      onComplete: () => {
        const duration = Math.round((Date.now() - startTime) / 1000)
        ctx.client.app.log(
          `Sesion: ${duration}s, Herramientas: ${toolCount}`
        )
      }
    },
    tools: {
      onCall: () => { toolCount++ }
    }
  }
}

Logging en Plugins

Un detalle importante: dentro de un plugin, no uses console.log(). Los logs del plugin deben ir a través de la API del cliente:

// Incorrecto
console.log("Algo pasó")

// Correcto
ctx.client.app.log("Algo pasó")

client.app.log() integra los mensajes del plugin con el sistema de logging de OpenCode. Los mensajes de console.log() pueden perderse o interferir con la TUI.

Dependencias Externas

Si tu plugin necesita dependencias npm que no son parte de OpenCode, crea un archivo package.json dentro del directorio de plugins:

.opencode/
  plugins/
    mi-plugin.ts
  package.json

El package.json lista las dependencias que tu plugin necesita:

{
  "dependencies": {
    "node-notifier": "^10.0.0",
    "chalk": "^5.0.0"
  }
}

OpenCode detecta este archivo y ejecuta bun install automáticamente antes de cargar los plugins. Las dependencias se instalan en el contexto del directorio .opencode/, sin contaminar el node_modules de tu proyecto principal.

Desarrollo de un Plugin Local

El flujo completo para crear un plugin local:

Paso 1: Crear el archivo

mkdir -p .opencode/plugins

Paso 2: Escribir el plugin

Crea .opencode/plugins/proteger-produccion.ts:

import type { PluginContext } from "@opencode-ai/plugin"

export default function(ctx: PluginContext) {
  const dangerousCommands = ['rm -rf', 'drop table', 'DELETE FROM', 'git push --force']

  return {
    shell: {
      beforeExecute: (command: string) => {
        const isDangerous = dangerousCommands.some(
          dc => command.toLowerCase().includes(dc.toLowerCase())
        )
        if (isDangerous) {
          ctx.client.app.log(`Comando peligroso bloqueado: ${command}`)
          return 'deny'
        }
        return 'allow'
      }
    }
  }
}

Paso 3: Probar

Reinicia OpenCode. El plugin se carga automáticamente al detectar el archivo en .opencode/plugins/. Prueba pidiendo al agente que ejecute un comando que contenga alguno de los patrones bloqueados para verificar que el plugin funciona.

Paso 4: Iterar

Los plugins locales se recargan cada vez que inicias una nueva sesión de OpenCode. Modifica el archivo, reinicia, y los cambios se aplican inmediatamente. No necesitas compilar ni publicar nada durante el desarrollo.

Cuando Usar Cada Mecanismo de Extension

OpenCode ofrece múltiples formas de extender su funcionalidad. Elegir la correcta depende de tu necesidad:

NecesidadMecanismoPor qué
Conectar con Sentry/Slack/GitHubMCP ServerProtocolo estándar, interoperable
Notificar al completar sesiónPlugin (hook)Reacciona a eventos del ciclo de vida
Proteger archivos de ediciónPlugin (permission)Intercepta antes de que ocurra
Ejecutar un script y devolver resultadoCustom ToolMenor overhead de configuración
Logging de todas las interaccionesPlugin (hooks)Acceso a todos los eventos
Convenciones de testingSkillInstrucciones reutilizables
Ejecutar review rápidoCommandAtajo a prompt predefinido

La mayoría de usuarios solo necesitarán MCP servers, skills y comandos custom. Los plugins son para casos avanzados donde necesitas interceptar el comportamiento de OpenCode o reaccionar a eventos que no están expuestos de otra forma.

Buenas Practicas


Siguiente: Capitulo 16: GitHub Actions y CI/CD —>