Claude Code Notifier Plus开源项目:需要时才提醒

作者:技术拾光

时间:2026-08-02

浏览:383

ClaudeCodeNotifierPlus是一款开源工具,用于在ClaudeCode需要用户确认或任务完成时发送系统通知,支持点击跳转至对应VSCode或终端窗口。它覆盖CLI和VSCode扩展两种模式,通过文件通信、锁机制和去重逻辑避免重复通知,并附带项目名和任务摘要,提升多窗口下的工作效率。

文章目录

前言

最近使用 Claude Code 进行开发工作时,一个实际问题逐渐凸显:AI 编写代码时,开发人员无需全程紧盯屏幕。

例如,让工具执行重构任务后,切换到浏览器查阅资料、查看文档,或者前往另一个项目继续处理事务。几分钟后切回,发现工具早已停在权限确认环节;或者任务已经完成,但毫不知情。项目窗口一多,这种反复切换变得更加明显。

因此,诞生了一个小工具:Claude Code Notifier Plus。它的目标十分明确:当 Claude Code 需要人工介入时,通过系统通知进行提醒,点击通知后能够跳转到对应的 VS Code 窗口或终端窗口。

Claude Code Notifier Plus开源项目:需要时才提醒

为什么重新开发通知插件

Claude Code 本身具备 Hook 机制,理论上只需监听通知事件并调用系统通知工具即可。最初也是按照这个思路进行的。

但在实际使用中遇到一个问题:在 VS Code 扩展模式下,依赖 Notification hook 的方式并不可靠。Claude Code 的 VS Code 扩展会在其 webview 内部处理部分交互,许多 CLI 模式下可用的通知逻辑,到了 VS Code 中就会失效。

因此,该插件没有继续依赖单一的 Notification hook,而是直接注册更具体的事件:

场景Claude Code Hook
请求工具权限PermissionRequest
需要用户确认或回答Elicitation
主任务结束Stop
Subagent 结束SubagentStop

这样,CLI 和 VS Code 扩展模式都能够覆盖。通知的触发条件也更加清晰:不是"任何通知都弹出",而是围绕真正需要返回处理的几个关键节点。

功能特点

当前版本为 1.5.0,主要功能如下:

  • 双模式支持:终端中的 Claude Code、VS Code 扩展中的 Claude Code 均可触发通知。
  • 纯 CLI fallback:即使 VS Code 未启动,也可以直接发送系统通知。
  • 点击跳转:macOS 上配合 terminal-notifier,点击通知可跳回对应的 VS Code 项目窗口,或 Terminal / iTerm2 / Warp 等终端窗口。
  • 任务上下文:通知中会携带项目名和任务摘要,无需猜测是哪个窗口。
  • 多实例安全:多个 VS Code 窗口同时运行时,通过 marker、lock、去重逻辑避免重复弹出。
  • 焦点感知与延迟通知:VS Code 处于前台时可不弹通知,也可设置延迟,让 VS Code 内部 popup 有机会先被手动取消。
  • 选中代码发送到 Claude Code:选中代码后,可通过右键、快捷键、编辑器按钮,将 @file#line 引用插入 Claude Code 输入框。

快速安装指南

在 VS Code 扩展商店搜索 Claude Code Notifier Plus,或通过命令行安装:

 复制代码code --install-extension tutuge.claude-code-notifier-plus

如果在 macOS 上希望支持点击通知后跳转到准确窗口,推荐安装:

 复制代码brew install terminal-notifier

还需要为 terminal-notifier 授予一次辅助功能权限:

 复制代码系统设置 -> 隐私与安全性 -> 辅助功能 -> 开启 terminal-notifier

该权限的作用并非读取内容,而是允许其将对应窗口提升到前台。没有此权限时,点击通知通常只能激活 App,无法精确切换到某个项目窗口。

整体架构解析

先看整体流程:

Claude Code Notifier Plus开源项目:需要时才提醒

