Repository navigation
Releases
v1.9.1 视频打磨完成
docs: 视频打磨完成 —— 补齐动画层次与音画对位
体检发现两个遗留问题,都修了。
修的问题
1. S2 痛点的动画层次太少
体检测出 S2 只有 2 个动画操作,其他场景 4–24 个。原因是它只有打字机,
元素没有进出场,看起来比别的场景「薄」。
补了 4 个层次:
- 终端窗口从下方升起(位移 34px + 缩放 0.965→1)
- 计时器数字每次跳动做一次缩放脉冲(1.0→1.14)
- 「等待审批」徽章出现后,终端边框开始呼吸(黄光 28→54px)
- 整窗阴影随呼吸同步变化
现在 S2 有 5 个动画操作,7 个场景全部达标。
2. 音效比画面动作早了 1.1 秒
这是更要紧的问题。原来的音效时间是「估的」,不是从动画代码里算的:
| 动作 | 画面实际时刻 | 原音效时刻 | 误差 |
|---|---|---|---|
| S2 审批徽章出现 | 9.52s | 9.90s | +0.38s |
| S3 裂缝 | 14.96s | 14.85s | −0.11s |
| S3 答案砸下 | 15.68s | 16.70s | +1.02s |
| S4 光点到达手机 | 26.48s | 25.35s | −1.13s |
| S5 卡片弹入 | 29.90s | 29.50s | −0.40s |
S4 那个尤其明显:通知音先响了一秒,光点才飞到手机——因果感全毁了。
现在全部按动画代码里的 p 值反算:
- 公式:
场景起始时间 + p值 × 场景时长 - 例:S4 光点在
p=0.72到达,场景 20s 起、9s 长 →20 + 0.72×9 = 26.48s
修正后 5 个音效的峰值都比背景高 5.4–11.1dB,误差归零。
验收
| 检查 | 结果 |
|---|---|
| 画面完整性 | 9/9 场景通过,内容占比 39.3%–48.2% |
| 字幕烧入 | 7/7 通过 |
| 字幕位置 | 86% 高度,不遮挡主体 |
| 音频电平 | mean −26.3dB,max −8.5dB |
| 音效对位 | 5 个关键点峰值高于背景 5.4–11.1dB |
| 动画层次 | 7/7 场景达标(S2 从 2 提升到 5) |
| 视觉焦点连续性 | 25 个采样点全部有明确内容 |
成品
docs/demo-v3.mp4 — 50.5 秒,4.34 MB,1280×720 @ 30fps
v1.9.0 新视频:视觉重做 + 声音设计
这一版做了什么
新视频(v4,视觉重做 + 声音设计)
用户要求「画面也要打磨」。这一版把视觉整个重做了,并补上了声音设计。
| 项 | 上一版 | 这一版 |
|---|---|---|
| 手机 | 圆角矩形 | 完整 iPhone 模型:灵动岛、状态栏(信号/电池)、锁屏时间、壁纸渐变、iOS 风格通知卡 |
| 因果表达 | 两个框 + 一条线 | 光点从电脑沿贝塞尔弧线飞到手机,到达时手机亮起、通知弹出 |
| 光 | 无 | 背景光斑、屏幕自发光、手机环境光晕、关键元素辉光 |
| 景深 | 单层 | 三层:背景光斑(模糊)/ 主体 / 暗角 |
| 文字 | 淡入 | 打字机逐字 + 光标闪烁 |
| 转场 | 硬切 | 扫描线扫过(只用在 4 个关键转场) |
声音设计:从「一段正弦波垫底」升级为完整设计。
- 配乐:A 小调 → F → C 大调 → A 小调的四段和声进行,配合「压抑 → 转折 → 明亮 → 收」
- 5 个音效,每个都有明确存在理由(不堆砌):打字声、通知音、冲击声、警示音、转场扫过
- 砍掉了 5 个语义重叠的(powerdown / rip / pop / click / chord)—— 多了显廉价
量化验收
| 检查 | 结果 |
|---|---|
| 画面完整性 | 9/9 场景通过,内容占比 39.3%–48.2%(上一版 8.6–32.6%) |
| 字幕烧入 | 7/7 通过 |
| 字幕位置 | 86% 高度,不遮挡主体 |
| 音频电平 | mean -26.3dB,max -9.0dB |
| 音效对位 | 5 个关键音效峰值高于背景 4.0–8.7dB |
| 光点轨迹 | (6,127) → 弧线 → (240,136) |
修的两个 bug
#progress元素漏写 —— JS 引用但 HTML 没有,导致renderFrame抛 null 错,所有动画都不执行。加了 id 存在性自检,35 个引用全部验证。- 配乐时长不匹配 —— pad.wav 49.5s 而视频 50.5s,
amix duration=first把混音截断,成片少 1 秒。
安装
命令行:在 DSH 对话里说「帮我装 dsh-alert」
从 GitHub:
cd ~/.dsh/profiles/desktop
npm install github:ysl289/dsh-alert
# 在 package.json 的 dsh.profile.bundles 里加 "dsh-alert"下载包:见下方 Assets
装完必须重启 DSH(Desktop 重启应用;Web 重启 dsh web 再刷新浏览器)。
附件说明
| 文件 | 内容 |
|---|---|
dsh-alert-1.9.0.tgz |
npm 包,26 个文件,包含全部代码 |
dsh-alert-1.9.0-source.zip |
完整源码,含测试(422 项)与文档 |
注意:GitHub Release 页面只显示压缩包,不显示单个代码文件。
要浏览源码请去 仓库文件树。
v1.8.1 新视频:摸鱼叙事版
新视频
49.5 秒,第一人称摸鱼叙事。开场就是你说的那句:
AI 在电脑上跑任务
你在客厅摸鱼刷手机
它跑完了 —— 你不知道
第三句出现时,画面右上角的电脑屏幕会自己熄灭 —— 不用旁白,一眼看懂「错过了」。
全片结构
| 时间 | 场景 |
|---|---|
| 0–6s | 钩子:摸鱼场景 + 屏幕熄灭 |
| 6–14s | 痛点:终端卡在「等你审批」,计时器 0→28 分钟 |
| 14–20s | 转折:「每跑一次你要回来瞄一眼」→ 一道裂缝 →「那就让它自己叫你」 |
| 20–28s | 它是什么:左边电脑四个触发点,右边手机通知,中间发光连线 |
| 28–35.5s | 七个通道:ntfy 高亮推荐,微信标灰写「有门槛」 |
| 35.5–41.5s | 四道防打扰闸门 |
| 41.5–49.5s | 结尾:仓库地址 + $ 帮我装 dsh-alert 命令条 + 「去摸鱼吧,有事它叫你」 |
按审美 skill 做的取舍
读了 taste 和 video-editing 两个 skill,落实了几条:
- 一镜一强调色:蓝=正确/成功,红=出错/焦虑,全片红只出现 3 次
- 硬切不溶解:场景之间直接切
- 裁掉 UI chrome:还原界面时只留内容区
- 不让画面死黑:3.5% 不透明度网格底纹
量化验收
不只看「命令跑通」,逐项验:
| 检查 | 结果 |
|---|---|
| 每个场景有画面 | 8/8 通过(内容占比 8.6%–32.6%) |
| 字幕真的烧进去 | 7/7 通过(底部亮像素 +1655 ~ +2772) |
| 字幕不挡内容 | 字幕在 85% 高度,主体在 24–45% |
| 音频不是静音 | -26.5 dB |
| SRT 格式 | 19 条,序号/时间/重叠全过 |
第一版 S3 转折那场只有 11/64 网格填充,太空。加了「问题句 → 发光裂缝 → 答案 → 下划线 → 四个标签」五层结构,并把这场的时长从 4 秒扩到 6 秒。
视频文件:docs/demo-v2.mp4(1.90 MB)· 字幕源 docs/demo-v2.srt
完整说明见 README
v1.8.0 完整说明 + 字段补全 + 铃铛图标
装法
插件市场:DSH → 设置 → 插件 → 搜 dsh-alert → 安装 → 重启 DSH
命令行:在 DSH 对话里说「帮我装 dsh-alert」
手动:下载下面的 dsh-alert-1.8.0.tgz,然后
cd ~/.dsh/profiles/desktop
npm install /path/to/dsh-alert-1.8.0.tgz
# 在 package.json 的 dsh.profile.bundles 里加 "dsh-alert"装完必须重启 DSH(Desktop 重启应用;Web 重启 dsh web 再刷新浏览器)。
这个版本做了什么
1. 字段说明全部补齐
原来 20 个字段只有 placeholder,没告诉用户「去哪儿拿」。现在缺 help 的字段:0 个。
重点补的都是实际踩过的坑:
- QQ openid:写明不是 QQ 号,来自
C2C_MESSAGE_CREATE事件的author.user_openid - 企业微信:CorpID / Secret / AgentId 分别写清在管理后台哪一层
- Bark:只取斜杠后那段
- Server酱:SCT 与 sctp 两代 key 的区别
- webhook:按地址开头选平台
2. 设置侧边栏齿轮换铃铛
先说清为什么大家都是齿轮 —— 挖 app.asar 确认,宿主代码里是这样:
/** Nav glyph by section id; unknown ids fall back to the settings gear. */
function navIcon(id) {
if (id === 'account') return IconUser...
if (id === 'models') return IconData...
...
return IconSettingsOutlineMedium; // ← 所有第三方插件都落到这里
}register() 里根本没有 icon 字段,插件侧改不了,不是我们没写对。
所以做了受控 DOM 补丁:按标题文字认出自己那一项,把 svg 换成铃铛。
按文字而不是按 class,因为宿主 class 是 hash 过的(AnXs9G_navCell),跟着写必挂。
安全边界:找不到就什么都不做、只认文字完全相等那项、确认是 svg 才换、
打过标记不重复处理、dispose 时清理观察器。
真实浏览器验证:只有「手机提醒」换成铃铛,账户和模型保持齿轮。
3. README 重写
从 391 行散装改成 564 行、31 个章节,按读者动线排:
它解决什么 → 特性 → 触发时机 → 防打扰 → 通道对比 → 安装 → 快速开始
→ 各通道配置 → 设置项 → 联动 → 设计 → FAQ → 排错 → 开发 → 隐私。
通道按真实动手成本排序并标注限额,企业微信明确写清门槛,
排错表覆盖实测到的全部错误码。
附件
| 文件 | 用途 |
|---|---|
dsh-alert-1.8.0.tgz |
npm 包(81 KB,26 个文件),手动安装用 |
dsh-alert-1.8.0-source.zip |
完整源码(2.4 MB,70 个文件),含测试与文档 |
测试
422 项断言,三平台(ubuntu/windows/macos)× 两版本(node 20/22)CI 全绿。
新增 10 条导航图标测试,其中一条专门挡实现错误:
把 ['rect', 6, 2, ...] 当成属性对象遍历了(ICON_PATHS 是位置参数数组)。
完整说明见 README
v1.7.3 全通道真打审计
fix(channels): 全通道真打审计,修 4 处问题
按「用无效凭据打真实接口,看服务端怎么答」的办法把所有通道过了一遍 ——
这就是上次抓出 Server酱 522 端点 bug 的办法。
审计结果:端点全部正确
| 通道 | 真实响应 | 判定 |
|---|---|---|
| ntfy | HTTP 200,消息真的发出去了 | ✅ |
| Bark | 400 failed to get device token |
✅ 端点对,仅 key 无效 |
| Server酱 | 400 [AUTH]错误的Key |
✅ |
| Webhook/钉钉 | 200 errcode 300005 token is not exist |
✅ |
| 企业微信 | 40013 invalid corpid(gettoken 接口通) |
✅ |
10004 机器人不存在(getAppAccessToken 接口通) |
✅ |
两段式通道(企业微信 / QQ)的 prepare → build 路径也验证了,
token 换取接口可达,错误如实带出。
修了 4 处真问题
1. ntfy 默认优先级太低(最要紧的一条)
原来默认 default = priority 3。按 ntfy 的语义,3 只进通知栏、不弹窗。
这插件的全部意义就是「别错过」,结果装好之后通知悄悄躺在通知栏里 —— 等于白装。
改成 high(4,弹窗置顶)。实测服务端记录 priority: 4。
2. Bark 的排查规则误伤其他通道
规则写的是 /Bark.*code 400|failed to get device|key/i ——
里面那个裸的 key 会把 Server酱 的 [AUTH]错误的Key
和钉钉的 keywords not in content 一起匹配进来。
用户遇到 Server酱 的 key 错,看到的是「Bark Key 不对」,被指错方向。
改成只认 Bark 特有说法:failed to get device token|Bark code \d+。
3. /errcode 400/ 吃掉 40013
400 是 40013 的前缀,所以 CorpID 错误会同时显示
「CorpID 不对」和「消息格式对不上」两条,自相矛盾。加了 \b 边界。
4. check() 丢了服务端的错误原因
Bark 和 Server酱 在非 2xx 时只返回 HTTP 400,
把 body 里的 failed to get device token / [AUTH]错误的Key 丢了。
排查规则是靠文本匹配的,丢了 message 就永远命中不了。
现在会拼上 HTTP 400: <服务端原因>。
顺带补齐的排查规则
- QQ
10004 机器人不存在→ AppID 不对(原来没规则) - 我们自己输出的「无效收件人:xxx」→ 并进 81013 那条
- 通用
HTTP 5xx→ 对方服务器出错,不是你配错 HTTP 413→ 消息太长,关掉「带上 AI 回复」或调短摘要
测试(402 → 412)
新增 test/troubleshoot.test.mjs 10 条,用实测到的真实错误文本逐条验证:
- 每条错误至少命中一条规则
- 每条错误命中语义正确的那条
- Server酱 的
[AUTH]错误不能误判成 Bark - 钉钉关键词错误不能误判成 Bark
errcode 40013不能误判成格式错误- 真正的格式错误仍能命中格式规则
- 每条规则都必须有 fix(只给标题不给做法等于没解决)
这些断言直接把这次发现的三个误匹配钉死,以后改规则会立刻挂。
v1.7.2 修 IM 认不到服务(真根因)
fix(im): 装好了也认不到 —— Cordis 必须 inject 才能读兄弟插件的服务
用户反馈「我都装好了根本选择不了啊,检测也屡屡失败」。
真正的根因
不是用户没装好。dsh-im 5.7.0 确实装在 profile 里(bundles 第 22 项),
被禁用也没有。问题是我的代码从来读不到它的服务。
Cordis 里兄弟插件的服务不能直接读:
ctx.dshIm // ❌ throws "cannot get property \"dshIm\" without inject"
ctx.inject(['dshIm'], scoped => {
scoped.dshIm // ✅ 只有这条路
})
我原来用 try/catch 包住直接访问,异常被吞掉、返回 undefined,
于是 im.installed 永远是 false —— 界面一直让用户去装,点「发送测试」必然失败。
这个约束我用一个隔离实验确认过:root 上 provide 服务、两个插件是兄弟 fiber,
B 直接读会抛,C 用 inject 能拿到。
顺带发现同一个 bug 的第二个受害者
costMeter 也用了同样的 safeGet(ctx, 'costMeter') ——
成本联动从上线起就没生效过,只是没人发现。一并修了。
改了什么
1. 服务一律走运行时 inject
ctx.inject(['dshIm'], scoped => { imService = scoped.dshIm })
ctx.inject(['costMeter'], scoped => { costService = scoped.costMeter })
桥接层的入参从 ctx 改成服务对象或取服务的函数,
彻底避免再有人误以为「读 ctx 就行」。
2. 联动探测分成两层
- loader 类(pocket / claudeStyle / desktopNotify):读 loader,apply 时就能定
- 服务类(dshIm / costMeter):等 inject 回调才知道
之前两层都一次算死,inject 还没回调就把服务记成 false,之后再也不更新。
现在服务类改成惰性读取,就绪后立刻反映。
3. 顺手修掉一个 UI 显示问题
渠道代号原来直接显示内部值(feishu),改成「飞书」。
验证(这次不看测试,看真实 Cordis)
写了两个真实 Cordis 集成脚本:
=== status ===
im.installed: true
im.bots: [{"botId":"bot_real","channel":"feishu"}]
=== 发送测试 ===
ready: im
{"ok":true,"results":[{"channel":"im","ok":true,"attempts":1}]}
dsh-im 收到调用次数: 1
修前:installed=false、bots=[]、发送必失败。
修后:三项全通过。
测试(393 → 402)
旧的 im.test.mjs / integrations.test.mjs 是拿 { dshIm: fake } 当 ctx 传的 ——
在 JS 里能过,在真实 Cordis 下必然失败,正是它们给了假的安全感。
现在改成:
- 单元测试直接喂服务对象
- 集成测试的 mock ctx 故意不把 dshIm 挂在 ctx 外层,
只通过 inject 提供 —— 谁再改回「直接读 ctx」会立刻挂 - 新增断言:服务挂在 ctx 上但没走 inject 时,必须报 false
一个调试教训
我第一版验证脚本写 plugin.apply(ctx, {}).catch(...),
但 apply 是同步函数,.catch 抛 TypeError → Cordis 判定 fiber 失败
→ 释放 inject 作用域 → imService 被清空。看起来像「插件没认到服务」,
其实是我的验证脚本错了。这类假阴性最耗时间。
v1.7.1 IM 引导修:必须重启 DSH
fix(im): 第三步做完没反应 —— 没告诉用户要重启 DSH
用户实测反馈:跟着 IM 引导做完第三步,界面上什么都没有出现。
根因
我写了「装好并配好目标后,这一页会自动变成选择框」—— 这是错的。
dsh-im 自己的 README 明确写着:
安装后重启 Desktop 或
dsh web,Web 刷新浏览器,再打开「设置 → IM机器人」
插件不是热加载的。用户装完 dsh-im 后没有重启 DSH,面板里的 ctx.dshIm
压根不存在,所以 installed 一直是 false,界面当然不变。
用户完全按引导做完了,是我给了错误预期。
改了什么
1. 说清必须重启
- 在「没装」分支加了警示块:装完必须重启 DSH,插件不是热加载的
- 去掉「会自动变成选择框」这个错误承诺
2. 三个分支都加「重新读取」按钮
原来只有页面底部的「重新读取」,用户在 IM 面板里改完 dsh-im 的东西
得自己去找。现在:
- 没装 → 「我已配好,重新读取」
- 装了没机器人 → 「配好了,重新读取」
- 有机器人 → 底部「重新读取」+ 说明「这里不会自动刷新」
3. load() 补上 busy 状态
原来 busy 只服务于保存按钮,点刷新没有任何反馈。
现在读取期间按钮显示「读取中…」。
4. 渠道代号翻成中文
下拉框原来直接显示 dsh-im 的内部代号(feishu),改成「飞书」。
覆盖 wechat/wecom/feishu/dingtalk/qqbot/telegram/slack/discord/whatsapp/imessage/matrix。
认不出的代号原样显示,缺字段显示「未知渠道」,都不崩。
顺带核实了一个我担心的点
怕是 listBots() 返回结构写错了,专门去 dsh-im 的 10MB 打包产物里挖了实现:
async listBots(){
let t=[];
for(let{adapter:o} of this.#e.values())
for(let e of await o.listBots()) t.push({botId: aa(e), channel: o.channel})
return t // ← {botId, channel} 对象数组,我的假设是对的
}所以桥接层没问题,问题纯粹在「没告诉用户重启」。
测试(388 → 393)
- IM 面板必须说明装完要重启 DSH
- 不能再出现「会自动变成选择框」这种承诺
- 四个分支都要有「重新读取」入口
- 渠道代号必须翻成中文(feishu → 飞书等)
- 未知代号原样显示、缺字段显示占位,都不崩
v1.7.0 引导逐个核对完成
把所有渠道引导逐个核完了,依据是实测 + 官方文档,不再有凭印象写的部分。
Webhook:实测三家
| 平台 | 无效 token 返回 |
|---|---|
| 钉钉 | {"errcode":300005,"errmsg":"token is not exist"} |
| 飞书 | {"code":19001,...}(用 code 不是 errcode) |
| 企微群 | {"errcode":93000,...} |
修的错
- 钉钉 token 错误码
300001→300005(实测) - 补上飞书 / 企微群的独立排查规则
- 步骤顺序反了:原来第 1 步让选平台,但用户还没拿到地址,无从选起。
改成先拿地址,再按地址开头(oapi.dingtalk.com/open.feishu.cn/qyapi.weixin.qq.com)识别
QQ:拿到官方文档正文,纠正三处
- Token 已弃用 —— 官方明确只支持 Access Token 鉴权,引导里补了「Token 字段不用管」
- openid 来自
C2C_MESSAGE_CREATE事件,不是「去开发设置翻日志」 - 40034105 对应
C2C_MSG_REJECT—— 用户在机器人资料卡关了主动推送
保留了「用 dsh-im 更省事」的替代方案:这条路客观难,不给退路等于坑人。
Bark
用 API 探测确认三种地址形式都识别同一个 device token,验证了「Key 取斜杠后那段」是对的。
测试
365 → 388 项。新增 19 条 webhook 测试(三家格式差异、错误码判定、模板替换)
- 4 条 QQ 官方事实断言。
docs/GUIDE-SOURCES.md 补了 Webhook 实测记录表、
QQ 官方事实表,并把待补清单缩到只剩三项。
v1.6.3 · 加 44 秒介绍视频 + 企业微信难度重排
视频
仓库里加了 44 秒介绍片:docs/demo.mp4(2.2 MB,中文字幕)
7 个场景:钩子 → 痛点 → 方案 → 通道 → 防打扰 → pocket 联动 → CTA
做法:HTML+CSS 动画 → 无头 Chrome 经 CDP 逐帧截图(50ms/帧)→ ffmpeg 编码 → 烧字幕 → 加合成配乐。
顺带修:企业微信难度问题
用户实测卡在 errcode 60020(企业可信IP)。家庭宽带是动态 IP,个人用户基本做不成。
按真实动手成本重排通道(原来按「官方程度」排,不合理):
\
旧:ntfy → 微信 → Bark → QQ → Server酱 → Webhook → 自定义 → IM
新:ntfy → Bark → Server酱 → Webhook → 微信 → 自定义 → QQ → IM
\\
企业微信降到第 5 位,卡片直标「较麻烦 / 要注册企业、配 IP」,选中后顶部明确警告,并给出「换 ntfy」这条能走通的路。
测试
343 项,新增 4 条锁住这次的教训:
- 企业微信必须在选之前说明 IP 限制
- 通道按真实成本排序,ntfy 必须第一
- 麻烦的通道要在卡片上就标出来
- 60020 的排查建议要给活路
v1.6.1 · 修引导:企业微信补上「进管理后台」
修用户实际卡住的引导问题。
问题
跟着企业微信引导做到第 2 步就卡住了。原文写「管理后台 → 应用管理 → 创建应用」,但从没说管理后台是什么、怎么进 —— 用户手上只有手机上的企业微信 App,浏览器里根本没有那个页面。
修复
- 企业微信引导从 5 步扩到 7 步,第 2 步专门讲怎么进管理后台(浏览器 → 右上角「管理后台」→ 微信扫码),并给了直达链接、点明「这不是手机 App」
- 注意事项不再写在正文里(\注意:xxx\ → 独立 note 字段),改用警示底色 + 橙色边框 + 图标标注
- 「在哪儿做」里不再混进注意事项,位置和提醒分开
- 清掉会被渲染成纯文本的 Markdown 星号
测试
从 331 增到 339 项,新增的都在防这类问题:
- 提到「管理后台」这类入口必须说清怎么进
- 必须点明是网页不是 App
- 每步的「在哪儿做」必须含可操作线索
- 注意事项必须走 note,不靠正文「注意:」
- 渲染层必须把 note 渲染成带警示色的块
CI 三平台四版本全绿。