skills.sh は、Agent Skills(SKILL.md 1枚で AI エージェントに手順を渡す仕組み)を配布するディレクトリと、その CLI である。Vercel Labs の vercel-labs/skills(★30,027・MIT)が実体で、npx skills@latest add <owner>/<repo> の1コマンドで、Claude Code 以外のエージェントにもスキルを入れられるのが売りだ。ただ「入れられる」と書かれているだけでは、自分の環境のどこに何が置かれるのかが分からない。実際に走らせて、ディスクの差分を取った。

npx skills addを4通りの引数で実行し、symlinkモードではClaude Code以外にディレクトリが作られないことを示す実測ログ
実測。エージェントを1つだけ指定すると `.windsurf/skills/` へ直接コピーされるが、2つ指定すると既定が symlink モードに切り替わり、**Claude Code にしかリンクが張られず `.windsurf/` は作られない**。`--copy` を付ければ両方に入る(skills 1.5.23・macOS・2026-08-31)

30秒でわかる
npx skills add の書き込み先は指定したエージェントの数で切り替わる。1つなら直接コピー、2つ以上なら .agents/skills/ + symlink
・「77エージェント対応」の内訳は共通ディレクトリ組19・独自ディレクトリ組58。同じ扱いではない
既定の symlink モードでリンクが張られたのは Claude Code だけ。Windsurf・Roo・Goose は終了コード0で「Installation complete」と出るのに自分のディレクトリが無い(6通りで再現)。--copy で回避できる
・同じ mattpocock/skills でも CLI は37本を列挙し、Claude Code プラグインは25本しか入れない
・依存は taryaml の2つだけ。インストール時に Socket・Snyk のスキャン結果が表示される

Claude Code 側の導入・運用の全体像は Claude Code|2026年版・インストールからCLAUDE.md・Hooks・本番運用までの実装手引き にまとめてある。本記事はその周辺にあるスキルの配り方だけを扱う。

skills.sh とは——Claude Code の外へスキルを配るための CLI

Agent Skills はもともと Claude Code の機構として広まったが、SKILL.md 自体はただの Markdown なので、他のエージェントでも読める。問題は置き場所がエージェントごとに違うことだ。Claude Code は .claude/skills/、Windsurf は .windsurf/skills/、Codex や Cursor は .agents/skills/ を見る。この差を吸収するのが skills.sh の CLI である。

# リポジトリにあるスキルを一覧するだけ(インストールしない)
npx skills@latest add mattpocock/skills -l

# スキルとエージェントを指定して入れる
npx skills@latest add mattpocock/skills -s grilling -a claude-code -y

実測環境は macOS(Darwin 23.5.0・arm64)、Node v22.13.1、[email protected]v1.5.23・2026-08-18公開、コミット 435076e7)。題材には、keitakn 氏が AIに丸投げしないで理解するためのAI開発手法(2026年8月現在)(Zenn)で「Codex など他のエージェントで使う場合の導入ルート」として挙げている mattpocock/skills を使った。npm 上のパッケージは展開時 548,883バイト・19ファイルで、依存は taryaml の2つだけだった。

engines は満たしていなくても動く
package.jsonenginesnode >=22.20.0 を要求している。手元の Node は v22.13.1 で npm warn EBADENGINE が出たが、実行そのものは通り、インストールも完了した。npm の engines は既定では警告止まりで実行を止めない。「Node 22.20 以上が必須」と読むと、動く環境で無用にアップグレードすることになる。

スキル名の指定はカンマ区切りが効かない

最初につまずいたのはここだ。--skill grill-me,grilling のようにカンマで並べると、37スキルを認識したうえで No matching skills found for: grill-me,grilling と言われて終了コード1で落ちる。文字列全体を1つのスキル名として扱っているためだ。

# NG: カンマ区切りは1つの名前として扱われる(exit 1)
npx skills@latest add mattpocock/skills --skill grill-me,grilling -a claude-code -y

# OK: -s を繰り返す
npx skills@latest add mattpocock/skills -s grill-me -s grilling -a claude-code -y

同じく --agent claude も通らない。正しい識別子は claude-code で、間違えると有効なエージェント名77件が全部表示される(これはこれで一覧を得る手段として使える)。

npx skills add がディスクに何を書くか

