跳到正文

Claude Code mods 入门

Mods 是随插件一起发布并在 Claude Code 会话中运行的钩子。从一个空文件夹开始构建一个 mod,然后浏览两个更大的 mod。

本文目录文章开头
部分图表可能显示不完整

模组(mod)是一个在 Claude Code 会话中运行的小型 JavaScript 或 TypeScript 文件。它可以监视正在发生的事情、改变 Claude Code 的行为,或在终端和桌面应用中绘制自己的界面。你无需学习 API 就可以尝试一个模组。运行 claude,然后描述你想要的模组。当它询问时允许热重载,模组会在回合结束时显示。

Claude Code 已经允许你改变它的许多行为方式:设置、权限规则、斜杠命令、技能和状态栏。模组更进一步:它们可以重写或替换 Claude Code 的行为,并绘制自定义界面。在底层,模组是随插件一起发布的钩子(hooks),每个钩子都能在你的会话中实时看到每一个事件。

这使得模组成为让 Claude Code 适应你工作方式的一种途径。你可以添加一个你经常查看的读数,在让你紧张的命令前设置一个守卫,或者为你喜欢的变更审阅方式构建一个审阅视图。

本指南从一个空文件夹开始构建一个模组,Token Weather,一个绘制在提示符上方的上下文窗口实时预报。它大约有 80 行代码。然后介绍了两个更大的模组,Blast Radius 和 Replay Theater,以展示 API 还能做什么。这三个模组的完整代码都在 anthropics/claude-code-playground 中。

图 AToken Weather、Blast Radius 和 Replay Theater,在一个终端会话中依次运行

A one-line terminal band cycling through three forecasts, a yellow sun for Clear, a blue umbrella for Showers and a pink lightning bolt for Storm, each with its token count out of 200k and a small bar chart of recent turns.

图 BToken Weather 随上下文窗口填充而变化的区间:18% 时晴朗(Clear),67% 时阵雨(Showers),81% 时暴风雨(Storm)

Claude Code 2.1.287 或更高版本。模组默认启用,无需开启。API 可能在版本之间发生变化。每次 Claude Code 加载模组时,都会将类型声明写入模组的 .claude-plugin/types/ 文件夹中,这些声明是你所用版本的权威依据。

模组的工作原理

模组是一个 Claude Code 插件,其行为位于一个 JavaScript 或 TypeScript 模块中:

  • 该文件夹是一个普通插件,带有一个 .claude-plugin/plugin.json 清单文件。
  • hooks/hooks.json 指定 modules 下的一个模块。
  • 该模块导出 register(on, options)。在其中,on(event, matcher?, hook) 添加一个钩子。

每个钩子的结构都相同:

CODEJavaScript

on("tool.call", { tool: "Bash" }, async ($, e, next) => {
  // $    the mods API: ui, session, state, store, fs, process, clock, http, tool, command, model, ...
  // e    this event's input, as plain data
  // next passes e to the other plugins and then to Claude Code's own behavior
  return next(e);
});

钩子形成一条链,就像中间件一样。你的钩子运行,next(e) 将事件交给下一个插件,最后 Claude Code 执行它本来就要执行的操作。钩子可以做三件事之一:

钩子形成一条链

一个事件先经过你的钩子,然后是其他插件,最后是 Claude Code

一个事件先经过你的钩子,然后是其他插件,最后是 Claude Code。每个钩子调用 next(e) 将事件传递下去,结果再流回。做出响应的钩子会在不调用 next 的情况下返回。

操作方式示例
观察const r = await next(e); /* look */ return r记录每一次文件编辑。在每个回合后取一次读数。
重写return next({ ...e, command: safer })改变链中其余部分看到的内容。
响应return { deny: "…" } 而不调用 next拒绝一次工具调用。自行提供一个命令或工具。

这些事件涵盖工具调用、提交的提示、回合的开始与结束、会话的开始与结束、斜杠命令,以及 ui.render:界面绘制时的每一个元素。该模块运行在自己的沙箱中,没有 DOM 也没有 Node,因此它之外的一切都要通过 $。

