Zurück zu Fähigkeiten

build-cli-plugin

pjt222
Aktualisiert 2 days ago
1 Ansichten
17
2
17
Auf GitHub ansehen
Metadesign

Über

Diese Fähigkeit bietet eine Vorlage zum Erstellen von CLI-Plugins oder Adaptern mithilfe des abstrakten Basisklassenmusters. Sie behandelt die Definition von Plugin-Verträgen, die Implementierung von Installationsstrategien (Symlink/Kopieren/Anhängen) sowie die Handhabung von Erkennung, Registrierung und idempotenten Operationen. Verwenden Sie sie, wenn Sie CLI-Tools um Unterstützung für neue Frameworks erweitern oder Plugin-Systeme für Multi-Target-Anwendungen erstellen.

Schnellinstallation

Claude Code

Empfohlen
Primär
npx skills add pjt222/agent-almanac -a claude-code
Plugin-BefehlAlternativ
/plugin add https://github.com/pjt222/agent-almanac
Git CloneAlternativ
git clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/build-cli-plugin

Kopieren Sie diesen Befehl und fügen Sie ihn in Claude Code ein, um diese Fähigkeit zu installieren

Dokumentation

Build a CLI Plugin

Add a new plugin or adapter to a CLI tool's pluggable architecture using the abstract base class pattern.

When to Use

  • Adding support for a new target framework to a CLI installer
  • Building a plugin system for a multi-target command-line tool
  • Extending an existing adapter architecture with a new strategy variant
  • Porting content delivery to a framework that uses a different file layout

Inputs

  • Required: Framework or target the plugin supports (name, config paths, conventions)
  • Required: Path to the base class or plugin contract
  • Required: Installation strategy: symlink, copy, file-per-item, or append-to-file
  • Optional: Content types the plugin handles (e.g., skills only, skills + agents, full support)
  • Optional: Scope support (project-level, global, both)

Procedure

Step 1: Define the Contract

The base class establishes the interface all plugins must implement:

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); }
}

Static fields define the plugin's identity and capabilities:

  • id: Used in --framework <id> option and result reporting
  • displayName: Shown in human-readable output
  • strategy: Determines how content reaches the target
  • contentTypes: Filters which items this adapter receives

If the base class does not exist yet, create it first. The pattern scales to any number of plugins.

Got: A base class with static identity fields and abstract methods.

If fail: If the base class has methods that don't apply to all plugins (e.g., not all frameworks support audit), provide default implementations that return sensible no-ops.

Step 2: Choose the Installation Strategy

StrategyWhen to useExample
symlinkTarget reads source files directly. Cheapest, stays in sync.Claude Code reads .claude/skills/<name>/ symlinks
copyTarget needs files in its own directory. Modifications don't propagate.Some IDEs index only their own dirs
file-per-itemTarget expects one file per item with specific format.Cursor .mdc rules files
append-to-fileTarget reads a single instructions file.Aider CONVENTIONS.md, Codex AGENTS.md

Strategy determines the implementation shape:

  • Symlink: symlinkSync(source, target) — handle relative vs. absolute paths
  • Copy: cpSync(source, target, { recursive: true }) — handle overwrites
  • File-per-item: writeFileSync(target, transform(content)) — may need format conversion
  • Append-to-file: Wrap content in markers for idempotent insert/replace/remove

Got: Strategy selected with clear rationale based on how the target framework discovers content.

If fail: If unsure, check the framework's documentation for how it discovers configuration or instruction files. Default to symlink if the framework reads arbitrary directories.

Step 3: Implement Detection

Detection tells the CLI which frameworks are present in a project:

// 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',
  },
];

Detection strategies:

  • Directory presence: .claude/, .cursor/, .gemini/
  • Config file: opencode.json, .aider.conf.yml
  • Instruction file: AGENTS.md, CONVENTIONS.md
  • Global markers: ~/.openclaw/, ~/.hermes/

Always return the marker in the detection result so users can understand why a framework was detected.

Got: A detection rule that reliably identifies the framework without false positives.

If fail: If the framework has no unique marker (generic directory name), use a combination of markers or require explicit --framework specification.

Step 4: Implement Install with Idempotency

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 };
}

Idempotency rules:

  • Skip if target exists and --force is not set
  • Overwrite if --force is set (remove first, then install)
  • Dry-run always succeeds with action: 'created'
  • Return value must always be { action, path, details? }

Got: Install creates content at the target path, skips if already present, respects --force and --dry-run.

If fail: If symlink creation fails on Windows/NTFS, fall back to directory junction or copy. Log the fallback.

Step 5: Implement Uninstall with Cleanup

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 };
}

Cleanup considerations:

  • Remove only what the plugin installed — never delete user-created files
  • For append-to-file: remove the marked section, not the entire file
  • Leave parent directories intact (other plugins may use them)

Got: Uninstall removes only the plugin's content and nothing else.

