返回技能列表

build-cli-plugin

pjt222
更新于 2 days ago
7 次查看
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 logic, and choosing installation strategies like symlinking or file appending. Use it to add support for new frameworks to a CLI installer or to extend a multi-target tool with a plugin system.

快速安装

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

抽象基底クラスパターンを使って、CLI ツールのプラグ可能なアーキテクチャに新しいプラグインまたはアダプタを追加する。

使用タイミング

  • CLI インストーラに新しい対象フレームワークのサポートを追加するとき
  • 複数ターゲットのコマンドラインツール用のプラグインシステムを構築するとき
  • 既存のアダプタアーキテクチャを新しい戦略バリアントで拡張するとき
  • 異なるファイルレイアウトを使うフレームワークへコンテンツ配信を移植するとき

入力

  • 必須: プラグインがサポートするフレームワークまたはターゲット(名前、設定パス、慣習)
  • 必須: 基底クラスまたはプラグイン契約のパス
  • 必須: インストール戦略: symlinkcopyfile-per-itemappend-to-file のいずれか
  • 任意: プラグインが扱うコンテンツタイプ(例: skills のみ、skills + agents、フルサポート)
  • 任意: スコープサポート(プロジェクトレベル、グローバル、両方)

手順

ステップ1: 契約を定義する

基底クラスはすべてのプラグインが実装すべきインターフェースを確立する:

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

静的フィールド はプラグインの識別と能力を定義する:

  • id: --framework <id> オプションと結果レポートで使われる
  • displayName: 人間可読な出力で表示される
  • strategy: コンテンツがターゲットへ届く方法を決定する
  • contentTypes: このアダプタが受け取るアイテムをフィルタする

基底クラスがまだ存在しない場合、まずそれを作成する。このパターンは任意の数のプラグインへスケールする。

期待結果: 静的識別フィールドと抽象メソッドを持つ基底クラス。

失敗時: 基底クラスにすべてのプラグインに当てはまらないメソッドがある場合(例: すべてのフレームワークが audit をサポートするわけではない)、合理的な no-op を返すデフォルト実装を提供する。

ステップ2: インストール戦略を選ぶ

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

戦略は実装の形を決める:

  • Symlink: symlinkSync(source, target) — 相対パスと絶対パスを扱う
  • Copy: cpSync(source, target, { recursive: true }) — 上書きを扱う
  • File-per-item: writeFileSync(target, transform(content)) — フォーマット変換が必要かも
  • Append-to-file: 冪等な挿入/置換/削除のためにマーカーで囲む

期待結果: 戦略が、対象フレームワークがコンテンツを発見する方法に基づく明確な根拠とともに選択される。

失敗時: 不確かな場合、フレームワークのドキュメントで設定や指示ファイルをどう発見するかを確認する。フレームワークが任意のディレクトリを読むなら symlink を既定とする。

ステップ3: 検出を実装する

検出は CLI にプロジェクトに存在するフレームワークを伝える:

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

検出戦略:

  • ディレクトリの存在: .claude/.cursor/.gemini/
  • 設定ファイル: opencode.json.aider.conf.yml
  • 指示ファイル: AGENTS.mdCONVENTIONS.md
  • グローバルマーカー: ~/.openclaw/~/.hermes/

ユーザーがフレームワークが検出された理由を理解できるよう、検出結果に必ずマーカーを返す。

期待結果: フレームワークを偽陽性なく確実に特定する検出ルール。

失敗時: フレームワークに固有マーカーがない(汎用ディレクトリ名)場合、複数マーカーの組み合わせを使うか、明示的な --framework 指定を要求する。

ステップ4: 冪等性を伴うインストールを実装する

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

冪等性ルール:

  • ターゲットが存在し --force が設定されていなければ スキップ
  • --force が設定されていれば 上書き(先に削除してからインストール)
  • Dry-run は常に action: 'created' で成功する
  • 戻り値 は常に { action, path, details? } でなければならない

期待結果: インストールがターゲットパスにコンテンツを作成し、既に存在すればスキップし、--force--dry-run を尊重する。

失敗時: Windows/NTFS で symlink 作成が失敗する場合、ディレクトリジャンクションまたはコピーへフォールバックする。フォールバックをログに記録する。

ステップ5: クリーンアップを伴うアンインストールを実装する

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

クリーンアップの考慮事項:

  • プラグインがインストールしたものだけを削除する — ユーザー作成ファイルは決して削除しない
  • append-to-file の場合: ファイル全体ではなくマーカー区間だけを削除する
  • 親ディレクトリは残す(他プラグインが使うかもしれない)

期待結果: アンインストールがプラグインのコンテンツのみを削除し、それ以外は何も削除しない。

失敗時: 削除が失敗(権限、ロックファイル)する場合、例外を投げるのではなくエラー結果を返す。

ステップ6: 一覧表示と監査を実装する

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

期待結果: 一覧は壊れたリンク検出を伴ってインストール済みアイテムをすべて返す。監査は健全性を要約する。

失敗時: ターゲットディレクトリが存在しない場合、空の結果を返す(エラーではない — フレームワークに何もインストールされていないだけ)。

ステップ7: プラグインを登録する

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

登録によりアダプタが利用可能になる:

  • 自動検出(detectFrameworks()getAdaptersForDetections()
  • 明示的選択(--framework my-framework
  • 一覧(listAdapters()

期待結果: アダプタが tool detect 出力に現れ、--framework でターゲット指定できる。

失敗時: アダプタが現れない場合、static id が検出ルールの id と一致し、register() が呼ばれていることを確認する。

ステップ8: テストを書く

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

最低限テストする内容: dry-run のパス、検出の存在、コンテンツタイプサポート。

期待結果: アダプタ固有のテストがインストールパスと挙動を確認する。

失敗時: CI でフレームワークが検出されない(マーカーディレクトリがない)場合、テストで --framework を明示的に使う。

バリデーション

  • プラグインが基底クラスを正しく拡張している
  • 静的フィールド(iddisplayNamestrategycontentTypes)が設定されている
  • 検出ルールが偽陽性なくフレームワークを特定する
  • install() が冪等である(存在すればスキップ、--force を尊重)
  • uninstall() がプラグインが作成したコンテンツのみを削除する
  • listInstalled() が壊れた symlink を検出する
  • audit() が健全性を正確に報告する
  • プラグインが登録され tool detect に現れる
  • dry-run テストが通る

よくある落とし穴

  • 相対 vs 絶対 symlink を忘れる: プロジェクトスコープの symlink は相対(移植可能)にすべき。グローバルスコープの symlink は絶対(cwd に依存しない)にすべき。
  • 欠落親ディレクトリを扱わない: コンテンツ作成前に常に mkdirSync(dir, { recursive: true })
  • マーカーなしの append-to-file: 冪等マーカー(<!-- start:id --> / <!-- end:id -->)なしでは、繰り返しインストールでコンテンツが重複する。追記コンテンツは常に囲む。
  • 検出の偽陽性: 汎用ディレクトリ名(例: .config/)は複数フレームワークに一致しうる。ディレクトリ内の特定ファイルマーカーを使う。
  • supports() チェックを忘れる: インストーラはディスパッチ前に supports(item.type) を呼ぶ。contentTypes が誤っているとアダプタは静かにアイテムをスキップする。

関連スキル

  • scaffold-cli-command — このプラグインを使う CLI コマンドを構築する
  • test-cli-application — アダプタテストを含む CLI ツールのテストパターン
  • design-cli-output — install/uninstall 結果のターミナル出力

GitHub 仓库

pjt222/agent-almanac
路径: i18n/ja/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是理想选择。

查看技能