1. Claude Code Mods 到底在折腾什么
第一次听到 Claude Code Mods 这个词,很多人会以为是某种插件市场或者主题包。其实不是。它更像是一套“给 Claude Code 这个终端里的编程助手加装外挂”的思路和工具集合。核心目标就两个:第一,让 Claude 能调用更多外部工具,比如读写文件、执行命令、查询数据库;第二,在终端里画出更直观的界面,比如进度条、选择菜单、表格输出,而不是干巴巴地吐一堆文字。
我最初接触 Claude Code 是因为在 VS Code 里写 TypeScript 项目,经常需要它帮我批量改文件、跑测试、查日志。但默认状态下,它只能“看”和“说”,不能“动手”。比如我想让它把某个目录下所有.ts文件里的console.log删掉,它只能告诉我怎么做,不能直接执行。后来发现 Claude Code Mods 这套东西,才真正把它变成了一个能干活的小助手。
这套东西适合谁?如果你已经在用 Claude Code,或者正在找一种在终端里更高效地与 AI 协作的方式,那它值得花时间研究。如果你连 Claude Code 都没装过,也没关系,我会从最基础的安装和配置讲起,保证你能跟着走通。关键词里提到的 JS、TS、终端,正好对应了它的主要使用场景:用 JavaScript 或 TypeScript 写扩展逻辑,在终端里完成日常开发任务。
提示:Claude Code Mods 不是官方唯一指定的叫法,社区里也有人叫它“Claude Code 扩展”“Claude 工具链”,但核心意思一致——给 Claude 加工具、在终端画界面。
2. 为什么要在终端里给 Claude 加工具
2.1 终端才是开发者的主战场
很多人习惯在网页聊天窗口里让 AI 写代码,然后复制粘贴到编辑器。这种方式在写小片段时还行,一旦涉及多文件修改、运行测试、查看 git 状态,就非常低效。终端里天然有文件系统、有 shell、有各种命令行工具,Claude 如果能直接调用这些,就能把“建议”变成“执行”。
举个例子:你让 Claude 帮你把项目里所有import路径从相对路径改成别名路径。在网页里,它只能给你一段正则或者一个脚本,你得自己保存、运行、检查。在终端里,如果 Claude 有文件读写工具,它可以直接扫描目录、修改文件、再跑一次tsc验证。整个过程你只需要确认结果,不用来回搬运代码。
2.2 工具调用让 Claude 从“顾问”变成“操作员”
Claude Code 本身已经具备一定的工具调用能力,比如读取文件、执行命令。但默认的工具集比较基础,而且输出格式偏文本。Mods 的作用就是扩展这个工具集,并且让输出更适合终端阅读。
比如你可以加一个“查询数据库”的工具,让 Claude 直接连上本地 SQLite,根据你的自然语言描述生成 SQL 并执行,然后把结果以表格形式画在终端里。再比如加一个“调用内部 API”的工具,让 Claude 在写代码时能实时获取接口文档,而不是靠记忆瞎猜。
2.3 终端界面让交互更顺手
纯文本输出在终端里看多了会累。Mods 允许你用 JS/TS 写一些简单的 TUI(终端用户界面)组件,比如带颜色的状态栏、可选择的列表、进度条。这些在调试长任务时特别有用。比如你让 Claude 批量处理 100 个文件,它可以用进度条告诉你当前到第几个,而不是刷屏。
注意:终端界面不是必须的,但一旦用过就回不去。尤其是处理耗时任务时,有进度反馈和没有反馈,体验差距很大。
3. 核心概念拆解:工具、Mods、终端界面
3.1 Claude Code 的工具调用机制
Claude Code 在终端里运行时,会维护一个“工具注册表”。每个工具都有名字、描述、参数 schema 和执行函数。当 Claude 决定调用某个工具时,它会输出一个结构化的调用请求,终端里的运行时负责执行并把结果返回给 Claude。这个循环就是所谓的“agent loop”。
默认工具通常包括:读文件、写文件、列目录、执行 shell 命令。这些已经能覆盖大部分场景,但不够灵活。比如你想让 Claude 查一下当前项目的 npm 依赖树,它只能执行npm ls然后解析文本输出。如果你有一个专门的工具直接返回结构化依赖数据,Claude 的理解会更准确。
3.2 Mods 的本质:注册自定义工具
Mods 的核心就是让你用 JS/TS 写一个模块,导出一个或多个工具定义。运行时加载这些模块,把它们注册到工具注册表里。之后 Claude 在对话中就可以像调用内置工具一样调用你的工具。
一个最简单的工具定义大概长这样:
// my-tool.ts export const tool = { name: "count_lines", description: "统计指定文件的行数", parameters: { type: "object", properties: { filePath: { type: "string", description: "文件路径" } }, required: ["filePath"] }, async execute({ filePath }: { filePath: string }) { const content = await Bun.file(filePath).text(); return { lines: content.split("\n").length }; } };这个工具注册后,你问 Claude“帮我看看 src/index.ts 有多少行”,它就会调用count_lines并返回结果。整个过程不需要你手动跑wc -l。
3.3 终端界面是怎么画出来的
终端界面本质上是在 stdout 上输出特定控制字符。比如 ANSI 转义序列可以移动光标、改变颜色、清屏。更复杂的界面可以用ink(React for CLI)或者blessed这类库。Mods 通常不强制你用某个库,但会提供一些辅助函数,让你在工具执行过程中输出进度信息。
比如你写一个批量重命名的工具,可以在执行时输出:
for (let i = 0; i < files.length; i++) { process.stdout.write(`\r处理中: ${i + 1}/${files.length}`); await renameFile(files[i]); } process.stdout.write("\n完成\n");这样终端里就会在同一行刷新进度,而不是刷屏。虽然简单,但很实用。
4. 从零开始:安装 Claude Code 并配置 Mods
4.1 安装 Claude Code
Claude Code 的安装方式取决于你的操作系统。在 macOS 和 Linux 上,通常可以通过 npm 全局安装:
npm install -g @anthropic-ai/claude-code安装完成后,运行claude命令应该能看到欢迎信息。如果提示找不到命令,检查 npm 全局 bin 目录是否在 PATH 里。
在 Windows 上,建议用 WSL2 或者 Git Bash,因为 Claude Code 的很多工具依赖 Unix 风格的路径和命令。原生 PowerShell 也能跑,但可能会遇到路径分隔符和权限问题。
提示:如果你在 VS Code 里用集成终端,确保终端类型是 bash 或 zsh,而不是 PowerShell。VS Code 默认在 Windows 上可能用 PowerShell,需要手动切换。
4.2 配置 API 密钥和模型
Claude Code 需要访问 Anthropic 的 API。你需要设置环境变量:
export ANTHROPIC_API_KEY="你的密钥"如果你用的是第三方兼容接口,比如某些国内模型服务,可以通过ANTHROPIC_BASE_URL指向对应的端点。具体配置方式参考你所用服务的文档。
注意:不要把 API 密钥硬编码在代码里,也不要把包含密钥的文件提交到 git。用
.env文件并加入.gitignore是基本操作。
4.3 安装和加载 Mods
Mods 通常以 npm 包或者本地目录的形式存在。假设你有一个本地 mods 目录:
mkdir -p ~/.claude/mods cd ~/.claude/mods npm init -y npm install typescript ts-node然后创建一个hello.ts:
export const tools = [ { name: "say_hello", description: "向指定名字问好", parameters: { type: "object", properties: { name: { type: "string" } }, required: ["name"] }, async execute({ name }: { name: string }) { return { message: `你好,${name}!` }; } } ];在 Claude Code 的配置文件里(通常是~/.claude/config.json)加上:
{ "mods": ["~/.claude/mods/hello.ts"] }重启 Claude Code 后,问它“跟张三问好”,它应该会调用say_hello并返回消息。
4.4 验证工具是否注册成功
在 Claude Code 里输入/tools或者类似的命令(取决于版本),应该能看到已注册的工具列表。如果没看到,检查文件路径是否正确、TypeScript 是否编译通过、运行时是否支持 TS 直接加载。
我踩过的坑:一开始用.js文件写工具,但项目里用了 ESM 语法,导致require和import混用报错。后来统一用.ts加ts-node才解决。如果你也遇到模块加载问题,先确认package.json里的type字段和你的导入语法是否匹配。
5. 用 TypeScript 写一个实用的文件搜索工具
5.1 需求分析
假设你经常需要在项目里搜索包含特定关键词的文件,但grep的输出太原始,你想让 Claude 帮你搜索并整理结果。我们可以写一个工具,接收关键词和目录,返回匹配的文件列表和行号。
5.2 工具定义
import { readdir, readFile } from "fs/promises"; import { join, extname } from "path"; export const tools = [ { name: "search_in_files", description: "在指定目录下搜索包含关键词的文件", parameters: { type: "object", properties: { keyword: { type: "string", description: "搜索关键词" }, dir: { type: "string", description: "搜索目录,默认为当前目录" }, ext: { type: "string", description: "文件扩展名过滤,如 .ts" } }, required: ["keyword"] }, async execute({ keyword, dir = ".", ext }: { keyword: string; dir?: string; ext?: string }) { const results: Array<{ file: string; line: number; content: string }> = []; async function walk(currentDir: string) { const entries = await readdir(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(currentDir, entry.name); if (entry.isDirectory()) { if (entry.name === "node_modules" || entry.name === ".git") continue; await walk(fullPath); } else if (entry.isFile()) { if (ext && extname(entry.name) !== ext) continue; const content = await readFile(fullPath, "utf-8"); const lines = content.split("\n"); lines.forEach((line, index) => { if (line.includes(keyword)) { results.push({ file: fullPath, line: index + 1, content: line.trim() }); } }); } } } await walk(dir); return { count: results.length, results: results.slice(0, 50) }; } } ];5.3 在终端里展示结果
Claude 拿到结果后,可以用表格形式输出。但如果你想自己控制输出格式,可以在工具里直接打印:
if (results.length === 0) { console.log("没有找到匹配项"); } else { console.log(`找到 ${results.length} 处匹配:`); results.slice(0, 20).forEach(r => { console.log(` ${r.file}:${r.line} ${r.content}`); }); }这样 Claude 只需要告诉你“搜索完成”,具体结果已经在终端里展示出来了。
5.4 参数选择与性能考量
dir默认当前目录,避免全盘搜索。- 跳过
node_modules和.git,否则搜索会很慢。 - 限制返回数量(比如 50 条),防止输出爆炸。
- 如果文件很大,逐行读取可能内存占用高,可以考虑流式读取,但实现复杂度上升。
实操心得:搜索工具最容易出的问题是编码。如果项目里有非 UTF-8 文件,
readFile会报错。可以加 try-catch 跳过无法读取的文件,或者用iconv-lite转换编码。
6. 终端界面进阶:用 Ink 画一个选择菜单
6.1 为什么需要选择菜单
有时候 Claude 需要你从多个选项里选一个,比如“你要修改哪个文件?”纯文本输入容易出错,用方向键选择更友好。Ink 是一个用 React 写终端界面的库,适合做这种交互。
6.2 安装 Ink
npm install ink react6.3 写一个选择组件
import React, { useState } from "react"; import { render, Box, Text, useInput } from "ink"; const items = ["src/index.ts", "src/utils.ts", "src/config.ts"]; function Select({ onSelect }: { onSelect: (item: string) => void }) { const [index, setIndex] = useState(0); useInput((input, key) => { if (key.upArrow) setIndex(i => Math.max(0, i - 1)); if (key.downArrow) setIndex(i => Math.min(items.length - 1, i + 1)); if (key.return) onSelect(items[index]); }); return ( <Box flexDirection="column"> {items.map((item, i) => ( <Text key={item} color={i === index ? "green" : "white"}> {i === index ? "> " : " "}{item} </Text> ))} </Box> ); } export function showSelect(): Promise<string> { return new Promise(resolve => { const { unmount } = render( <Select onSelect={item => { unmount(); resolve(item); }} /> ); }); }6.4 在工具里调用
async execute() { const selected = await showSelect(); return { selected }; }这样 Claude 调用这个工具时,终端里会出现一个可选列表,你用上下键选择后回车,结果返回给 Claude。
注意:Ink 组件会接管终端输入,确保在调用前后没有其他进程在读取 stdin。另外,Ink 的渲染是异步的,不要在组件里做同步阻塞操作。
7. 常见问题与排查技巧实录
7.1 工具注册失败
现象:Claude 说“我没有这个工具”。
排查:
- 检查配置文件路径是否正确,
~是否被展开。 - 检查 TypeScript 文件是否有语法错误,运行时是否支持 TS。
- 查看 Claude Code 启动日志,通常会有加载失败的提示。
7.2 工具执行超时
现象:Claude 调用工具后卡住,最后报超时。
排查:
- 工具内部是否有死循环或长时间阻塞操作。
- 网络请求是否设置了超时。
- 文件搜索是否遍历了过大的目录。
7.3 终端界面乱码
现象:Ink 组件渲染后终端显示错乱。
排查:
- 终端是否支持 ANSI 转义序列(大多数现代终端都支持)。
- 是否在 Windows 的 cmd 里运行,建议换 Windows Terminal 或 WSL。
- 检查是否有其他输出干扰了 Ink 的渲染。
7.4 模块导入报错
现象:Cannot use import statement outside a module。
排查:
- 在
package.json里设置"type": "module"。 - 或者用
ts-node的 ESM 模式。 - 确保 Node 版本支持 ESM(建议 18+)。
7.5 常见问题速查表
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 工具未注册 | 路径错误、语法错误 | 检查配置和日志 |
| 执行超时 | 阻塞操作、大目录遍历 | 加超时、限制范围 |
| 界面乱码 | 终端不支持、输出干扰 | 换终端、隔离输出 |
| 导入报错 | 模块系统不匹配 | 统一 ESM 或 CJS |
| 编码错误 | 非 UTF-8 文件 | 跳过或转码 |
避坑技巧:写工具时尽量保持单一职责,一个工具只做一件事。这样调试容易,Claude 也更容易正确调用。另外,给工具起名时用下划线分隔,描述写清楚,Claude 的判断会更准。
8. 工具选型与扩展思路
8.1 用 Bun 还是 Node
Bun 启动快,内置 TypeScript 支持,适合写小工具。Node 生态更成熟,兼容性好。如果你只是写几个简单的文件操作工具,Bun 更省事。如果需要用一些老库,Node 更稳。
8.2 用 ts-node 还是 tsx
ts-node配置多,但功能全。tsx更轻量,开箱即用。我目前用tsx,因为不需要额外配置就能跑 TS 文件。
8.3 扩展方向
- 接入数据库:写一个查询工具,让 Claude 直接查数据。
- 接入内部 API:让 Claude 在写代码时获取实时接口文档。
- 自动化部署:写一个部署工具,Claude 确认后直接执行。
- 代码生成:根据模板生成重复性代码文件。
个人体会:工具不在多,在于精。先把最常用的三五个场景做成工具,用顺了再扩展。一开始就追求大而全,反而容易因为调试成本高而放弃。
9. 我在实际使用中的几点体会
Claude Code Mods 最吸引我的地方是它把“AI 建议”和“实际执行”之间的鸿沟填上了。以前让 AI 写个脚本,还得自己保存、运行、看结果。现在直接在终端里说“帮我做”,它就能调用工具完成。虽然偶尔会调错工具或者参数传错,但整体效率提升明显。
另外,终端界面的加入让交互更自然。尤其是处理批量任务时,有进度条和选择菜单,体验比纯文本好太多。如果你也在用 Claude Code,建议从最简单的文件搜索工具开始试,跑通后再加复杂功能。遇到问题先看日志,大部分错误都有明确提示。
最后分享一个小技巧:给工具写描述时,用自然语言写清楚“什么时候用这个工具”,Claude 的判断准确率会高很多。比如不要只写“搜索文件”,而是写“当用户需要查找包含特定关键词的文件时使用”。这个细节我试过,效果差别很大。