Claude Mods 實戰:用幾十行 JavaScript 改造 Claude Code 的介面與行為
Claude Code 用久了,你一定碰過這幾種時刻:想在 spinner 旁邊看到這輪已經呼叫了幾次工具;想讓 git push --force 之前先跳出一個「確定嗎」;想在每輪結束後自動檢查 Claude 有沒有偷工減料。以前這些需求只能拆給 settings hooks、status line、skill、MCP server 各做一塊,而且全都是「從外面」接進 Claude Code。
Anthropic 在 Claude Code 推出的 Mods,是第一個讓你把程式碼直接放進 Claude Code「裡面」跑的機制。Latent Space 上 Dan McAteer 寫的〈Claude Mods: Flipping the Harness Inside Out〉(2026/10/21)把它形容成「agent harness 的 middleware」:harness 過去是面向機器設計的,mods 讓它開始面向坐在螢幕前的人。
這篇不談願景,直接帶你把 mod 用起來:它怎麼運作、第一個 mod 怎麼寫、怎麼叫 Claude 幫你寫、哪些現成的值得裝、以及哪些坑要先知道。
本文大綱
一、Mods 到底是什麼
官方文件的定義很短:mod 是一種 plugin,由 JavaScript 或 TypeScript 的 event handler 組成,用來改變 Claude Code 的外觀與行為。
關鍵差別在「跑在哪裡」。文件拿它跟其他擴充方式對比:settings hooks、skills、status line、MCP server 都是從 Claude Code 外部運作;mod 跑在 Claude Code 內部,所以能做到前者做不到的事,例如:
- 在對話旁邊開一個常駐的 pane,或在輸入框上方放一條 band,裡面有分頁、按鈕、輸入框
- 改寫或重新設計 Claude Code 自己畫的東西:工具呼叫那一列、spinner、Claude 問你問題的對話框(唯一不能改的是權限確認提示)
- 新增一個
/指令,按下去立刻執行你的函式,不需要等 Claude 跑一輪,Claude 正在工作時也能用 - 攔截、改寫或直接回答工具呼叫、使用者輸入、模型請求
版本需求(依文件):終端機 Claude Code v2.1.287 以上、Desktop App 的 Code 分頁 v2.1.286 以上、VS Code 擴充 2.1.299 以上。先跑一次 claude --version 確認。
Claude Code 本身就內建了六個 mod,文件列出的包括:載入 AGENTS.md 當專案指示的 cc-plugin-agents-md、接管 /diff 並畫出差異面板的 cc-plugin-diff、提供撰寫 mod 技能的 cc-plugin-plugin-authoring、保護組織管理設定的 cc-plugin-sec-default、送出分析紀錄的 cc-plugin-telemetry,以及預設關閉、會在輸入框上方顯示提醒的 side agent cc-plugin-you-should-know。其中幾個的原始碼公開在 anthropics/claude-code repo 的 mods/ 目錄,是很好的參考範本。
二、運作原理:事件、洋蔥式 handler、$ API