实现上拆分为几个模块:

  • extension.js:VS Code 扩展入口,负责安装 hook、监听 pipe 文件、读取配置、发送通知。
  • hooks/notify.js:被 Claude Code hook 调用的脚本,负责将不同事件整理成统一 payload。
  • lib/hook-installer.js:自动修改 ~/.claude/settings.json,注册事件 hook。
  • lib/system-notification.js:封装 macOS / Windows / Linux 系统通知,以及 macOS 上的点击跳转。
  • lib/payload.js:解析 payload、过滤事件类型。

简单来说,Claude Code 事件先进入 notify.js,再写入一个固定文件;VS Code 扩展监听该文件,最后根据配置决定是否发送系统通知。

这个设计看起来多了一层,但它解决了一个实际问题:hook 脚本和 VS Code 扩展不在同一个进程中。hook 是 Claude Code 启动的 Node 进程,扩展运行在 VS Code extension host 中,两者需要一个足够简单、跨环境稳定的通信方式。

自动注册 Hook 机制

插件启动时会将 hook 写入 ~/.claude/settings.json。核心逻辑如下:

 复制代码const VSCODE_HOOK_EVENTS = [
  'PermissionRequest',
  'Elicitation',
  'Stop',
  'SubagentStop',
];function buildHookEntry(notifyScriptPath, options) {
  return {
    type: 'command',
    command: `node "${notifyScriptPath}" # claude-code-notifier-plus`,
    ...options,
  };
}

这里有两个细节:

  1. hook entry 中带有一个 sentinel:# claude-code-notifier-plus。
  2. 每次安装前会先清理旧的 managed hook,再写入新的 hook。

为什么要这样做?因为用户自己的 settings.json 中可能已经有其他 hook,插件不能粗暴覆盖整个 hooks 配置。sentinel 的作用就是只识别自己管理的那一段:

 复制代码function isManaged(hookEntry) {
  return Array.isArray(hookEntry.hooks) &&
    hookEntry.hooks.some((h) =>
      typeof h.command === 'string' && h.command.includes(SENTINEL)
    );
}

这样,后续升级、路径变化、卸载时,都可以只处理插件自己的 hook,尽量不影响用户已有配置。这是编写工具类扩展时容易忽略的一点:自动化配置要可回收、可重复执行、不能污染用户配置。

Payload 归一化处理

Claude Code 传给 hook 的数据不应直接透传到通知层。不同事件的数据结构不同,通知层也不需要了解所有细节,因此 notify.js 会先进行归一化处理:

 复制代码const EVENT_MAP = {
  PermissionRequest: (data) => ({
    event: 'permission_prompt',
    text: ` Requesting permission: ${data.tool_name || 'a tool'}`,
  }),
  Elicitation: () => ({
    event: 'elicitation_dialog',
    text: ' Claude has a question for you',
  }),
  Stop: () => ({
    event: 'idle_prompt',
    text: ' Task completed',
  }),
  SubagentStop: () => ({
    event: 'subagent_stop',
    text: ' Subagent finished',
  }),
};

最终写给扩展侧的是统一格式:

 复制代码{
  event,
  text,
  project,
  taskTitle,
  cwd,
  termBundleId,
  ts: Date.now()
}

其中 project 来自 cwd 的目录名,taskTitle 会从 transcript 中取第一条用户消息并截断。这样通知中就不只是"任务完成",而是能看到类似:

 复制代码 MyBlog ·  写一篇 Claude Code Notifier Plus 的文章
 任务完成

项目窗口一多,这个上下文非常重要。否则通知弹出后,还需要自己回忆"这是哪个任务",体验会差很多。

Pipe、Marker 和 Lock 机制

扩展和 hook 之间使用了三个文件:

 复制代码const CLAUDE_DIR = path.join(os.homedir(), '.claude');
const NOTIFY_FILE = path.join(CLAUDE_DIR, 'notify-plus.pipe');
const MARKER_FILE = path.join(CLAUDE_DIR, 'notify-plus.active');

