車輪の再発明?上等だよ!Agent Skills同期CLIを作った
はじめに
AIエージェントを複数使い始めると、最初に壊れるのはコードではなく、Skills管理でした。
最近、Claude Code、Gemini、Pi、OpenCodeなど、ターミナルで動くAIコーディングエージェントを複数使うようになりました。
どれも強力です。
強力なんですが、使えば使うほど自分のホームディレクトリがよくわからないことになっていきます。
「このSkillはClaude Codeに入れたっけ?」
「Piにも同じものを置いたっけ?」
「MacBook側は更新したっけ?」
「そもそも最新版どれだっけ?」
最初は「まあ手でコピーすればええか...」と思っていました。
この判断がだいたい間違いです。
小さい不便は、最初は我慢できます。
そして、だいたい後でちゃんと破綻します。
自宅のMacには入っているSkillが、持ち出し用のMacBookには入っていない。
Claude Codeには入れたけど、Piには入れていない。
Gemini用にコピーしたけど、どれが最新版かわからない。
GitHubから取ってきたSkillもあれば、自分で書いたローカルSkillもある。
気づくと、Agent Skillsの管理がただのコピペ運用になっていました。
これはかなり嫌でした。
Agent Skillsは、エージェントごとに期待するディレクトリが違うし、スキル本体の置き場所も揃っていない。さらに、AIエージェントの進化が速すぎて、運用もすぐ変わります。
「これ、ちゃんと同期する小さいCLIが欲しいなぁ」と思いました。
そこで作ったのが sksync です。
sksyncとは
sksync は、Agent Skillsを複数のAIエージェント間で同期するためのCLIツールです。
GitHubやローカルディレクトリから取得したSkillを .sksync/skills/ に保存し、Claude Code / Gemini / Pi など各エージェントが期待するディレクトリへsymlinkします。
できること
sksync では、だいたい次のようなことができます。
- GitHub / skills.sh / ローカルディレクトリからSkillを追加
- 複数エージェントへ同時に展開
- lockfileによる再現性の担保
-
outdatedによる更新確認 -
removeによる安全な削除 - BundleによるSkillセット共有
例えば、GitHub上のSkillを追加して、PiとClaude Codeに展開するならこうです。
sksync add owner/repo/path/to/skill --agent pi --agent claude-code
skills.sh のURLから追加することもできます。
sksync add https://www.skills.sh/vercel-labs/skills/find-skills --agent universal
追加したSkillは .sksync/skills/ に保存され、各エージェントのSkillsディレクトリにはsymlinkとして配置されます。
つまり、エージェント側から見ると普通のファイルとして読めます。
ここはかなり大事にしました。
MCPサーバーのような中間レイヤーを挟むのではなく、各エージェントが期待する場所に、期待する形でSkillが存在する。
この「ネイティブなファイルとして置く」という素朴さが、自分の欲しかった手触りでした。
技術スタック
sksync は Rust 製のCLIツールです。
大きくは、以下のような超シンプルな構成で作っています。
- Rust: CLI本体
- GitHub Actions: リリースビルドと配布
Rustを選んだ理由は、単一バイナリとして配布しやすく、ファイル操作を安全に書きやすいからです。
sksync 自体は大きな常駐プロセスを持たず、GitHubやローカルからSkillを取得して、必要な場所に配置するシンプルなCLIとして作っています。
なぜ自作したか
もちろん、いきなり「世界にまだ存在しない革新的なツールを作るぞ!」となったわけではありません。
というか、似たようなツールやアプローチがあることは普通に知っていました。
GitHub CLIのgh skillコマンドを使う方法もあります。
skills.shやSkillKitのように、より本格的なパッケージマネージャやトランスパイラとして扱うツールもあります。
なので、冷静に見るとかなり車輪の再発明です。
しかも開発中に何度か思いました。
「これ、既存ツールをちゃんと使えば済むのでは?」
「また自分専用CLIを増やしているだけでは?」
「未来の自分、本当にこれメンテするの?」
全部その通りです。
ただ、それでも作りました。
理由は、欲しかった "手触り" が少し違ったからです(ここ大事!)
自分が欲しかったのは、もっと小さいものでした。
- 複数のAIエージェントに展開できる
- エージェントからは普通のファイルとして見える
- GitHubやローカルのSkillを持ってこられる
- lockfileで別PCでも再現できる
- でも、勝手にファイルを壊さない
- 常駐プロセスや重い抽象化は要らない
つまり、Agent Skills用の軽量な同期エンジンが欲しかった。
既存ツールが悪いという話ではありません。
むしろ、既存ツールがあったからこそ「自分が欲しいものとの差分」が見えました。
そして、その差分が自分には地味に大きかった。
昔ならここで我慢していたと思います。
でも今は、AIと壁打ちしながら、自分の運用にぴったり合う小さなツールを作れる時代です。
なので、素直に車輪を再発明することにしました。
車輪の再発明、上等です。
ただし、せめて安全な車輪にしたい。
特に sksync はファイルやsymlinkを実際に触るCLIです。
こういうツールで一番怖いのは、便利機能が足りないことではありません。
意図しないファイルを消すことです。
基本設計
sksync の基本設計はシンプルです。
.sksync/skills/
├── review/
│ └── SKILL.md
└── research/
└── SKILL.md
~/.claude/skills/
└── review -> path/to/.sksync/skills/review
~/.agents/skills/
└── review -> path/to/.sksync/skills/review
Skill本体は .sksync/skills/ に置きます。
各エージェントのSkillsディレクトリには、そこへのsymlinkを張ります。
この設計にすると、Skill本体の管理場所は一箇所になります。
一方で、Claude CodeやPiなどのエージェントからは、それぞれの標準ディレクトリにSkillが存在するように見えます。
また、依存関係や取得元は設定ファイルに書き、実際に解決されたバージョンはlockfileに記録します。
これにより、別のPCでも同じ状態を再現しやすくなります。
sksync install
lockfileがある場合、install はその情報を優先して使います。
「今そのGitHubリポジトリのHEADが何か」ではなく、「前回解決したもの」を基準にできるのが重要です。
AIエージェントの設定は、ちょっとした差分でも体験が変わります。
だからこそ、再現性はかなり大事です。
addよりremoveが怖い
CLIを作っていて一番考えたのは、実は add ではなく remove でした。
add は失敗しても、基本的には「追加できなかった」で済みます。
もちろん途中まで作ったファイルのrollbackは必要ですが、まだ被害は限定的です。
一方で remove は違います。
間違って消したら終わりです。
特に sksync のように、ユーザーのホームディレクトリやプロジェクトディレクトリ配下にsymlinkを張るツールでは、削除のルールをかなり慎重に決める必要があります。
sksync remove は、次のような考え方にしています。
sksync remove skill-name
このとき、何でも消していいわけではありません。
基本ルールはこれです。
- 管理下のsymlinkだけを消す
- 通常ファイルは勝手に消さない
- Skill本体を消す場合も、設定された
skillDir配下だけを対象にする - 期待した状態と違う場合は、安全側に倒して失敗する
たとえば、エージェントのSkillsディレクトリに同名の通常ファイルがあったとしても、それがsksync管理のsymlinkでないなら消しません。
もし同名ファイルを雑に消す実装にしていたら、ユーザーが手で置いたSkillまで消してしまいます。
「同じ名前だから消していい」は危険です。
同じ名前でも、それは自分が作ったsymlinkではないかもしれません。
これは少し不便に見えるかもしれません。
でも、ファイルを触るCLIでは「便利さ」より「消さないこと」のほうが大事です。
自分が管理していないものは消さない。
これは sksync の設計でかなり強く意識したルールです。
outdatedで先に見る
もう一つ、作ってよかったコマンドが outdated です。
sksync outdated
これは、lockfileに記録されているGitのcommitと、リモート側のrefを比較して、更新できるSkillがあるかを確認するコマンドです。
ポイントは、いきなり更新しないことです。
更新は便利ですが、AIエージェントのSkillは挙動に直結します。
レビュー用Skillの文言が変わるだけで、エージェントの出力が変わることもあります。
なので、まずは見る。
sksync outdated
更新できるものを確認してから、
sksync update
で実際に更新する。
この分離は、CLIの手触りとしてかなり大事でした。
パッケージマネージャでも outdated があると安心します。
それと同じで、Agent Skillsでも「今どれが古いのか」を先に知りたい。
特に複数PCで運用していると、「このMacだけ古いSkillを使っていた」みたいなことが起きます。
outdated はそのズレに気づくためのコマンドです。
安全性のルール
sksync を作っていて、ファイルを触るCLIにはいくつかのルールが必要だと感じました。
まとめると、こんな感じです。
上書きしない
既存の通常ファイルがあるなら、勝手に上書きしない。
これは基本です。
便利そうに見えても、ユーザーが手で置いたファイルをCLIが壊すのは最悪です。
管理していないものは消さない
remove で消す対象は、基本的にsksyncが管理しているものだけです。
「同じ名前だから消していい」は危険です。
同名でも、ユーザーが手で置いたものかもしれません。
lockfileがあるならそれを信じる
install では、lockfileがある場合は解決済みのsourceを優先します。
毎回最新を取りに行くと、別PCで微妙に違う状態になってしまいます。
再現性を持たせるには、lockfileを中心に考える必要があります。
変更前に見せる
破壊的な操作や複数ファイルを触る操作では、dry-runやplanが重要です。
sksync plan --dry-run
「何が起きるか」を先に見せる。
これは地味ですが、CLIの安心感に直結します。
失敗時は安全側に倒す
衝突、drift、missing sourceなど、期待した状態と違うときは無理に進めません。
- エラーにする
- ユーザーに判断してもらう
ツールを作るときの基本ですが、この「止まる勇気」は、同期ツールではかなり大事だと思っています。
bundleで共有する
個人利用だけなら、add と install だけでもかなり使えます。
ただ、Agent Skillsはチームで共有したくなる場面もあります。
例えば、
- レビュー用Skillセット
- フロントエンド開発用Skillセット
- プロジェクト固有の調査Skillセット
- 新メンバーに配りたい標準Skillセット
こういうものを、URLを一つずつ共有して各自に入れてもらうのは面倒です。
そこで sksync には bundle という仕組みを入れました。
sksync bundle inspect ./bundles/review-workflow
sksync bundle add ./bundles/review-workflow --agent pi --agent claude-code
bundleは、複数のSkillをまとめた「インストールセット」です。
ここで意識したのは、bundleをランタイム上の特別な存在にしないことです。
bundleはあくまで「まとめて追加するための単位」です。
一度追加された後は、通常のSkill依存関係として扱います。
この設計にした理由は、責務を増やしすぎたくなかったからです。
bundleをランタイム上の概念にすると、「bundleに属しているSkillとは何か」「bundleを更新したら個別Skillはどうなるのか」など、考えることが一気に増えます。
最初のバージョンでは、もっと素朴にしました。
bundleは配るためのもの。
インストール後は通常のSkillとして扱う。
このくらいの割り切りが、CLIとして扱いやすいと感じています。
作ってわかったこと
今回 sksync を作ってみて、一番感じたのは「AI時代の車輪の再発明」はかなり意味が変わったということです。
昔は、自分専用のCLIを作るのはそれなりに重い作業でした。
設計して、実装して、テストして、ドキュメントを書いて、配布方法を整える。
小さい不満を解消するには、ちょっとコストが高すぎることが多かった。
でも今は、AIと相談しながらかなり速く作れます。
もちろん、AIに丸投げすれば安全なツールができるわけではありません。
特にファイル操作や削除を含むCLIでは、人間が設計方針を決める必要があります。
どこまで自動化するか?
どこで止めるか?
何を絶対に消さないか?
どの状態を信頼するか?
このあたりは、むしろAI時代だからこそ人間がちゃんと決めるべき部分だと思います。
正直に言うと、sksync は「絶対に世界中の人が必要としているツール」というより、自分の生活の中にあった小さい不満から生まれたツールです。
でも、毎日触る道具の小さい違和感は、積み重なるとかなり大きいです。
そして今は、その違和感を潰すための自作ツールを、かなり現実的なコストで作れるようになりました。
...と、ここまでそれっぽく書きましたが、要するに「Skillsを手でコピーするのが嫌だった」だけです。
でも、その程度の不満でもツールにしていい時代になった...
いい時代ですよねぇ(涙)
sksync は、自分の手に馴染む小さな道具が欲しくて作ったものです。
でも作ってみると、Agent Skills管理だけでなく、CLI設計全般について学びがありました。
特に、
- ファイルを触るCLIでは、
addよりremoveの設計が大事 - 更新系のコマンドは、いきなり変えるより先に見せる
- lockfileは「再現性」のためのUX
- symlinkは雑に見えて、ネイティブな体験を保つためには強い
- Bundleのような機能は、責務を広げすぎないことが大事
このあたりは、他のCLIを作るときにも使える考え方だと思います。
まとめ
複数のAIエージェントを使うようになると、Agent Skillsの管理は思った以上に散らかります。
最初はコピペでなんとかなります。
でも、マシンが増え、エージェントが増え、Skillが増えると、すぐに破綻します。
sksync は、そのストレスを解消するために作った小さなCLIです。
- Skill本体は
.sksync/skills/に集約 - 各エージェントにはsymlinkでネイティブに展開
- lockfileで再現性を担保
-
removeは管理下のものだけを安全に削除 -
outdatedで更新前に状態を確認 - bundleでチーム用Skillセットも共有
既存ツールを否定したいわけではありません。
むしろ、既存ツールがあるからこそ、自分の欲しいものとの差分が見えました。
AI時代の車輪の再発明は、単なる自己満足ではなくなりつつあります。
自分の運用に合う小さな道具を、自分で設計して、自分で育てる。
そのハードルがかなり下がりました。
そして、ファイルを触るCLIを作るなら、便利さと同じくらい「壊さないこと」を設計する。
これが、sksync を作って一番よかった学びです。
できたてホヤホヤでまだバグとかあるかもですが、興味があれば、ぜひ触ってみてください。
Discussion