返回技能列表

build-cli-plugin

pjt222
更新于 2 days ago
5 次查看
17
2
17
在 GitHub 上查看
design

关于

This skill guides developers in building CLI plugins or adapters using the abstract base class pattern. It covers defining the plugin contract, implementing idempotent install/uninstall operations, and choosing installation strategies like symlinking or file appending. Use it to add support for new frameworks to a CLI tool or to extend an existing plugin architecture.

快速安装

Claude Code

推荐
主要方式
npx skills add pjt222/agent-almanac -a claude-code
插件命令备选方式
/plugin add https://github.com/pjt222/agent-almanac
Git 克隆备选方式
git clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/build-cli-plugin

在 Claude Code 中复制并粘贴此命令以安装该技能

技能文档

Build a CLI Plugin

Añadir un nuevo plugin o adaptador a la arquitectura conectable de una herramienta CLI usando el patrón de clase base abstracta.

Cuándo Usar

  • Añadir soporte para un nuevo framework objetivo a un instalador CLI
  • Construir un sistema de plugins para una herramienta de línea de comandos multi-objetivo
  • Extender una arquitectura de adaptadores existente con una nueva variante de estrategia
  • Portar entrega de contenido a un framework que usa una disposición de archivos diferente

Entradas

  • Requerido: Framework u objetivo que el plugin soporta (nombre, rutas de configuración, convenciones)
  • Requerido: Ruta a la clase base o contrato del plugin
  • Requerido: Estrategia de instalación: symlink, copy, file-per-item, o append-to-file
  • Opcional: Tipos de contenido que el plugin maneja (p. ej., solo skills, skills + agentes, soporte completo)
  • Opcional: Soporte de scope (a nivel de proyecto, global, ambos)

Procedimiento

Paso 1: Definir el Contrato

La clase base establece la interfaz que todos los plugins deben implementar:

export class FrameworkAdapter {
  static id = 'base';            // Unique identifier
  static displayName = 'Base';   // Human-readable name
  static strategy = 'symlink';   // Installation strategy
  static contentTypes = ['skill']; // What this adapter handles

  async detect(projectDir) { return false; }
  getTargetPath(projectDir, scope) { throw new Error('Not implemented'); }
  async install(item, projectDir, scope, options) { throw new Error('Not implemented'); }
  async uninstall(item, projectDir, scope, options) { throw new Error('Not implemented'); }
  async listInstalled(projectDir, scope) { return []; }
  async audit(projectDir, scope) { return { framework: this.constructor.displayName, ok: [], warnings: [], errors: [] }; }
  supports(contentType) { return this.constructor.contentTypes.includes(contentType); }
}

Campos estáticos definen la identidad y capacidades del plugin:

  • id: Usado en la opción --framework <id> y reporte de resultados
  • displayName: Mostrado en la salida legible para humanos
  • strategy: Determina cómo el contenido llega al objetivo
  • contentTypes: Filtra qué items recibe este adaptador

Si la clase base no existe aún, crearla primero. El patrón escala a cualquier número de plugins.

Esperado: Una clase base con campos estáticos de identidad y métodos abstractos.

En caso de fallo: Si la clase base tiene métodos que no aplican a todos los plugins (p. ej., no todos los frameworks soportan audit), proporcionar implementaciones por defecto que retornen no-ops sensatos.

Paso 2: Elegir la Estrategia de Instalación

EstrategiaCuándo usarEjemplo
symlinkEl objetivo lee archivos fuente directamente. Más barato, se mantiene en sincronía.Claude Code lee symlinks .claude/skills/<name>/
copyEl objetivo necesita archivos en su propio directorio. Las modificaciones no se propagan.Algunos IDEs solo indexan sus propios directorios
file-per-itemEl objetivo espera un archivo por item con formato específico.Archivos de reglas .mdc de Cursor
append-to-fileEl objetivo lee un único archivo de instrucciones.CONVENTIONS.md de Aider, AGENTS.md de Codex

La estrategia determina la forma de la implementación:

  • Symlink: symlinkSync(source, target) — manejar rutas relativas vs. absolutas
  • Copy: cpSync(source, target, { recursive: true }) — manejar sobrescrituras
  • File-per-item: writeFileSync(target, transform(content)) — puede necesitar conversión de formato
  • Append-to-file: Envolver contenido en marcadores para inserción/reemplazo/eliminación idempotente

Esperado: Estrategia seleccionada con justificación clara basada en cómo el framework objetivo descubre contenido.

En caso de fallo: Si no se está seguro, verificar la documentación del framework para ver cómo descubre archivos de configuración o instrucción. Por defecto symlink si el framework lee directorios arbitrarios.