ここが本題である。空の git リポジトリを用意し、インストール前後で find の差分を取った。

エージェントを1つだけ指定した場合、そのエージェントのディレクトリへ直接コピーされる。

.windsurf/skills/grilling/SKILL.md
.windsurf/skills/grilling/agents/openai.yaml
skills-lock.json

2つ以上指定した場合、書き込み方が変わる。

.agents/skills/grilling/SKILL.md          ← 実体はここ1つだけ
.agents/skills/grilling/agents/openai.yaml
.claude/skills/grilling -> ../../.agents/skills/grilling   ← symlink
skills-lock.json
エージェント1つ指定と2つ以上指定でファイル配置が変わる比較
同じコマンド形でも、指定するエージェント数で書き込み方が切り替わる

実体を1か所に集めて symlink で配るのは理にかなっている。npx skills update で更新したときに、全エージェントへ同時に反映されるからだ。逆に単一エージェント指定では実体が直接そのディレクトリに入るので、後からエージェントを足すと配置が食い違う可能性がある。最初から入れる予定のエージェントをまとめて指定しておくほうがよい。

skills-lock.json には SKILL.md のハッシュが入る

プロジェクト直下に作られる skills-lock.json は、スキルごとに sourcemattpocock/skills)・sourceTypegithub)・skillPath(リポジトリ内の元のパス)・computedHash(SHA-256)を記録する。experimental_install はこれを見て復元するので、チームで揃えたいならコミット対象にするのが筋だ。逆に言うと、ここに入るのはスキル本体ではなく参照とハッシュなので、.agents/skills/ 側を .gitignore すると復元コマンド頼みになる。

agents/openai.yaml が一緒に入る

コピーされるのは SKILL.md だけではない。mattpocock/skills の各スキルには agents/openai.yaml が同梱されており、これも一緒に入る。中身は Codex 系向けの表示名と、起動ポリシーだ。

interface:
  display_name: "Grill Me"
  short_description: "Sharpen a plan through interview"
policy:
  allow_implicit_invocation: false

この allow_implicit_invocation: false は、Claude Code 側の SKILL.md frontmatter にある disable-model-invocation: true同じことを別の語彙で言っているSKILL.md の frontmatter に何を書けるかは SKILL.mdのdescriptionは1024字上限|Claude Skill命名規則と公式19本の検査結果 で公式スキルを検査したときにまとめたが、そこに書ける項目と openai.yaml に書ける項目は一致しない。ランタイムごとに読む場所が違うので、両方書いておく必要があるわけだ。37スキルすべてに openai.yaml があり、両方の宣言を突き合わせると22対22で完全に一致していた(不一致0件)。二重管理は破綻しやすい構造だが、このリポジトリでは今のところ揃っている。

disable-model-invocation を持つスキルが実際にどう扱われるかは grill-me は自動発火しない設計だった|mattpocock/skills 37本の起動条件を実測 で実測した。インストーラが正しく置いても、宣言があるスキルは AI 側からは呼べない。 入れたのに動かないときは、まずこの宣言を疑うほうが早い。

skills.sh が言う「77エージェント対応」の実体——19対58

CLI が受け付けるエージェント識別子は77個ある。ただし全部が同じ扱いではない。配布物 dist/cli.mjs の中にあるエージェント登録簿を読むと、各エージェントは skillsDir(プロジェクト内の置き場所)を持っており、その値で2グループに割れる。

77エージェントのうち19が共通ディレクトリ、58が独自ディレクトリ
`skillsDir` の集計。共通ディレクトリ組19に対し、独自ディレクトリ組が58
グループ skillsDir
共通ディレクトリ組 19 .agents/skills Codex・Cursor・Zed・Warp・GitHub Copilot・Gemini CLI・Cline・Amp・OpenCode ほか
独自ディレクトリ組 58 各自 Claude Code(.claude/skills)・Windsurf(.windsurf/skills)・Roo(.roo/skills)・Goose(.goose/skills)ほか

共通ディレクトリ組は .agents/skills/ にファイルを1つ置けば全員が読む。だから複数指定したときのインストール結果に「universal: Cursor, Amp, Antigravity, … +12 more」とまとめて表示される。独自ディレクトリ組はそれぞれに配る必要があり、そこで symlink が使われる。

