游乐游手机版
首页/AI热点日报/热点详情

Claude Code Notifier Plus开源项目:Claude Code按需提醒方案

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

前言

最近在用 Claude Code 处理开发任务时,一个很现实的问题越来越明显:AI 自动写代码时,人其实没必要始终盯着屏幕。

比如把一个重构任务交给工具执行后,人可能会切到浏览器查资料、翻文档,或者去另一个项目继续工作。几分钟后再切回来,才发现它早已停在权限确认界面;或者任务其实已经完成,但自己完全没有察觉。尤其当 VS Code 项目窗口一多,这种来回切换带来的低效会更加突出。

所以就做了一个小工具:Claude Code Notifier Plus。它的目标非常明确:当 Claude Code 需要用户关注时,通过系统通知及时提醒,并且支持点击通知直接返回对应的 VS Code 窗口或终端窗口。

为什么还要再做一个通知插件

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

这个权限并不是用于读取屏幕内容,而是允许它将对应窗口 raise 到前台。如果没有这个权限,点击通知通常只能激活 App,本身却无法精确切换到某个具体项目窗口。

整体架构

先看整体流程:

从实现上看,主要拆成以下几块:

  • 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 在某些环境下不可用,就退回到 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,把具体窗口 raise 到前台。在 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 引用。它传递的是文件引用,而不只是简单复制一段文本,因此后续 Claude Code 还可以基于完整上下文继续分析和读取文件。

配置项

目前所有配置都放在 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 工具越来越强,工程师的工作方式也会逐步变化。以前更关注的是“如何让工具更聪明”,现在也需要思考“如何让工具在合适的时机打断人”。打断太少,容易错过确认和反馈;打断太多,又会变成新的噪音。这个插件就是围绕这个小而实际的问题做出的一次优化和实践。

来源:https://juejin.cn/post/7663405446762446874

相关热点

继续查看同栏目近期热点。

延伸阅读

补充最近整理过的热点入口。