notify-plus.pipe 并非严格意义上的 Unix pipe,而是一个固定路径的普通文件。hook 将 JSON payload 写入,扩展使用 fs.watch 监听变化:

 复制代码fileWatcher = fs.watch(NOTIFY_FILE, () => {
  handleNotification();
});

如果 fs.watch 不可用,则 fallback 到 fs.watchFile:

 复制代码fs.watchFile(NOTIFY_FILE, { interval: 500 }, (curr, prev) => {
  if (curr.mtimeMs > prev.mtimeMs) handleNotification();
});

notify-plus.active 用于记录当前活跃的 VS Code extension host PID。hook 运行时会检查该文件,如果发现扩展未启动,则直接走 CLI fallback 发送通知:

 复制代码function isExtensionActive() {
  const pids = content.split('n').filter(Boolean).map(Number);
  return pids.some(pid => {
    try {
      process.kill(pid, 0);
      return true;
    } catch (_) {
      return false;
    }
  });
}

还有一个 lock 文件:notify-plus.pipe.lock。多个 VS Code 窗口都可能监听同一个 pipe,如果没有 lock,同一条 payload 可能被多个窗口同时读取并多次弹出。因此,扩展在处理通知前会先获取锁:

 复制代码function acquireLock() {
  const lockFile = NOTIFY_FILE + '.lock';
  const fd = fs.openSync(lockFile, 'wx');
  fs.closeSync(fd);
  return true;
}

这里使用 wx,即文件不存在时才创建成功。这种方式虽然简单,但足以处理"多个扩展进程同时抢同一条通知"的问题。

此外,扩展中还有一个 2 秒窗口的去重机制:

 复制代码function isDuplicate(event, text) {
  const key = `${event}:${text}`;
  const now = Date.now();
  if (key === lastNotifKey && now - lastNotifTime < 2000) return true;
  lastNotifKey = key;
  lastNotifTime = now;
  return false;
}

Lock 解决跨窗口竞争,dedup 解决短时间内同一事件重复触发。这两个点关注的问题不同,都需要具备。

CLI 与 VS Code 的区分处理

点击通知要跳回哪里?这个问题比"发送一条通知"更复杂。

如果 Claude Code 运行在 VS Code 扩展中,点击通知应回到 VS Code 项目窗口。如果 Claude Code 运行在 iTerm2 或 Warp 中,点击通知应回到对应终端。最麻烦的是:VS Code 中也可能有 terminal,进程树判断很容易误判。

因此,hook 中优先检查 VSCODE_PID:

 复制代码const isVSCode = !!process.env.VSCODE_PID ||
  process.env.TERM_PROGRAM === 'vscode';const detectedTermBundleId = isVSCode
   ''
  : (getTerminalBundleId() || '');

如果是 VS Code 环境,就不再尝试检测终端,避免将 VS Code 内部 terminal 误认为外部终端。只有纯 CLI 场景,才去查看 TERM_PROGRAM、LC_TERMINAL,再不行就沿进程树向上查找:

 复制代码if (/^Terminal$/.test(comm)) return 'com.apple.Terminal';
if (/iTerm/i.test(comm)) return 'com.googlecode.iterm2';
if (/[Ww]arp/.test(comm)) return 'dev.warp.Warp-Stable';

这个判断并非追求覆盖所有终端,而是优先保证主流场景不跳错。在工具类插件中,"不支持某些边缘终端"通常比"跳到错误窗口"更容易被接受。

macOS 点击跳转实现

macOS 上普通的 osascript display notification 可以发送通知,但点击行为不易控制。因此,增强模式下使用 terminal-notifier。

发送通知时会带上 -activate 和 -execute:

 复制代码const args = [
  '-title', ' Claude Code',
  '-message', safe,
  '-activate', activateBundleId,
  '-group', groupId,
];args.push('-execute', `${executeScript}; ${removeCmd}`);

-activate 负责激活 App,-execute 负责进一步执行 AppleScript,将具体窗口提升到前台。VS Code 场景下会按窗口标题匹配项目名:

 复制代码set wins to every window whose title contains "MyBlog"