Paso 3: Implementar la Detección

La detección le indica al CLI qué frameworks están presentes en un proyecto:

// In detector.js — each rule checks for a filesystem marker
const RULES = [
  {
    id: 'my-framework',
    displayName: 'My Framework',
    check: (dir) => existsSync(resolve(dir, '.myframework/')),
    marker: '.myframework/',
    scope: 'project',
  },
];

Estrategias de detección:

  • Presencia de directorio: .claude/, .cursor/, .gemini/
  • Archivo de configuración: opencode.json, .aider.conf.yml
  • Archivo de instrucción: AGENTS.md, CONVENTIONS.md
  • Marcadores globales: ~/.openclaw/, ~/.hermes/

Siempre devolver el marcador en el resultado de detección para que los usuarios puedan entender por qué se detectó un framework.

Esperado: Una regla de detección que identifica el framework de manera confiable sin falsos positivos.

En caso de fallo: Si el framework no tiene un marcador único (nombre de directorio genérico), usar una combinación de marcadores o requerir especificación explícita de --framework.

Paso 4: Implementar Install con Idempotencia

async install(item, projectDir, scope, options) {
  const targetDir = this.getTargetPath(projectDir, scope);
  const targetPath = resolve(targetDir, item.id);

  // Idempotency: skip if already installed (unless force)
  if (existsSync(targetPath) && !options.force) {
    return { action: 'skipped', path: targetPath };
  }

  if (options.dryRun) {
    return { action: 'created', path: targetPath, details: 'dry-run' };
  }

  // Ensure parent directory exists
  mkdirSync(targetDir, { recursive: true });

  // Strategy-specific installation
  if (this.constructor.strategy === 'symlink') {
    const relPath = relative(targetDir, item.sourceDir);
    symlinkSync(relPath, targetPath);
  } else if (this.constructor.strategy === 'copy') {
    cpSync(item.sourceDir, targetPath, { recursive: true });
  }

  return { action: 'created', path: targetPath };
}

Reglas de idempotencia:

  • Saltar si el objetivo existe y --force no está establecido
  • Sobrescribir si --force está establecido (eliminar primero, luego instalar)
  • Dry-run siempre tiene éxito con action: 'created'
  • Valor de retorno debe ser siempre { action, path, details? }

Esperado: Install crea contenido en la ruta objetivo, salta si ya está presente, respeta --force y --dry-run.

En caso de fallo: Si la creación de symlink falla en Windows/NTFS, recurrir a junction de directorio o copia. Registrar el respaldo.

Paso 5: Implementar Uninstall con Limpieza

async uninstall(item, projectDir, scope, options) {
  const targetDir = this.getTargetPath(projectDir, scope);
  const targetPath = resolve(targetDir, item.id);

  if (!existsSync(targetPath)) {
    return { action: 'skipped', path: targetPath };
  }

  if (options.dryRun) {
    return { action: 'removed', path: targetPath };
  }

  // Remove the installed content
  rmSync(targetPath, { recursive: true });

  return { action: 'removed', path: targetPath };
}

Consideraciones de limpieza:

  • Eliminar solo lo que el plugin instaló — nunca borrar archivos creados por el usuario
  • Para append-to-file: eliminar la sección marcada, no el archivo entero
  • Dejar los directorios padres intactos (otros plugins pueden usarlos)

Esperado: Uninstall elimina solo el contenido del plugin y nada más.

En caso de fallo: Si la eliminación falla (permisos, archivo bloqueado), devolver un resultado de error en lugar de lanzar excepción.

Paso 6: Implementar Listado y Auditoría

async listInstalled(projectDir, scope) {
  const targetDir = this.getTargetPath(projectDir, scope);
  if (!existsSync(targetDir)) return [];

  const entries = readdirSync(targetDir);
  return entries.map(name => {
    const fullPath = resolve(targetDir, name);
    const broken = lstatSync(fullPath).isSymbolicLink()
      && !existsSync(fullPath);
    return { id: name, type: 'skill', broken };
  });
}

async audit(projectDir, scope) {
  const items = await this.listInstalled(projectDir, scope);
  const ok = items.filter(i => !i.broken);
  const broken = items.filter(i => i.broken);
  return {
    framework: this.constructor.displayName,
    ok: [`${ok.length} skills installed`],
    warnings: [],
    errors: broken.map(i => `Broken: ${i.id}`),
  };
}

Esperado: El listado retorna todos los items instalados con detección de enlaces rotos. La auditoría resume la salud.

