1实现原理 · 为什么它能做到
「每一行都是收据」:片中所有文字不是创作出来的,而是从用户自己的 Claude Code 会话史里抽出的逐字原话,并且在渲染前必须由用户逐行过目批准。
Every line on screen is a receipt. The user approves each one before anything renders.
「普通的一天」不是模型想象的,而是统计出来的:harvest.py 从会话里的工具调用归类活动(读/改/测/构建/跑/装/提交/PR/部署/取数/渲染/上网/委派),统计各活动在会话中的出现率与中位位置,再用「会话最多的那个星期 + 该星期中位首条消息时间」定义这一天。
# the day: the weekday with the most sessions, and its usual start time (mornings preferred)
用户原话按类型分章:CATS 用正则把消息分成 building(同意/放行)、restart、knock(问进度)、notes(纠正)、side-by-side(差一点)、good-part(夸)、frames、watching、kept(记住/下次)等章节类型,并为每类设了「可引用的最大长度」。
CATS = { # chapter type → pattern over the lowercased message (+ max length for short quotable lines)
素材读取面被限定且声明为纯本地:只读 ~/.claude/projects/*/*.jsonl、其下 memory/*.md 与相关项目的 CLAUDE.md,不写不改不上传。
Reads (never writes, never uploads):
隐私闸门是可执行的结构校验,而不是口头约定:new-film.mjs 在生成 story.js 前逐字段正则扫描,命中凭证形状(sk-/ghp_/github_pat_/xox?-/AKIA/BEGIN 私钥)、邮箱、文件路径即报错,命中 URL 只警告;并且 story.approved 未置 true 时会提醒必须先给用户看每一行。
if (SECRET.some(r => r.test(t))) errs.push(`${at}.${k}: looks like a credential; remove it`);
时间轴**锁死**在 archetypes.json:每章有 bars 与若干事件秒数,且全部落在 96 BPM 网格上;画面(scenes.js)与配乐(score.py)都从这一张表取时间,因此换章节顺序也不会跑偏。
Single source of truth for the film clock. scenes.js draws every event at these times and scripts/score.py composes on them, so picture and music cannot drift.
画面是逐帧绘制的(不是 DOM/CSS 动画):kit 在一个隐藏的 p5/WEBGL canvas 上画每一张,再合成到可见的 #out;「boil」= 约 8 张/秒、每 3 帧换一张、任何停顿不超过 3 帧,这几条由 qc.py 直接从渲染出的 MP4 量出来验证。
/* the kit paints in a hidden p5/WEBGL canvas and composites each drawing into #out */
配乐与画面同源且纯本地合成:score.py 读同一份 story.json + archetypes.json,按章写作两小节(96 BPM、D 大调,主题是键入的 claude = D E F# A B A),本地用加法弦乐/Karplus-Strong 拨弦/合成厅渲染并 master,无网络无凭证。
No network, no credentials. Needs numpy, scipy, ffmpeg.
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| cli | HyperFrames CLI(钉死 0.8.70,经 npx 从 npm 取;check 与 render) |
| cli | ffmpeg(配乐母带、QC 解码与响度量测、渲染链路) |
| package | p5 2.3.2 (LGPL-2.1) / p5.brush 2.2.1 (MIT) / puppeteer 25.9.0(含其钉死的 Chrome for Testing),由 lockfile 钉住 |
| package | Python3 + numpy + scipy(配乐合成与母带);harvest.py 只用标准库 |
| network | Google Fonts:合成页在预览/渲染时加载手写字体 Gochi Hand(SIL OFL) |
| network | 素材来源(唯一):用户本机的 Claude Code 转录、memory 文件与相关项目的 CLAUDE.md;不上传 |
4风险提醒 风险提醒:黄色 · 留意使用
- 读取用户本机全部 Claude Code 历史 — harvest.py 扫 ~/.claude/projects 下全部 jsonl 与 memory/*.md,并把其中最私人的句子挑成候选台词。虽然不上传,但这是本 skill 最大的隐私接触面,建议只在用户自己的机器、自己的账号下使用。
- 隐私闸是黑名单与提示性门禁 — 拒收靠固定正则(凭证/邮箱/路径),绕得过的敏感内容不会被发现;'用户批准'只是 warn(见 second_pass 的 discrepancy),不是硬门禁。SKILL.md 的措辞容易被读成强制。
- 产物本身敏感 — receipts.json / story.json / 成片都含用户原话。SKILL.md 要求 never commit or share,但没有任何技术手段阻止误分享(例如误把 film 目录提交进仓库)。
- 渲染非完全离线、每次命令取 CLI — 页面在预览/渲染时从 fonts.googleapis.com 加载 Gochi Hand;每条 render/check 都经 npx 从 npm 取 [email protected],离线或断网环境不可用。
- 依赖链较长且平台假设未声明 — 需要 Node 22+、Python 3.9+、ffmpeg、numpy/scipy、以及 puppeteer 拉来的 Chrome for Testing(约 500MB 量级磁盘);SKILL.md 未声明 macOS/Windows 支持边界。
- 门槛会劝退相当多用户 — 至少 10 个本地会话跨 5 个活跃日才 enough;只看 claude.ai 网页版、或才用了几天的用户会被直接挡掉(这是有意的诚实设计,但要知道)。
5第二遍独立确认
- [ok] 脚本是否读凭证(黄/橙分界的关键) — 全目录 grep process.env / os.environ 零命中;harvest.py 走 argparse 参数(--root/--tz/--project),score.py/qc.py 无环境变量入口。new-film.mjs 的 SECRET 常量是用于**拒绝**凭证形状的正则,不是读取。因此橙档第一条不成立。
- [ok] 『Nothing is uploaded』声明是否真的成立 — harvest.py(读转录)import 仅 argparse/collections/datetime/glob/json/os/re/statistics,无任何网络模块;score.py 只 subprocess 调 ffmpeg;shoot.mjs 只 page.goto('file://…');qc.py 只跑 ffmpeg 解码。唯一出网行为是 setup 的 npm ci、每次 npx 取 hyperframes 与页面加载 Google Fonts。
- [ok] Use 门槛(至少 10 个本地会话、跨 5 个活跃日)是否真在代码里 — harvest.py:256 'enough': len(sessions) >= 10 and len(days) >= 5,并在运行摘要里打印 'enough material: {out["enough"]}';与 SKILL.md 的 Use 条件一致,且 SKILL.md 要求 enough 为 false 时停下。
- [ok] 隐私闸(凭证/邮箱/路径/URL)是否真有拦截面 — new-film.mjs 的 validate() 对每章每个字符串字段:SECRET 命中 → errs('looks like a credential; remove it');EMAIL 命中 → errs;PATH 命中 → errs;URL 命中 → warns。errs 非空即 exit 1,故是硬拦截。
- [discrepancy] 『故事必须经用户批准』是否被强制 — SKILL.md 的 Rules 写 'Approval before render.',但代码层面 story.approved 未置 true 时只产生一条 warn('story.approved is not true: show the user every on-screen line …'),并不阻断 --sync 或渲染。也就是说这是一道人工程序纪律,不是技术门禁;SKILL.md 的措辞(`story.approved` is set only by the user's yes)容易被读成强制。该出入对 pin commit 有效。
- [discrepancy] 『8 到 13 章』是否被校验 — SKILL.md Workflow 第 3 步写 '8 to 13 chapters.',但 new-film.mjs 的 validate() 只校验:非空、首章必须是 intro、末章必须是 tag、kept 必须紧邻 tag 之前、各章型必需字段;没有章数上下限检查。故 8–13 是写作纪律而非校验规则。
- [ok] 单一时间轴声明是否成立(画面与配乐同源) — archetypes.json 的 about 明写 scenes.js 与 scripts/score.py『both read it』;score.py 文档行也声明读 story.json + archetypes.json('the same table scenes.js draws from')。两处一致,未见第二处时钟。
- [ok] 没有本地转录时的行为 — harvest.py:137 'if not sessions: raise SystemExit(f\'no sessions under {a.root}: nothing to harvest\')',且没有任何兜底造数路径;与 SKILL.md『this skill never invents receipts』一致。
6结论
94c054dd0a6d242e…ba7a0bb6d3