这与设置钩子有何不同。 设置钩子会为每个事件运行一个 shell 命令,并通过 stdin 和 stdout 传递 JSON。而模组只加载一次,会一直留在会话中。它可以保存状态、绘制随事件更新的界面,还可以回调 Claude Code:打开面板、运行进程、注册斜杠命令,或注册模型可调用的工具。

Claude Code 自身也在使用它们。Claude Code 的一些自身功能就是以模组形式构建的,包括 AGENTS.md 支持和对话旁边的 /diff 面板。它们的源码(含测试)位于公开的 anthropics/claude-code 仓库的 mods/ 下,因此你可以阅读团队是如何构建它们的。

构建你的第一个模组:Token Weather

Token Weather 在每一轮之后读取上下文窗口的使用程度,并在提示符上方绘制一行内容:一个天气图标、百分比、窗口中已使用的 token 数、最近几轮的小图表,以及上一轮增加了多少。

已使用预报
低于 25%☀ 晴
25–49%☁ 多云
50–74%☂ 阵雨
75–89%☇ 暴风雨
90% 及以上↯ 尽快压缩

这是它在真实会话中的样子。每一轮都会读取更多文件,色带从 ☀ 晴 填充到 ☂ 阵雨 再到 ☇ 暴风雨:

图 CToken Weather 在一个完整终端会话中经历三轮:200k 窗口的 18%,然后是 67%,然后是 81%

捷径:让 Claude 来构建它

你可以跳过这六个步骤。Claude Code 知道如何编写模组,所以你可以描述你想要的模组,让它来完成工作。用 claude 启动一个会话,并粘贴下面的提示词:

CODEText

Make me a Claude Code mod called token-weather: a live forecast of my context window, shown in the band above the prompt.

What it should show, on one line:
- A weather icon and word for how full the context window is: under 25% ☀ Clear (yellow), 25–49% ☁ Cloudy (cyan), 50–74% ☂ Showers (blue), 75–89% ☇ Storm (magenta), 90% and up ↯ Compact soon (red).
- The percentage used, then the tokens used out of the window, like "134.4k / 200k".
- A small chart of the last 12 turns, drawn with ▁▂▃▄▅▆▇█.
- How much the last turn added, like "▲ +98.3k last turn".

It should update after every turn.

Claude 会询问一次是否为该会话开启热重载。允许它,当 Claude 的回合结束时,色带就会出现在提示符上方。从那时起,每次更改都会原地重新加载,因此你可以不断要求调整(“让暴风雨从 70% 开始”、“在末尾加上美元成本”),并观察色带的变化。该模组仅在此会话中加载,其文件夹稍后会被清理,因此要保留它,请将文件夹复制出来并像安装任何插件一样安装它(第 6 步)。

注意,提示词只描述了你想看到什么。你不需要了解 API 就能编写一个。Claude Code 内置的编写模组指南涵盖了方法:将状态保存在哪里以便在重新加载后保留、如何用 claude plugin validate 检查插件,以及要挂钩哪些事件。修改“它应该显示什么”这几行,它就是你的模组,而不是我们的。

如果你更想先了解它是如何构建的,或者想检查 Claude 写的内容,请继续阅读。

第 1 步:创建文件夹

检查你的 Claude Code 是否足够新:

CODEShell

claude --version   # 2.1.287 or later

创建以下结构:

CODEText

token-weather/
├── .claude-plugin/
│   ├── plugin.json
│   └── types/            (written by Claude Code when it loads the mod)
├── hooks/
│   ├── hooks.json
│   └── token-weather.mjs
├── types/
│   └── index.d.ts        (added in step 3)
└── tests/
    └── token-weather.test.ts   (added in step 5)

.claude-plugin/plugin.json 是一个标准的插件清单:

CODEJSON