En caso de fallo: Si el directorio objetivo no existe, retornar resultados vacíos (no es un error — el framework simplemente no tiene nada instalado).

Paso 7: Registrar el Plugin

// In adapters/index.js
import { MyFrameworkAdapter } from './my-framework.js';
register(MyFrameworkAdapter);

El registro hace que el adaptador esté disponible para:

  • Auto-detección (detectFrameworks()getAdaptersForDetections())
  • Selección explícita (--framework my-framework)
  • Listado (listAdapters())

Esperado: El adaptador aparece en la salida de tool detect y puede ser objetivo de --framework.

En caso de fallo: Si el adaptador no aparece, verificar que static id coincida con el id de la regla de detección y que register() haya sido llamado.

Paso 8: Escribir Pruebas

describe('adapter: my-framework (dry-run)', () => {
  it('targets the correct path', () => {
    const out = run('install create-skill --framework my-framework --dry-run');
    assert.match(out, /\.myframework/i);
  });
});

Probar al mínimo: ruta de dry-run, presencia de detección y soporte de tipo de contenido.

Esperado: Pruebas específicas del adaptador confirman la ruta de instalación y el comportamiento.

En caso de fallo: Si el framework no se detecta en CI (sin directorio marcador), usar --framework explícitamente en las pruebas.

Validación

  • El plugin extiende la clase base correctamente
  • Los campos estáticos (id, displayName, strategy, contentTypes) están establecidos
  • La regla de detección identifica el framework sin falsos positivos
  • install() es idempotente (saltar si existe, respetar --force)
  • uninstall() elimina solo contenido creado por el plugin
  • listInstalled() detecta symlinks rotos
  • audit() reporta la salud con precisión
  • El plugin está registrado y aparece en tool detect
  • Las pruebas de dry-run pasan

Errores Comunes

  • Olvidar symlinks relativos vs. absolutos: Los symlinks de scope de proyecto deben ser relativos (portables). Los symlinks de scope global deben ser absolutos (no dependientes del cwd).
  • No manejar directorios padres faltantes: Siempre mkdirSync(dir, { recursive: true }) antes de crear contenido.
  • Append-to-file sin marcadores: Sin marcadores idempotentes (<!-- start:id --> / <!-- end:id -->), las instalaciones repetidas duplican contenido. Siempre envolver el contenido añadido.
  • Falsos positivos de detección: Un nombre de directorio genérico (p. ej., .config/) puede coincidir con múltiples frameworks. Usar marcadores de archivo específicos dentro del directorio.
  • Olvidar la verificación supports(): El instalador llama a supports(item.type) antes de despachar. Si contentTypes es incorrecto, el adaptador salta items silenciosamente.

Habilidades Relacionadas

  • scaffold-cli-command — construir los comandos CLI que usan este plugin
  • test-cli-application — patrones de prueba para herramientas CLI incluyendo pruebas de adaptador
  • design-cli-output — salida del terminal para resultados de install/uninstall

GitHub 仓库

pjt222/agent-almanac
路径: i18n/es/skills/build-cli-plugin
0
agentsagentskillsai-assisted-developmentclaude-codeskillsteams

相关推荐技能

content-collections

Content Collections 是一个 TypeScript 优先的构建工具,可将本地 Markdown/MDX 文件转换为类型安全的数据集合。它专为构建博客、文档站和内容密集型 Vite+React 应用而设计,提供基于 Zod 的自动模式验证。该工具涵盖从 Vite 插件配置、MDX 编译到生产环境部署的完整工作流。

查看技能

polymarket

这个Claude Skill为开发者提供完整的Polymarket预测市场开发支持,涵盖API调用、交易执行和市场数据分析。关键特性包括实时WebSocket数据流,可监控实时交易、订单和市场动态。开发者可用它构建预测市场应用、实施交易策略并集成实时市场预测功能。

查看技能

creating-opencode-plugins

该Skill帮助开发者创建OpenCode插件,用于接入命令、文件、LSP等25+种事件。它提供了插件结构、事件API规范和JavaScript/TypeScript实现模式,适合需要拦截操作、扩展功能或自定义事件处理的场景。开发者可通过它快速构建响应式模块来增强OpenCode AI助手的能力。

查看技能

sglang

SGLang是一个专为LLM设计的高性能推理框架,特别适用于需要结构化输出的场景。它通过RadixAttention前缀缓存技术,在处理JSON、正则表达式、工具调用等具有重复前缀的复杂工作流时,能实现极速生成。如果你正在构建智能体或多轮对话系统,并追求远超vLLM的推理性能,SGLang是理想选择。

查看技能