開発全般で使える Claude Mods 集。現在は cache-ttl-compact (離席が続いてプロンプトキャッシュの 1 時間 TTL が失効する前に conversation を自動で compact する) を含む。

自分専用の Claude Code / Codex プラグインマーケットプレイス。
cd suntory-n-water-marketplace
git init
git add .
git commit -m "Initial marketplace setup"
gh repo create suntory-n-water-marketplace --public --source=. --push
Claude Code 内で以下を実行:
/plugin marketplace add Suntory-N-Water/suntory-n-water-marketplace
Claude Code 内で /plugin install を実行(例: ブログ・技術記事の執筆プラグイン):
# ブログ・技術記事の執筆
/plugin install blog-writing@suntory-n-water-marketplace
Codex 用のリポジトリマーケットプレイスを登録し、利用するプラグインをインストールする。
cd suntory-n-water-marketplace
codex plugin marketplace add .
codex plugin add blog-writing@suntory-n-water-marketplace
他のプラグインを利用する場合は、blog-writing を playwright-best-practices または general-dev-skills に置き換える。 インストール後は新しい Codex セッションを開始する。
plugins/
└── my-plugin/
├── .claude-plugin/
│ └── plugin.json # 必須: プラグインメタデータ
├── .codex-plugin/
│ └── plugin.json # 必須: Codex プラグインメタデータ
├── commands/ # スラッシュコマンド (任意)
│ └── my-command.md
├── agents/ # エージェント定義 (任意)
│ └── my-agent.md
├── skills/ # スキル定義 (任意)
│ ├── my-skill/
│ │ └── SKILL.md
│ └── deprecated/ # 使うのをやめたスキルの置き場 (任意)
│ ├── README.md
│ └── old-skill/
├── hooks/ # フック定義 (任意)
│ └── hooks.json
├── .mcp.json # MCP サーバー設定 (任意)
└── README.md
Claude Code 用のマーケットプレイスは .claude-plugin/marketplace.json、Codex 用の リポジトリマーケットプレイスは .agents/plugins/marketplace.json で管理する。 両方の plugin.json から同じ skills/ を参照する。
Claude Code 用と Codex 用の両方の plugin.json を作成する。Codex 用では、同梱する スキルの場所とインストール画面用のメタデータを指定する。
{
"name": "my-plugin",
"description": "プラグインの説明",
"version": "1.0.0",
"author": {
"name": "作者名"
}
}
Codex 用の .codex-plugin/plugin.json には、少なくとも skills と interface を追加する。 skills はディレクトリ指定ではなくスキルごとのパスの配列にする。Codex は skills/ 配下を再帰的に探すため、ディレクトリ指定だと skills/deprecated/ まで読み込まれる。
{
"name": "my-plugin",
"version": "1.0.0",
"description": "プラグインの説明",
"author": { "name": "作者名" },
"skills": ["./skills/my-skill"],
"interface": {
"displayName": "My Plugin",
"shortDescription": "プラグインの説明",
"longDescription": "プラグインの詳細な説明",
"developerName": "作者名",
"category": "Development",
"capabilities": ["Read", "Write"],
"defaultPrompt": ["このプラグインを使って作業して"]
}
}
.claude-plugin/marketplace.json の plugins 配列に追加:
{
"name": "my-plugin",
"description": "プラグインの説明",
"version": "1.0.0",
"author": { "name": "作者名" },
"source": "./plugins/my-plugin",
"category": "development"
}
Codex 用は .agents/plugins/marketplace.json の plugins 配列に、 source.path を ./plugins/my-plugin とするエントリを追加する。
git add .
git commit -m "Add my-plugin"
git push
使うのをやめたスキルは削除せず、plugins/<plugin-name>/skills/deprecated/ へ移す。 記述と履歴を残したまま読み込みだけを止められる。
skills/<skill-name>/ を skills/deprecated/<skill-name>/ へ移す.codex-plugin/plugin.json の skills 配列から該当パスを外すskills/deprecated/README.md にやめた理由と後継を書くbun run version-bump <plugin-name> minor でバージョンを上げるClaude Code は skills/ 直下の 1 階層しか見ないので、移した時点で読み込まれなくなる。 Codex は skills/ 配下を再帰的に探すため、配列から外す手順が要る。
プラグインのバージョンを semver 形式でバンプするスクリプトを用意している。
bun run version-bump <plugin-name> <major|minor|patch>
# パッチ: 1.0.0 -> 1.0.1
bun run version-bump playwright-best-practices patch
# マイナー: 1.0.0 -> 1.1.0
bun run version-bump blog-writing minor
# メジャー: 1.0.0 -> 2.0.0
bun run version-bump general-dev-skills majorhooks/cache-ttl-compact/register.ts 110 lines1import type { On, PluginOptions, Timer } from 'claude-code';
2
3const MINUTE = 60_000;
4
5export function register(on: On, options: PluginOptions): void {
6 const idleMs = Number(options.cacheTtlCompactIdleMinutes ?? 55) * MINUTE;
7 const tickMs = Number(options.cacheTtlCompactTickMinutes ?? 1) * MINUTE;
8
9 let timer: Timer | undefined;
10 let lastTurnEndedAt = 0;
11 let done = false;
12
13 on('session.start', async ($, e, next) => {
14 // $.clock.now() は host を通るので、claude-code/testing の mock.clock で
15 // 差し替えられる。Date.now() だと 1 時間待たないとテストできない。
16 lastTurnEndedAt = await $.clock.now();
17 $.ui.log(
18 `cache-ttl-compact: ${idleMs / MINUTE} 分の無操作で compact します ` +
19 `(確認の間隔は ${tickMs / MINUTE} 分)`,
20 );
21
22 timer?.cancel();
23 timer = $.clock.every(tickMs, async () => {
24 if (done) {
25 return;
26 }
27
28 const elapsed = (await $.clock.now()) - lastTurnEndedAt;
29 if (elapsed < idleMs) {
30 return;
31 }
32
33 done = true;
34 try {
35 const result = await $.session.compact();
36 // skip は boolean ではなく理由の文字列。空文字も string に含まれるため、
37 // if (result.skip) では compact 済みの型まで絞り込めない。
38 if (result.skip !== undefined) {
39 $.ui.log(
40 `cache-ttl-compact: 他の Hook が compact を拒否しました (${result.skip})`,
41 );
42 return;
43 }
44 $.ui.log(
45 `cache-ttl-compact: ${Math.round(elapsed / MINUTE)} 分の離席を検知し、` +
46 `${result.tokensBefore ?? '?'} → ${result.tokensAfter ?? '?'} トークンに compact しました`,
47 );
48 } catch (error) {
49 // $.session.compact() はターンの実行中だと reject される。
50 // タイマーの周期とターンの開始がぶつかった場合なので、次の周期に回す。
51 done = false;
52 $.ui.log(`cache-ttl-compact: compact できませんでした (${error})`, {
53 to: 'debug',
54 });
55 }
56 });
57
58 return next(e);
59 });
60
61 // この mod 以外の compact (/compact や閾値での自動 compact) の後も、
62 // 次のターンが始まるまでは compact 済みの会話なので compact し直さない。
63 on('session.compact', async (_$, e, next) => {
64 const result = await next(e);
65 if (
66 e.agentId === undefined &&
67 e.trigger !== 'precompute' &&
68 result.skip === undefined
69 ) {
70 done = true;
71 }
72 return result;
73 });
74
75 // done を戻すのは turn.complete ではなく turn.start。ターンの途中で
76 // 自動 compact が実行された場合、turn.complete で戻すと compact 済みの印が消える。
77 // turn.start はサブエージェントでは発火しないので agentId を見なくてよい。
78 on('turn.start', (_$, e, next) => {
79 done = false;
80 return next(e);
81 });
82
83 on('turn.complete', async ($, e, next) => {
84 // agentId があるのはサブエージェントのターン。サブエージェントのリクエストは
85 // メインの conversation のキャッシュを読まないので、TTL の起点にならない。
86 if (e.agentId !== undefined) {
87 return next(e);
88 }
89
90 lastTurnEndedAt = await $.clock.now();
91 return next(e);
92 });
93
94 on('session.end', async ($, e, next) => {
95 // /clear・/resume・/branch でも session.end は発火するが、プロセスは終了せず
96 // session.start も再発火しない。ここでタイマーを止めると再開されないので、
97 // 起点だけ新しい会話に合わせ直す。/branch の reason は resume になる。
98 if (e.reason === 'clear' || e.reason === 'resume') {
99 lastTurnEndedAt = await $.clock.now();
100 // /clear 直後の会話は空なので、次のターンが始まるまで compact しない。
101 done = e.reason === 'clear';
102 return next(e);
103 }
104
105 timer?.cancel();
106 timer = undefined;
107 return next(e);
108 });
109}
110