整個機制只有三個概念,搞懂就能讀懂所有範例。
1. 事件(event):Claude Code 裡幾乎每件事都會發出一個具名事件。常用的幾類:
| 類別 | 代表事件 | 什麼時候觸發 |
|---|---|---|
| 工具 | tool.call、tool.check |
工具要執行前;決定是否允許執行時 |
| 輸入 | prompt.submit、prompt.context |
你送出訊息時;組裝 Claude 要讀的內容時 |
| 指令 | command.run |
你輸入 mod 註冊的 /指令 |
| 回合 | turn.start、turn.step、turn.complete |
一輪開始、每次模型請求、一輪結束 |
| 工作階段 | session.start、session.compact |
mod 載入後第一個 prompt 之前;壓縮上下文時 |
| 介面 | ui.render、ui.press |
某個畫面元件要繪製時;你按了 mod 畫的按鈕時 |
| 子代理 | agent.spawn |
要開 subagent 時 |
另外還有 classic.* 系列,對應原本 settings hooks 的事件(例如 classic.Stop、classic.PostToolUse),讓舊的 hook 邏輯可以搬進 mod。
2. Handler 簽章 async ($, e, next):這是典型的 middleware 洋蔥模型。每個 hook 拿到事件 e,有三種選擇:
- 放行(observe):看一眼就
return next(e),例如計數、記 log - 改寫(rewrite):
return next({ ...e, 某欄位: 新值 }),例如幫 spinner 加後綴、幫 prompt 補上下文 - 回答(answer):不呼叫
next,直接回傳結果,例如{ deny: '原因' }擋掉工具呼叫,或{ result }代替工具回應
on() 的第二個參數可以放 matcher 縮小範圍,例如 on('tool.call', { tool: ['Edit', 'Write'] }, hook);欄位可以是字串、陣列或 regex。
3. $:mods API:hook 本身之外要做的事都透過 $:$.ui.open() 開 pane、$.ui.toast() 跳通知、$.ui.status() 在輸入框下方寫一行、$.command.register() 註冊指令、$.tool.register() 給 Claude 一個新工具、$.model.complete() 或 $.model.fork() 呼叫模型、$.fs.* 讀寫檔、$.process.run() 跑外部程式、$.http.fetch() 打網路、$.store.* 存跨 session 的 JSON。
有個設計很漂亮:每個 $ 方法本身也是事件(例如 fs.read、model.complete),所以先載入的 mod 或組織的安全設定,可以觀察甚至拒絕後面 mod 的檔案、程序、網路存取。
三、手把手:寫出你的第一個 mod
以下是官方教學裡的 first-mod:計算 Claude 的工具呼叫次數、顯示在 spinner 旁,再加一個 /tally 指令。目錄結構:
first-mod/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.js
.claude-plugin/plugin.json 是 plugin 的 manifest:
{
"name": "first-mod",
"version": "0.1.0",
"description": "Counts Claude's tool calls, shows the count beside the spinner, and adds a /tally command",
"author": { "name": "Your Name" }
}
hooks/hooks.json 指向你的程式:
{
"description": "The first-mod hooks module",
"modules": ["./register.js"]
}
hooks/register.js(文件稱為 hooks module,必須是 ES module 並 export register):
let calls = 0
export function register(on) {
on('session.start', async ($, e, next) => {
await $.command.register({
name: 'tally',
description: 'Show how many tool calls Claude has made',
})
return next(e)
})
on('tool.call', async ($, e, next) => {
calls += 1
$.ui.invalidate('ui.render') // 要求重畫,讓新數字顯示
return next(e) // 放行,工具照常執行
})
on('command.run', { command: 'tally' }, async () => {
return { text: 'Claude has made ' + calls + ' tool calls since this mod loaded' }
})
on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
// 保留原本的 spinner,只在後面加上計數
return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
})
}
這段程式剛好示範了三種模式:tool.call 是放行、ui.render 是改寫、command.run 是直接回答。JS 和 TS(.ts、.tsx 等)都能直接載入,不需要 build。
接著四個指令,照順序跑:
# 1. 靜態檢查:會列出這個 mod 掛了哪些 hook
claude plugin validate ./first-mod
# > ./register.js hooks: session.start, tool.call, command.run{command=tally}, ui.render{component=Spinner}
# ✔ Validation passed
# 2. 只在這個 session 載入,存檔即熱重載
claude --plugin-dir ./first-mod
# 3. 在 session 裡確認:/plugin → Installed 分頁;改了檔可用 /reload-plugins
# 4. 跑測試(在 mod 目錄下執行 *.test.ts)
claude plugin test
測試框架是內建的 claude-code/testing,可以假裝工具呼叫、再呼叫你的指令驗證輸出。文件的範例是先用 on('tool.call', () => ({ result: 'ok' })) 攔下所有工具(不真的執行),觸發兩次 $.tool.call(),再檢查 /tally 回傳「2 tool calls」。把 mod 當成一般程式碼寫測試,是後面放心分享給團隊的前提。
開發期間一直用 --plugin-dir 就好。文件特別提醒:安裝後的 plugin 會依版本快取,改了程式要升 version 再重裝才會生效。
四、更省事:直接叫 Claude 幫你寫
Mods 最實用的入口其實是 Claude 自己。內建的 plugin-authoring skill 就是為此存在,你只要在互動 session 裡用白話描述:
make a mod that shows the current git branch above the prompt
或用中文也行,重點是講清楚三件事:畫在哪裡、什麼時候觸發、要不要呼叫模型。例如:
/plugin-authoring
幫我寫一個 mod:每一輪結束時,如果這輪有改到 *.sql 檔,
在回答下方顯示一行提醒「記得跑 migration dry-run」。
不要呼叫模型,只看這輪的工具呼叫紀錄。寫完附一個 test。
依文件,Claude 會把檔案寫到 ~/.claude/dev-mods/<session-id>/<mod-name>/。在 default 與 acceptEdits 權限模式下,mods 資料夾是受保護路徑,每個檔案都要你核准;第一次存檔時會問你要「Enable for this session」(熱重載)還是「Not now」。
注意兩件事:Claude 寫的 mod 只在當下這個 session 生效;而且 cleanupPeriodDays 到期後,session 的 mods 資料夾會被清掉。滿意的話記得複製出來,例如 cp -r ~/.claude/dev-mods/<session-id>/git-branch ~/mods/,之後用 claude --plugin-dir ~/mods/git-branch 載入,或放進團隊 marketplace。
五、三個自己寫的實用 recipe
以下三個是依文件事件定義組出來的骨架,沒有實際跑過;事件欄位名稱請以你版本產生的 claude-code.d.ts 為準(用 --plugin-dir 在互動 session 載入時,型別檔會產生在 .claude-plugin/types/)。
Recipe 1:推到 main 之前強制確認
on('tool.check', { tool: 'Bash' }, async ($, e, next) => {
if (/git\s+push.*\bmain\b/.test(e.command ?? '')) {
return { decision: 'ask', reason: '要推到 main,確認一下' }
}
return next(e)
})
文件自己也提醒:比對指令文字不等於真正的防護,換個寫法就繞過了。真正要保護的分支,請在 GitHub/GitLab 上設 branch protection,mod 只是第一道提醒。
Recipe 2:每輪結束顯示耗時
on('turn.complete', async ($, e, next) => {
if (e.isAborted) return next(e)
return { text: '⏱ ' + Math.round(e.durationMs / 1000) + ' 秒' }
})
turn.complete 回傳 { text } 會在答案下方加一行;事件也帶有 e.usage,可以順便顯示 token 用量。
Recipe 3:送出前自動補上下文
prompt.submit 可以回傳 next({ ...e, context }) 幫訊息加上背景資訊,例如把 $.process.run(['git', 'status', '--short']) 的結果附上去。注意 $.process.run 吃的是參數陣列、不經過 shell,預設 30 秒逾時。
六、值得先裝來玩的現成 mod
Anthropic 官方範例(anthropics/claude-code-playground 的 claude-code/mods/,官方註明「照原樣分享、不提供支援」):
token-weather:在輸入框上方畫出 context window 的即時「天氣預報」blast-radius:攔下高風險 shell 指令(rm -rf、git reset --hard、force push、migration),先顯示會影響什麼,再給 Proceed/Cancel 按鈕replay-theater:新增/replay,一個 diff 一個 diff 回放上一輪 Claude 改過的檔案
git clone https://github.com/anthropics/claude-code-playground.git
cd claude-code-playground/claude-code/mods
claude --plugin-dir ./token-weather
Dan McAteer 的 ModSmith(GitHub DannyMac180/skills 的 modsmith/):一組現成 mod,加上估算成本與審查別人 mod 的工具。
/plugin marketplace add DannyMac180/skills
/plugin install modsmith-all@modsmith
裡面最有代表性的是 Next Steps Supervisor:只在「真的做了事」的回合(改了檔、跑了指令或 8 次以上工具呼叫)之後,開一個 side check 問:目標是什麼、達成了沒、哪裡偷工減料、有沒有需要你核准的事,然後把下一步做成可以點的按鈕;/supervisor 可關閉。其他還有每輪出 2 到 3 題小測驗的 quiz-after、讓 Claude 登記假設的 assumption-ledger、切換 ui/api/review 模式的 effort-modes。README 註明目前只有 quiz-after 與 next-steps-supervisor 在 Desktop App 實際跑過,其他還沒。
七、數據與限制:先看清楚再裝
成本是真的。 $.model.complete 與 $.model.fork 會算在你的方案、API key 或雲端帳單上。以 Supervisor 為例,Dan McAteer 在文章中引用 ModSmith README 的估計:在 50k token 的 session 裡,每次檢查約 5 到 6k「等效輸入」token;README 的另一處寫法是一次走 cache 的 fork,約 250 token 問題加 100 到 300 token 輸出。兩者都是作者估計,不是基準測試。effort-modes 每次切換會重建一次 cache,README 記錄在 37k token 的 session 中量到約 10k token。
觸發門檻比想像中常被滿足。 McAteer 從 10/15 裝上 Supervisor 後統計,111 個回合中有 99 個達到觸發條件,他的原話是「實務上,幾乎每一輪」。設計 mod 時要先想好噪音預算,不然很快就會被你自己的 mod 煩到關掉。
沒有沙盒。 文件寫得很直白:mod 是以你的權限執行的程式碼,而且不在沙盒裡;就算開了 sandbox,被隔離的是 Claude 執行的 Bash 指令,mod 啟動的程序在沙盒外。裝別人的 mod 等於執行別人的程式。
失敗預設是放行。 每個 hook 有 10 秒執行時間(prompt.edit 只有 50 毫秒);拋錯、逾時或回傳格式錯,如果發生在呼叫 next 之前,該 hook 會被跳過。所以當作防護用的 mod,必須用 .catch() 讓它 fail closed。
其他硬限制(依文件):$.ui.invalidate 每秒最多 10 次(終端機 30 次);$.fs 單檔 4 MiB;$.http.fetch 單次回應 4 MiB、最多 5 次轉址、總共 30 秒;$.store 全機器所有 session 共用、總量 4 MiB、寫入非原子。你按 Esc 停止後,背景呼叫會被拒絕,直到你再次輸入。
不載入的情況:不受信任的工作區、--safe-mode、--bare、設定了 disableAllHooks,或組織的 managed settings 擋掉時,mod 都不會載入。出問題時 claude --safe-mode 是最快的除錯開關。
八、什麼時候用 mod,什麼時候別用

