@quecue/dev-notify 0.1.0-alpha.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # 更新记录
2
2
 
3
+ ## 0.1.0 — 2026-11-03
4
+
5
+ ### 改进
6
+
7
+ - `setup` 合并为一次汇总确认:默认同时保存通知配置、发送测试通知并安装 Claude Code 通知 hooks,可用 `--no-test` / `--no-hooks` 跳过。
8
+ - 所有隐藏输入项在提示中明确标注「隐藏输入,不回显」。
9
+ - README 和 `--help` 补充完整的环境变量清单与路径变量说明。
10
+ - 编辑已有配置不再先询问是否继续;渠道选择显示当前值,直接回车保留。
11
+ - 远程回复授权移出 `setup` 主流程,改为提示单独运行 `reply setup`。
12
+ - 统一润色用户可见的中文提示与文档,改为更自然、直接的说法;安全边界和操作含义保持不变。
13
+ - 移除早期原型源码、测试和文档,主分支只保留 Node/TypeScript 实现;旧 `system` 凭据引用的迁移提示继续保留。
14
+
15
+ ### 修复
16
+
17
+ - 飞书「在 Telegram 回复 Claude」跳转改为发送普通提示并清除客户端缓存的旧强制回复目标,不再自动进入回复框,避免输入被提交到错误会话;Telegram 内联按钮仍自动进入回复状态。请长按提示或目标通知并选择「回复」后输入。
18
+
3
19
  ## 0.1.0-alpha.3 — 2026-11-03
4
20
 
5
21
  ### 新增
@@ -29,7 +45,7 @@
29
45
 
30
46
  - Windows x64 / Node 24:PC 优先、Telegram 优先、多选、自定义输入、不同答案竞争及重复提醒修复已由用户完成实机验收。
31
47
  - 自动化回归共 157 项,156 通过、0 失败、1 项需要显式启用的原生凭据测试跳过;包含真实 dev-notify CLI/后台进程的模拟网络闭环。
32
- - 核对的 Claude Code 版本为 2.1.316;其他版本和特殊宿主不保证采用同一并行路径。
48
+ - 核对的 Claude Code 版本为 2.1.316;其他版本和特殊宿主可能采用不同的并行路径。
33
49
  - macOS 终端输入和权限流程、Node 22 最低版本尚未实机验收。工具权限应答的实机验收不包含在上述问答测试中。
34
50
  - 网络中断、强杀和撤销授权后不能保证最终回执送达;详情见 SECURITY.md。
35
51
 
package/README.md CHANGED
@@ -1,21 +1,23 @@
1
1
  # dev-notify
2
2
 
3
- 跨平台开发任务通知工具,使用 **TypeScript / Node.js**,通过 npm 分发,无需 Python 或独立可执行文件。通用通知不依赖 Claude Code、Unity 或项目目录。
3
+ 跨平台开发任务通知工具,使用 **TypeScript / Node.js**,通过 npm 分发,不需要独立可执行文件或其他脚本运行时。通用通知不依赖 Claude Code、Unity 或项目目录。
4
4
 
5
- **npm 包名为 `@quecue/dev-notify`。`0.1.0-alpha.3` 提供 Claude Code 回复、原生 PC 与 Telegram 双端答题及结果反馈。** 版本变更与升级说明见 [CHANGELOG.md](CHANGELOG.md);公开版本以 npm registry 为准。
5
+ **npm 包名为 `@quecue/dev-notify`。`0.1.0` 提供 Claude Code 回复、原生 PC 与 Telegram 双端答题及结果反馈。** 版本变更与升级说明见 [CHANGELOG.md](CHANGELOG.md);公开版本以 npm registry 为准。
6
6
 
7
7
  ## 安装与使用
8
8
 
9
- 要求 Node.js 22 或以上,首发目标 Windows、macOS。运行时依赖锁定版本的 `@napi-rs/keyring`,由 npm 安装对应系统的预编译原生组件,不需要 Python、Rust 或 C++ 编译器。不要省略 npm optional 依赖。
9
+ 要求 Node.js 22 或以上,首发目标 Windows、macOS。运行时依赖锁定版本的 `@napi-rs/keyring`,由 npm 安装对应系统的预编译原生组件,不需要额外的语言工具链或编译器。请保留 npm 的 optional 依赖。
10
10
 
11
- 从 npm 安装当前已发布的 Alpha:
11
+ 从 npm 安装:
12
12
 
13
13
  ```sh
14
- npm install -g @quecue/dev-notify@alpha
14
+ npm install -g @quecue/dev-notify
15
15
  dev-notify --version
16
16
  dev-notify setup
17
17
  ```
18
18
 
19
+ 也可用环境变量覆盖凭据和接收人:`TELEGRAM_BOT_TOKEN`、`TELEGRAM_CHAT_ID` / `TELEGRAM_CHAT_IDS`、`FEISHU_WEBHOOK`、`FEISHU_SECRET`;路径变量 `DEV_NOTIFY_HOME`、`CLAUDE_CONFIG_DIR`。显式存在的变量优先,空值也会覆盖;完整规则和自定义变量名见下文「路径、模板和环境变量」。
20
+
19
21
  `0.1.0-alpha.1` 不含 Claude Code hooks;以下回复交互要求 `0.1.0-alpha.3` 或以上。升级后须重新运行 `dev-notify integration claude-code install` 更新完整 hooks,并停止、重新启动已有回复桥接;已有有效授权和通知凭据无需重新配置。使用自定义配置时,继续传入原来的 `--config` 路径。源码开发可在工程目录构建后执行:
20
22
 
21
23
  ```sh
@@ -36,23 +38,23 @@ node dist/cli.js send --title "分段预览" --content-file result.txt --dry-run
36
38
 
37
39
  若需要本地安装为 `dev-notify` 命令,在构建后运行 `npm install --global .`,之后可执行 `dev-notify setup`、`dev-notify send ...`。这是本地源码安装,不是从公开 registry 安装;本工程不会自动进行全局安装或发布。
38
40
 
39
- 正式包的安装/升级/卸载由 npm 负责。**卸载程序不会自动清除用户凭据**;如需清理,请先交互执行 `dev-notify credentials clear`,再卸载。该命令只删除当前配置引用的 Node 凭据,不删除其他账号、其他配置或 Python 原型凭据。
41
+ 正式包的安装、升级和卸载由 npm 负责。**卸载前建议先清理凭据**:npm 卸载不会自动删除用户凭据。请先交互执行 `dev-notify credentials clear`,再卸载。该命令只删除当前配置引用的 Node 凭据,不会动其他账号、其他配置或旧版原型凭据。
40
42
 
41
43
  ## 已提供的功能
42
44
 
43
- - Telegram、飞书双渠道通知,按渲染后的长度分段,不静默截断。
45
+ - Telegram、飞书双渠道通知,按渲染后的长度分段,不会悄悄截断内容。
44
46
  - 文本、严格 UTF-8 文件或标准输入;支持渠道选择、通知级别、辅助信息。
45
47
  - 交互配置、隐藏输入、系统凭据存储、环境变量覆盖。
46
48
  - 配置更新冲突检测、失败恢复、凭据清理。
47
- - 可选测试通知:只有额外确认后才发送,默认不发送。
48
- - 可选 Claude Code 集成:最终回复、等待通知;确认后合并安装,支持独立查询和移除。
49
+ - 安装时默认随汇总确认发送测试通知,可用 `--no-test` 跳过;测试失败不会撤销已保存的配置。
50
+ - 默认随汇总确认安装 Claude Code 集成:最终回复、等待通知;可用 `--no-hooks` 跳过,支持独立查询和移除。
49
51
  - 独立授权的 Telegram 按钮回复、直接回复消息、飞书跳转;逐段映射到具体会话。
50
52
  - 普通回复本地排队、终端提交与 `UserPromptSubmit` 收到确认,状态不混淆。
51
53
  - `AskUserQuestion` 原始选项、单选/多选、自定义答案,通过官方 hook JSON 直接回传。
52
54
  - 另外选择启用后,可逐次允许或拒绝工具权限,或填写自定义拒绝说明;不写永久权限规则。
53
55
  - `paths`、`doctor`、`status`、`send --dry-run` 离线诊断;`reply start/status/stop/disable/run` 管理桥接。
54
56
 
55
- **通用通知不依赖 Claude Code。**npm 安装本身不修改其他应用配置,也不启动服务。通知 hooks 和远程输入授权分开,默认只通知;授权后才允许回复桥接轮询 Telegram。没有自动配对、任意远程 shell 执行器或系统开机自启。
57
+ **通用通知不依赖 Claude Code。** npm 安装不会修改其他应用的配置,也不会启动后台服务。通知 hooks 和远程输入是两层独立授权:默认只发送通知,授权后才启用回复桥接。没有自动配对、远程 shell 执行器或系统开机自启。
56
58
 
57
59
  ## 配置向导
58
60
 
@@ -60,15 +62,15 @@ node dist/cli.js send --title "分段预览" --content-file result.txt --dry-run
60
62
  node dist/cli.js setup
61
63
  ```
62
64
 
63
- 1. 选择 Telegram、飞书或两者。
64
- 2. 隐藏输入 Token、webhook 和可选签名密钥。无法安全隐藏输入时停止,不回退到明文回显。
65
- 3. Telegram 手动填写数字 chat_id,多个用逗号分隔;用户需要先启动 Bot。不会自动启动 getUpdates 配对。
66
- 4. 展示渠道和接收人数,最终确认后保存。输入错误可以重试,Ctrl+C 取消。
67
- 5. 保存后单独询问是否发送固定内容的测试通知。测试失败不会撤销已成功保存的配置。
68
- 6. 另行询问是否配置 Claude Code 自动通知;展示用户级设置路径、正文发送范围后,再次确认才合并 hooks。拒绝时已有集成保持原样,不会被禁用。
69
- 7. Telegram 可用时另外询问是否授权回复;选择具体可信私聊用户、是否允许逐次处理工具权限,再确认。拒绝不改变已有授权,不自动启用全部通知接收人。
65
+ 1. 选择 Telegram、飞书或两者;编辑已有配置时显示当前选择,直接回车保留。
66
+ 2. Token、webhook 和签名密钥均以隐藏输入读取(不回显)。终端无法安全隐藏输入时,向导会停止,不会改用明文输入。
67
+ 3. Telegram 手动填写数字 chat_id,多个用逗号分隔;用户需要先启动 Bot。这里不会自动启动 getUpdates 配对。
68
+ 4. 汇总展示渠道、接收人数、测试通知、Claude Code 用户级设置路径和发送范围,一次确认后保存。输入错误可以重试,Ctrl+C 取消。
69
+ 5. 确认后默认发送固定内容的测试通知(`--no-test` 跳过)。测试失败不会撤销已成功保存的配置。
70
+ 6. 确认后默认合并安装 Claude Code 通知 hooks(`--no-hooks` 跳过)。已有集成保持原样,不重复添加。
71
+ 7. 远程回复授权不在 setup 中询问;Telegram 可用时提示另行运行 `reply setup`,选择具体可信私聊用户并单独确认。
70
72
 