{
  "name": "token-weather",
  "version": "0.1.0",
  "description": "A live forecast of the context window, drawn above the prompt.",
  "author": { "name": "You" }
}

hooks/hooks.json 指向该模块。一个模组只有一个:

CODEJSON

{
  "modules": ["./token-weather.mjs"]
}

第 2 步:绘制一些内容

提示符正上方的色带是一个名为 AbovePrompt 的组件。Claude Code 自身不会在那里绘制任何内容,所以这是一个很好的起点。挂钩它的 ui.render 事件并返回一个元素树:

CODEJavaScript

// hooks/token-weather.mjs
export function register(on) {
  on("ui.render", { component: "AbovePrompt" }, ($, e, next) => {
    const { Box, Text } = $.ui.resolve(e);
    return Box({
      paddingX: 1,
      children: [Text({ color: "yellow", bold: true, children: "☀  Clear skies" })],
    });
  });
}

这些元素不是全局的。$.ui.resolve(e) 会返回正在绘制的表面的构造函数,因为 Claude Code 绘制的每个表面支持的元素集合略有不同。JSX 也可以使用,以 h 作为工厂。

加载插件并启动会话:

CODEShell

claude --plugin-dir ./token-weather

"☀ 晴朗"出现在提示符上方。保持会话打开。该文件夹处于监视状态,因此每次保存都会就地重新加载模块,无需重启。这种快速反馈循环正是编写模组的乐趣所在。

提示:一旦你了解了其结构,就可以像 快捷方式 那样向 Claude 描述下一个模组。它会将插件写入一个在同一会话中支持热重载的文件夹。

第 3 步:读取真实数值并将其保存在 $.state 中

$.session.usage() 返回的数值与状态栏相同。context.tokens 是上一次响应所基于的输入,context.window 是模型的窗口,context.percent 是两者之比。该调用是免费的:只有当你请求 breakdown 时,它才会发送 token 计数请求。

在会话开始时以及每一轮之后读取数值:

CODEJavaScript

on("session.start", async ($, e, next) => {
  const result = await next(e);
  await takeReading($);
  return result;
});

on("turn.complete", async ($, e, next) => {
  const result = await next(e);
  if (!e.agentId) {
    await takeReading($); // main-loop turns only, not subagents
  }
  return result;
});

两个钩子都先调用 next(e),然后进行观察。两者都不会改变发生的事情。

将读数保存在哪里。模块级的 let readings = [] 看起来是显而易见的选择,但热重载是一次全新的加载:register 会重新运行,session.start 会再次触发,模块变量也会重置。请将历史记录放在 $.state 中。它在整个会话期间在宿主中保存命名值,并且它们能在重载后保留。

CODEJavaScript

// Held by the host, so the history survives a hot reload of this file.
const readings = { plugin: "token-weather", key: "readings" };

async function takeReading($) {
  const { context } = await $.session.usage();
  if (!context?.window) return;
  const tokens = context.tokens ?? 0;
  const percent = context.percent ?? Math.round((tokens / context.window) * 100);
  const { value: history = [] } = await $.state.get(readings);
  await $.state.set(readings, [...history, { tokens, window: context.window, percent }].slice(-HISTORY));
}

状态值是在插件的类型契约中声明的,这是一个 manifest 指向的小型 .d.ts 文件。添加 types/index.d.ts:

CODETypeScript

export type TokenWeatherReading = { tokens: number; window: number; percent: number };

declare module "claude-code" {
  interface PluginState {
    "token-weather": { readings: TokenWeatherReading[] };
  }
}

然后将 "types": "./types/index.d.ts" 添加到 plugin.json 中。如果你跳过这一步,claude plugin validate 会用一个错误阻止你,并指出修复方法:token-weather.readings is not declared: the manifest's types contract must name it in interface PluginState { … }。

作为回报,你将免费获得重绘功能。在渲染钩子运行期间创建的 $.state.get 会订阅该绘制,因此之后的每次 $.state.set 都会重绘该条带。你永远不需要调用 $.ui.invalidate。