if (count of wins) > 0 then
  perform action "AXRaise" of item 1 of wins
end if

该实现依赖辅助功能权限,因此插件首次启动时会提示用户授权。没有授权也并非完全不可用,只是从"跳到具体项目窗口"降级为"激活 VS Code 或终端 App"。

此外,通知中带有 groupId,点击后会执行:

 复制代码terminal-notifier -remove 'claude-notif-...'

这样,点击通知后,通知中心中不会一直堆积旧消息。这个细节对于高频使用来说非常重要,否则一天运行下来通知中心会变得混乱。

选中代码发送到 Claude Code

除了通知功能外,插件还增加了一个小功能:将选中的代码快速发送给 Claude Code。

入口包括:

  • 编辑器右上角按钮。
  • 右键菜单。
  • Lightbulb code action。
  • 快捷键 Cmd+Shift+I / Ctrl+Shift+I。
  • 资源管理器中右键某个文件。

实现上并未重新发明一套与 Claude Code webview 通信的协议,而是复用了 Claude Code 扩展已有的命令:

 复制代码await vscode.commands.executeCommand('claude-vscode.insertAtMention');

如果 Claude Code 面板尚未打开,则先打开最近的 Claude Code,再插入:

 复制代码try {
  await vscode.commands.executeCommand('claude-vscode.insertAtMention');
} catch {
  await vscode.commands.executeCommand('claude-vscode.editor.openLast');
  await vscode.commands.executeCommand('claude-vscode.insertAtMention');
}

这个功能看似与通知无关,但在实际工作流中非常顺手:选中一段代码,按快捷键,Claude Code 输入框中就会出现 @file#line-range 引用。它获取的是文件引用,而非单纯复制文本,因此后续还能基于完整上下文继续读取文件。

配置项说明

目前配置都放在 claudeCodeNotifierPlus.* 下:

 复制代码{
  "claudeCodeNotifierPlus.notifyOnPermissionRequest": true,
  "claudeCodeNotifierPlus.notifyOnQuestion": true,
  "claudeCodeNotifierPlus.notifyOnTaskComplete": true,
  "claudeCodeNotifierPlus.notifyOnSubagentStop": false,
  "claudeCodeNotifierPlus.systemNotification": true,
  "claudeCodeNotifierPlus.sound": false,
  "claudeCodeNotifierPlus.notificationDelay": 0,
  "claudeCodeNotifierPlus.showVSCodePopup": false,
  "claudeCodeNotifierPlus.clickToFocus": "window",
  "claudeCodeNotifierPlus.suppressWhenFocused": false
}

这里默认不开启 subagent_stop,是因为 subagent 在复杂任务中可能比较频繁,如果每个子任务完成都弹出通知,会显得比较嘈杂。默认只提醒主流程中真正需要注意的事件。

notificationDelay 这个配置也比较实用。例如,同时打开了 VS Code popup 和系统通知,可以设置几秒延迟;如果在这几秒内手动点掉 VS Code popup,就取消系统通知,避免重复打扰。

总结与展望

Claude Code Notifier Plus 本质上不是一个复杂的项目,它解决的是一个非常具体的效率问题:让 Claude Code 在需要人工介入时及时通知用户,而不是让用户一直盯着它。

本次实现中重点关注了几个方面:

  • 不依赖单一 Notification hook,而是监听更明确的事件。
  • hook 和 VS Code extension host 之间使用固定文件通信,简单、可调试、跨环境稳定。
  • CLI 和 VS Code 场景分开处理,避免点击通知跳错窗口。
  • 自动修改用户配置时,使用 sentinel 标记自己的内容,实现可重复安装和可清理。
  • 多窗口场景下使用 marker、lock、dedup 处理重复通知。

总的来说,AI Coding 工具越来越强大之后,工程师的工作方式也会随之变化。过去关注的是"如何让工具更聪明",现在也需要关注"如何让工具在合适的时候打断用户"。打断太少会错过确认,打断太多又会变成噪音。这个插件就是在这一小问题上的一次实践。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章