71
- 编辑已有配置需要先确认;密钥留空保留已存值,可选签名输入 `-` 清除。未选渠道会禁用,新条目只保留所选渠道的凭据。环境变量仍然优先;向导会提示存在覆盖,测试使用最终生效值,而不是忽略环境的刚输入值。
73
+ 编辑已有配置不再二次询问是否继续;密钥留空保留已存值,可选签名输入 `-` 清除。没有选中的渠道会被禁用,新条目只保留所选渠道的凭据。环境变量仍然优先(`TELEGRAM_BOT_TOKEN`、`TELEGRAM_CHAT_ID` / `TELEGRAM_CHAT_IDS`、`FEISHU_WEBHOOK`、`FEISHU_SECRET`);向导会提示哪些变量正在覆盖配置,测试和最终保存也都使用生效后的值。完整列表见「路径、模板和环境变量」。
72
74
 
73
75
  自动化使用无密钥模板和 CI secrets:
74
76
 
@@ -76,11 +78,11 @@ node dist/cli.js setup
76
78
  node dist/cli.js setup --template --yes
77
79
  ```
78
80
 
79
- 模板永不覆盖已有配置。`--yes` 只能配合 `--template`,不能跳过交互密钥保存确认。
81
+ 需要只保存配置、不发送测试或不安装 hooks 时,使用 `setup --no-test` / `setup --no-hooks`(可组合)。模板不会覆盖已有配置。`--yes` 只能配合 `--template` 使用;保存密钥前仍然需要交互确认。
80
82
 
81
83
  ## Claude Code 通知集成
82
84
 
83
- 要求使用支持命令 hook `command` + `args`(无 shell exec 形式)及 `async` 的新版 Claude Code。实际加载情况在 Claude Code 的 `/hooks` 中确认;旧版本不支持这些字段时应升级,不降级到不安全的路径拼接。
85
+ 要求使用支持命令 hook `command` + `args`(无 shell exec 形式)及 `async` 的新版 Claude Code。实际加载情况在 Claude Code 的 `/hooks` 中确认;如果旧版本不支持这些字段,请升级 Claude Code,本工具不会退回不安全的路径拼接方式。
84
86
 
85
87
  已有通知渠道配置时,无需重新输入凭据:
86
88
 
@@ -93,16 +95,16 @@ dev-notify integration claude-code remove
93
95
  源码验证时将上述 `dev-notify` 替换为 `node dist/cli.js`。安装可用 `--config <绝对或相对路径>` 绑定指定通知配置;执行时将其解析为绝对路径并保存到 hook 参数。自定义 `DEV_NOTIFY_HOME` 的使用者应在安装时设置该变量。
94
96
 
95
97
  - **配置范围**:默认用户级 `~/.claude/settings.json`,设置 `CLAUDE_CONFIG_DIR` 时使用该绝对目录下的 `settings.json`,覆盖该 Claude Code 配置目录下的全部项目。不会扫描或修改每个项目的设置。
96
- - **发送范围**:Stop 直接使用事件的 `last_assistant_message` 转发最终回复;没有正文则明确发送完成提示,不回读可能滞后的 transcript。不发送思考或工具结果。启用远程回复后还会发送结构化问题、选项,以及另行授权的工具权限请求参数;它们可能含敏感信息,启用前务必检查接收目标。
97
- - **等待提醒**:转发 `permission_prompt`、`elicitation_dialog`、`elicitation_url_dialog`、`agent_needs_input`;忽略 `idle_prompt`、认证成功等其他通知。当前会话最后一项调用为选择题、没有其他未结束调用,且结构化通知已完整投递时,按接收人抑制重复的 `permission_prompt`;飞书单独核对投递成功及目标。短期保留已结束题目的覆盖状态以处理迟到提醒;新一轮输入、后续其他工具调用、会话切换或交互有效期结束后不再沿用。未覆盖接收人、部分/失败投递及无法核验的情况仍发送通用提醒,其他通知类型不受影响。Notification 本身没有完整选项,不能拿提示猜答案;可靠选项交互见下节。不支持的 MCP 表单/URL 等请求保留本机处理。
98
+ - **发送范围**:Stop 直接使用事件的 `last_assistant_message` 转发最终回复;没有正文时会发送明确的完成提示,不会回读可能滞后的 transcript。不会发送思考过程或工具结果。通知底部附带项目绝对路径、会话名称(本地会话注册表可读取时)和会话 ID。启用远程回复后还会发送结构化问题、选项,以及另行授权的工具权限请求参数;它们可能含敏感信息,启用前请检查接收目标。
99
+ - **等待提醒**:转发 `permission_prompt`、`elicitation_dialog`、`elicitation_url_dialog`、`agent_needs_input`;忽略 `idle_prompt`、认证成功等其他通知。当前会话最后一项调用为选择题、没有其他未结束调用,且结构化通知已完整投递时,按接收人抑制重复的 `permission_prompt`;飞书会单独核对投递成功及目标。短期保留已结束题目的覆盖状态,以处理迟到提醒;新一轮输入、后续其他工具调用、会话切换或交互有效期结束后不再沿用。没有覆盖到的接收人、只投递了一部分或投递失败、以及无法核验的情况,仍会发送通用提醒;其他通知类型不受影响。Notification 事件本身不包含完整选项,因此不会根据提示文字猜测答案;可靠的选项交互见下节。不支持的 MCP 表单/URL 等请求保留在本机处理。
98
100
  - **合并与移除**:保留权限、环境变量和其他 hooks;重复安装不会累加。同一用户级设置中的本工具 hooks 会在确认后更新到当前 Node、CLI 和通知配置路径。只移除带本工具标记及匹配参数结构的处理器,不删除设置文件或凭据。
99
- - **与旧工具共存**:原 Python hooks、项目级或插件 hooks 不会自动删除,可能造成重复通知。安装时会提示当前用户级文件中的其他处理器;旧集成需由用户自行确认停用。
100
- - **事件与时序**:安装 Stop、Notification、SessionStart、UserPromptSubmit、SessionEnd、PreToolUse、PermissionRequest、PostToolUse、PostToolUseFailure、PermissionDenied,共 10 类。Notification 异步;其他事件同步登记状态。PreToolUse 只登记调用身份,不发送通知、不等 Telegram,超时上限 10 秒;PermissionRequest 才等待远程答案。Stop 包含通知发送,可能延迟本地返回;通知/生命周期上限 120 秒。
101
- - **交互与失败**:PermissionRequest hook 上限 360 秒,远程答题窗口 5 分钟;普通交互终端保留原生窗口,由 Claude 仲裁先到的完整答案。不把 hook 改成 async,也不模拟按键。无回答、撤销、超时或发送失败不默认选择。内部错误输出安全摘要到 stderr 并退出 0,不用会让 Stop 继续的退出码 2。当前已核对 Claude Code 2.1.316 的并行实现,其他版本、后台子任务及特殊宿主需另行验收,不能泛化为所有宿主都并行。
101
+ - **与旧工具共存**:旧版、项目级或插件 hooks 不会被自动删除,可能造成重复通知。安装时会提示当前用户级文件中的其他处理器;旧集成需由用户自行确认停用。
102
+ - **事件与时序**:安装 Stop、Notification、SessionStart、UserPromptSubmit、SessionEnd、PreToolUse、PermissionRequest、PostToolUse、PostToolUseFailure、PermissionDenied,共 10 类。Notification 异步;其他事件同步登记状态。PreToolUse 只登记调用身份,不发送通知、不等待 Telegram,超时上限 10 秒;PermissionRequest 才等待远程答案。Stop 包含通知发送,可能延迟本地返回;通知/生命周期上限 120 秒。
103
+ - **交互与失败**:PermissionRequest hook 上限 360 秒,远程答题窗口 5 分钟;普通交互终端保留原生窗口,由 Claude 仲裁先到的完整答案。hook 保持同步,不会改成 async,也不会模拟按键。没有回答、撤销授权、超时或发送失败时,都不会默认选择。内部错误会把安全摘要输出到 stderr,并以 0 退出;这里不使用会让 Stop 继续的退出码 2。当前已核对 Claude Code 2.1.316 的并行实现;其他版本、后台子任务及特殊宿主需要另行验收,不能据此推断所有宿主都并行。
102
104
  - **输入边界**:hook stdin 最多 8 MiB,接收超时 15 秒;超限拒绝而非截断。正常长正文沿用双渠道分段。`stop_hook_active: true` 的继续轮次不发送完成通知。
103
- - **加载限制**:安装后在 `/hooks` 检查并按界面确认,必要时重启会话。不会解除 `disableAllHooks` 或绕过托管策略。`status` 仅检查该用户级文件,不等于运行时已加载或网络验证。
105
+ - **加载限制**:安装后在 `/hooks` 检查并按界面确认,必要时重启会话。不会解除 `disableAllHooks`,也不会绕过托管策略。`status` 只检查该用户级文件,不检查运行时加载状态或网络连通性。
104
106
  - **环境和路径**:异步进程使用 Claude Code 的环境,可能与手动终端不同;环境变量优先规则不变。切换 Node 版本或移动源码/包目录后,应重新运行 `install` 更新固定路径。
105
- - **写入保护**:使用排他 `.dev-notify.lock`、内容快照、同目录临时文件及原子替换;无效 JSON 不覆盖。外部编辑器不遵守锁,安装期间不要并发编辑。强杀/断电可能留下锁或 `.dev-notify-*.tmp`,确认没有安装进程后再清理。
107
+ - **写入保护**:使用排他 `.dev-notify.lock`、内容快照、同目录临时文件及原子替换;无效 JSON 不会被覆盖。外部编辑器不遵守锁,安装期间请勿并发编辑。强杀或断电可能留下锁或 `.dev-notify-*.tmp`,请确认没有安装进程后再清理。
106
108
 
107
109
  启用过回复时先运行 `reply disable` 撤销授权并停止桥接,再运行 `integration claude-code remove` 后卸载 npm 包;npm 卸载不会自动移除 hooks。若先卸载了包,需重新安装后移除,或在 `/hooks` 中手动处理对应条目。单独移除 hooks 不会清除通知凭据。
108
110
 
@@ -117,34 +119,34 @@ dev-notify reply start
117
119
  dev-notify reply status
118
120
  ```