第 4 步:绘制预报

以下是完整的模块:

CODEJavaScript

// Token Weather: a live forecast of the context window, above the prompt.

const HISTORY = 12;
const BARS = "▁▂▃▄▅▆▇█";
const FORECAST = [
  { upTo: 25, icon: "☀", word: "Clear", color: "yellow" },
  { upTo: 50, icon: "☁", word: "Cloudy", color: "cyan" },
  { upTo: 75, icon: "☂", word: "Showers", color: "blue" },
  { upTo: 90, icon: "☇", word: "Storm", color: "magenta" },
  { upTo: Infinity, icon: "↯", word: "Compact soon", color: "red" },
];

// Held by the host, so the history survives a hot reload of this file.
const readings = { plugin: "token-weather", key: "readings" };

export function register(on) {
  on("session.start", async ($, e, next) => {
    const result = await next(e);
    await takeReading($);
    return result;
  });

  on("turn.complete", async ($, e, next) => {
    const result = await next(e);
    if (!e.agentId) {
      await takeReading($); // main-loop turns only, not subagents
    }
    return result;
  });

  on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
    const { value: history = [] } = await $.state.get(readings);
    if (e.props.hasSurvey || history.length === 0) {
      return next(e);
    }
    const { Box, Text } = $.ui.resolve(e);
    return band(Box, Text, history, e.props.bodyColumns);
  });
}

async function takeReading($) {
  const { context } = await $.session.usage();
  if (!context?.window) return;
  const tokens = context.tokens ?? 0;
  const percent = context.percent ?? Math.round((tokens / context.window) * 100);
  const { value: history = [] } = await $.state.get(readings);
  await $.state.set(readings, [...history, { tokens, window: context.window, percent }].slice(-HISTORY));
}

function band(Box, Text, history, columns) {
  const now = history[history.length - 1];
  const f = FORECAST.find((b) => now.percent < b.upTo);
  const parts = [
    Text({ color: f.color, bold: true, children: `${f.icon}  ${f.word}` }),
    Text({ children: `  ${now.percent}% of context` }),
    Text({ dimColor: true, children: `  ${short(now.tokens)} / ${short(now.window)}` }),
  ];
  if (columns >= 60) {
    parts.push(Text({ dimColor: true, children: "   last turns " }));
    parts.push(Text({ color: f.color, children: sparkline(history) }));
    if (history.length > 1) {
      parts.push(Text({ dimColor: true, children: trend(history) }));
    }
  }
  return Box({ flexDirection: "row", paddingX: 1, children: parts });
}

function sparkline(history) {
  const top = Math.max(...history.map((r) => r.tokens), 1);
  return history.map((r) => BARS[Math.floor((r.tokens / top) * (BARS.length - 1))]).join("");
}

function trend(history) {
  const delta = history[history.length - 1].tokens - history[history.length - 2].tokens;
  if (delta === 0) return "  steady";
  return delta > 0 ? `  ▲ +${short(delta)} last turn` : `  ▼ ${short(-delta)} last turn`;
}

function short(n) {
  if (n >= 1_000_000) return `${+(n / 1_000_000).toFixed(1)}M`;
  if (n >= 1_000) return `${+(n / 1_000).toFixed(1)}k`;
  return String(n);
}

有三个细节值得复制到你自己的模组中:

  • 组件的 props 位于 e.props 上。hasSurvey 表示某个调查需要该条带,因此钩子通过 next(e) 让步于它。bodyColumns 是条带的实际宽度,当有窗格停靠在转录旁边时,它比终端更窄。请据此调整树的大小。只有 e.component、e.surface、e.requestId 和 e.viewport 位于 e 的顶层。
  • 当你没有内容可绘制时,请跳过。返回 next(e) 会将条带交还给 Claude Code 和其他模组。
  • 使用单宽度符号,而不是 emoji。☀ ☁ ☂ ☇ ↯ 在每种终端字体中都能对齐。