If fail: If removal fails (permissions, locked file), return an error result instead of throwing.

Step 6: Implement Listing and Audit

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}`),
  };
}

Got: Listing returns all installed items with broken-link detection. Audit summarizes health.

If fail: If the target directory doesn't exist, return empty results (not an error — the framework has nothing installed).

Step 7: Register the Plugin

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

Registration makes the adapter available to:

  • Auto-detection (detectFrameworks()getAdaptersForDetections())
  • Explicit selection (--framework my-framework)
  • Listing (listAdapters())

Got: The adapter appears in tool detect output and can be targeted with --framework.

If fail: If the adapter doesn't appear, verify static id matches the detection rule's id and that register() was called.

Step 8: Write Tests

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);
  });
});

Test at minimum: dry-run path, detection presence, and content type support.

Got: Adapter-specific tests confirm the installation path and behavior.

If fail: If the framework isn't detected in CI (no marker directory), use --framework explicitly in tests.

Validation

  • Plugin extends the base class correctly
  • Static fields (id, displayName, strategy, contentTypes) are set
  • Detection rule identifies the framework without false positives
  • install() is idempotent (skip if exists, respect --force)
  • uninstall() removes only plugin-created content
  • listInstalled() detects broken symlinks
  • audit() reports health accurately
  • Plugin is registered and appears in tool detect
  • Dry-run tests pass

Pitfalls

  • Forgetting relative vs. absolute symlinks: Project-scope symlinks should be relative (portable). Global-scope symlinks should be absolute (not dependent on cwd).
  • Not handling missing parent directories: Always mkdirSync(dir, { recursive: true }) before creating content.
  • Append-to-file without markers: Without idempotent markers (<!-- start:id --> / <!-- end:id -->), repeated installs duplicate content. Always wrap appended content.
  • Detection false positives: A generic directory name (e.g., .config/) may match multiple frameworks. Use specific file markers inside the directory.
  • Forgetting supports() check: The installer calls supports(item.type) before dispatching. If contentTypes is wrong, the adapter silently skips items.

Related Skills

  • scaffold-cli-command — build the CLI commands that use this plugin
  • test-cli-application — testing patterns for CLI tools including adapter tests
  • design-cli-output — terminal output for install/uninstall results

GitHub Repository

pjt222/agent-almanac
Pfad: i18n/caveman-lite/skills/build-cli-plugin
0
agentsagentskillsai-assisted-developmentclaude-codeskillsteams

Verwandte Skills

content-collections

Meta

Diese Skill bietet eine produktionsgetestete Einrichtung für Content Collections – ein TypeScript-first-Tool, das Markdown/MDX-Dateien in typsichere Datensammlungen mit Zod-Validierung umwandelt. Verwenden Sie ihn beim Erstellen von Blogs, Dokumentationsseiten oder inhaltsstarken Vite + React-Anwendungen, um Typsicherheit und automatische Inhaltsvalidierung zu gewährleisten. Er behandelt alles von der Vite-Plugin-Konfiguration und MDX-Kompilierung bis hin zur Deployment-Optimierung und Schema-Validierung.

Skill ansehen

polymarket

Meta

Diese Fähigkeit ermöglicht es Entwicklern, Anwendungen mit der Polymarket-Prognosemärkte-Plattform zu erstellen, einschließlich API-Integration für Handel und Marktdaten. Sie bietet außerdem Echtzeit-Datenstreaming über WebSocket, um Live-Trades und Marktaktivitäten zu überwachen. Nutzen Sie sie zur Implementierung von Handelsstrategien oder zur Erstellung von Tools, die Live-Marktaktualisierungen verarbeiten.

Skill ansehen

creating-opencode-plugins

Meta

Diese Fähigkeit unterstützt Entwickler dabei, OpenCode-Plugins zu erstellen, die in über 25 Ereignistypen wie Befehle, Dateien und LSP-Operationen eingreifen. Sie bietet die Plugin-Struktur, Event-API-Spezifikationen und Implementierungsmuster für JavaScript/TypeScript-Module. Nutzen Sie sie, wenn Sie den Lebenszyklus des OpenCode KI-Assistenten mit benutzerdefinierter ereignisgesteuerter Logik abfangen, überwachen oder erweitern müssen.

Skill ansehen

sglang

Meta

SGLang ist ein hochperformantes LLM-Serving-Framework, das sich auf schnelle, strukturierte Generierung für JSON, Regex und agentenbasierte Workflows unter Verwendung seines RadixAttention-Prefix-Cachings spezialisiert. Es bietet deutlich schnellere Inferenz, insbesondere für Aufgaben mit wiederholten Präfixen, was es ideal für komplexe, strukturierte Ausgaben und Mehrfachdialoge macht. Wählen Sie SGLang gegenüber Alternativen wie vLLM, wenn Sie constrained decoding benötigen oder Anwendungen mit umfangreicher Präfix-Weitergabe entwickeln.

Skill ansehen