119
121
 
120
- 第一条在确认后更新到本版完整 hooks;缺少生命周期事件、Stop 仍异步、绑定其他配置或用户级 hooks 被禁用时拒绝启用/使用回复。普通终端回复还要求实际观察到该会话的 UserPromptSubmit,因此升级中途未登记的旧轮次可能只有通知,下一次正常提交后才开放按钮。第二条**另行授权**具体 Telegram 私聊用户。通知接收人、群聊成员、能看到飞书卡片的人不会自动获得输入权。`reply setup` 默认不授予工具权限批准能力,需单独选 Yes。不会调用 Python、自动迁移旧凭据或默认同意权限。
122
+ 第一条在确认后更新到本版完整 hooks;缺少生命周期事件、Stop 仍异步、绑定其他配置或用户级 hooks 被禁用时,会拒绝启用或使用回复。普通终端回复还要求实际观察到该会话的 UserPromptSubmit,因此升级中途未登记的旧轮次可能只有通知,下一次正常提交后才开放按钮。第二条**另行授权**具体 Telegram 私聊用户。通知接收人、群聊成员、能看到飞书卡片的人不会自动获得输入权。`reply setup` 默认不授予工具权限批准能力,需要单独选择 Yes。不会自动迁移旧凭据,也不会默认同意权限。
121
123
 
122
124
  ### 普通回复
123
125
 
124
- - 完成通知每段都提供「💬 回复 Claude」;也可直接回复对应 Telegram 消息。飞书按钮跳到同一 Bot 的 Telegram 私聊,再进入回复框。通用 `send` 不附带会话回复功能。
125
- - Bot 启动并核验成功、会话可精确定位时才展示按钮;未就绪仍发送普通通知并报告安全错误。必须从启用后的新通知进入,旧 Python 消息和新授权前的映射不继承。
126
- - 文本清理换行和控制字符后作为**一次**终端输入提交;最多 4000 Unicode 字符,超限拒绝,不截断、不拆成多个任务。输入可包含 Claude 命令;这等同在本机提交内容,不是只读功能。
127
- - Claude 工作中收到的普通回复先进入本地队列;只在 Stop 后尝试输入,不拿普通回复填写工具权限弹窗。排队超过 5 分钟取消;本地提交新任务会取消尚未注入的队列,不抢占本地输入。
128
- - 「已排队」「已转发」「Claude 已收到回复」是不同状态:只有相同会话/进程、匹配文本摘要的 `UserPromptSubmit` 才发收到确认;收到不代表任务完成。确认发回原 Telegram 私聊,并向已配置飞书发送不含回复原文的收到提示。注入失败或结果未知不自动重试。
129
- - 映射有效期 24 小时;目标必须匹配会话 ID、PID、进程出生标识及 TTY。会话关闭/切换、PID 重用、多个匹配或目标不确定时拒绝。每会话只保留一个未确认远程回复,Telegram update 在处理前登记 offset,崩溃可能丢失,不重放输入。
126
+ - 完成通知每段都提供「💬 回复 Claude」,点击后自动进入回复状态;也可直接回复对应 Telegram 消息。飞书按钮跳到同一 Bot 的 Telegram 私聊,并通过一条普通提示清除旧的自动回复目标,不会自动拉起回复框;请长按提示或目标通知并选择「回复」后输入。手动选择的回复目标不会被清除,需要先自行关闭回复栏。通用 `send` 不附带会话回复功能。
127
+ - Bot 启动并核验成功、会话可精确定位时才展示按钮;桥接未就绪时仍会发送普通通知,并报告安全错误。请从启用后的新通知进入;旧版消息和新授权前的映射不会被继承。
128
+ - 文本清理换行和控制字符后作为**一次**终端输入提交;最多 4000 Unicode 字符,超限会拒绝,不会截断,也不会拆成多个任务。输入可以包含 Claude 命令,效果等同于在本机提交内容,并非只读操作。
129
+ - Claude 工作中收到的普通回复先进入本地队列;只在 Stop 后尝试输入,普通回复不会被用来填写工具权限弹窗。排队超过 5 分钟会取消;本地提交新任务会取消尚未注入的队列,不抢占本地输入。
130
+ - 「已排队」「已转发」「Claude 已收到回复」是三个不同状态:只有相同会话/进程、匹配文本摘要的 `UserPromptSubmit` 才会发出收到确认。收到确认说明 Claude 已经收到回复,任务完成后还会发送最终通知。确认会发回原 Telegram 私聊,并向已配置飞书发送不含回复原文的收到提示。注入失败或结果未知时,不会自动重试。
131
+ - 映射有效期 24 小时;目标必须匹配会话 ID、PID、进程出生标识及 TTY。会话关闭或切换、PID 重用、多个匹配或目标不确定时,会拒绝提交。每个会话只保留一个未确认的远程回复;Telegram update 在处理前登记 offset,崩溃时可能丢失这条更新,但已提交的输入不会被重放。
130
132
 
131
133
  ### 等待选项与自定义输入
132
134
 
133
- - **AskUserQuestion**:展示实际问题和全部选项正文,长内容分段。单选点击提交;多选逐项选择后提交;每题都有「自定义输入」。多题逐题完成,原始 `questions` 与 `answers` 通过官方 `PermissionRequest.decision.updatedInput` 回传,不模拟方向键或 Enter。旧题按钮和过期自定义输入不回答新题。
134
- - **工具权限**:仅在 `reply setup` 另外授权后,展示工具名称/参数及「本次允许」「拒绝」「自定义拒绝说明」。通过 `PermissionRequest` 输出一次性的 allow/deny;自定义说明只用于拒绝反馈,不变相批准。不会输出 `updatedPermissions`、修改默认模式或持久写规则,仍受 Claude 托管/deny 规则限制。
135
- - **不能声称复刻全部本地按钮**:官方明确 `permission_suggestions` 并非本地权限 UI 的完整选项列表,因此不伪造「始终允许」等按钮。MCP Elicitation 表单/URL 等目前只有等待提醒,需回本机处理。
136
- - 本机原生界面不被替换。Telegram 提供「关闭远程答题(本机仍可操作)」入口;远程超时或关闭不会默认作答。Telegram 是选项点击入口,飞书使用跳转链接,不声称具备飞书回调服务。
137
- - **两阶段反馈**:每次答案提交先报告「已登记,等待确认」;多题必须全部完成才回传。随后以 PostToolUse 的实际答案反馈「答案内容已生效」或「本次提交未生效」。两端同时提交相同答案时,只确认内容一致,不虚称 Telegram 赢得竞争。权限允许以工具完成作保守确认;拒绝、取消或工具执行失败不足以证明远程决定被采用时,明确报告结果未知。
138
- - **失效与异常**:本机先答后结束远端请求,桥接尝试把旧选项替换为「已处理/已过期」状态按钮,不再接受答案;编辑失败也拒绝旧回调。回传后最多等待 5 分钟确认,缺少结果时反馈「暂时无法确认」,不会重放答案。反馈需要桥接在线;网络异常、强杀、撤销授权后不保证送达,不为了补通知重新授权或无限重发。最终反馈发送前登记状态,发送失败记为投递未知,防止崩溃后重复发送;若只收到等待提示而没有最终反馈,请检查终端及 reply status。
135
+ - **AskUserQuestion**:展示实际问题和全部选项正文,长内容会分段。单选点击提交;多选逐项选择后提交;每题都有「自定义输入」。多题逐题完成,原始 `questions` 与 `answers` 通过官方 `PermissionRequest.decision.updatedInput` 回传,不会模拟方向键或 Enter。旧题按钮和过期自定义输入不会回答新题。
136
+ - **工具权限**:仅在 `reply setup` 另外授权后,展示工具名称/参数及「本次允许」「拒绝」「自定义拒绝说明」。通过 `PermissionRequest` 输出一次性的 allow/deny;自定义说明只用于拒绝反馈,不会变相批准。不会输出 `updatedPermissions`,不会修改默认模式,也不会写入持久规则;相关操作仍受 Claude 的托管和 deny 规则限制。
137
+ - **本地按钮不会被完整复刻**:官方明确 `permission_suggestions` 并非本地权限 UI 的完整选项列表,因此这里不会伪造「始终允许」等按钮。MCP Elicitation 表单/URL 等目前只有等待提醒,需要回到本机处理。
138
+ - 本机原生界面始终保留。Telegram 提供「关闭远程答题(本机仍可操作)」入口;远程超时或关闭时不会默认作答。Telegram 是选项点击入口,飞书使用跳转链接,不提供飞书回调服务。
139
+ - **两阶段反馈**:每次答案提交先报告「已登记,等待确认」;多题必须全部完成才回传。随后以 PostToolUse 的实际答案反馈「答案内容已生效」或「本次提交未生效」。两端同时提交相同答案时,只会确认内容一致,不会声称 Telegram 一定生效。权限允许以工具完成作保守确认;拒绝、取消或工具执行失败无法证明确实采用的远程决定时,会明确报告结果未知。
140
+ - **失效与异常**:本机先答后结束远端请求,桥接会尝试把旧选项替换为「已处理/已过期」状态按钮,不再接受答案;编辑失败时也会拒绝旧回调。回传后最多等待 5 分钟确认,缺少结果时反馈「暂时无法确认」,不会重放答案。反馈依赖桥接在线;网络异常、进程被强杀或撤销授权后,反馈可能无法送达。本工具不会为了补发通知而重新授权,也不会无限重发。最终反馈发送前会登记状态,发送失败记为投递未知,防止崩溃后重复发送;若只收到等待提示而没有最终反馈,请检查终端及 reply status。
139
141
  - **防误投**:PreToolUse 保存 tool_use_id 与输入摘要,PermissionRequest 仅绑定唯一匹配调用;完成事件按 tool_use_id 结束请求。缺关联、同一 prompt 内重复同内容导致歧义、字段被其他 hook 改写或调用已结束时拒绝远程答题,保留本机处理,不猜测目标。
140
142
 
141
143
  ### 平台与后台服务