保存文件后,正在运行的会话会立即加载它。在读取了几个大文件的几轮之后,条带会从晴朗变为阵雨再到暴风雨,正如本节开头的录制所示。

第 5 步:验证和测试

claude plugin validate 以 Claude Code 将要使用的方式读取 manifest 和模块的源代码,并报告模块钩子和调用的内容:

CODEText

$ claude plugin validate ./token-weather
  > types ./types/index.d.ts declares state: token-weather.readings
  > ./token-weather.mjs hooks: session.start, turn.complete, ui.render{component=AbovePrompt}
  > ./token-weather.mjs calls: $.session.usage (via takeReading), $.state.get, $.state.set (via takeReading), $.ui.resolve
  > ./token-weather.mjs state writes: token-weather.readings
  > ./token-weather.mjs state reads: token-weather.readings
√ Validation passed

claude plugin test 针对真实的 Claude Code 运行时运行插件的 *.test.ts 文件。测试通过 on 注册的钩子会在链中之后于模组运行,并模拟 Claude Code 将要给出的回答,因此你可以精确控制 $.session.usage() 返回的内容:

CODETypeScript

// tests/token-weather.test.ts
import { describe, expect, test } from "claude-code/testing";

describe("token-weather", () => {
  test("the band follows the context window", async ($, on) => {
    // Hooks registered here run after the mod and stub what Claude Code would answer.
    let tokens = 36_100;
    on("session.start", ($, e) => ({ cwd: e.cwd }));
    on("session.usage", () => ({
      value: { startedAt: 0, rateLimits: [], context: { tokens, window: 200_000, percent: Math.round(tokens / 2_000) } },
    }));
    on("turn.complete", () => ({ text: "" }));

    await $.session.start({ surface: "terminal", isInteractive: true, cwd: "/work" } as any);
    const ui = await $.ui.mount({
      plugin: "token-weather",
      surface: "terminal",
      component: "AbovePrompt",
      props: { hasSurvey: false, isWorking: false, maxRows: 10, bodyColumns: 120 },
    } as any);
    expect(await ui.find({ type: "Text", text: /Clear/ })).toBeDefined();

    tokens = 134_400;
    await $.turn.complete({ reason: "answer", answer: "ok", durationMs: 1 } as any);
    expect(await ui.find({ type: "Text", text: /Showers/ })).toBeDefined();
    expect(await ui.find({ type: "Text", text: /67% of context/ })).toBeDefined();
    expect(await ui.find({ type: "Text", text: /▲ \+98\.3k last turn/ })).toBeDefined();
    await ui.unmount();
  });
});

CODEText

$ claude plugin test ./token-weather
(pass) token-weather > the band follows the context window
 1 pass
 0 fail

测试还会检查第 3 步中的重绘行为。条带在 turn.complete 之后更新,而模组从未请求重绘。

模组就是一个插件,因此它的发布方式也相同。将其放入市场,市场可以简单到只是一个包含 .claude-plugin/marketplace.json 的文件夹:

CODEJSON

{
  "name": "my-mods",
  "owner": { "name": "You" },
  "plugins": [{ "name": "token-weather", "source": "./token-weather" }]
}

CODEShell

claude plugin marketplace add ./my-mods
claude plugin install token-weather@my-mods --scope user

要将你的版本与一个完成的版本进行比较,请参阅 anthropics/claude-code-playground 中的 完整的 Token Weather 模组。

分享你的模组

mod 是 Claude Code 插件,所以分享方式与其他插件完全相同,无需学习新东西。把 mod 放进一个带 marketplace 文件的 GitHub 仓库,该仓库就成了你的 marketplace。任何人都可以从中安装,你也可以用普通的 push 来更新它。

安装只需在 Claude Code 中执行三条命令:

CODEText

/plugin marketplace add your-org/my-mods
/plugin install token-weather@my-mods
/reload-plugins

重新加载后 mod 即会启动。如果没有显示,请重启 Claude Code。