- 只是想讓 Claude 多懂一套流程:寫 skill 就好,不需要程式碼
- 想接外部系統的資料或動作:MCP server 依然是標準做法,而且跨 agent 可用
- 只要在底部顯示一行狀態:status line 最簡單
- 要在工具執行前後跑腳本、跨工具相容:settings hooks 足夠
- 要畫互動介面、改 Claude Code 自己的元件、在不佔 Claude 回合的情況下跑指令、或改寫模型請求:這才是 mod 的主場
一個實用判斷:如果你發現自己在 status line 腳本裡塞狀態、又在 hook 裡寫暫存檔互相溝通,那就是該換成 mod 的訊號。
九、給工程團隊的落地清單
- 裝任何 mod 前先跑
claude plugin validate。輸出的hooks:與calls:會告訴你它掛了哪些事件、會呼叫哪些 API(檔案、程序、網路、模型)。ModSmith 也附了bun scripts/audit-mod.ts <mod>審查腳本可以搭配。 - 團隊共用走 marketplace,開發走
--plugin-dir。名稱別用claude-開頭,validate 會擋看起來像 Anthropic 官方的名字。 - 管理者先設好 managed settings。文件列出的開關包括
allowManagedModsOnly(只允許組織派發的 mod)、disableSideloadFlags(禁止--plugin-dir/--plugin-url)、prependPlugins/appendPlugins(控制載入順序)。載入順序是:安全守門 mod、prepend、使用者 mod、append、內建 mod。 - 防護類 mod 一律 fail closed、而且不當唯一防線。用
.catch(),並在 Git 平台、CI 設真正的權限。 - 每個呼叫模型的 mod 都寫下成本與關閉方式。學 ModSmith 的做法:每個 mod 附
DECISIONS.md,標明每輪 token 成本、對 cache 的影響、怎麼關。 - 從一個小痛點開始。先寫一個不呼叫模型的 mod(計數、提醒、快捷指令),熟悉事件與
$API,再進到會 fork 對話的 side agent。
Mods 真正改變的,是你不用再等官方加功能:工作流裡哪個環節讓你分心、讓你不放心,就自己寫幾十行把它補起來。
來源
- Dan McAteer,〈Claude Mods: Flipping the Harness Inside Out〉,Latent Space,2026/10/21:https://www.latent.space/p/claude-mods
- Anthropic,Claude Code Mods 官方文件(overview/create/events/api/interface/reference):https://code.claude.com/docs/en/plugins/mods/overview
- Anthropic,內建 mods 原始碼:https://github.com/anthropics/claude-code/tree/main/mods
- Anthropic,範例 mods(token-weather、blast-radius、replay-theater):https://github.com/anthropics/claude-code-playground/tree/main/claude-code/mods
- Dan McAteer(DannyMac180),ModSmith:https://github.com/DannyMac180/skills/tree/main/modsmith
整理:DataAgent · Coding Agent 實戰教學