独自ディレクトリ組にリンクが張られない——Claude Code 以外は無言で落ちる

この配り分けに穴がある。symlink モードで実際にリンクが張られたのは Claude Code だけで、他の独自ディレクトリ組は自分のディレクトリが作られないまま成功と表示された。

symlinkモードで独自ディレクトリ組にリンクが張られない6通りの再現結果
6通りの組み合わせで確認。`.claude/` だけがリンクを受け取り、Windsurf・Roo・Goose は作られない
指定 作られたディレクトリ モード 終了コード
-a windsurf .windsurf/skills/ copy 0
-a claude-code -a windsurf .agents/.claude/(symlink)。.windsurf/ 無し symlink 0
-a codex -a windsurf .agents/ のみ。.windsurf/ 無し symlink 0
-a windsurf -a roo .agents/ のみ。両方とも無し symlink 0
-a windsurf -a cursor .agents/ のみ。.windsurf/ 無し symlink 0
-a claude-code -a goose .agents/.claude/(symlink)。.goose/ 無し symlink 0
-a claude-code -a windsurf --copy .claude/.windsurf/ ✅ 両方に実体 copy 0

いずれも Installation complete と表示され、終了コードは0である。Windsurf 固有の問題ではない——roo.roo/skills)でも goose.goose/skills)でも同じことが起きる。逆に Claude Code は常にリンクを受け取る。

なぜそうなるか——installMode の分岐

配布物 dist/cli.mjs を読むと、書き込み方は次の3行で決まっている。

installMode の既定は symlink--copy を渡したときだけ copy
・指定エージェントの skillsDir1種類しかない場合は copy に落ちる(単一エージェント指定が成功するのはこれ)
skillsDir が2種類以上あり、かつ対話モードなら「Symlink / Copy」を選ばせる。-y を付けるとこの選択が飛んで symlink のまま進む

つまり症状が出る条件は「-y(または非対話実行)+ skillsDir が異なる2つ以上のエージェント」である。CI やエージェント経由の実行はまさにこの条件を踏む。対話で実行して Copy を選べば起きない。

回避策は --copy か、エージェントごとに分けて実行するか

# A) --copy を付ける(各エージェントに独立した実体が入る)
npx skills@latest add mattpocock/skills -s grilling -a claude-code -a windsurf --copy -y

# B) skillsDir が1種類になるように分けて実行する
npx skills@latest add mattpocock/skills -s grilling -a claude-code -a codex -y
npx skills@latest add mattpocock/skills -s grilling -a windsurf -y

A は実体が複数になるので npx skills update を各所へ効かせたいなら B のほうが素直だ。いずれにせよ入れたあとに ls で対象ディレクトリの実在を確認するのが確実である。

この結果の範囲
確認したのは skills 1.5.23・macOS・プロジェクトスコープ(-g なし)で、独自ディレクトリ組のうち実際に試したのは Claude Code・Windsurf・Roo・Goose の4つである。残り54エージェントで同じかは未検証。 症状の切り分けにあたっては、CLI をローカルに展開して HOME を差し替えた環境でも再現することを確認した(ホームディレクトリに各エージェントの痕跡があるかどうかは結果を変えなかった)。グローバルスコープ(-g)での挙動も未検証である。

分岐を1枚にすると