142
144
 
143
- - **Windows**:原生 `claude` 进程,系统 PowerShell 编译控制台帮助类,核验出生标识后使用 AttachConsole / WriteConsoleInputW,不抢前台。共享控制台存在另一个 claude 进程时拒绝。Node 托管的旧 CLI 不在当前可验证输入范围。
144
- - **macOS**:核验前台 claude 进程及精确 TTY,优先 tmux 字面粘贴缓冲区,否则 Ghostty AppleScript;文本不作为 shell 参数执行。系统可能要求辅助功能/Automation 权限,拒绝时不切到任意窗口。两条适配器已做模拟验证,**未做 macOS 实机输入验收**。
145
- - 不支持 VS Code 集成终端、WSL、SSH、其他终端的自动回退;元数据缺失、CLI 升级改变内部会话文件格式时拒绝输入,不扫描 transcript 猜目标。原生输入与本地活动之间不具备原子性:回复期间不要在相同终端手工编辑/切换会话。
146
- - 桥接是本机 Node 后台进程,不监听公网端口、不设置系统自启动。明确授权后可手动 `reply start`,也可由支持的后续 hook 事件按需启动。24 小时没有 Telegram 更新会退出;连续轮询失败 5 次或 409 冲突退出,状态报告安全摘要。
147
- - 一个 Bot 只能有一个 getUpdates 消费者。请先自行停用旧 Python 桥接和其他机器的同 Bot 实例;同用户的本工具使用 Bot 指纹锁,但无法跨机器或与不遵守锁的程序互斥。不会删除 webhook 或强杀别的进程;共用会导致冲突和消息丢失。
145
+ - **Windows**:原生 `claude` 进程,系统 PowerShell 编译控制台帮助类,核验出生标识后使用 AttachConsole / WriteConsoleInputW,不抢占前台。共享控制台存在另一个 claude 进程时会拒绝。Node 托管的旧 CLI 不在当前可验证输入范围。
146
+ - **macOS**:核验前台 claude 进程及精确 TTY,优先 tmux 字面粘贴缓冲区,否则使用 Ghostty AppleScript;文本不会作为 shell 参数执行。系统可能要求辅助功能/Automation 权限,权限被拒绝时不会改投任意窗口。两条适配器已做模拟验证,**尚未做 macOS 实机输入验收**。
147
+ - 不支持 VS Code 集成终端、WSL、SSH、其他终端的自动回退;元数据缺失、CLI 升级改变内部会话文件格式时,会拒绝输入,不会扫描 transcript 猜测目标。原生输入与本地活动不是原子操作:回复期间请不要在相同终端手工编辑或切换会话。
148
+ - 桥接是本机 Node 后台进程,不监听公网端口,也不设置系统自启动。明确授权后可手动 `reply start`,也可由支持的后续 hook 事件按需启动。24 小时没有 Telegram 更新会退出;连续轮询失败 5 次或遇到 409 冲突会退出,状态中只报告安全摘要。
149
+ - 一个 Bot 只能有一个 getUpdates 消费者。请先自行停用旧版桥接和其他机器上的同 Bot 实例;同一用户的本工具会用 Bot 指纹锁互斥,但无法跨机器或约束不遵守锁的程序。本工具不会删除 webhook,也不会强制结束其他进程;多个实例共用会导致冲突和消息丢失。
148
150
 
149
151
  ```sh
150
152
  dev-notify reply stop # 请求停止本次桥接,不撤销授权;后续 hook 可再启动
@@ -152,26 +154,24 @@ dev-notify reply disable # 确认后撤销授权并请求停止;彻底关
152
154
  dev-notify reply run # 前台诊断启动/网络问题,不与 start 实例并行
153
155
  ```
154
156
 
155
- 停止通过实例随机标识的控制文件请求,不根据旧 PID 强杀进程。已开始的系统输入不能撤回。`status` 是本地进程/心跳快照,不等于端到端 Telegram 或真实 Claude 验收。
157
+ 停止通过实例随机标识的控制文件请求,不会根据旧 PID 强制结束进程。已经开始的系统输入无法撤回。`status` 只反映本地进程和心跳,Telegram 端到端和真实 Claude 仍需另行验收。
156
158
 
157
- 授权在 `<通知配置>.reply.json`,消息/会话/offset/队列/交互状态在 `<通知配置>.reply-data/`;Bot 锁在 `paths` 所示 data 目录的 `reply-bots/`。排队回复原文、问题、工具输入和待回传答案会短暂写入本地状态;提交前移除普通回复原文;结构化交互保留到完成或确认超时,再清除问题、参数与答案正文,短期保留结果收据和消息 ID 以反馈及拒绝旧按钮。有效期是逻辑检查,不保证停机后的文件自动擦除。POSIX 使用 0700/0600,Windows 依赖账号目录 ACL;同用户进程仍可读取。不要提交、共享或打包这些文件(已加忽略规则及 npm 白名单)。
159
+ 授权在 `<通知配置>.reply.json`,消息/会话/offset/队列/交互状态在 `<通知配置>.reply-data/`;Bot 锁在 `paths` 所示 data 目录的 `reply-bots/`。排队回复原文、问题、工具输入和待回传答案会短暂写入本地状态;提交前会移除普通回复原文;结构化交互保留到完成或确认超时,随后清除问题、参数与答案正文,短期保留结果收据和消息 ID,用于反馈及拒绝旧按钮。有效期检查只是逻辑判断,停机后文件不会自动擦除。POSIX 使用 0700/0600,Windows 依赖账号目录 ACL;同一用户的其他进程仍然可以读取。请不要提交、共享或打包这些文件(已加入忽略规则及 npm 白名单)。
158
160
 
159
- 强杀可能留下锁;先确认相应桥接/写入进程已退出,再手工处理其残留锁,不要在进程运行中删除。`reply disable` 不擅自删除历史状态;需要清除时先禁用/停止,确认后手工删除上述专属文件。清理凭据、移除 hooks 和删除回复状态是独立操作。
161
+ 强杀可能留下锁;请先确认相应桥接/写入进程已经退出,再手工处理残留锁,不要在进程运行中删除。`reply disable` 不会擅自删除历史状态;需要清除时,请先禁用或停止,确认后手工删除上述专属文件。清理凭据、移除 hooks 和删除回复状态是三项独立操作。
160
162
 
161
163
  ## 系统凭据与兼容性
162
164
 
163
- Node 版使用 **Windows Credential Manager / macOS Keychain**,通过原生 Node API 传递秘密,不调用 Python,不将秘密放进命令行参数,不写入明文 JSON。
165
+ Node 版使用 **Windows Credential Manager / macOS Keychain**,通过原生 Node API 传递秘密,不将秘密放进命令行参数,不写入明文 JSON。
164
166
 
165
167
  - 底层库:`@napi-rs/keyring`,固定版本记录于 lockfile。
166
168
  - service:`dev-notify.node.credentials.v1`;account:随机 32 位十六进制 ID。
167
169
  - 配置引用:`"credentials": {"backend":"keyring","id":"..."}`。
168
170
  - 为兼容 Windows 通用凭据 blob 大小,凭据包限制 2400 UTF-8 字节,超限报错,不截断。
169
- - 原生组件加载失败、存储锁定或拒绝访问时明确报错,不降级为明文文件。macOS 可能要求系统授权;离线不代表一定无系统提示。
171
+ - 原生组件加载失败、存储锁定或拒绝访问时会明确报错,不会降级为明文文件。macOS 可能要求系统授权;离线环境仍可能出现系统提示。
170
172
  - 默认不枚举系统条目,只访问当前引用或本次新建的随机条目。
171
173
 
172
- 保留的 Python 原型使用 `backend: "system"`,Windows 是 DPAPI 文件,与 Node Credential Manager 不是同一存储。Node 不会读取或删除它。遇到旧配置可运行 `setup` 重新输入生成 Node 引用;原 Python 凭据保持不变。不要用 Python 原型编辑 Node 的 `keyring` 配置。
173
-
174
- Python 源码及测试仍在 `src/dev_notify/`、`tests/`、`tools/`,本轮没有删除。历史文档位于 `docs/PYTHON_PROTOTYPE.md`、`docs/PYTHON_SECURITY.md`,不进入 npm 包。
174
+ 旧版原型使用 `backend: "system"`,Windows 上是 DPAPI 文件,与 Node Credential Manager 不是同一存储。Node 不会读取或删除它。遇到旧配置时,可以运行 `setup` 重新输入并生成 Node 引用,原凭据保持不变。早期原型的源码、测试和文档已从主分支移除,仍可在 Git 历史中查阅。
175
175
 
176
176
  ## 路径、模板和环境变量
177
177
 
@@ -190,35 +190,44 @@ Python 源码及测试仍在 `src/dev_notify/`、`tests/`、`tools/`,本轮没
190
190
  }