mod 是在你机器上的 Claude Code 内部运行的代码,拥有与 Claude Code 相同的访问权限,由其发布者编写,而非 Anthropic。因此,安装 mod 应像安装软件包一样:先阅读仓库,只安装你信任的人发布的 mod。在你运行命令之前,不会安装任何东西。

Claude 目录接受包含 mod 的插件,你可以在 claude.ai/directory/manage 提交你的插件,这样人们无需你提供链接也能找到它。

另外两个 MOD

Token Weather 只负责观察和绘制。接下来的两个 mod 会介入事件、打开面板并接收输入。

两者的完整代码都在 anthropics/claude-code-playground 中:Blast Radius 和 Replay Theater,每个都附有 README,解释其构建方式。

Blast Radius:在运行前查看危险命令会改变什么

当 Claude 以 rm -rf、git reset --hard、git clean、强制推送或数据库迁移调用 Bash 时,Blast Radius 会拦截该调用。它会推算命令将触及哪些内容,并打开一个带有 Proceed 和 Cancel 的面板。按下 2,Claude 会收到附带原因的拒绝;按下 1,命令将按原样运行。

FIG DBlast Radius 拦截 rm -rf build,并列出它将删除的 9 个文件(1.1 MB)。Cancel 拒绝它;第二次尝试时,Proceed 执行它。

它使用三个钩子:Bash 上的 tool.call,以及 Pane 和 AbovePrompt 上的 ui.render。其核心是上表中的 "answer" 动作:

CODEJavaScript

on("tool.call", { tool: "Bash" }, async ($, e, next) => {
  const risk = classify(String(e.command ?? ""));
  if (risk === null) return next(e);                 // everything else runs as normal

  const report = await measure($, risk, await $.session.cwd());  // git status, git clean -n, du, ...
  held = { command: e.command, risk, report, decision: null };
  const opened = await $.ui.open({ id: "blast-radius", title: "Blast Radius", focus: true });
  if (!opened.isPlaced) held.where = "band";         // too narrow for a pane: draw above the prompt

  while (held.decision === null && !next.signal.aborted) {
    await $.process.run(["sleep", "0.25"]);          // time inside $ calls doesn't count against the hook's time limit
  }
  if (held.decision === "proceed") return next(e);   // let it run
  return { deny: `Blast Radius held this command: the user pressed Cancel. It would have: ${report.summary}.` };
});

它教给我们:

  • $.process.run 用于试运行。报告来自工具自身的命令:git status --porcelain、git clean -n、git log HEAD..origin/main、showmigrations。参数以 argv 数组形式传入,因此路径中的任何内容都不会作为 shell 代码执行。
  • 拦截调用。每个钩子在每次分发时有 10 秒的独立时间,但在 $ 调用内部等待的时间不计入。循环会等待短暂的 sleep 进程,直到按钮的 onPress 设置决定,并在 next.signal 中止时(你按下了 Esc)放弃。
  • 带快捷键的按钮。Button({ label: "Proceed", hotkey: "1", onPress }) 可以通过点击、Tab 加 Enter 或数字键操作。
  • 降级到提示带。当终端足够宽时,会在转录旁停靠一个面板。当 $.ui.open 回答 isPlaced: false 时,相同的报告会绘制在提示符上方:
A terminal with no side pane: a yellow-bordered box above the prompt shows the command, the two files with uncommitted changes it would discard, and numbered Proceed and Cancel choices.
FIG E在 120 列宽度下,Blast Radius 在提示符上方的提示带中绘制其对 git reset --hard 的报告

它是安全网,而非权限系统。它读取的是命令文本,因此 $(…)、别名以及调用 rm 的脚本都能绕过它。要硬性阻止,请使用权限规则。

Replay Theater:逐步查看上一轮的编辑