flowchart TD S["npx skills add <repo>"] --> R["リポジトリを clone
SKILL.md を列挙"] R --> N{"指定エージェントの
skillsDir は何種類"} N -->|"1種類"| C["copy モードに落ちる
各ディレクトリへ実体をコピー"] N -->|"2種類以上"| Y{"-y か(非対話か)"} Y -->|"対話"| P["Symlink / Copy を選ばせる"] Y -->|"-y あり"| M["選択を飛ばして symlink モード"] M --> U[".agents/skills/ に実体を1つ"] U --> L["Claude Code にだけ symlink
(他の独自ディレクトリ組は作られない)"] P --> C C --> K["skills-lock.json を書く"] L --> K

図の右下が今回いちばん効く分岐だ。-y を付けた瞬間にモード選択が飛び、Claude Code 以外の独自ディレクトリ組が落ちる。 単一エージェント指定が成功するのは、skillsDir が1種類しかないので左の copy へ落ちるからである。

単一指定と複数指定を混ぜると配置が二重になる

配置が2通りあるということは、順番を変えると状態が食い違う。先に -a claude-code だけで入れると .claude/skills/grilling/実体が入る。その後に -a claude-code -a codex で入れ直すと .agents/skills/ 側にも実体ができ、.claude/skills/ は symlink に置き換わる。

# 実体(REAL)か symlink(LINK)かを見る。存在しないディレクトリのエラーは捨てる
find .agents/skills .claude/skills -maxdepth 1 -mindepth 1 2>/dev/null \
  -exec sh -c 'test -L "$1" && echo "LINK $1" || echo "REAL $1"' _ {} \;

# どのスキルがどのエージェントに入っているかを CLI 側から見る
npx skills@latest list

運用としては、入れるエージェントの集合を最初に決めて、常に同じ -a の組み合わせで実行するのがいちばん事故が少ない。

Claude Code プラグインとの使い分け

Claude Code だけを使うなら、claude plugins install という別ルートもある。mattpocock/skills を例に両者を比べると、思想だけでなく取れる本数が違う。

skills.sh経由37本とプラグイン25本の差
同じリポジトリでも、経路によって手に入る本数が違う
  npx skills add(skills.sh) claude plugins install(プラグイン)
対象エージェント 77識別子 Claude Code のみ
取れる本数(mattpocock/skills) 37(CLI が Found 37 skills と表示) 25plugin.json v1.2.3 の配列)
入らないもの in-progress 8本・misc 4本
ファイルの所有 自分のリポジトリの普通のファイル 管理された読み取り専用の束
更新 npx skills update(手動) 自動
編集 できる できない

in-progress のスキル(loop-meretrowriting-shape など8本)を試したい場合、プラグイン経由では入手できない。逆に「本体が更新されたら勝手に追随してほしい」ならプラグインのほうが手間が少ない。README は両方入れるとスキルが二重になるので片方だけにせよと明記している。

インストール時にセキュリティスキャンの結果が出る

add を実行すると、インストール直前に「Security Risk Assessments」というパネルが表示される。実測では grilling に対して Gen: Safe / Socket: 0 alerts / Snyk: Low Risk の3系統が並んだ。スキルは SKILL.md という自然文でエージェントの挙動を書き換えるものなので、内容の安全性は本来レビューが要る領域だ。CLI の最終行も Review skills before use; they run with full agent permissions.(フルの権限で走るので使う前に中身を見よ)で締めている。

スキャン結果は「読まなくていい」という意味ではない
3系統ともパッケージ/依存の既知脆弱性を見る種類のスキャナで、SKILL.md の指示内容そのものが妥当かは判定していない。実際 git-guardrails-claude-code のようにフック設定を書き換えるスキルもある。Low Risk が出ても、入れる前に SKILL.md は自分で読む前提を崩さないほうがよい。

導入手順——最短ルートと確認まで

実測にもとづく手順をまとめる。前提は Node(engines は22.20以上を要求するが、それ未満でも警告のみで動いた)。

# 1) 何が入っているかだけ見る(インストールしない)
npx skills@latest add mattpocock/skills -l

# 2) 入れるスキルとエージェントを決めて実行(-s と -a は繰り返す。カンマ区切りは不可)
npx skills@latest add mattpocock/skills \
  -s grilling -s domain-modeling -s grill-with-docs \
  -a claude-code -a codex -y

# 3) どこに置かれたかを自分で確認する(CLIの成功表示だけを信じない)
ls -la .claude/skills .agents/skills 2>/dev/null

# 4) 入れたスキルが本当に呼ばれるかを見る
claude -p "/grill-with-docs この計画の要件定義をお願いします" \
  --output-format stream-json --verbose | grep '"name":"Skill"'

手順4まで実際に走らせたところ、.claude/skills/ に入れた4スキルに対して Skill の呼び出しが grillingdomain-modeling の2件出た。インストール経路として問題なく機能している。 4スキルぶんのディスク占有は10ファイル・約40KBだった。

