
Claude Code の mods を作ってみる
Introduction
Claude Code には、その見た目や動作を TypeScript で
(ある程度)変更できる「mods」という拡張手法があります。
設定ファイルとスクリプトなどで記述する hooks と違い、
Claude Code の中で動作するコードとして、
プロンプトの送信やツールの呼び出しに割り込んだり、
スラッシュコマンドを足すなどの利用方法があります。
題材として
「プロンプトに添付した画像を、Claude に渡る前に小さくしてトークンを減らす mod」
をつくってみます。
※hooks からは添付画像が参照できず、mods でないと作れない例
題材の mod は、試した画像で、コンテキストの増加が 51〜69% 減りました。
(API の usage 使用)
ちなみに、自分の使い方だと、全体から見た効果は小さめでした。
コードはGitHub に置いてます。
Claude mods?
mods は、Claude Code の見た目と動きを変えられるプラグインです。
中身は JavaScript / TypeScript で書いたイベントハンドラで、
ツールの呼び出し・プロンプトの送信・画面の描画といったイベントが起きると
Claude Code がそれを呼び出します。
ハンドラはイベントの処理内容を変えることもできます。
※Claude Code v2.1.287 以降であれば標準で有効
最小の構成
mod は3つのファイルで作成できます。
my-mod/
├── .claude-plugin/plugin.json … 名前・バージョン・説明
├── hooks/hooks.json … 読み込むモジュール({ "modules": ["./register.ts"] })
└── hooks/register.ts … フックを登録するモジュール
register.ts では、on(イベント, フック) でフックを登録します。
フックは ($, e, next) の3つを受け取ります。
$— Claude Code の機能へアクセスするためのインターフェイスe— イベントの中身next(e)— 下の層(ほかの mod と Claude Code 本体)を呼ぶ関数。eを書き換えて渡せば、その内容で処理が進む
import type { Register } from 'claude-code'
export const register: Register = on => {
// プロンプトが送信されるたびに呼ばれる
on('prompt.submit', async ($, e, next) => {
$.ui.log(e.text.length + ' 文字のプロンプト') // 会話に1行出す(モデルには送られない)
return next(e) // そのまま通す
})
}
mod から外部へアクセスするには $ を通じて行います。
claude plugin validate <mod> を実行すると、
その mod がどのイベントを拾い、$ の何を呼ぶかを読み込む前に一覧できます。
hooks(settings.json)との違い
Claude Code には以前から hooks がありますが、
こちらは「決まったタイミングで外部のコマンドを走らせる設定」で、mods とは別物です。
hooks は、設定ファイルに書いたコマンドが別のプロセスとして動きます。
mods は TypeScript のモジュールで、Claude Code の中で動きます。
そのため mods は、画面に表示を出したり(パネル・帯・通知・会話への1行など)、
スラッシュコマンドを足したりできます。
hooks でもメッセージの表示はできますが、パネルや帯の表示、スラッシュコマンドの追加はできません。
プロンプトの送信時に受け取れるものも違います。
hooks の UserPromptSubmit が受け取る入力には
prompt(テキスト)しかなく(Hooks reference)、添付した画像は見えません。
mods の prompt.submit には e.attachments があり、
$.prompt.media() で画像のバイト列まで読めます。
Environment
- MacBook : macOS 26.5.2
- Claude Code : 2.1.322(model : Claude Fable 5.2. 高解像度の扱いになるモデル)
- ImageMagick 7.1.2-23(余白の切り落としに使用。無い場合 mac 標準の
sipsを使う)
※本記事の内容は2026年11月時点のものです
Image Resizing Mod
画像を最適化
Claude Code にスクショを貼って「このエラーを見て」と頼むことがあるかと思います。
このとき画像が使うトークンは、ファイルの容量ではなくピクセル数で決まります。
トークン数 = ⌈幅 / 28⌉ × ⌈高さ / 28⌉
PNG の圧縮率を高めたり JPEG の品質を下げたりしても、この数は変わりません。
減らせるのはピクセル数だけです。
Claude Code は添付画像を2000px まで縮小してから送りますが、
それでも Retina の画面全体のスクリーンショット(3014×1774px)は 2000×1177px、
約 3,100 トークンになります。
そこで、送信の直前にもう少しピクセル数を減らす、というのが題材の mod です。
方針
やりたいことは「添付された画像を、小さくした画像に差し替える」です。
mods の API によると、現時点ではそのままできないので、
今回は以下のように実装しました。
prompt.submitで添付画像を取り出し、小さくして一時ファイルに保存する- 「画像はこのファイルにあるので Read して」という指示を、プロンプトに添える(
context) session.appendで、会話に入る直前のメッセージから元の画像ブロックを外す
Claude から見ると、画像つきのプロンプトの代わりに「ファイルを読んで」という指示が届き、
Read ツールで小さい画像を読むことになります。(Read 方式)
使う mods の機能
この mod で使った機能の一覧です。以降の節で順に見ていきます。
| 機能 | 何に使ったか |
|---|---|
prompt.submit フック |
送信に割り込み、添付を読んで、Claude 向けの指示(context)を足す。送信の取り消しもできる |
session.append フック |
会話に入る直前のメッセージから、元の画像ブロックを外す |
prompt.mention フック |
@ファイル で読むファイルを、小さくした写しにすり替える |
$.prompt.media |
添付画像のバイト列を読む |
$.process.run |
外部コマンド(ImageMagick など)を実行する |
$.command.register と command.run フック |
スラッシュコマンド /imgdec を足す |
$.store |
設定をセッションをまたいで保存する |
$.ui.log |
会話に結果を1行出す |
$.prompt.fill |
入力欄に文字を書き足す |
mod を作る
プロンプトの送信に割り込む(prompt.submit)
prompt.submit のフックは、プロンプトが送信されるたびに呼ばれます。
e.attachments に添付の一覧が入っていて、next(e) に渡す e を書き換えれば、
その内容で送信が進みます。
ここでは context に Claude 向けの指示を足しています。
//説明用に一部を省いています
on('prompt.submit', async ($, e, next) => {
const images = (e.attachments ?? []).filter(a => a.type === 'image')
// スラッシュコマンドの実行や、画像の無いプロンプトはそのまま通す
if (e.command !== undefined || images.length === 0) {
return next(e)
}
const settings = await readSettings($)
if (!settings.isOn) {
return next(e)
}
const dir = await makeDir($) // 送信ごとの一時フォルダ
const replaced: boolean[] = [] // 画像ごとに「外すか」
const reduced: Reduced[] = []
for (const [index, image] of images.entries()) {
const one = image.id === undefined ? undefined : await reduce($, settings, dir, image.id, index)
replaced.push(one !== undefined)
if (one !== undefined) reduced.push(one)
}
if (reduced.length === 0) {
return next(e) // 小さくできた画像が無ければ、何もしない
}
const note =
`${reduced.length} image(s) the user attached to this prompt were reduced ` +
`and saved as files; the inline copies were removed. ` +
`Read each file to view the image, and do not read the original image file:\n${reduced.map(one => one.path).join('\n')}`
pending.push(replaced) // session.append に渡す
$.ui.log(reduced.map(one => one.summary).join(' / '))
const entered = await next({ ...e, context: [...(e.context ?? []), note] })
// ほかの mod などがプロンプトを止めたら、記録を取り消す(次の送信に残さない)
if (entered.drop !== undefined) {
const at = pending.indexOf(replaced)
if (at !== -1) pending.splice(at, 1)
}
return entered
})
ポイントは以下。
- 何もしないときは
next(e)をそのまま返す。問題があれば元のプロンプトを通す contextに入れた文は Claude が読み、画面には出さないnext(e)の結果を見られる。ほかの mod が送信を止めた(drop)ときは、自分の記録を取り消す
会話に入るメッセージを書き換える(session.append)
prompt.submit では、添付画像そのものは外せません。
画像ブロックを外すのは、メッセージが会話に入る直前に呼ばれる
session.append のフック。
2つ目の引数 { door: 'prompt' } は絞り込みで、
プロンプトから入るメッセージだけを対象にしています。
on('session.append', { door: 'prompt' }, ($, e, next) => {
const replaced = pending[0]
if (replaced === undefined || !e.message.content.some(b => b.type === 'image')) {
return next(e)
}
pending.shift()
let index = 0
const content = e.message.content.filter(
block => block.type !== 'image' || replaced[index++] !== true,
)
return next({ ...e, message: { ...e.message, content } })
})
2つのフックは別々に呼ばれるので、
どの画像を外すかはモジュールの変数(pending)で受け渡しています。
複数枚を添付したときは、小さくした画像だけを外し、そのまま通す画像は残します。
外部コマンドを呼ぶ($.process.run)
mod の実行環境には Node が無いので、sharp のような画像ライブラリは使えません。
画像の処理は、$.process.run で外部のコマンドを呼んで行います。
// 画像を書き出す(base64 の文字列を標準入力から渡す)
await $.process.run(['sh', '-c', 'base64 --decode > "$1"', 'sh', path], { stdin: base64 })
// 大きさと、余白を切り落としたときの枠(%@)を調べる。ここでは何も書き出さない
await $.process.run(['magick', path, '-fuzz', '2%', '-format', '%w %h %@', 'info:'])
// → "1672 941 1546x737+63+102"
// ① 余白の切り落としと ② 縮小を、1回で書き出す
await $.process.run(['magick', path, '-crop', '1546x737+63+102', '+repage', '-resize', '1024x1024>', path])
一律に縮小すると文字も一緒に小さくなりますが、余白を削るだけなら文字の大きさは変わりません。
なので、余白の切り落としを先に行い、それでも大きいときだけ縮小しています。
ImageMagick が無い macOS では標準の sips で縮小だけ行い、
どのツールも無ければ画像をそのまま通します。
小さくした画像は、mktemp -d で作ったフォルダに置きます。
最初は /tmp/claude-imgdec/ という固定のフォルダでしたが、
/tmp はマシン上の全ユーザーがREAD可能なので変えました。
mktemp -d のフォルダは自分だけが読める権限(700)で作られます。
mod は入れた人の権限でそのまま動くので気をつけましょう。
スラッシュコマンドと設定の保存($.command・$.store)
細かい文字を元の解像度で読ませたいときに止められるよう、スラッシュコマンドを足しました。
| コマンド | 動き |
|---|---|
/imgdec |
今の設定と、直近の結果を表示 |
/imgdec off / on |
止める / 再開する |
/imgdec max 1280 |
長辺の上限を変える(256〜2000px。既定は 1024) |
/imgdec mode resend / read |
渡し方を変える(あとの節で説明。既定は read) |
コマンドは session.start で登録し、command.run のフックで答えます。
設定は $.store に保存するので、セッションをまたいで残ります。
on('session.start', async ($, e, next) => {
await $.command.register({
name: 'imgdec',
description: 'Reduce attached images before Claude reads them',
argumentHint: 'on|off|max <px>|mode read|resend',
immediate: true, // Claude の作業中でもすぐ実行できる
})
return next(e)
})
on('command.run', { command: 'imgdec' }, async ($, e) => runImgdec($, e.args))
command.run のフックが返す { text } の text が、コマンドの出力になります。
imgdec: ON
- Mode: read
- Max edge: 1024px
- Trim margins: on
- Image tool: ImageMagick
- Saved to: /var/folders/…/T/claude-imgdec.*/
- Last result:
- 2000×1177 → 1024×603 (resize, ~3,096 → 814 tokens) /var/folders/…/T/claude-imgdec.UeADqs/0.jpg
Usage: /imgdec [on|off|max <px>|mode read|resend]
@ の読み込みに割り込む(prompt.mention)と入力欄の操作($.prompt.fill)
Read 方式は、Claude が Read を呼ぶぶん API のリクエストが1回増えます。
会話が長いほど、この1回のコストが高くなります。
なので、Read を挟まない方法も試しました。
@ファイル で指定した画像は Claude Code が Read なしで直接添付する、という動作を利用します。
※ @ が画像に展開されるのは、人が自分で送信したプロンプトだけ
※ mod が本文に @パス を書き足したり、mod から送信し直したりしても展開されない
① @ で指定された画像をすり替える
prompt.mention のフックは、読むファイルのパスを書き換えられます。
on('prompt.mention', async ($, e, next) => {
const one = await copyAndShrink($, e.path) // 写しを作って小さくする
if (one === undefined) return next(e)
const attached = await next({ ...e, path: one.path }) // 読むファイルをすり替える
// 「添付したのは縮小した写し。元のファイルは読まないで」という Claude 向けの注記を足す
return { ...attached, context: [...(attached.context ?? []), MENTIONED_NOTE] }
})
② 再送信モード(/imgdec mode resend)
ドラッグ&ドロップや貼り付けの画像は、送信をいったん取り消して、
入力欄に @縮小済みファイル を書き足します。
prompt.submit のフックが { drop } を返すと送信は取り消され、
入力欄の内容は元に戻ります。
もう一度 Enter を押すと、今度は人の送信なので @ が展開されます。
const filled = await $.prompt.fill({ text: mentionsOf(paths), mode: 'append' })
if (filled.isFilled) { // 書き足せたときだけ、送信を取り消す
return { drop: 'Image reduced. Press Enter again to send it.' }
}
入力欄には元の画像も残っているので、2回目の送信では Read 方式と同じく、session.append で元の画像ブロックを外します。
外さないと、元の画像と縮小した写しの両方が届きます。
会話に結果を出す($.ui.log)
小さくしたときは、$.ui.log で送信した発言のすぐ上に結果を1行出しています。
この行はモデルには送られません。
imgdec: 2000×1177 → 1024×603 (resize, ~3,096 → 814 tokens)
左側の 2000×1177 は、Claude Code 本体が縮小した後のサイズです。
※ mods はほかにも、パネルや入力欄の上の帯、ステータスラインへの表示が可能
配布とインストール
mod はプラグインとして配布できます。
配る側は、GitHub のリポジトリに「マーケットプレイス」の定義を1つ置くだけ。
claude-mod-imgdec/ … リポジトリ
├── .claude-plugin/marketplace.json … マーケットプレイスの定義
└── imgdec/ … mod 本体(前述の3ファイル)
{
"name": "claude-mod-imgdec",
"owner": { "name": "nakamura-shuta" },
"plugins": [{ "name": "imgdec", "source": "./imgdec" }]
}
使う側は、マーケットプレイスを登録してから、
<プラグイン名>@<マーケットプレイス名> でインストール。
# 余白の切り落としに使う(macOS は無くても縮小だけは動く)
brew install imagemagick # Linux は sudo apt install imagemagick
claude plugin marketplace add nakamura-shuta/claude-mod-imgdec
claude plugin install imgdec@claude-mod-imgdec
起動中のセッションには /reload-plugins で反映されます。
それ以外は、次に Claude Code を起動したときに読み込まれます。
なお、pluginを更新したいときは、plugin.json の version を上げて push します。
使う側は以下でバージョンアップ可能。
claude plugin marketplace update claude-mod-imgdec
claude plugin update imgdec@claude-mod-imgdec
アンインストールは claude plugin uninstall imgdec@claude-mod-imgdec です。
Result
手元で試した結果です。トークン数は、画像の寸法を前述の式に当てはめた概算です。
| 画像 | mod なし(本体の縮小後) | mod あり | 処理 | 削減 |
|---|---|---|---|---|
| 画面スクショ A(3014×1774) | 2000×1177・約 3,096 | 1024×603・約 814 | 縮小 | 約 74% |
| 画面スクショ B(3018×1742)、上限 1280px | 1999×1154・約 3,024 | 1280×739・約 1,242 | 縮小 | 約 59% |
| テスト画像(2400×1600) | 2000×1333・約 3,456 | 1024×682・約 925 | 縮小 | 約 73% |
| 余白の多い図(1600×1000) | 1600×1000・約 2,088 | 801×421・約 464 | 余白カット | 約 78% |
| 文字が1行だけの画像(800×600) | 800×600・約 638 | 578×36・約 42 | 余白カット | 約 93% |
読みやすさは、上限によって差が出ました。
範囲を絞ったスクリーンショットなら 1024px で足りますが、
画面全体を撮ると、一覧の小さな説明文などはつぶれ気味になります。
細かい文字まで読ませたいなら 1280px 以上がよさそうです。
usage で測る
claude -p で条件ごとに新しいセッションを起動し、API の usage で測りました。
表の数値は、画像なしの場合を基準にした、最後のリクエストの入力トークン(通常の入力+キャッシュ書き込み+キャッシュ読み出し)の増加分です。
※モデルは Fable 5.2、上限は 1024px
| 渡し方 | リクエスト | 図(1672×941、余白あり) | 単純な絵(2200×1400) |
|---|---|---|---|
| 元のまま(mod なし) | 1回 | 2,164 | 3,476 |
| Read 方式 | 2回 | 1,059(−51%) | 1,320(−62%) |
@ のすり替え |
1回 | 849(−61%) | 1,071(−69%) |
縮小済みの画像を直接 @ で指定 |
1回 | 828(−62%) | 1,051(−70%) |
最後の行は再送信モードの参考値です(実際の再送信では、短い注記のぶんが少し加わります)。
どの渡し方でもコンテキストの増加が半分以下になりました。
Read 方式だけリクエストが1回増えるので、長い会話では @ か再送信のほうが有利。
全体から見ると効果は小さい
画像1枚ぶんは半分以下になりましたが、
自分の使い方では、Claude Code 全体のトークン削減という観点でみると効果はほとんどありませんでした。
(私の場合)1週間ぶんのセッション記録のトークン数をざっくり集計したところ、
ほとんどは長い会話やサブエージェントでのキャッシュの読み直しで、
画像を毎日 10 枚くらい貼ると見積もっても、減らせるのはトークン数の合計の 1% にも届かない程度です。
上限を気にするなら、こういった手法のほうが はるかに効果的です。
スクリーンショットを何百、何千枚も貼るタスク(画像が中心の処理)なら多少効果あるかもしれません。
Tips
mod を作っていて気づいた点など。
-
$.ui.logには mod の名前が自動で付く
$.ui.log('imgdec: …')と書いたら、画面にはimgdec: imgdec: …と出ました。
名前は Claude Code が付けるので、自分では書かなくてよいです。 -
@で添付できる画像にはファイルサイズの上限がある
@で指定した画像は、ファイルが 256KiB を超えると、
エラーも出ずに何も添付されませんでした(Claude Code 2.1.322 で確かめた範囲)。
そのため@と再送信では、写しを 200KB 以下の JPEG にしています。 -
外部コマンドは OS で動きが違う
sips -Z 1024は「長辺を 1024px にする」動きで、小さい画像は引き伸ばされます。
base64のオプションも macOS と Linux で違いました(両方で通るのはbase64 --decode)。
$.process.runで呼ぶコマンドは、対応する OS ごとに確かめる必要があります。
Other Tools
今回紹介したようなツールはすでにいろいろあります。
| 名前 | 仕組み | 違い |
|---|---|---|
| paste-preview | mod。貼り付けた画像に注釈を付けて、編集後のファイルを Read させる | 仕組みは今回とほぼ同じだが、目的は注釈で、縮小はしない |
| token-goat | hooks と MCP。Read する画像や、ツールが返す画像を縮小する | プロンプトに添付した画像の扱いは書かれていない |
| vision-squeezer | MCP と hooks。Read する画像をトークン予算に収める | 貼り付け・ドラッグした画像は縮小できないと明記されている |
Summary
mods の紹介として、添付した画像を小さくする mod を作ってみました。
mods 自体は簡単に試せますし、
hooksだけではできないことも実現できたりするので、
気になる方は触ってみてください。
References
- Vision - Claude API Docs(「Resolution and token cost」の節。トークンの計算式と、モデルごとの上限)
- Claude Code CHANGELOG(2.1.97・2.1.122・2.1.126: 添付画像の縮小、2.1.299:
$.prompt.media) - Mods overview - Claude Code Docs
- Hooks reference - Claude Code Docs
- Create a mod - Claude Code Docs
- Mods reference - Claude Code Docs
- Use the mods API - Claude Code Docs
- ImageMagick: Command-line Options(
-trim・-resize・-fuzz) - Claude Code に画像を渡すときのコンテキスト消費を減らす - CodeNote
- Manage costs effectively - Claude Code Docs(「Reduce token usage」「Why usage climbs in a long session」)
- nakamura-shuta/claude-mod-imgdec(GitHub)