191
191
  ```
192
192
 
193
- - `TELEGRAM_BOT_TOKEN`:Token。
194
- - `TELEGRAM_CHAT_ID`、`TELEGRAM_CHAT_IDS`:主接收人和逗号分隔接收人,任一显式存在即完全替换文件名单;显式空名单撤销接收人。大整数 chat_id 必须用字符串。
193
+ ### 凭据与接收人
194
+
195
+ - `TELEGRAM_BOT_TOKEN`:Telegram Bot Token。
196
+ - `TELEGRAM_CHAT_ID`、`TELEGRAM_CHAT_IDS`:主接收人和逗号分隔接收人,任一显式存在即完全替换文件名单;两者同时存在时合并去重。显式空名单撤销接收人。大整数 chat_id 必须用字符串。
195
197
  - `FEISHU_WEBHOOK`:飞书中国站 HTTPS 自定义机器人地址。
196
198
  - `FEISHU_SECRET`:可选签名密钥。
199
+ - 配置中的 `token_env`、`webhook_env`、`secret_env` 可把上述变量名改成其他合法的环境变量名。
200
+
201
+ ### 路径
202
+
203
+ - `DEV_NOTIFY_HOME`:绝对路径,配置位于其 `config/config.json`,数据位于其 `data/`。
204
+ - `CLAUDE_CONFIG_DIR`:Claude Code 配置目录的绝对路径,hooks 写入其中的 `settings.json`;未设置时为 `~/.claude`。
205
+ - Windows 还可用 `APPDATA`、`LOCALAPPDATA` 覆盖默认配置/数据根目录;Linux 开发辅助路径可用 `XDG_CONFIG_HOME`、`XDG_STATE_HOME`。
197
206
 
198
- 凭据环境变量显式存在时优先,包括空值;不存在才延迟读取系统引用,同次配置加载只读取一次。禁用渠道不会仅为读取系统秘密而打开后端。Token/webhook/secret 不能直接填写进 JSON。
207
+ 凭据环境变量显式存在时优先,包括空值;不存在时才延迟读取系统引用,同次配置加载只读取一次。禁用渠道不会仅为读取系统秘密而打开后端。Token、webhook 和 secret 不能直接写进 JSON。
199
208
 
200
209
  ## 更新与清理安全
201
210
 
202
- 保存流程:写入新随机系统条目 → 校验可读 → 原子替换 JSON → 删除旧 Node 条目。配置切换前失败保留旧配置,并尝试清理本次新凭据;清理失败只报告非敏感引用 ID。旧 Python 条目从不删除。
211
+ 保存流程:写入新随机系统条目 → 校验可读 → 原子替换 JSON → 删除旧 Node 条目。配置切换前失败保留旧配置,并尝试清理本次新凭据;清理失败只报告非敏感引用 ID。旧版原型的条目从不删除。
203
212
 
204
- 配置写入使用排他 `.setup.lock`,写前及切换前核对原文件。不要在向导保存时用编辑器并发改配置;编辑器不遵守此锁。断电/强杀可能留下锁或孤立条目,存储和 JSON 不是跨系统原子事务。确认没有向导运行后才能处理残留锁;程序不会擅自扫描并删除不明条目。
213
+ 配置写入使用排他 `.setup.lock`,写入前及切换前都会核对原文件。请在向导保存期间避免用编辑器并发修改配置;编辑器不遵守这个锁。断电或强杀可能留下锁或孤立条目,存储和 JSON 也不是跨系统原子事务。确认没有向导运行后,才能处理残留锁;程序不会主动扫描或删除未知条目。
205
214
 
206
215
  ```sh
207
216
  node dist/cli.js credentials clear