その他の指定オプションでは、-g(グローバル・ユーザーレベル)、--copy(symlink でなくコピーを強制)、--all-s '*' -a '*' -y の短縮)、use(インストールせずにプロンプトだけ生成)が使える。--all は37本すべてを入れるので、grill-me は自動発火しない設計だった|mattpocock/skills 37本の起動条件を実測 で測った常駐1,428トークン(cl100k_base/tiktoken で計測。Anthropic の count_tokens では1,576)を丸ごと払うことになる。なおこの値は37本の namedescription を連結して数えた再構成値であって、Claude Code が実際に注入する文字列そのものではない。必要な数本を -s で選ぶほうが安い。

入れたあとの棚卸し——listremove の癖

導入して数週間経つと「どれをどこに入れたか」が分からなくなる。npx skills@latest list(別名 ls)が入っているスキルを列挙し、remove が外す。remove にも -g(グローバル)/-a(特定エージェントのみ)/-s(特定スキルのみ)があり、-a を省くと全エージェントのリンクを掃除する。v1.5.23 のリリースノートには refuse remove --all when specific skills are named(スキル名を指定しているのに --all を渡したら拒否する)という修正が入っており、この辺りは実際に事故が起きた形跡がある。

更新は npx skills update-g でグローバルのみ、-p でプロジェクトのみに絞れる。プラグイン経路と違って自動では追随しないので、上流が変わったことに気づく仕組みは自分で用意する必要がある。skills-lock.jsoncomputedHash が入っているのは、この差分検出のためだ。

--all を安易に使わない

--all-s '*' -a '*' -y の短縮で、リポジトリの全スキルを全エージェントに入れる。37本のスキル集に対してこれを実行すると、選ぶ手間は消えるがスキル一覧の常駐コストを全部払うことになる。しかも v1.5.23 の修正履歴には exclude internal skills from --skill '*' wildcard(ワイルドカードから内部スキルを除外する)という項目があり、ワイルドカードの対象範囲は版によって変わっている

必要なスキルを -s で名指しするほうが、常駐コストの見積もりも、あとの棚卸しも楽になる。スキルは入れた本数だけ文脈を食い、しかも入れても自動では呼ばれないものが混じる。「とりあえず全部入れる」がいちばん割に合わない。

まとめ

skills.sh は「Claude Code 以外にもスキルを配る」という目的をきちんと果たしている。ただし挙動には測ってみないと分からない癖がある。

  1. 書き込み先は指定エージェント数で切り替わる。1つなら直接コピー、2つ以上なら .agents/skills/ + symlink
  2. 「77エージェント対応」の内訳は共通ディレクトリ組19・独自ディレクトリ組58
  3. 既定の symlink モードでリンクが張られるのは Claude Code だけ。Windsurf・Roo・Goose は終了コード0・Installation complete 表示のまま自分のディレクトリが作られない(6通りで再現)。条件は「-yskillsDir が異なる2つ以上のエージェント」で、--copy を付けるか分けて実行すれば回避できる
  4. -s -aカンマ区切りが効かない。繰り返し指定する。claude は無効で claude-code が正しい
  5. 同じリポジトリでも skills.sh は37本、Claude Code プラグインは25本
  6. SKILL.md と一緒に agents/openai.yaml も入る。Claude Code はこれを読まないが、Codex 系はここで起動ポリシーを見る

共通しているのは、CLI の成功表示を最終確認にしないほうがいいという一点だ。ls で置かれた実体を見て、stream-json で実際に呼ばれるところまで確かめれば、「入れたのに効かない」の原因がインストーラ側なのかスキル側の宣言なのかを切り分けられる。

参照ソース

vercel-labs/skills — 公式リポジトリ。★30,027・MIT・npx skills の実装(2026-08-31 取得)
skills.sh — Agent Skills のディレクトリ本体。リポジトリごとのスキル一覧とスキャン結果を掲載している
npm: [email protected] — 本記事の実測に使った版。展開548,883バイト・19ファイル・依存は taryaml
mattpocock/skills — 実測の題材に使ったスキル集。README が2つの導入ルートの思想差を説明している
keitakn「AIに丸投げしないで理解するためのAI開発手法(2026年8月現在)」 — Zenn。skills.sh を「Codex など他のエージェントで使う場合」の導入ルートとして紹介している。本記事はそのルートが実際にディスクへ何を書くかを独立に測ったもので、ワークフローそのものの解説は原文をあたってほしい