把"这个 AI 生成的东西能不能卖"做成一张台账:字段、存档命令、每月复查
一人公司用 AI 出图、出声、出视频去卖,最脆的一环不是质量,是授权。
ClipJury 这个站干了一件笨但有用的事:把 175 个 AI 工具的许可条款逐句摘录下来存档,然后在 9 月 5 日和 10 月 3 日各重读一遍。结果是十周内约一半变过,接下来四周又有约三分之一再变;它查的 187 个工具里,免费档能安全商用的只有 14 个。Suno 的免费档现在明写”仅供个人非商业使用且需署名”,商用权要到 Pro 档,8 美元/月按年付(96 美元/年)。
没有一条变更会发邮件通知你。它就是在网页上悄悄改了一句话。
你不需要自己去读 175 份条款。你只需要为自己真正在用的那 5 到 10 个工具,建一张能举证的台账。
台账长什么样
一个 CSV 就够,放在仓库里跟代码一起版本管理。字段不要多,多了就不会填:
tool,use_case,plan_paid,monthly_cost,commercial_rights,quote,source_url,read_date,next_check
suno,短视频背景音乐,no,0,no,"Free-tier songs are licensed for personal, non-commercial use only",https://suno.com/pricing,2026-10-20,2026-11-20
elevenlabs,旁白,yes,22,yes,"Commercial use rights included on paid plans",https://elevenlabs.io/pricing,2026-10-20,2026-11-20
八个字段的含义:
tool/use_case:工具和它在你生意里的用途。同一个工具两种用途可能授权不同plan_paid/monthly_cost:你现在买的是哪一档、多少钱commercial_rights:yes/no/unclear。unclear 按 no 处理quote:从条款页抄下来的原句,不是你的总结。将来吵架时只有原句有用source_url/read_date:这句原句从哪来、哪天读的next_check:下次复查日期,一般填 30 天后
关键在 quote。营销 FAQ 说”随便用”没用,定价页的 feature 表格说”可用于商业”也不够——一定要找到条款、许可协议或者定价页小字里那句明确授权的话。找不到,就填 unclear。
存档:把那天的网页存下来
抄原句只能证明你读过,不能证明页面上当时写的是这句话。下面这个脚本把页面正文和哈希一起存进仓库:
// scripts/snap-terms.mjs
// 用法:node scripts/snap-terms.mjs suno https://suno.com/pricing
import { writeFileSync, mkdirSync } from 'node:fs';
import { createHash } from 'node:crypto';
const [slug, url] = process.argv.slice(2);
if (!slug || !url) { console.error('usage: node scripts/snap-terms.mjs <slug> <url>'); process.exit(1); }
const res = await fetch(url, { headers: { 'user-agent': 'Mozilla/5.0' } });
const html = await res.text();
const text = html
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
const day = new Date().toISOString().slice(0, 10);
const dir = `licenses/${slug}`;
mkdirSync(dir, { recursive: true });
writeFileSync(`${dir}/${day}.txt`, text);
const sha = createHash('sha256').update(text).digest('hex').slice(0, 16);
const meta = { slug, url, readAt: new Date().toISOString(), httpStatus: res.status, sha256_16: sha, bytes: text.length };
writeFileSync(`${dir}/${day}.json`, JSON.stringify(meta, null, 2));
console.log(`${slug} ${day} status=${res.status} sha=${sha} bytes=${text.length}`);
跑完 git add licenses && git commit -m "terms snapshot 2026-10-20"。
注意它存的是去掉标签后的正文和你抓取时的状态位。正文变了哈希就变,这就是下面的 diff 依据。
有些站点是客户端渲染,fetch 拿到的正文是空的——看输出的 bytes,小于 2000 基本就是没抓到,改用浏览器手动存 PDF 或者在页面上用”打印为 PDF”,把 PDF 一起提交进仓库,同样在 meta 里记一句 method: manual-pdf。
每月复查:只 diff,不重读
// scripts/check-terms.mjs
// 用法:node scripts/check-terms.mjs (快照重抓一遍,和上次的哈希比)
import { readFileSync, writeFileSync, readdirSync, existsSync } from 'node:fs';
import { createHash } from 'node:crypto';
const base = 'licenses';
const rows = [];
for (const slug of readdirSync(base)) {
const files = readdirSync(`${base}/${slug}`).filter((f) => f.endsWith('.txt')).sort();
if (!files.length) continue;
const last = files[files.length - 1];
const { url } = JSON.parse(readFileSync(`${base}/${slug}/${last.replace('.txt', '.json')}`, 'utf8'));
const res = await fetch(url, { headers: { 'user-agent': 'Mozilla/5.0' } });
const html = await res.text();
const text = html.replace(/<script[\s\S]*?<\/script>/gi, ' ').replace(/<style[\s\S]*?<\/style>/gi, ' ')
.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
const prevSha = createHash('sha256').update(readFileSync(`${base}/${slug}/${last}`, 'utf8')).digest('hex').slice(0, 16);
const nowSha = createHash('sha256').update(text).digest('hex').slice(0, 16);
const changed = prevSha !== nowSha;
rows.push({ slug, changed, prevSha, nowSha });
if (changed) {
const day = new Date().toISOString().slice(0, 10);
writeFileSync(`${base}/${slug}/${day}.txt`, text);
writeFileSync(`${base}/${slug}/${day}.json`, JSON.stringify(
{ slug, url, readAt: new Date().toISOString(), httpStatus: res.status, sha256_16: nowSha, bytes: text.length }, null, 2));
}
}
const bad = rows.filter((r) => r.changed);
console.log(`checked ${rows.length} tools, changed ${bad.length}`);
for (const r of bad) console.log(` CHANGED ${r.slug} ${r.prevSha} -> ${r.nowSha}`);
process.exitCode = bad.length ? 2 : 0;
退出码非 0,就能直接挂在 cron 或 GitHub Actions 上,变了就推你一条消息。变了不等于变坏——ClipJury 自己记录过一次反方向的变更:Google 把 Flow 的可见水印从”按档位”改成了用户开关,中国内地、韩国、越南以外地区都能自己关掉,等于省下每月 100 到 200 美元的升级费。
所以 diff 出来之后要做的是人工读一遍新的 quote,更新台账里的 commercial_rights 和 read_date。这一步不能自动化,30 秒的事,别省。
三条硬规则
- 免费档当 demo。 除非条款里有那句可引用的授权原句,否则免费档生成的东西不进你的付费产品。
- 生成当天存档。 你在哪天生成的,就用哪天的条款。事后改条款不该追溯到你——但你要能证明那天写的是什么。
- 工具消失也要记。 厂商倒闭、域名失效、条款页下线,都是真实发生过的。你的台账里那行
quote加本地快照,是唯一还能拿出来的东西。
怎么知道自己做对了
5 条,全过才算建完:
- 台账覆盖了所有进你产品的 AI 工具。 检查方法:打开你的产品,逐个素材问”这是哪来的”,每一个都能在 CSV 里找到一行。找不到就是漏了。
- 每一行都有原句。
quote字段为空或写的是你自己的总结,这行作废。 snap-terms.mjs对每个工具都跑通过一次,且bytes大于 2000。 小于这个数说明抓的是个空壳,存档是假的。check-terms.mjs在 cron 里真的跑过一个月,并且你收到过至少一次通知。 没收到过通知,说明要么没跑,要么没配通知。- 你能在 60 秒内回答”我上个月卖的那个视频里的音乐,商用授权是哪一句、哪天读的、花多少钱”。 答不上来,台账就是摆设。
几个坑
- 定价页和条款页经常打架。 一半的误判来自”用付费档的措辞去证明免费档的权利”。永远认你实际付钱那一档的原文。
- 别用第三方汇总站当依据。 它们能帮你找线索,但举证时没用。原句必须来自厂商自己的页面。
- 模型厂商的条款和生成工具是两回事。 你用 API 自己搭的和用网页产品点的,授权可能完全不同;走 API 通常更宽松,但要去读那一家的服务条款,不要想当然。
- 水印≠授权。 能不能去掉水印是一件事,去掉之后能不能卖是另一件事,两条规则写在不同的页面上,都得看。
- 客户会问。 一旦你接企业单,对方采购会要你出具授权证明。这时候这张台账不是合规负担,是成交凭证,也是你比同行贵一点的底气。
最后一句
这套东西建起来大概两小时,之后每月十分钟。
它买的不是安全感,是一个具体的东西:当某个工具悄悄改了条款、或者干脆消失的时候,你能拿出一页纸说清”我那时候是被允许的”。一个人做买卖,这种纸比任何提示词都值钱。