208
217
  ```
209
218
 
210
- 清理必须交互确认,先去除当前 JSON 引用,再尝试删除其 Node 条目;环境变量保持不变。共享同一旧引用的配置副本也会受影响,操作前会告知。删除失败明确警告,不伪装成已清理。
219
+ 清理必须交互确认,先去除当前 JSON 引用,再尝试删除其 Node 条目;环境变量保持不变。共享同一旧引用的配置副本也会受影响,操作前会告知。删除失败会明确警告,不会显示为已清理。
211
220
 
212
221
  ## 格式、发送与退出码
213
222
 
214
- 支持标题、粗体、斜体、删除线、行内代码、代码块、引用、列表、任务列表、等宽表格。Telegram 按 UTF-16 长度,飞书按 UTF-8 请求预算分段,emoji 不从代理对中切开。跨段标签补齐,标题 `(1/N)`,每段保留辅助信息。链接显示为“文字 (URL)”,纯文本回退保留地址。不是完整 CommonMark 引擎,复杂转义或嵌套不保证完全原样排版。
223
+ 支持标题、粗体、斜体、删除线、行内代码、代码块、引用、列表、任务列表、等宽表格。Telegram 按 UTF-16 长度,飞书按 UTF-8 请求预算分段,emoji 不会从代理对中切开。跨段标签会补齐,标题带 `(1/N)`,每段保留底部信息。链接显示为“文字 (URL)”,纯文本回退时保留地址。这不是完整的 CommonMark 引擎;复杂的转义或嵌套可能无法完全按原样排版。
215
224
 
216
- 单接收人中途失败停止其后续分段,Telegram 继续其他接收人。HTML/卡片格式错误回退;Telegram 明确 `429` 按服务端要求有限重试。超时不盲目重发,不承诺恰好一次投递。
225
+ 单接收人中途失败会停止其后续分段,Telegram 继续发送给其他接收人。HTML/卡片格式错误时会回退;Telegram 明确返回 `429` 时,按服务端要求有限重试。超时后不会盲目重发,也无法承诺恰好一次投递。
217
226
 
218
227
  - `send` 会把指定内容上传到第三方,请检查接收人与敏感信息。
219
228
  - `--dry-run` 不输出正文/凭据,不联网。
220
229
  - `doctor` / `status` 可能读取本地凭据,不验证网络和实际授权。
221
- - 文件缺失、损坏配置、UTF-8 错误立即报错,不退回发送标题。
230
+ - 文件缺失、配置损坏或 UTF-8 错误会立即报错,不会退回为只发送标题。
222
231
 
223
232
  | 代码 | 含义 |
224
233
  | --- | --- |
@@ -227,7 +236,7 @@ node dist/cli.js credentials clear
227
236
  | 2 | 参数、配置、文件或凭据操作错误 |
228
237
  | 130 | 用户取消 |
229
238
 
230
- 向导保存成功后测试通知失败,命令仍以保存成功结束,并明确显示测试失败,两者不混淆。保存后取消 hook 配置仍返回 0 并保留渠道;hook 配置出错返回 1,明确提示渠道已保存。集成 `status` 在条目缺失、重复或当前用户级文件禁用 hooks 时返回 1。内部 hook 处理命令始终非阻塞退出,失败通过 stderr 报告。
239
+ 向导保存成功后测试通知失败,命令仍以保存成功结束,并明确显示测试失败,两者会清楚区分。保存后取消 hook 配置仍返回 0 并保留渠道;hook 配置出错返回 1,明确提示渠道已保存。集成 `status` 在条目缺失、重复或当前用户级文件禁用 hooks 时返回 1。内部 hook 处理命令始终非阻塞退出,失败通过 stderr 报告。
231
240
 
232
241
  ## 开发与验证
233
242
 
@@ -239,8 +248,8 @@ npm pack --dry-run
239
248
 
240
249
  `node-src/` 是 TypeScript 源码,`dist/` 是编译输出,`test-js/` 是测试。默认测试只用假凭据和模拟后端/网络;本机原生假条目往返测试需显式设置 `DEV_NOTIFY_NATIVE_TEST=1`,且只在 Windows 启用,使用随机 ID,结束后清理。
241
250
 
242
- npm 发布白名单只有 `dist/*.js`、README、SECURITY、CHANGELOG、LICENSE 和必需的 package.json;不包含 Python、测试、配置或密文。无 postinstall、自动启动和自写下载脚本;预编译原生依赖由 npm 的平台 optional 包解析,**不应使用 `--omit=optional`**。macOS x64/arm64 和 Windows x64/arm64 预编译包由依赖提供,提供包不等于本项目已实机验收。
251
+ npm 发布白名单只有 `dist/*.js`、README、SECURITY、CHANGELOG、LICENSE 和必需的 package.json;不包含测试、配置或密文。没有 postinstall、自动启动或自写下载脚本;预编译原生依赖由 npm 的平台 optional 包解析,**请勿使用 `--omit=optional`**。macOS x64/arm64 和 Windows x64/arm64 预编译包由依赖提供,提供包并不等同于本项目已完成实机验收。
243
252
 
244
- 本机验证环境为 Windows x64、Node 24。自动化回归 157 项:156 通过、0 失败、1 项需显式启用的原生凭据测试跳过。覆盖临时 hooks、回复/授权/过期/防重放、单选/多选/自定义、单次权限 JSON、双端竞争、反馈异常及按接收人的重复提醒去重。实际 CLI 子进程完成后台 start → PreToolUse 登记 → PermissionRequest → 假 Bot 按钮 → JSON 答案 → 模拟 PostToolUse → 最终反馈 → 通知去重/后续工具提醒 → stop;该闭环使用模拟网络和进程元数据,不是实机输入证明。另由用户在 Windows 原生 Claude Code 2.1.316 与真实 Telegram 完成本机优先、远端优先、多选、自定义、不同答案竞争,以及延迟通用提醒去重的实机验收。隔离编译和测试不覆盖真实 hooks 引用的 dist;Windows 原生输入帮助类的自动化测试仅编译/布局验证,不执行真实 Send。macOS 两条终端适配器只有模拟验证;macOS 实机、Node 最低版本、版本管理器切换、工具权限应答实机及完整公开发布审计仍未验收。
253
+ 本机验证环境为 Windows x64、Node 24。自动化回归 163 项:162 通过、0 失败、1 项需显式启用的原生凭据测试跳过。覆盖临时 hooks、回复/授权/过期/防重放、单选/多选/自定义、单次权限 JSON、双端竞争、反馈异常及按接收人的重复提醒去重。实际 CLI 子进程完成后台 start → PreToolUse 登记 → PermissionRequest → 假 Bot 按钮 → JSON 答案 → 模拟 PostToolUse → 最终反馈 → 通知去重/后续工具提醒 → stop;该闭环使用模拟网络和进程元数据,不能替代实机输入验证。另由用户在 Windows 原生 Claude Code 2.1.316 与真实 Telegram 完成本机优先、远端优先、多选、自定义、不同答案竞争,以及延迟通用提醒去重的实机验收。隔离编译和测试不会覆盖真实 hooks 引用的 dist;Windows 原生输入帮助类的自动化测试只做编译/布局验证,不会执行真实 Send。macOS 两条终端适配器只有模拟验证;macOS 实机、Node 最低版本、版本管理器切换、工具权限应答实机及完整公开发布审计尚未完成验收。
245
254
 
246
255
  采用 MIT,见 [LICENSE](LICENSE)。安全边界见 [SECURITY.md](SECURITY.md)。版本更新需单独发布到 npm,GitHub 推送不会自动更新 npm 安装包。
package/SECURITY.md CHANGED
@@ -1,76 +1,76 @@
1
1
  # 安全边界(Node/npm)
2
2
 
3
- 当前源码为 `0.1.0-alpha.3`,npm 包名为 `@quecue/dev-notify`;源码更新不代表已发布该版本。未宣称完成安全审计或双平台实机验收。Python 原型说明另见 `docs/PYTHON_SECURITY.md`。
3
+ 当前源码为 `0.1.0`,npm 包名为 `@quecue/dev-notify`;发布版本以 npm registry 为准,源码更新不等同于已发布。本项目尚未完成安全审计或双平台实机验收。
4
4
 
5
5
  ## 凭据与权限
6
6
 
7
- - Node 系统凭据使用固定版本 `@napi-rs/keyring`:Windows Credential Manager / macOS Keychain。秘密通过进程内原生接口传递,不使用 shell、Python、子进程参数或明文落盘。
7
+ - Node 系统凭据使用固定版本 `@napi-rs/keyring`:Windows Credential Manager / macOS Keychain。秘密通过进程内原生接口传递,不使用 shell、子进程参数或明文落盘。
8
8
  - Node service 为 `dev-notify.node.credentials.v1`,每份凭据使用随机 ID。JSON 只保存 `keyring` 引用,不枚举其他系统条目。
9
- - 环境变量优先且显式空值也覆盖。环境变量和系统凭据都不能防御已被控制的同用户进程;秘密会进入 Node 内存,不能保证所有副本被安全清零。
10
- - Keychain 可能请求系统授权;锁定、权限不足、原生库加载失败明确报错,不自动降级明文。提供预编译包不代表 macOS 权限流程已验收。
11
- - Python 的 `system` 引用与 Node 存储隔离,不读取、不删除旧 Python 凭据;重新输入后的 Node 引用不能交给旧 Python 原型编辑。
12
- - 交互秘密使用 raw mode 隐藏输入,异常/Ctrl+C/EOF 恢复终端状态;无法隐藏输入即拒绝,不回退回显。原生读取不保证无系统弹窗。
9
+ - 环境变量优先,显式空值也会覆盖。环境变量和系统凭据都无法防御已被控制的同用户进程;秘密会进入 Node 内存,无法保证所有副本都被安全清零。
10
+ - Keychain 可能请求系统授权;锁定、权限不足、原生库加载失败时会明确报错,并且不会自动降级为明文。提供预编译包时,macOS 权限流程仍需单独验收。
11
+ - 旧版原型的 `system` 引用与 Node 存储隔离;Node 不读取、不删除旧凭据,重新输入后的 Node 引用也不需要再交给旧原型编辑。
12
+ - 交互秘密使用 raw mode 隐藏输入,异常/Ctrl+C/EOF 都会恢复终端状态;无法隐藏输入时,向导会拒绝继续,不会退回明文回显。原生读取仍可能触发系统弹窗。
13
13
 
14
14
  ## 配置事务与清理
15
15
 
16
16
  - 最终确认前不写配置和凭据。确认后先创建新随机条目并校验,再切换 JSON,成功后删除旧 Node 条目。
17
17
  - 配置切换失败保留原配置;只尝试清理本次创建的条目,清理失败只输出非敏感 ID。
18
- - 使用排他 `.setup.lock` 和内容快照防止多个向导互相覆盖;外部编辑器不遵守锁,保存期间不要手工编辑。
19
- - 存储和 JSON 不是跨系统原子事务。强制结束/断电可能留下锁、临时文件或孤立凭据,程序不扫描并删除未知数据。
18
+ - 使用排他 `.setup.lock` 和内容快照,防止多个向导互相覆盖;外部编辑器不遵守这个锁,保存期间请勿手工编辑。
19
+ - 存储和 JSON 不是跨系统原子事务。强制结束或断电可能留下锁、临时文件或孤立凭据;程序不会主动扫描或删除未知数据。
20
20
  - `credentials clear` 需要交互确认,移除当前引用并删除其 Node 条目;共享引用的配置副本会受影响,操作前告知。环境变量不会清除,删除失败明确警告。
21
- - POSIX mode 不能代替 Windows ACL,非敏感配置仍可能暴露接收人和本机路径。
21
+ - POSIX mode 无法替代 Windows ACL;非敏感配置仍可能暴露接收人和本机路径。
22
22
 
23
23
  ## 网络与应用边界
24
24
 
25
- - 非 dry-run 的 `send`、向导保存后**另行同意**的固定测试消息,以及**明确启用 Claude Code 集成后**收到的 Stop / 指定 Notification 事件会发送通知。
26
- - 向导测试使用环境覆盖后的最终配置;保存成功与测试失败分别报告,不伪装为全部成功。
25
+ - 非 dry-run 的 `send`、向导汇总确认中**明示**的固定测试消息,以及**随汇总确认安装 Claude Code 集成后**收到的 Stop / 指定 Notification 事件会发送通知。
26
+ - 向导测试使用环境覆盖后的最终配置;保存成功和测试失败会分别报告,不会显示为全部成功。
27
27
  - HTTP 启用系统 TLS 验证、禁止重定向,超时 10 秒,响应大小最多 1 MiB;底层 URL、秘密、服务端原始正文不打印。
28
- - 网络超时可能发生服务端已接受但客户端未知,不承诺恰好一次,不能为避免失败而无限重发。
29
- - 接收人环境变量完整替换旧名单,不隐式残留旧授权。
30
- - npm 安装不修改应用设置或启动服务。通知集成与远程回复是两层独立授权;只安装 hooks 不授予输入权。未授权时只处理最终回复、指定等待通知和辅助元数据。
31
- - 启用回复后允许可信 Telegram 私聊用户提交本机 Claude 输入,包括 Claude 命令,具有实际控制风险。不是任意 shell 执行器,也不能把这种权限描述为只读。不开放公网监听、不扫描 transcript、项目正文或版本控制信息;读取本地 Claude 会话进程元数据定位目标。
28
+ - 网络超时可能发生服务端已接受、客户端却不知道的情况,因此无法承诺恰好一次投递;为避免失败,也不会无限重发。
29
+ - 接收人环境变量会完整替换旧名单,不会隐式保留旧授权。
30
+ - npm 安装不会修改应用设置,也不会启动服务。通知集成与远程回复是两层独立授权;只安装 hooks 不会授予输入权。未授权时只处理最终回复、指定等待通知和辅助元数据。
31
+ - 启用回复后,可信 Telegram 私聊用户可以提交本机 Claude 输入,包括 Claude 命令,因此具有实际控制风险。它不是任意 shell 执行器,也不应被描述为只读权限。桥接不开放公网监听,不会扫描 transcript、项目正文或版本控制信息;它只读取本地 Claude 会话的进程元数据来定位目标。
32
32
  - `doctor`、`status`、`paths` 和 dry-run 不联网;本地凭据读取可能触发系统授权。
33
33
 
34
34
  ## Claude Code 集成
35
35
 
36
- - `setup` 提供可选启用入口;安装前展示发送范围、目标配置及用户级设置路径并确认。默认写入 `~/.claude/settings.json`,尊重绝对路径 `CLAUDE_CONFIG_DIR`。启用后作用于该配置目录下的所有项目,最终回复可能含密钥或业务敏感内容,程序不承诺自动脱敏;接收人与发送范围需用户自行确认。
37
- - hook 直接使用 `last_assistant_message`,不回读 transcript、不上传思考或工具结果;缺少正文只发送明确的完成提示。输入最大 8 MiB、读取超时 15 秒,超限拒绝不截断。权限等 Notification 也可能含敏感内容。
38
- - 生成 `command` + `args` 的无 shell exec 形式,路径不经 shell 展开;固定安装时 Node/CLI/配置绝对路径,命令参数不含秘密。需要支持该形式与 `async` 的新版 Claude Code;切换 Node 或移动安装目录后重新安装 hooks。
39
- - 保留其他设置和 hooks,重复安装去重更新,移除仅匹配本工具标记及参数结构。原 Python、项目或插件 hooks 不自动删除,可能重复发送。`status` 不是全局策略或实际事件投递验收;不会解除禁用、降低权限或绕过托管限制。
40
- - 写入使用专用排他锁、前后快照及同目录临时文件原子替换;拒绝损坏 JSON。外部编辑器不遵守锁,不能宣称无条件消除竞态;强杀可能留锁或临时文件,程序不清理不属于本次创建的锁。
41
- - Notification 异步,Stop 与会话生命周期同步,状态登记后再发送,可能延迟本地返回。PreToolUse 仅登记调用身份,不发网络通知或等待答案;PermissionRequest 与普通交互终端原生窗口并行等待,最多 5 分钟,不默认选择。新增 PostToolUse、PostToolUseFailure、PermissionDenied 处理结果,完整安装共 10 类事件。当前核对的是 Claude Code 2.1.316 实现,不保证旧版及特殊宿主采用同一并行路径。
42
- - 无持久通知发送队列、无整条自动重发或跨进程恰好一次保证。继续中的 Stop 和重复空闲提醒被忽略;内部出错仅输出安全摘要并退出 0,不用退出 2 让 Claude 继续。只有明确授权的结构化交互返回对应官方控制 JSON。
43
- - 移除 hooks、清理通知凭据、卸载 npm 是不同操作。卸载前先单独移除 hooks;不在卸载脚本中擅自修改用户设置。
36
+ - `setup` 默认把通知 hooks 纳入同一次汇总确认;确认前会展示发送范围、目标配置及用户级设置路径。`--no-hooks` 可以只保存通知配置。默认写入 `~/.claude/settings.json`,并尊重绝对路径 `CLAUDE_CONFIG_DIR`。启用后作用于该配置目录下的所有项目,最终回复可能含密钥或业务敏感内容,程序不会自动脱敏;接收人与发送范围需要用户自行确认。
37
+ - hook 直接使用 `last_assistant_message`,不会回读 transcript,也不会主动上传思考过程或工具结果;缺少正文时只发送明确的完成提示。通知底部包含 `cwd` 绝对路径、会话名称与会话 ID;会话名称只从本地 Claude Code 会话注册表读取。输入最大 8 MiB、读取超时 15 秒,超限时会拒绝,不会截断。权限等 Notification 也可能含敏感内容。
38
+ - 生成 `command` + `args` 的无 shell exec 形式,路径不经过 shell 展开;固定安装时使用 Node/CLI/配置的绝对路径,命令参数不含秘密。需要支持该形式与 `async` 的新版 Claude Code;切换 Node 或移动安装目录后,请重新安装 hooks。
39
+ - 保留其他设置和 hooks,重复安装会去重更新,移除时只匹配本工具标记及参数结构。旧版、项目或插件 hooks 不会被自动删除,可能造成重复发送。`status` 只反映本地状态,不涉及全局策略或实际事件投递验收;不会解除禁用、降低权限或绕过托管限制。
40
+ - 写入使用专用排他锁、前后快照及同目录临时文件原子替换;损坏的 JSON 会被拒绝。外部编辑器不遵守这个锁,因此不会宣称无条件消除竞态;强杀可能留下锁或临时文件,程序不会清理不属于本次创建的锁。
41
+ - Notification 异步,Stop 与会话生命周期同步,状态登记后再发送,可能延迟本地返回。PreToolUse 只登记调用身份,不发送网络通知,也不等待答案;PermissionRequest 与普通交互终端原生窗口并行等待,最多 5 分钟,不会默认选择。新增 PostToolUse、PostToolUseFailure、PermissionDenied 处理结果,完整安装共 10 类事件。当前核对的是 Claude Code 2.1.316 实现;旧版及特殊宿主的并行路径可能不同。
42
+ - 没有持久通知发送队列,不会自动重发整条消息,也不提供跨进程的恰好一次保证。继续中的 Stop 和重复空闲提醒会被忽略;内部出错时只输出安全摘要并退出 0,不会使用退出码 2 让 Claude 继续。只有明确授权的结构化交互才会返回对应的官方控制 JSON。
43
+ - 移除 hooks、清理通知凭据和卸载 npm 是三项不同操作。卸载前请先单独移除 hooks;卸载脚本不会擅自修改用户设置。
44
44
 
45
45
  ## 回复、交互与本地状态
46
46
 
47
- - `reply setup` 单独选择明确的私聊 chat_id,必须在当前通知名单中;群聊、非私聊、发送者与 chat_id 不一致、Bot 消息和未授权通知接收人均不获输入权。飞书只提供 Telegram deep link,知道链接不构成授权。`/chatid` 只返回发件人本人的私聊 ID,不授予权限。
48
- - 授权绑定 Bot 指纹、随机代次、当前有效用户及可选的权限能力。切换 Token、撤销用户或重新授权不继承旧路由;操作前及提交前重读授权。撤销不能撤回已经开始的原生输入或消除检查与调用之间的全部竞态。
49
- - 每次读取授权都检查绑定当前通知配置的完整 hooks,缺生命周期、Stop 异步或用户级禁用时拒绝回复;普通输入还要求实际观察到该会话 UserPromptSubmit。用户级文件检查不能证明项目/托管策略或运行会话已加载,须在 /hooks 确认;不承诺消除所有运行时竞态。
50
- - 消息每段发送成功后保存消息 ID 到会话映射;映射失败报告部分失败,不重发已发送段。会话必须匹配 PID/出生标识/TTY;会话切换、关闭、目标不唯一时拒绝。支持范围仅 Windows 原生 claude 控制台、macOS 前台 claude 的 Ghostty/tmux;不对 WSL/SSH/VS Code 等猜测路由。
51
- - 普通回复工作中只排队,Stop 后再尝试;5 分钟过期、本地新输入或目标变化取消。单条最多 4000 Unicode 字符,清理控制字符/换行后一次输入;不按 shell 执行、不截断、不自动拆任务。控制台共享且有多个 claude 进程时拒绝;本地用户同时编辑/切换终端仍可能竞争,不能声称原生输入与 UI 活动原子互斥。
52
- - update offset 在处理前落盘;输入先登记 pending,再调用原生适配器。强杀可能丢消息,但不重放已提交或结果未知的输入。普通回复原文在提交前移除;仅匹配 UserPromptSubmit 才向原私聊及已配置飞书报告收到,飞书确认不附回复原文。不把「转发」当作「收到」或「完成」。
53
- - AskUserQuestion 使用原始 questions + answers,通过 PermissionRequest decision.updatedInput 回传;保留本机原生界面,单选、多选、逐题自定义不经终端注入。由 Claude 仲裁先到的完整答案。每个按钮带请求/题目序号,过期或旧题不回答新题;超时/撤销不默认作答。
54
- - PreToolUse 的 tool_use_id 与输入摘要用于关联;PermissionRequest 无该 ID,只接受唯一匹配,歧义、重复同内容、缺失或已结束时拒绝远程处理。完成事件按 tool_use_id 关联,不依靠通知顺序猜目标。
55
- - 通用 permission_prompt 去重只用于通知投递,不授予输入或批准权限:仅当前会话最后一项调用是选择题、无其他未结束调用、结构化通知完整成功时,按接收人覆盖;飞书另绑定当前 webhook/签名配置的摘要。已结束题目可在原交互有效期内覆盖迟到提醒,新输入及后续工具调用不继承。Notification 缺少精确调用身份时以该保守会话范围判断,不声称跨并发/跨轮次精确去重;状态异常、投递失败或关联不明确时保留通用提醒。
56
- - Telegram 登记反馈不等于生效。PostToolUse 返回的实际答案用于内容确认;两端同时提交相同答案不能证明来源。拒绝/失败、取消、缺少结果或回传后 5 分钟确认超时,不能证明实际决定时明确结果未知,不重放答案。结果收据在发送前登记,网络失败/强杀可能未送达;不无限重发、不保证恰好一次。撤销授权后不再发送。旧选项替换为已处理/过期状态;编辑失败时仍拒绝旧请求。
57
- - 问题结束后清除请求输入和答案正文,短期保留消息 ID、结果收据及输入摘要;未收到结果时保留至确认期限,实际清理由活跃桥接或生命周期事件触发。
58
- - 工具权限必须额外 opt-in;每次点击才输出 PermissionRequest 的一次 allow/deny。自定义文本只作为拒绝 message,不将任意文本变成批准。禁止持久写 updatedPermissions 或更改 bypass/权限模式;不覆盖 Claude deny/托管策略。permission_suggestions 不是完整 UI 列表,不伪造所有本地按钮。MCP 表单/URL 等尚不提供远程应答。
59
- - 问题、选项、工具参数、回复正文和答案可能包含敏感信息;启用后问题和相应请求会发送到授权私聊及已配置飞书,不能保证自动脱敏。排队文本、请求输入、待回传答案及会话路径会持久写入专属本地状态;0600/0700 不能代替 Windows ACL,也不能防御同用户进程。逻辑 TTL 不等于断电后的自动擦除。
60
- - 专属文件为 `<config>.reply.json`、`<config>.reply-data/` 与数据目录 `reply-bots/`,已加入忽略规则且不在 npm 白名单中。禁用不擅自删除历史状态;需清除时先禁用并停止,再确认删除专属文件。Bot Token 只在系统凭据/环境/内存中,不写到回复状态。
61
- - 桥接不监听端口、不配置系统自启动。用户授权后手动或 hook 按需启动,24 小时无更新退出;连续轮询失败 5 次或 409 冲突退出。不删除 webhook,不强杀旧 Python 或其他实例;必须由用户保证跨机器、跨实现的 Bot 独占。本工具锁只能约束遵守相同本地锁目录的实例。
62
- - stop 用随机实例标识发送控制请求,不依据旧 PID 杀进程;stop 不撤销授权,后续 hook 可重启。彻底关闭使用 disable,原生调用已开始时不能撤回。心跳/PID 是状态快照,不是网络验收;崩溃残留锁需先核实进程退出后人工处理,不自动抢锁。
47
+ - `reply setup` 单独选择明确的私聊 chat_id,且必须在当前通知名单中;群聊、非私聊、发送者与 chat_id 不一致、Bot 消息和未授权的通知接收人都不会获得输入权。飞书只提供 Telegram deep link,知道链接并不构成授权。`/chatid` 只返回发件人本人的私聊 ID,不授予权限。
48
+ - 授权绑定 Bot 指纹、随机代次、当前有效用户及可选的权限能力。切换 Token、撤销用户或重新授权时,不会继承旧路由;操作前及提交前都会重读授权。撤销无法撤回已经开始的原生输入,也无法消除检查与调用之间的全部竞态。
49
+ - 每次读取授权都会检查绑定当前通知配置的完整 hooks;缺少生命周期、Stop 异步或用户级禁用时,会拒绝回复。普通输入还要求实际观察到该会话的 UserPromptSubmit。用户级文件检查无法证明项目/托管策略或运行会话已经加载,请在 `/hooks` 确认;不会承诺消除所有运行时竞态。
50
+ - 消息每段发送成功后,会把消息 ID 保存到会话映射;映射失败会报告部分失败,不会重发已发送的段落。会话必须匹配 PID/出生标识/TTY;会话切换、关闭、目标不唯一时会拒绝。支持范围仅 Windows 原生 claude 控制台、macOS 前台 claude 的 Ghostty/tmux;不会为 WSL/SSH/VS Code 等终端猜测路由。
51
+ - 普通回复在工作中只排队,Stop 后再尝试;5 分钟过期、本地新输入或目标变化会取消。单条最多 4000 Unicode 字符,清理控制字符/换行后一次输入;不会按 shell 执行,不会截断,也不会自动拆成多个任务。控制台共享且有多个 claude 进程时会拒绝;本地用户同时编辑或切换终端仍可能竞争,原生输入与 UI 活动并非原子互斥。
52
+ - update offset 在处理前落盘;输入先登记 pending,再调用原生适配器。强杀可能丢失消息,但已提交或结果未知的输入不会被重放。普通回复原文在提交前移除;只有匹配 UserPromptSubmit 才会向原私聊及已配置飞书报告收到,飞书确认不附回复原文。「已转发」「已收到」「已完成」会分别报告,不会混为一谈。
53
+ - AskUserQuestion 使用原始 questions + answers,通过 PermissionRequest decision.updatedInput 回传;保留本机原生界面,单选、多选、逐题自定义都不经过终端注入。由 Claude 仲裁先到的完整答案。每个按钮带请求/题目序号,过期或旧题不会回答新题;超时或撤销时不会默认作答。
54
+ - PreToolUse 的 tool_use_id 与输入摘要用于关联;PermissionRequest 没有该 ID,只接受唯一匹配,遇到歧义、重复同内容、缺失或已结束时会拒绝远程处理。完成事件按 tool_use_id 关联,不会依靠通知顺序猜测目标。
55
+ - 通用 permission_prompt 去重只用于通知投递,不授予输入或批准权限:仅当前会话最后一项调用是选择题、没有其他未结束调用、结构化通知完整成功时,才会按接收人覆盖;飞书另绑定当前 webhook/签名配置的摘要。已结束题目可在原交互有效期内覆盖迟到提醒,新输入及后续工具调用不会继承。Notification 缺少精确调用身份时,以该保守会话范围判断,不会声称跨并发或跨轮次精确去重;状态异常、投递失败或关联不明确时,会保留通用提醒。
56
+ - Telegram 的「已登记」反馈只说明答案已经记录,生效情况会在后续确认。PostToolUse 返回的实际答案用于内容确认;两端同时提交相同答案时,无法证明来源。拒绝/失败、取消、缺少结果或回传后 5 分钟确认超时,无法证明实际决定时,会明确报告结果未知,不会重放答案。结果收据在发送前登记,网络失败或强杀可能导致未送达;不会无限重发,也不提供恰好一次保证。撤销授权后不再发送。旧选项会替换为已处理/过期状态;编辑失败时仍会拒绝旧请求。
57
+ - 问题结束后清除请求输入和答案正文,短期保留消息 ID、结果收据及输入摘要;没有收到结果时,保留到确认期限,实际清理由活跃桥接或生命周期事件触发。
58
+ - 工具权限必须额外 opt-in;每次点击才输出 PermissionRequest 的一次 allow/deny。自定义文本只作为拒绝 message,任意文本都不会变成批准。不会持久写入 updatedPermissions,也不会更改 bypass/权限模式;Claude 的 deny 和托管策略仍然生效。permission_suggestions 不是完整的 UI 列表,因此不会伪造所有本地按钮。MCP 表单/URL 等暂不支持远程应答。
59
+ - 问题、选项、工具参数、回复正文和答案可能包含敏感信息;启用后,问题和相应请求会发送到授权私聊及已配置飞书,无法自动脱敏。排队文本、请求输入、待回传答案及会话路径会持久写入专属本地状态;0600/0700 无法替代 Windows ACL,也无法防御同一用户的进程。逻辑 TTL 不会在断电后自动擦除文件。
60
+ - 专属文件为 `<config>.reply.json`、`<config>.reply-data/` 与数据目录 `reply-bots/`,已加入忽略规则,且不在 npm 白名单中。禁用不会擅自删除历史状态;需要清除时,请先禁用并停止,再确认删除专属文件。Bot Token 只存在于系统凭据、环境变量或内存中,不会写入回复状态。
61
+ - 桥接不监听端口,也不配置系统自启动。用户授权后可手动或由 hook 按需启动;24 小时没有更新会退出,连续轮询失败 5 次或遇到 409 冲突也会退出。不会删除 webhook,也不会强制结束旧版或其他实例;跨机器、跨实现的 Bot 独占需要由用户保证。本工具的锁只能约束遵守相同本地锁目录的实例。
62
+ - stop 使用随机实例标识发送控制请求,不会依据旧 PID 结束进程;stop 不撤销授权,后续 hook 仍可重启。彻底关闭请使用 disable;原生调用一旦开始就无法撤回。心跳/PID 只是状态快照,网络连通性需要单独验证;崩溃残留的锁需要先核实进程已退出,再人工处理,程序不会自动抢占锁。
63
63
 
64
64
  ## npm 与测试
65
65
 
66
- - 运行时有原生凭据依赖,版本和平台 optional 包锁定于 `package-lock.json`。不要省略 optional 依赖。项目无 postinstall、自写二进制下载、后台启动脚本。
67
- - npm 白名单仅包含编译 JS、README、SECURITY、CHANGELOG、LICENSE 和 package.json。测试、Python、配置、日志、密文不进入本包。
68
- - 第三方原生包带有其许可证与 Rust 依赖声明,公开前仍需审查依赖供应链和许可;不能把固定版本等同于完成审计。
69
- - 默认测试用假凭据/临时目录和模拟系统后端、网络。`DEV_NOTIFY_NATIVE_TEST=1` 显式开启 Windows 原生测试,仅创建本次随机 ID 的假条目,结束清理,不读取用户现有条目。
70
- - npm 卸载程序不自动删除用户配置/凭据。需要清理时先运行交互清理命令,不在卸载生命周期脚本偷偷操作系统存储。
66
+ - 运行时有原生凭据依赖,版本和平台 optional 包锁定于 `package-lock.json`。请保留 optional 依赖。项目没有 postinstall、自写二进制下载或后台启动脚本。
67
+ - npm 白名单仅包含编译后的 JS、README、SECURITY、CHANGELOG、LICENSE 和 package.json。测试、配置、日志、密文不会进入本包。
68
+ - 第三方原生包带有其许可证与 Rust 依赖声明,公开前仍需审查依赖供应链和许可;固定版本并不等同于完成审计。
69
+ - 默认测试使用假凭据、临时目录和模拟系统后端、网络。`DEV_NOTIFY_NATIVE_TEST=1` 会显式开启 Windows 原生测试,只创建本次随机 ID 的假条目,结束后清理,不读取用户现有条目。
70
+ - npm 卸载不会自动删除用户配置或凭据。需要清理时,请先运行交互清理命令;卸载生命周期脚本不会偷偷操作用户的系统存储。
71
71
 
72
72
  ## 公开发布
73
73
 
74
- 核对包名、源码权属、依赖许可;扫描完整目录、实际 npm 包和 Git 历史。密钥泄漏必须撤销轮换,删除文件不能撤销泄漏。正式公开前应设置私密漏洞报告渠道;当前没有公开安全联络地址。
74
+ 核对包名、源码权属、依赖许可;扫描完整目录、实际 npm 包和 Git 历史。密钥一旦泄漏,必须撤销并轮换;删除文件无法撤销已经发生的泄漏。正式公开前应设置私密漏洞报告渠道;当前还没有公开安全联络地址。
75
75
 
76
- Windows x64 / Node 24 已做隔离测试及真实 CLI 假网络闭环;自动化中的 Windows 原生输入帮助类仅编译/布局验证,不向真实 Claude 会话注入。用户另在 Claude Code 2.1.316 与真实 Telegram 完成了 PC/远端优先、单选/多选/自定义、不同答案竞争及重复提醒去重的实机验收;该问答路径不使用终端输入注入。工具权限应答实机、macOS 终端输入与系统权限流程、其他架构及 Node 最低版本仍未验证,不宣称全面兼容。
76
+ Windows x64 / Node 24 已做隔离测试及真实 CLI 假网络闭环;自动化中的 Windows 原生输入帮助类只做编译/布局验证,不会向真实 Claude 会话注入。用户另在 Claude Code 2.1.316 与真实 Telegram 完成了 PC/远端优先、单选/多选/自定义、不同答案竞争及重复提醒去重的实机验收;该问答路径不涉及终端输入注入。工具权限应答实机、macOS 终端输入与系统权限流程、其他架构及 Node 最低版本尚未验证,因此不宣称全面兼容。
package/dist/channels.js CHANGED
@@ -11,7 +11,7 @@ export async function requestJson(url, payload, fetcher = fetch) {
11
11
  const response = await fetcher(url, { method: 'POST', body: JSON.stringify(payload),
12
12
  headers: { 'Content-Type': 'application/json' }, redirect: 'error', signal: AbortSignal.timeout(10000) });
13
13
  if (!response.body)
14
- throw new TransportError('服务端响应为空');
14
+ throw new TransportError('服务端没有返回内容。');
15
15
  const reader = response.body.getReader();
16
16
  const chunks = [];
17
17
  let size = 0;
@@ -23,7 +23,7 @@ export async function requestJson(url, payload, fetcher = fetch) {
23
23
  size += value.byteLength;
24
24
  if (size > 1024 * 1024) {
25
25
  await reader.cancel();
26
- throw new TransportError('服务端响应超过大小限制');
26
+ throw new TransportError('服务端响应过大,已停止读取。');
27
27
  }
28
28
  chunks.push(value);
29
29
  }
@@ -33,13 +33,13 @@ export async function requestJson(url, payload, fetcher = fetch) {
33
33
  }
34
34
  const result = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
35
35
  if (!result || typeof result !== 'object' || Array.isArray(result))
36
- throw new TransportError('服务端未返回 JSON 对象');
36
+ throw new TransportError('服务端返回的不是 JSON 对象。');
37
37
  return result;
38
38
  }
39
39
  catch (error) {
40
40
  if (error instanceof TransportError)
41
41
  throw error;
42
- throw new TransportError('网络请求失败或响应无效;未自动重发,投递状态可能未知');
42
+ throw new TransportError('网络请求失败或响应无效。为避免重复通知,不会自动重发;这条消息可能没有送达。');
43
43
  }
44
44
  }
45
45
  function text(title, content, footer, icon, html) {
@@ -63,7 +63,7 @@ function card(message, title, content) {
63
63
  }
64
64
  export function makePlan(channel, message, config) {
65
65
  if (!message.title.trim())
66
- throw new Error('通知标题不能为空');
66
+ throw new Error('通知标题不能为空。');
67
67
  const blocks = parseMarkdown(message.content);
68
68
  if (channel === 'telegram') {
69
69
  const icon = icons[message.level];
@@ -76,7 +76,7 @@ export function makePlan(channel, message, config) {
76
76
  const title = partTitle(message.title, index, chunks.length);
77
77
  const plain = text(title, chunk.plain, message.footer, icon, false);
78
78
  if (plain.length > 4096)
79
- throw new Error('Telegram 分段超过消息限制');
79
+ throw new Error('Telegram 分段后仍然超过消息长度限制。');
80
80
  items.push({ recipient: chatId, plain, payload: { chat_id: chatId,
81
81
  text: text(title, chunk.html, message.footer, icon, true), parse_mode: 'HTML', disable_web_page_preview: true,
82
82
  ...(message.reply?.users.includes(chatId) ? { reply_markup: { inline_keyboard: [[{ text: '💬 回复 Claude', callback_data: 'dn:r:' + message.reply.token }]] } } : {}) } });
@@ -88,7 +88,7 @@ export function makePlan(channel, message, config) {
88
88
  const items = chunks.map((chunk, index) => {
89
89
  const payload = card(message, partTitle(message.title, index, chunks.length), chunk);
90
90
  if (bytes(payload) + 128 > 28000)
91
- throw new Error('飞书分段超过请求大小限制');
91
+ throw new Error('飞书分段后仍然超过请求大小限制。');
92
92
  return { recipient: 'feishu', payload };
93
93
  });
94
94
  return { channel, items, recipients: 1 };
@@ -144,12 +144,12 @@ export async function deliver(plan, config, request = requestJson, sleep = delay
144
144
  }
145
145
  else {
146
146
  failed.add(item.recipient);
147
- error = '渠道拒绝发送,请检查凭据、接收人授权、签名或频率限制';
147
+ error = '渠道拒绝了这次发送。请检查凭据、接收人授权、签名和频率限制。';
148
148
  }
149
149
  }
150
150
  catch (cause) {
151
151
  failed.add(item.recipient);
152
- error = cause instanceof TransportError ? cause.message : '发送失败;投递状态可能未知';
152
+ error = cause instanceof TransportError ? cause.message : '发送失败,这条消息可能没有送达。';
153
153
  }
154
154
  if (!telegram && !failed.has(item.recipient) && index + 1 < plan.items.length)
155
155
  await sleep(250);