在一轮运行期间,Replay Theater 会记录每次 Edit 和 Write 调用:文件,以及编辑前后的文本。当该轮结束时,提示符上方会出现一条提示。按下 r(或输入 /replay),一个面板会逐个 diff 地展示编辑,带有一个编号步骤条和 Prev、Next 和 Close 按钮。

FIG FReplay Theater:跨 3 个文件进行 5 次编辑的重命名后的提示,随后是面板中的步骤 1 到 5

它从不阻止或更改编辑。它只是观察:

CODEJavaScript

on("tool.call", async ($, e, next) => {
  if (EDIT_TOOLS.has(e.tool)) state.pending.push(...(await stepsFor($, e)));  // old/new text → diff
  return next(e);                                                              // the edit runs untouched
});

on("turn.start", ($, e, next) => { if (!e.agentId) state.pending = []; return next(e); });

on("turn.complete", async ($, e, next) => {
  const r = await next(e);
  if (!e.agentId && state.pending.length) state.replay = state.pending;       // one replay per turn
  return r;
});

on("session.start", async ($, e, next) => {
  const r = await next(e);
  await $.command.register({ name: "replay", description: "Step through the last turn's file edits" });
  return r;
});
on("command.run", { command: "replay" }, async ($, e) => ({ text: (await openReplay($)) ? "Replaying" : "No edits" }));

它教会你什么:

  • 配对事件。turn.start 和 turn.complete 将编辑括在一起,每轮生成一次回放,而 e.agentId 则将子代理的轮次排除在分组之外。
  • 注册斜杠命令。在 session.start 中使用 $.command.register,然后在 command.run 上回答它。
  • 读取文件。对于 Write,$.fs.read 在写入落地之前获取旧内容,因此差异是真实的。
  • 放置是界面的职责。在全屏模式下,面板停靠在右侧。在 80 列宽度下,它在提示词上方内联打开。无论哪种方式,模组都绘制相同的树。
A tall terminal window with a magenta-bordered box above the prompt: a numbered step strip, the file greet.js, a one-line diff, and Prev, Next and Close buttons.
图 G80 列宽度下的回放剧场,内联绘制在提示词上方

四个值得保留的习惯

  • 依赖 Claude Code 为你编写的类型。每次加载你的模组时,Claude Code 都会将你的构建声明写入模组的 .claude-plugin/types/ 文件夹,因此你的编辑器和 tsc -p 无需额外步骤即可工作。它们是每个事件、$ 上的每个方法以及每个元素的 props 的参考。
  • 从 e.props 读取 props。hasSurvey、bodyColumns 等都存在那里,而不是在 e 本身上。
  • 为热重载做好规划。每次保存都会重新运行 register 和 session.start,因此请将数据保存在 $.state 中,而不是模块变量中。
  • 当绘图不显示时,读取日志。运行 claude --debug 并查找提示某个钩子返回了未通过验证的树的行。

你会模组什么?

这里的三个模组各来自一个问题:我的上下文有多满?、这个命令即将删除什么?以及Claude 刚刚更改了什么?你的问题会有所不同,而这正是重点。一些起步想法:

  • 基于 $.session.usage() 的成本或速率限制仪表,作为带有 $.ui.status 的状态行
  • 一个 prompt.submit 钩子,将你团队的规范添加到每个提示词中
  • 一个面板,列出 Claude 本次会话读取的文件,作为它所见内容的实时地图
  • 一个专注计时器,在长轮次结束时发送带有 $.ui.toast 的提示消息
  • 一个针对你的技术栈调整的 tool.call 守卫,例如生产 kubectl 上下文或 terraform apply

做了一个你现在每天都在使用的模组?在 X 或 LinkedIn 上发布它,并附上它运行时的 GIF 或截图,让其他开发者看到可能性。将插件放入市场(分享你的模组)并链接到它,这样任何喜欢它的人都可以用三条命令安装它。

阅读显示设置

调整正文排版

当前浏览器记住选择,应用于文章正文。手机上正文宽度以屏幕可用空间为限。

图表完整性提示

普通文章遇到图表保存问题时显示提示。