wjs-mining-voicedrop — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited wjs-mining-voicedrop (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
VoiceDrop 收件箱(jianshuo.dev/files 上的 VoiceDrop-*.m4a)→ 逐条转写 → 交给 wjs-mining-articles 出公众号草稿。这是 VoiceDrop iOS app(开口即录、停即上传)的 Mac 端闭环。
本 skill 自身的产出 = ① 公众号草稿(`~/code/wechat-publish/`)+ ② 本地音频/SRT 存档(`~/code/voicedrop/archive/`)+ ③ R2 上的处理标记(`articles/<stem>.json` 或 `.empty`)+ ④ 一份批次报告(处理几条、各出几篇、哪些标了无语音及原因、还剩几条未处理)。 完整接口契约见 agents/interface.yaml。
复用,不重写。 本 skill 只做两件本身没有的事:收件箱的进出(列/下载/标记)和逐条编排。转写交 wjs-transcribing-audio,成文交 wjs-mining-articles,一行都不重写。
R2 永不删,用标记文件表示处理状态。 音频一直留在 R2,直到用户自己在 app 里删。「未处理」= 还没有 articles/<stem>.json(已成文)也没有 articles/<stem>.empty(无语音)标记的 VoiceDrop-*.m4a;list 已自动只列未处理的。一条成功成文后写 mark-done,没语音/损坏写 mark-empty——两者都让这条不再被重复处理。绝不 delete(delete 只留给用户在 app 里手动清理)。
/wjs-mining-voicedropwjs-mining-articleswjs-transcribing-audio 出 SRT,再 wjs-mining-articlesVoiceDrop-*.m4a 前缀,其余不碰~/code/.env 里有 FILES_TOKEN(收件箱鉴权)和火山 ASR creds(VOLC_ASR_* / VOLC_TTS_*,转写用)。set -a; source ~/code/.env; set +a。唯一的新增代码:scripts/voicedrop-inbox.sh(list / download / mark-done / mark-empty / delete,token 运行时从 ~/code/.env 读,绝不落代码)。list 只列未处理;mark-done/mark-empty 写处理标记;delete 只给手动清理用,成文流程不调它。
INBOX=~/.claude/skills/wjs-mining-voicedrop/scripts/voicedrop-inbox.sh
set -a; source ~/code/.env; set +a # FILES_TOKEN + 火山 ASR creds用绝对路径 $INBOX 调脚本——不要写成 scripts/voicedrop-inbox.sh,那依赖「人恰好在 skill 根目录」这个隐藏假设,换目录就崩。
"$INBOX" list # 打印未处理的 VoiceDrop-*.m4a,一行一个~/code/.env」并停,不进入循环。串行。批次韧性:单条任何一步失败 → 记录原因、跳到下一条、绝不中止整批、绝不漏标。 每条录音最终必须落到三个终态之一:已成文(mark-done)/ 无语音(mark-empty)/ 失败(不标,留待下次)——绝不「处理了却什么都没标」。对每个 <name>:
"$INBOX" download <name> ~/code/voicedrop/archive音频落 ~/code/voicedrop/archive/<name>。R2 上的原件始终保留,本地这份只是离线副本。
dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$audio" 2>/dev/null)dur 为空(非音频/损坏)→ "$INBOX" mark-empty <name> corrupt;< 1.0 秒(误传/静音)→ "$INBOX" mark-empty <name> silent。标完报告用户,跳到下一条——这条已是终态,不再重复处理。
volc_asr_stream.py + build_srt_from_asr.py + 在 session 内做 AI 润色改错别字)。SRT 落 ~/code/voicedrop/archive/<stem>.srt。"$INBOX" mark-empty <name> no-speech,报告、跳下一条。{"schema":2,"status":"ready","sourceAudio":"<name>","articles":[{"title","body"},…]},可含 transcript/srt)写到临时文件,再: "$INBOX" mark-done <name> /tmp/<stem>.json这条就标成已成文、app 里也能看到,且不会被服务器或下次再挖。 转写失败 / 用户没勾任何选题 / 挖不出文章 → 不标 done 也不标 empty,留未处理,下次再来,报告原因。
处理了几条、各挖出几篇草稿(落在 ~/code/wechat-publish/)、哪些标了无语音及原因(corrupt/silent/no-speech)、本地存档路径、R2 还剩几条未处理。
download(存档) → 判别 → 成文 ? mark-done : 无语音 ? mark-empty : 留着不标
↑ 绝不 deletemark-done;损坏/静音/无语音 → mark-empty(带 reason);真失败(转写报错、用户中止、没挖出文章)→ 不标,留未处理下次再试。绝不出现「跑过一遍却没留任何标记」——那会让这条每次都被重新处理。| 复用 | 用法 |
|---|---|
wjs-transcribing-audio | 每条音频 → SRT(中文火山豆包,含润色改错别字) |
wjs-mining-articles | 每个 SRT → 选题清单 → 成文 → 微信草稿(含它自己的人工闸) |
~/code/.env | FILES_TOKEN + 火山 ASR creds |
| VoiceDrop app | 上游:文件名形如 VoiceDrop-<时间戳>-<时长>-<星期>-<时段>[-<城市-城区>].m4a(全 ASCII)。本 skill 靠 VoiceDrop- 前缀 + .m4a 后缀认领;中间的时长/星期/时段/地点是上下文,成文时可借来判断这条录音是何时何地的口述 |
服务器 miner(~/code/voicedrop/mining/mine.py,每 2h) | 同一套标记约定:成文写 articles/<stem>.json、无语音写 articles/<stem>.empty、永不删音频。它会自动处理收件箱,所以本 skill 跑时 list 常常已经空了——这是预期,本 skill 是手动补位 |
本 skill 唯一新增代码:scripts/voicedrop-inbox.sh。
mark-done/mark-empty;delete 仅用户在 app 里手动用。mark-empty,否则它每次都被重新下载转写,永远「待处理」。mark-done。VoiceDrop-*.m4a 前缀;list 也只列未处理的。mark-empty,别送去转写。~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.