build-cli-plugin
О программе
Этот навык помогает разработчикам создавать CLI-плагины или адаптеры, используя шаблон абстрактного базового класса. Он охватывает определение контракта плагина, реализацию идемпотентных операций установки/удаления и выбор стратегий установки, таких как символьные ссылки или добавление в файлы. Используйте его для добавления поддержки новых фреймворков в CLI-инструмент или для расширения существующей архитектуры плагинов.
Быстрая установка
Claude Code
Рекомендуетсяnpx skills add pjt222/agent-almanac -a claude-code/plugin add https://github.com/pjt222/agent-almanacgit 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, oappend-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 resultadosdisplayName: Mostrado en la salida legible para humanosstrategy: Determina cómo el contenido llega al objetivocontentTypes: 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
| Estrategia | Cuándo usar | Ejemplo |
|---|---|---|
| symlink | El objetivo lee archivos fuente directamente. Más barato, se mantiene en sincronía. | Claude Code lee symlinks .claude/skills/<name>/ |
| copy | El objetivo necesita archivos en su propio directorio. Las modificaciones no se propagan. | Algunos IDEs solo indexan sus propios directorios |
| file-per-item | El objetivo espera un archivo por item con formato específico. | Archivos de reglas .mdc de Cursor |
| append-to-file | El 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
--forceno está establecido - Sobrescribir si
--forceestá 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 asupports(item.type)antes de despachar. SicontentTypeses incorrecto, el adaptador salta items silenciosamente.
Habilidades Relacionadas
scaffold-cli-command— construir los comandos CLI que usan este plugintest-cli-application— patrones de prueba para herramientas CLI incluyendo pruebas de adaptadordesign-cli-output— salida del terminal para resultados de install/uninstall
GitHub репозиторий
Похожие навыки
content-collections
МетаЭтот навык предоставляет проверенную в продакшене настройку для Content Collections — TypeScript-ориентированного инструмента, который преобразует файлы Markdown/MDX в типобезопасные коллекции данных с валидацией Zod. Используйте его при создании блогов, сайтов документации или контентных приложений на Vite + React для обеспечения типобезопасности и автоматической проверки содержимого. Он охватывает всё: от настройки плагина Vite и компиляции MDX до оптимизации развертывания и валидации схем.
polymarket
МетаЭтот навык позволяет разработчикам создавать приложения на платформе прогнозных рынков Polymarket, включая интеграцию с API для торговли и получения рыночных данных. Он также обеспечивает потоковую передачу данных в реальном времени через WebSocket для отслеживания текущих сделок и рыночной активности. Используйте его для реализации торговых стратегий или создания инструментов, обрабатывающих обновления рынка в реальном времени.
creating-opencode-plugins
МетаЭтот навык помогает разработчикам создавать плагины OpenCode, которые подключаются к более чем 25 типам событий, таким как команды, файлы и операции LSP. Он предоставляет структуру плагина, спецификации API событий и шаблоны реализации для модулей на JavaScript/TypeScript. Используйте его, когда вам нужно перехватывать, отслеживать или расширять жизненный цикл ассистента OpenCode AI с помощью пользовательской событийно-ориентированной логики.
sglang
МетаSGLang — это высокопроизводительный фреймворк для обслуживания больших языковых моделей (LLM), специализирующийся на быстрой структурированной генерации JSON, regex и рабочих процессов агентов с использованием кэширования префиксов RadixAttention. Он обеспечивает значительно более высокую скорость вывода, особенно для задач с повторяющимися префиксами, что делает его идеальным для сложных структурированных результатов и многократных диалогов. Выбирайте SGLang вместо альтернатив, таких как vLLM, когда вам требуется ограниченное декодирование или вы создаете приложения с интенсивным совместным использованием префиксов.
