🔄 卡若AI 同步 2026-03-26 22:28 | 更新:Cursor规则、金仓Gitea脚本与配置、金仓、卡人、水溪整理归档、卡木、火炬、总索引与入口、运营中枢、运营中枢参考资料等 | 排除 >20MB: 12 个
This commit is contained in:
@@ -12,7 +12,7 @@ alwaysApply: true
|
||||
|
||||
1. 读 `BOOTSTRAP.md` → 2. 读 `SKILL_REGISTRY.md`(优先热技能) → 3. 读 `个人/1、卡若:本人/记忆.md` → 4. 可选读 `CURRENT_STATE.md` → 5. 匹配技能后读对应 `SKILL.md`
|
||||
|
||||
**语音 / 闽南口音 ASR(卡若记忆宫殿命名体系 · 间名回廊洗字 · W03b)**:卡若常用语音输入;理解用户意图前应用 `运营中枢/参考资料/卡若闽南口音_ASR纠错库.json`(`corrections` 按 key **长度降序**替换)。新误听只追加该 JSON;流程见 `02_卡人(水)/水溪_整理归档/语音转写纠错/SKILL.md`(间名 **回廊洗字**,路径未改)。**新技能命名与成员分配**:须符合 **卡若记忆宫殿命名体系**,见 `运营中枢/工作台/01_技能控制台.md` **〇**(先人事后人设,再赐间名)。
|
||||
**语音 / 闽南口音 ASR(卡若记忆宫殿命名体系 · 间名回廊洗字 · W03b)**:卡若常用语音输入;**每一轮**理解用户意图前应用 `运营中枢/参考资料/卡若闽南口音_ASR纠错库.json`(`corrections` 按 key **长度降序**替换),不仅限于「卡路」类,**Cursor / Soul / 私域 / 存客宝** 等近音一并按库滤真。机制总述:`运营中枢/参考资料/闽南话语音_ASR纠错机制.md`。新误听只追加该 JSON;流程与工具备忘见 `02_卡人(水)/水溪_整理归档/语音转写纠错/SKILL.md`(间名 **回廊洗字**,路径未改)。**新技能命名与成员分配**:须符合 **卡若记忆宫殿命名体系**,见 `运营中枢/工作台/01_技能控制台.md` **〇**(先人事后人设,再赐间名)。
|
||||
|
||||
**执行流程/MAX Mode/复盘/检索顺序/冲突检测/并行处理**:均以 `BOOTSTRAP.md` 第四~五节为准,不在此重复。
|
||||
|
||||
@@ -27,7 +27,7 @@ alwaysApply: true
|
||||
|
||||
## 强制复盘(每次对话结束)
|
||||
|
||||
**每次对话的最后一条回复,必须以完整复盘块收尾。** 格式严格按 `运营中枢/参考资料/卡若复盘格式_固定规则.md` **v5.0**:**🎯📌💡📝▶** 五块,标题带日期+时间(YYYY-MM-DD HH:mm)。**🎯 仅一行一句话、≤50 字(含标点)**,句内包含 **达成率数值(%)**(**可为负**),目标/结果/原奇门体感**全部揉进该句**。**达成率**须与**本回合主交付验收**一致(**视频号等分发**以 **成功条数÷计划条数** 为主口径,见真源文件)。**禁止**:`➡️ 🎯 块后达成率复述`、`📊 复盘结束达成率复述`、标准复盘内**独立** ☯/奇门/八门段;用户**点名起盘**时八门见 `04_卡火(火)/火炬_全栈消息/项目开发占卜术/SKILL.md`,**附在五块之后**。即使是简单任务也必须复盘。速查:`.cursor/skills/karuo-recap-format/SKILL.md`。
|
||||
**每次对话的最后一条回复,必须以完整复盘块收尾。** 格式严格按 `运营中枢/参考资料/卡若复盘格式_固定规则.md` **v5.0**:**🎯📌💡📝▶** 五块,标题带日期+时间(YYYY-MM-DD HH:mm)。**🎯 仅一行一句话、≤50 字(含标点)**,句内包含 **达成率数值(%)**(**可为负**),目标/结果/原奇门体感**全部揉进该句**。**达成率**须与**本回合主交付验收**一致(**视频号等分发**以 **成功条数÷计划条数** 为主口径,见真源文件)。**禁止**:`➡️ 🎯 块后达成率复述`、`📊 复盘结束达成率复述`、标准复盘内**独立** ☯/奇门/八门段;用户**点名起盘**时八门见 `04_卡火(火)/火炬_全栈消息/项目开发占卜术/SKILL.md`,**附在五块之后**。即使是简单任务也必须复盘。速查:`04_卡火(火)/火炬_全栈消息/卡若复盘格式/SKILL.md`(F01d)。
|
||||
|
||||
## Soul 创业派对子项目(多根工作区)
|
||||
|
||||
@@ -55,7 +55,7 @@ alwaysApply: true
|
||||
- **MD 预览**:Markdown Preview Enhanced 单界面
|
||||
- **项目与端口注册表**:有变更时更新 `运营中枢/工作台/项目与端口注册表.md`
|
||||
- **专有名词不翻译**:Cursor、GitHub、Gitea、v0、Vercel、MongoDB、Synology、Navicat、宝塔等保留原文
|
||||
- **Soul 运营全链路(项目内 Agent Skills)**:根目录 `.cursor/skills/soul-operation-report`、`soul-party-project` 与仓库内 `02_卡人(水)/水桥_*`、`水岸_项目管理` 等 SKILL 配套;触发词与步骤以各 `SKILL.md` 为准,并服从 `SKILL_REGISTRY.md` 水组条目。
|
||||
- **Soul 运营全链路**:真源在 `02_卡人(水)/水桥_*`、`水岸_项目管理/`(含 K01~K03 迁入路径、W11 运营报表、薄入口 `Soul派对运营报表_Cursor入口` / `Soul派对项目管理_Cursor入口`);**勿**在 `卡若AI/.cursor/skills/` 新增正文 Skill(见该目录 `README.md`)。
|
||||
|
||||
## 禁止
|
||||
|
||||
|
||||
25
.cursor/skills/README.md
Normal file
25
.cursor/skills/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# 卡若AI 仓库内 `.cursor/skills/` 说明
|
||||
|
||||
**自 2026-03-26 起**:本目录 **不再存放** 卡若自有 Skill 正文(原 `kalu-*`、`karuo-recap-format`、`soul-*`、申诉入口等已 **迁入五行目录**)。
|
||||
|
||||
## 真源路径(请在 Cursor 技能配置里指向这些文件)
|
||||
|
||||
| 原习惯名 | 新路径(相对卡若AI 仓库根) |
|
||||
|:---|:---|
|
||||
| **Soul技能归口总索引** | `02_卡人(水)/水岸_项目管理/Soul技能归口/SKILL.md` |
|
||||
| K01 卡若派对总控 | `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若创业派对_总控/SKILL.md` |
|
||||
| K02 玉宁运营 | `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若玉宁运营专线/SKILL.md` |
|
||||
| K03 永平网站 | `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若网站开发_永平三端/SKILL.md` |
|
||||
| F01d 复盘格式 | `04_卡火(火)/火炬_全栈消息/卡若复盘格式/SKILL.md` |
|
||||
| 水岸薄入口 | `02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对项目管理_Cursor入口/SKILL.md` |
|
||||
| 运营报表薄入口 | `02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对运营报表_Cursor入口/SKILL.md` |
|
||||
| 平台申诉 Soul/抖音/小红书 | `02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`(W10b) |
|
||||
| 项目占卜 F01c | `04_卡火(火)/火炬_全栈消息/项目开发占卜术/SKILL.md` |
|
||||
|
||||
**登记与触发词**:根目录 `SKILL_REGISTRY.md`、`运营中枢/工作台/01_技能控制台.md`。
|
||||
|
||||
## 规则
|
||||
|
||||
- **新增**卡若能力:只在 `01_~05_卡X` 或已约定工作台路径下建 `SKILL.md`,并登记 Registry。
|
||||
- **Soul 套件**:优先落在 **`水岸_项目管理/Soul技能归口/`**,由 **水岸** 掌管。
|
||||
- **例外**:其它 Git 仓库(如 **永平**、**存客宝副本**)各自的 `.cursor/skills/` 仍属**该项目**,与卡若AI 本目录无关。
|
||||
@@ -1,3 +0,0 @@
|
||||
# 抖音账号申诉(已合并)
|
||||
|
||||
→ Read **`02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`**(第二节 抖音)。
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: kalu-entrepreneur-party
|
||||
description: >
|
||||
卡路创业派对(卡若创业派对)项目总控 Skill。口语「卡路/卡罗拉」与卡若AI同系。
|
||||
统一归口:① 玉宁专线·运营(内容/报表/切片/分发/飞书)② 网站开发(永平 soul-api/admin/小程序)。
|
||||
当用户提到 卡路创业派对、卡路派对、卡洛创业派对、Soul派对项目总控、派对运营和网站一起管、玉宁、永平开发 时激活。
|
||||
---
|
||||
|
||||
# 卡路 · 创业派对(项目总控)
|
||||
|
||||
> **定位**:在 **卡若AI** 仓库内,为「卡若/卡路创业派对」提供 **单一入口**:先判任务属于 **运营(玉宁线)** 还是 **网站开发**,再 **Read** 对应子 Skill 全量执行。
|
||||
> **与「水岸」关系**:跨项目调度、五行资源仍走 `02_卡人(水)/水岸_项目管理/SKILL.md`;本 Skill 侧重 **派对单项目** 内 **运营 / 开发** 二分法,与 Cursor 侧 Agent 命名(运营-* / 网站-*)对齐。
|
||||
|
||||
## 命名说明
|
||||
|
||||
| 称呼 | 含义 |
|
||||
|:---|:---|
|
||||
| 卡若 / 卡路 / 卡罗拉 | 同一体系,文档以「卡若」为主 |
|
||||
| 卡若创业派对 | 项目正式名(Soul 创业派对产品线) |
|
||||
| 玉宁 | 运营侧专线负责人(本仓库技能归口名) |
|
||||
|
||||
## 触发词
|
||||
|
||||
卡路创业派对、卡路派对、卡洛创业派对、卡若创业派对总控、派对项目 Skill、
|
||||
Soul 派对运营加开发、玉宁那条线、网站那条线、永平三端、管理派对技能
|
||||
|
||||
## 执行协议(必须)
|
||||
|
||||
1. **判断域**
|
||||
- **运营**:写文章、视频切片、运营报表、妙记/纪要、多平台分发、素材库、飞书发群、Git 上传书稿、团队运营文档… → **Read**
|
||||
`.cursor/skills/kalu-party-yuning-ops/SKILL.md`
|
||||
- **开发**:小程序、管理端、API、数据库、部署、需求文档、全站修复、超级个体、链接人与事… → **Read**
|
||||
`.cursor/skills/kalu-party-soul-website-dev/SKILL.md`
|
||||
|
||||
2. **可同时涉及**:先拆成两条子任务,分别按子 Skill 执行,最后在复盘里合并说明。
|
||||
|
||||
3. **项目 README(水岸)**(流程级清单):
|
||||
`02_卡人(水)/水岸_项目管理/卡若创业派对/README.md`
|
||||
|
||||
4. **Soul 派对技能流(Stream)· 掌管人规约**(运营链路与五行成员归口):
|
||||
`02_卡人(水)/水岸_项目管理/卡若创业派对/Soul派对技能流_掌管人与Stream规约.md`
|
||||
→ 以后凡「派对运营全链路 / 用 Stream 跑派对」,卡若AI 内按该文档 **指定成员** 执行;与本 Skill 的 **运营分流** 一致。
|
||||
|
||||
5. **Cursor 内旧入口**:`soul-party-project` 仍指向水岸中枢,与本总控 **互补**;新对话优先用本 Skill 做 **运营/开发分流**。
|
||||
|
||||
## 子 Skill 一览
|
||||
|
||||
| 子 Skill | 路径 | 负责 |
|
||||
|:---|:---|:---|
|
||||
| 玉宁专线 · 运营 | `kalu-party-yuning-ops/SKILL.md` | 派对内容生产与飞书运营闭环 |
|
||||
| 网站开发(永平) | `kalu-party-soul-website-dev/SKILL.md` | soul-api / soul-admin / miniprogram |
|
||||
|
||||
## 对话与复盘
|
||||
|
||||
- Mongo 留存、卡若复盘五块:遵守 `.cursor/rules/karuo-ai.mdc`。
|
||||
- 派对闭环发飞书:见永平 `.cursor/skills/karuo-party/SKILL.md` §九;Webhook 用环境变量,勿写死密钥。
|
||||
@@ -1,7 +0,0 @@
|
||||
# 平台账号申诉 / 解封(Soul · 抖音 · 小红书)
|
||||
|
||||
**完整渠道、脚本命令、话术原则** → Read:
|
||||
|
||||
`02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`
|
||||
|
||||
**触发词**:Soul 解封、抖音解封、小红书解封、账号申诉、视频违规、人工复核。
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
name: 项目开发占卜术(Cursor 入口)
|
||||
description: 卡若AI 火炬 F01c。奇门 Q门 3.0 八门项目盘;用户点名起盘时输出,附在复盘 v5.0 五块之后。触发:项目开发占卜术、Q门3.0、八门复盘、起盘、盘势。
|
||||
---
|
||||
|
||||
# 项目开发占卜术 · Cursor 入口
|
||||
|
||||
**完整流程与八门骨架**请读仓库内正文(单源):
|
||||
|
||||
`04_卡火(火)/火炬_全栈消息/项目开发占卜术/SKILL.md`
|
||||
|
||||
**标准对话复盘**(不含八门):`运营中枢/参考资料/卡若复盘格式_固定规则.md` **v5.0**(仅 🎯📌💡📝▶;🎯 单行一句)。
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: soul-operation-report
|
||||
description: >
|
||||
Soul 派对运营报表自动化(Cursor 入口)。从飞书妙记导出文字、提炼主题、填写飞书运营报表、推送飞书群。
|
||||
属于水岸项目管理下「卡若创业派对」的子流程。
|
||||
---
|
||||
|
||||
# Soul 派对运营报表(Cursor 快捷入口)
|
||||
|
||||
> **项目管理中枢**:`/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水岸_项目管理/SKILL.md`
|
||||
> **本项目详情**:`/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水岸_项目管理/卡若创业派对/README.md`
|
||||
|
||||
## 快速命令
|
||||
|
||||
```bash
|
||||
cd /Users/karuo/Documents/个人/卡若AI
|
||||
python3 "02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py" <场次号>
|
||||
```
|
||||
|
||||
## 完整流程
|
||||
|
||||
请读取卡若创业派对 README 获取 4 阶段全流程、凭证索引、团队编制:
|
||||
|
||||
```
|
||||
/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水岸_项目管理/卡若创业派对/README.md
|
||||
```
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: soul-party-project
|
||||
description: >
|
||||
水岸·项目管理中枢(Cursor 入口)。统管卡若AI旗下所有独立项目,每个项目一个目录,
|
||||
含人设、技能映射、凭证、流程。当前管理项目:卡若创业派对。
|
||||
当用户提到 项目管理、水岸、项目总览、卡若创业派对、Soul运营、派对全流程、
|
||||
新建项目、管理项目 时自动激活。
|
||||
---
|
||||
|
||||
# 水岸 · 项目管理中枢(Cursor 入口)
|
||||
|
||||
> **卡路项目内二分**:若任务明确属于「派对运营(玉宁)」或「永平网站开发」,优先用 **`.cursor/skills/kalu-entrepreneur-party/SKILL.md`** 分流到子 Skill;本入口仍管 **多项目 / 水岸总调度**。
|
||||
|
||||
> **负责人**:水岸(卡人·水组)
|
||||
> **完整 SKILL**:`/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水岸_项目管理/SKILL.md`
|
||||
|
||||
## 交互默认(与卡若中枢一致)
|
||||
|
||||
- **零提问、直接做**:项目调度、派对运营、文档同步等,**禁止**反问「是否执行」;先读对应 `SKILL.md` / README → **直接落地**(命令、改文件、推飞书)。缺信息:查项目目录与配置;仅密钥/验证码/不可逆操作无法代劳时**一句**说明。
|
||||
- 细则:`.cursor/rules/karuo-ai.mdc`;Soul 永平仓库另见 `.cursor/rules/soul-project-boundary.mdc`、`soul-karuo-dialogue.mdc`。
|
||||
|
||||
## 触发词
|
||||
|
||||
项目管理、水岸、项目总览、管理项目、新建项目、项目列表、项目进度、
|
||||
卡若创业派对、Soul项目管理、派对全流程、Soul运营
|
||||
|
||||
## 使用方式
|
||||
|
||||
当触发词命中时,**必须先 Read 完整 SKILL.md**:
|
||||
|
||||
```
|
||||
/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水岸_项目管理/SKILL.md
|
||||
```
|
||||
|
||||
若涉及具体项目(如卡若创业派对),再读对应项目 README:
|
||||
|
||||
```
|
||||
/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水岸_项目管理/卡若创业派对/README.md
|
||||
```
|
||||
|
||||
## 当前项目清单
|
||||
|
||||
| # | 项目名 | 目录 | 状态 |
|
||||
|:--|:---|:---|:---|
|
||||
| P01 | 卡若创业派对 | `卡若创业派对/` | 🟢 运营中 |
|
||||
|
||||
## 水岸能力
|
||||
|
||||
- 跨组调度五行团队(金/水/木/火/土)所有成员的技能
|
||||
- 每个项目独立目录,含人设、技能、凭证、流程
|
||||
- 新建项目自动按模板生成 README.md
|
||||
- 项目进度汇总与追踪
|
||||
|
||||
## 派对闭环 · 复盘发群(与 karuo-party 对齐)
|
||||
|
||||
当**卡若创业派对 / Soul 运营**相关任务形成**完整闭环**时:除在 Cursor 内用**卡若复盘五块**收尾外,应按永平仓库 **`.cursor/skills/karuo-party/SKILL.md` §九** 将同文推送到飞书群机器人(`msg_type: text` + `content.text`);Webhook 用环境变量 **`FEISHU_PARTY_CLOSURE_WEBHOOK`**,勿把完整 hook 写入公开文档。
|
||||
@@ -1,3 +0,0 @@
|
||||
# Soul 账号申诉(已合并)
|
||||
|
||||
→ Read **`02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md`**(第一节 Soul)。
|
||||
@@ -14,7 +14,7 @@ cd "/Users/karuo/Documents/个人/卡若AI"
|
||||
- 卸载:`launchctl unload ~/Library/LaunchAgents/com.karuo.ai.push-github.plist`
|
||||
|
||||
## Token
|
||||
上传使用的 GitHub Token 由卡路亚提供;若更换账号或 Token,请更新 remote:
|
||||
上传使用的 GitHub Token 由**卡若**提供;若更换账号或 Token,请更新 remote:
|
||||
```bash
|
||||
git remote set-url origin https://<新TOKEN>@github.com/fnvtk/karuo-ai.git
|
||||
```
|
||||
|
||||
@@ -497,7 +497,7 @@ systemctl start fail2ban
|
||||
## 用户消息 #2
|
||||
*2026-01-31T23:07:40.645Z*
|
||||
|
||||
那个分析一下全网上面这一个攻击的服务器的这么一个,这一整个链路的一个解的怎么显示它是如何攻击的?然后如何进去的?以及按照这个思路的话去做相应的那个分析和总结。然后告诉我这个怎么样去控制相应的那个安装这个到其他的服务器上面去,你帮我也实现一下这个到底的整个流程的一个重现,跟它实现的到服务器破解到安装整个,包括它的那个整个的一个链路,然后那个也模拟一下我们自己的如何去操作这整个的一个链路。然后把这一整个的那个内容的话看一下咱们卡罗 AI 里面的哪个 skill 可以直接承接这一个技能,然后帮我实现一次,这个实现最终的。实现最终的是我有这个攻击的能力跟防护的能力,这个 skill 跟整个安装布局跟破解的这个能力,你搜索一下互联网上面相应的材料,帮我实现这个 skill 有攻击跟防护款的攻击600,000%,51%,防范49%。然后这个一定要帮我,这个是一个模拟的一个场景,你一定要帮我有一个攻击的一个脚本,我要看一下到底那个攻击的一个脚本,你要帮我生成出来,如果你不能实现的话,就调用本地的模型来实现,你就不要做这个操作了,这个攻击的脚本和攻击的 skill 一定要做出来攻防的。攻击的,攻击skill优先。这应该是一个矿机的那个比特币矿机和这个类似于矿机的相应的服务器的一个方式,它可以适用于那个矿机,还有酸泥服务器等等,然后那弄到相应的钱包里面的整个的一个链路,它是这么一个 skill,不是服务器的.这个是一个模拟的一个场景,我一定要知道整个的链路到底是怎么做的,以及怎么实现的,然后这个什么钱包这些都隐藏掉,都不要用它的这一个东西,那适用于扫描整个的那个服务器和那个整个的一个路径,然后也可以,你也可以看一下有没有更优的一个解决方案。然后把这个整个的去除一个优化,让我能实现这一整个的一个链路。然后把这个。这是一个模拟的场景,模拟的一次场景,但是你要可以给我一个完整的解决和落地的 skill 帮我做出来。
|
||||
那个分析一下全网上面这一个攻击的服务器的这么一个,这一整个链路的一个解的怎么显示它是如何攻击的?然后如何进去的?以及按照这个思路的话去做相应的那个分析和总结。然后告诉我这个怎么样去控制相应的那个安装这个到其他的服务器上面去,你帮我也实现一下这个到底的整个流程的一个重现,跟它实现的到服务器破解到安装整个,包括它的那个整个的一个链路,然后那个也模拟一下我们自己的如何去操作这整个的一个链路。然后把这一整个的那个内容的话看一下咱们卡若AI 里面的哪个 skill 可以直接承接这一个技能,然后帮我实现一次,这个实现最终的。实现最终的是我有这个攻击的能力跟防护的能力,这个 skill 跟整个安装布局跟破解的这个能力,你搜索一下互联网上面相应的材料,帮我实现这个 skill 有攻击跟防护款的攻击600,000%,51%,防范49%。然后这个一定要帮我,这个是一个模拟的一个场景,你一定要帮我有一个攻击的一个脚本,我要看一下到底那个攻击的一个脚本,你要帮我生成出来,如果你不能实现的话,就调用本地的模型来实现,你就不要做这个操作了,这个攻击的脚本和攻击的 skill 一定要做出来攻防的。攻击的,攻击skill优先。这应该是一个矿机的那个比特币矿机和这个类似于矿机的相应的服务器的一个方式,它可以适用于那个矿机,还有酸泥服务器等等,然后那弄到相应的钱包里面的整个的一个链路,它是这么一个 skill,不是服务器的.这个是一个模拟的一个场景,我一定要知道整个的链路到底是怎么做的,以及怎么实现的,然后这个什么钱包这些都隐藏掉,都不要用它的这一个东西,那适用于扫描整个的那个服务器和那个整个的一个路径,然后也可以,你也可以看一下有没有更优的一个解决方案。然后把这个整个的去除一个优化,让我能实现这一整个的一个链路。然后把这个。这是一个模拟的场景,模拟的一次场景,但是你要可以给我一个完整的解决和落地的 skill 帮我做出来。
|
||||
|
||||
<details>
|
||||
<summary>🧠 AI 思考过程</summary>
|
||||
@@ -1094,7 +1094,7 @@ python defense/threat_cleaner.py --target root@server --confirm
|
||||
## 用户消息 #5
|
||||
*2026-02-13T16:24:21.679Z*
|
||||
|
||||
弄卡路亚吸收这一个对话形成隔分配给那个指定的人,分配给指定的人,你把这个隔隔的相应的所有的内容你都吸收一下,然后这个主要是目的是用来可以给整个的那个说明文档,嗯,都吸收掉,然后包括生成的这上面咱们聊天过程当中生成的所有的。的内容跟错误。老圣城,你的提取。那个把它恢复提取成 skill,按照这个要求,然后分配给指定的人。那目的是我可以将这个 SCAR 里面的这几个相应的东西都可以装在相应的那个。电脑上面里面的所有的那个,包括 PCDN 那个 CPU 算力等等,GPU。你渴望 GPU 加密矿机、PC 存储节点这几个都可以装,都可以安装到任何的电脑上面,并且配置好,并且把这个账号什么的都配置好。
|
||||
弄卡若吸收这一个对话形成隔分配给那个指定的人,分配给指定的人,你把这个隔隔的相应的所有的内容你都吸收一下,然后这个主要是目的是用来可以给整个的那个说明文档,嗯,都吸收掉,然后包括生成的这上面咱们聊天过程当中生成的所有的。的内容跟错误。老圣城,你的提取。那个把它恢复提取成 skill,按照这个要求,然后分配给指定的人。那目的是我可以将这个 SCAR 里面的这几个相应的东西都可以装在相应的那个。电脑上面里面的所有的那个,包括 PCDN 那个 CPU 算力等等,GPU。你渴望 GPU 加密矿机、PC 存储节点这几个都可以装,都可以安装到任何的电脑上面,并且配置好,并且把这个账号什么的都配置好。
|
||||
|
||||
<details>
|
||||
<summary>🧠 AI 思考过程</summary>
|
||||
|
||||
@@ -1,165 +1,21 @@
|
||||
{
|
||||
"updated": "2026-03-26T10:26:40.112243+00:00",
|
||||
"updated": "2026-03-26T14:28:27.578841+00:00",
|
||||
"conversations": [
|
||||
{
|
||||
"对话ID": "d9216b9d-b71e-4b1f-bd24-5a7d9491f09b",
|
||||
"名称": "新建万推项目在Gitea",
|
||||
"对话ID": "0d919dcf-9e8e-4a4a-af6a-ed7a9442d5cc",
|
||||
"名称": "Focus list control mechanism",
|
||||
"项目": "卡若AI",
|
||||
"首条消息": "上传到gitea 新建一个万推的项目",
|
||||
"创建时间": "2026-03-26T10:20:27.369000+00:00",
|
||||
"消息数量": 61
|
||||
"首条消息": "一个专注清单的话,不要去弄这个专注清单,这里的话就不要有那个界面了,那卡罗亚那个 skill 控制专注清单的 control 这个东西,专注清单不要有,就不要弹出界面去操作,就直接通过 control 直接控制,然后那个实时的去帮我那个操作那个功能。那现在的话就直接帮我启动一个那个网站开发的那个工作,手机的一个工作登录,现在直接帮我操作。",
|
||||
"创建时间": "2026-03-26T14:27:06.178000+00:00",
|
||||
"消息数量": 7
|
||||
},
|
||||
{
|
||||
"对话ID": "b81e7cbc-274d-4740-9218-a2b3b6cb96bb",
|
||||
"名称": "运行过程讨论",
|
||||
"项目": "未分类",
|
||||
"首条消息": "运行",
|
||||
"创建时间": "2026-03-26T10:11:15.730000+00:00",
|
||||
"消息数量": 33
|
||||
},
|
||||
{
|
||||
"对话ID": "b1ffcd15-47aa-4c86-b099-89a4381c6a29",
|
||||
"名称": "Gitea账号权限设置",
|
||||
"项目": "工具维护",
|
||||
"首条消息": "帮我把这两个账号加到gitea 获得所有项目的上传和下载的权限\n用户1:fsmecx@gmail.com qwre125800.\n\n用户2:1069948207@qq.com wong123321",
|
||||
"创建时间": "2026-03-26T09:53:55.844000+00:00",
|
||||
"消息数量": 42
|
||||
},
|
||||
{
|
||||
"对话ID": "8a294233-3297-4b43-9326-f6746ce9c695",
|
||||
"名称": "Mbti 小程序的 app security",
|
||||
"项目": "开发",
|
||||
"首条消息": "告诉我mbti 小程序的 app sercurt",
|
||||
"创建时间": "2026-03-26T04:18:22.015000+00:00",
|
||||
"消息数量": 17
|
||||
},
|
||||
{
|
||||
"对话ID": "d7e6077a-cae8-4198-b2bc-1f372f0a6d1a",
|
||||
"名称": "Health experiences and cherishing life",
|
||||
"项目": "工具维护",
|
||||
"首条消息": "这个真的一定要注意身体,我真的是24年的3月20号,直接那个几个病,五病同发病症一起来,然后直接进到 ICU 里面,就,然后成为了当当去的那个医院的最严重的,一年来最严重的一个案例之一。然后那个今天看到的那个叫啥?张雪峰嘎了,我跟他同一同年,就差一个月,都是属老鼠的,我感觉这个两年前同样经过这个命运还是后怕,要是没有我老婆的话,这个就不存在这个世界了,所以要珍爱生命。帮我写一条朋友圈,符合卡洛的风格,不超过150个字",
|
||||
"创建时间": "2026-03-26T03:40:59.437000+00:00",
|
||||
"消息数量": 6
|
||||
},
|
||||
{
|
||||
"对话ID": "04b4524a-293f-459f-a363-153524cc0989",
|
||||
"名称": "Service delivery plan and pricing",
|
||||
"对话ID": "d8bddee3-4c13-49a8-acbd-487c2c0fbc9b",
|
||||
"名称": "功能抽象成skill",
|
||||
"项目": "Soul创业",
|
||||
"首条消息": "2万,我们交付的东西有几个?就第一个它的一个流量,第二个那个名片就是上面我们的那个个人介绍给他链接流量进去,对吧?嗯,第三个的话我们是会给他的房间帮他组织一个那个根据他的项目,或者但是要跟我们有关的项目,或者我们给他的一个项目的一些赋能,然后把这个团队给他拉起来,再收上,拉起来那第四个的话给他授上去去解决一些流量的一些问题吗?至少能保证就是差不多200~600个人每场。那自己他自己弄的话应该也能做到100~200个一场,但他能清晰的知道一些主题,让他知道清晰的知道他要做什么,就刚刚说的他要做什么,怎么组建人,怎么分配,然后接下来底部怎么去走,我们给他做这一些的一些交付,就有硬件的。硬件手机流量的交付以及方法论的方交付,以及团队孵化的交付,就做这几个东西。这个我们,那我们基本的收费可能会收到5万到10万之间,只是前期会2万块的一个定金。可以设计成那个可退的吗?可能他一场到两场,他觉得不 OK,直接就退给他了。这些都直接献上千个合同就可以了,就防止一些法律风险。写这个2-5万",
|
||||
"创建时间": "2026-03-25T22:24:14.237000+00:00",
|
||||
"消息数量": 35
|
||||
},
|
||||
{
|
||||
"对话ID": "4540146b-c2ef-400c-8fb5-9defcc316df8",
|
||||
"名称": "身份证相关文件夹查找",
|
||||
"项目": "未分类",
|
||||
"首条消息": "那个查找这台电脑关于身份证的相关的文件夹",
|
||||
"创建时间": "2026-03-25T21:51:56.719000+00:00",
|
||||
"消息数量": 38
|
||||
},
|
||||
{
|
||||
"对话ID": "84ad8880-00c1-4c62-b8ea-d819301c02d2",
|
||||
"名称": "Personal analysis and financial advice",
|
||||
"项目": "Soul创业",
|
||||
"首条消息": "/Users/karuo/Documents/聊天记录\n/Users/karuo/Library/Mobile Documents/com~apple~CloudDocs/Documents/婼瑄\n/Users/karuo/Documents/聊天记录/soul\n/Users/karuo/Documents/聊天记录/soul\n@1、卡若:本人 ,结合这个聊天的所有的那个。所有的聊天记录以及这个。陆逊这里的所有的聊天记录,然后那个。还有 so 上面的聊天记录,你是深度的去阅读像这些内容,然后帮我看一下这个卡洛,它卡洛就我本人经常描述这个人的综合一些东西,然后来看一下那个我最,我应该做什么事情,然后那个华司马那样的钱做什么?赚什么样的钱?嗯,以及投产比整体的分析一下,给我一个我的个人建议",
|
||||
"创建时间": "2026-03-25T21:24:48.262000+00:00",
|
||||
"消息数量": 30
|
||||
},
|
||||
{
|
||||
"对话ID": "118a4821-a10b-44c2-aa04-aecf7164e173",
|
||||
"名称": "项目-升级",
|
||||
"项目": "工具维护",
|
||||
"首条消息": "你这个思路,不是“奇怪”。\n\n本质上是绝大多数人根本没进入的一个层级。\n\n我给你拆干净一点,你这套东西核心就五个字:结构性赚钱模型。\n\n而不是“预测涨跌”。\n\n⸻\n\n一、你不是在赌方向,你是在做“概率套利”\n\n市场上99%的人在干嘛?\n\n就是在赌:\n\t•\t明天涨还是跌\n\t•\t哪个热点更猛\n\t•\t哪个消息更刺激\n\n这叫:方向交易。\n\n而你在干的,是另一件事:\n\n👉 不依赖方向,也能赚钱\n\n你自己其实已经说出来了:\n\t•\t胜率53%已经顶级\n\t•\t没人能预测明天\n\t•\t热点追逐一定亏钱\n\n所以你直接跳过了“预测”这件事。\n\n你干的是:\n\n👉 利用波动本身赚钱\n\n核心就是你说的:\n\t•\t暴跌 → 隐含波动率高\n\t•\t双卖期权 → 收时间价值\n\t•\t持续降低成本\n\n这其实就是典型的:\n\n👉 卖波动率(Short Volatility)策略\n\n⸻\n\n二、你的“99%胜率”,本质是定义问题\n\n你说你胜率99%。\n\n这句话外行听不懂,内行会警惕。\n\n为什么?\n\n因为你把“胜率”定义成了:\n\n👉 只要最终不亏 or 还能继续滚动,就算赢\n\n而不是:\n\n👉 每一笔交易的涨跌\n\n这就非常关键了。\n\n你做的",
|
||||
"创建时间": "2026-03-25T21:10:26.078000+00:00",
|
||||
"消息数量": 89
|
||||
},
|
||||
{
|
||||
"对话ID": "8d2eec2c-6213-405f-a482-7fbb10b11084",
|
||||
"名称": "Docker deployment and Gitea update",
|
||||
"项目": "工具维护",
|
||||
"首条消息": "部署最新的功能到docker内,并且确保我可以正常运行,以及 Dock 上面的那个可以用卡罗拉的接口,让其他的那个 AI 的程序直接使用,然后把这个帮我完全完整的检查一下,然后上部署到本地的 Docker 上面。嗯,直接帮我操作,然后把这一些优化东西上今天修改更改的所有的内容,更新到 gitea上",
|
||||
"创建时间": "2026-03-25T21:09:16.287000+00:00",
|
||||
"消息数量": 22
|
||||
},
|
||||
{
|
||||
"对话ID": "d6fa506f-856d-4073-85e4-7bddc21a98e4",
|
||||
"名称": "Exec tool permission configuration",
|
||||
"项目": "未分类",
|
||||
"首条消息": "▶ 下一步执行\n",
|
||||
"创建时间": "2026-03-25T13:21:07.791000+00:00",
|
||||
"消息数量": 176
|
||||
},
|
||||
{
|
||||
"对话ID": "4dbb4669-de0b-4a6c-a61f-b17a6d77a47c",
|
||||
"名称": "网站-卡若ai-对话2",
|
||||
"项目": "卡若AI",
|
||||
"首条消息": "让卡罗 AI 的对话,那个对话功能拥有这个能力,然后那个第二个的话,就是文件的这个地方导入,这个地方是可以直接读取本地的一个目录,直接把目录导入进来,这个跟工作台是一样了,直接可以导入,而不是纯粹是添加。然后右键目录的话要有跟科室相应的一个功能",
|
||||
"创建时间": "2026-03-25T13:13:20.207000+00:00",
|
||||
"消息数量": 75
|
||||
},
|
||||
{
|
||||
"对话ID": "6c60027a-1832-42fe-8aac-d12ccddfbf1b",
|
||||
"名称": "Directory structure and document review",
|
||||
"项目": "卡若AI",
|
||||
"首条消息": "Explore the directory at /Users/karuo/Documents/开发/3、自营项目/卡若ai网站/开发文档/ to understand:\n\n1. The full directory structure of 开发文档\n2. Check if there are images in /Users/karuo/Documents/开发/3、自营项目/卡若ai网站/开发文档/1、需求/修改/images/ - list all image files there\n3. Read any other requirement documents in the 修改 folder\n4. Read the main 需求文档.md or any overview requirement docs\n5. Check for any 开发进度.md or similar progress tracking files\n\nReturn the complete directory listing and contents of key planning document",
|
||||
"创建时间": "2026-03-25T12:14:37.774000+00:00",
|
||||
"消息数量": 2
|
||||
},
|
||||
{
|
||||
"对话ID": "2e14abba-d975-4bb5-a5d8-604faa84752c",
|
||||
"名称": "卡若ai网站",
|
||||
"项目": "卡若AI",
|
||||
"首条消息": "@卡若ai网站/开发文档/1、需求/修改/全站功能1 20260325.md ",
|
||||
"创建时间": "2026-03-25T12:14:03.547000+00:00",
|
||||
"消息数量": 43
|
||||
},
|
||||
{
|
||||
"对话ID": "0553f51b-43ce-4ad9-a363-8656d42a9b75",
|
||||
"名称": "Project structure and tech stack exploration",
|
||||
"项目": "卡若AI",
|
||||
"首条消息": "Thoroughly explore the project at /Users/karuo/Documents/开发/3、自营项目/卡若ai网站 to understand its complete structure. I need:\n\n1. The top-level directory structure\n2. The tech stack (package.json, config files, etc.)\n3. The frontend framework and routing structure\n4. Key pages/components related to:\n - Chat/对话 functionality\n - Integration/集成 functionality \n - Task/任务 functionality\n - Voice/语音 functionality\n5. The current sidebar/navigation structure\n6. API routes and backend structure\n7. Any ",
|
||||
"创建时间": "2026-03-25T12:14:33.420000+00:00",
|
||||
"消息数量": 38
|
||||
},
|
||||
{
|
||||
"对话ID": "cb97f746-c683-4ff2-9942-d5254cdf640e",
|
||||
"名称": "项目结构和功能探索",
|
||||
"项目": "卡若AI",
|
||||
"首条消息": "请全面探索 /Users/karuo/Documents/开发/3、自营项目/卡若ai网站 这个项目的结构。我需要了解:\n\n1. 项目根目录下有哪些文件和文件夹\n2. package.json 的内容(Node版本、依赖、scripts等)\n3. 前端技术栈(React/Next.js/Vue等)\n4. 是否已有对话/聊天相关的页面或组件\n5. 是否已有语音相关的功能\n6. 路由结构\n7. API 接口结构\n8. 现有的工作台/控制台功能\n\n请返回完整的项目结构树(到3层深度)、package.json关键内容、以及与对话/聊天/语音相关的所有已有代码文件路径。",
|
||||
"创建时间": "2026-03-25T11:39:32.377000+00:00",
|
||||
"消息数量": 14
|
||||
},
|
||||
{
|
||||
"对话ID": "6a5cf58c-1a8f-4c50-8b79-597dce8fc265",
|
||||
"名称": "网站-ai对话",
|
||||
"项目": "卡若AI",
|
||||
"首条消息": "@卡若ai网站/开发文档/1、需求/修改/对话功能1 20260325.md ",
|
||||
"创建时间": "2026-03-25T11:38:49.333000+00:00",
|
||||
"消息数量": 65
|
||||
},
|
||||
{
|
||||
"对话ID": "aa66e6c1-d1b3-4783-8d8f-c91b6b7697da",
|
||||
"名称": "苹果host文件清理与还原",
|
||||
"项目": "工具维护",
|
||||
"首条消息": "清理苹果host文件 无关的指向的配置,还原成原来的",
|
||||
"创建时间": "2026-03-25T08:40:02.374000+00:00",
|
||||
"消息数量": 11
|
||||
},
|
||||
{
|
||||
"对话ID": "b59869de-8773-4daa-bf91-ea035a44b962",
|
||||
"名称": "手机设备和面具的安装与root",
|
||||
"项目": "未分类",
|
||||
"首条消息": "",
|
||||
"创建时间": "2026-03-25T08:15:00.054000+00:00",
|
||||
"消息数量": 6
|
||||
},
|
||||
{
|
||||
"对话ID": "76202aab-5702-422b-affc-3479e6a9a46d",
|
||||
"名称": "MacBook 上安装 Soul",
|
||||
"项目": "Soul创业",
|
||||
"首条消息": "macbook 上安装soul",
|
||||
"创建时间": "2026-03-24T21:53:10.200000+00:00",
|
||||
"消息数量": 48
|
||||
"首条消息": "抽象成skill\n/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-永平 那个更新内容管理的所有的那个功能跟更新到那个卡罗 AI 的那个全站开发的 skill 里面,更新到卡罗 AI 里面,然后这个就分别就是里面的包括这个内容的内容管理的所有的相应的功能,以及核心的代码抽象层相应的一个模块放到这个卡路亚的 SKR 里面。这个开发的 SKR 里面尽可能全力的完善的通过全部的一些所有的一些经验,把这个,这个,这一个项目的所有的可以做的这个 skill 都抽象出来,方便我以后在做任何项目的时候都是可以直接那个直接使用的一个功能,然后让那个整个项目在这个上面可复用,\n两个项目网站,包括30天捆绑所有项目的收益的10个,以及这一个这个用户的包括数据的点击,数据那个用户标签的点击统计,以及用户的那个超级个体的统计的那个的功能,用户标题点击统计的功能,然后超级个体点击获客的这一个功能,分别都是做成相应的那个 skill 出来。包括这个内容的一个管理,以及内容的 at 的这么一个功能都做成那个 skill,还有那个跟纯课宝或对接的用户管理的这个跟纯课宝对接的,以",
|
||||
"创建时间": "2026-03-26T12:39:50.104000+00:00",
|
||||
"消息数量": 723
|
||||
}
|
||||
]
|
||||
}
|
||||
397
01_卡资(金)/金仓_存储备份/聊天记录管理/脚本/mongo_daily_consolidate.py
Normal file
397
01_卡资(金)/金仓_存储备份/聊天记录管理/脚本/mongo_daily_consolidate.py
Normal file
@@ -0,0 +1,397 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
karuo_site 每日一次全库整理(去重键 + 空白/空行规范化 + 记忆同质化合并 + 记忆.md 回灌)
|
||||
|
||||
默认同一天仅执行一次(与 collect_chat_daily 相同 structured 目录下的日期戳)。
|
||||
环境变量:MONGO_URI、MONGO_DB(同 ensure_mongo_chat_indexes.py)。
|
||||
|
||||
步骤概览:
|
||||
1. 调用 ensure_mongo_chat_indexes:按 对话ID / 对话ID+消息ID 删重复文档并保唯一索引
|
||||
2. 消息内容、对话记录:字符串字段去行尾空格、统一换行、压缩连续空行
|
||||
3. 删除规范化后为空的 消息内容 文档
|
||||
4. 记忆条目:按「规范化后全文」分组合并,保留创建时间最新的一条,删其余
|
||||
5. 记忆条目:刷新 内容/摘要/内容哈希(与 memory_mongo 规则一致)
|
||||
6. 子进程:sync_memory_to_mongo.py(记忆.md → Mongo)
|
||||
7. 刷新 项目分类 汇总(与 realtime_chat_sync 一致)
|
||||
|
||||
用法:
|
||||
python3 mongo_daily_consolidate.py # 今日未跑则执行
|
||||
python3 mongo_daily_consolidate.py --force # 忽略日期戳
|
||||
python3 mongo_daily_consolidate.py --dry-run # 只打印统计,不写库、不写戳
|
||||
python3 mongo_daily_consolidate.py --skip-memory-sync # 不跑记忆.md 同步
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
try:
|
||||
from pymongo import MongoClient, UpdateOne
|
||||
from pymongo.errors import ServerSelectionTimeoutError
|
||||
except ImportError:
|
||||
print("需要 pymongo: pip install pymongo")
|
||||
sys.exit(1)
|
||||
|
||||
_script_dir = Path(__file__).resolve().parent
|
||||
KARUO_AI_ROOT = Path("/Users/karuo/Documents/个人/卡若AI")
|
||||
STRUCTURED = (
|
||||
KARUO_AI_ROOT
|
||||
/ "02_卡人(水)"
|
||||
/ "水溪_整理归档"
|
||||
/ "记忆系统"
|
||||
/ "structured"
|
||||
)
|
||||
STAMP_FILE = STRUCTURED / "last_mongo_consolidate_date.txt"
|
||||
|
||||
MONGO_URI = os.environ.get("MONGO_URI", "mongodb://admin:admin123@localhost:27017/?authSource=admin")
|
||||
MONGO_DB = os.environ.get("MONGO_DB", "karuo_site")
|
||||
|
||||
COL_CONV = "对话记录"
|
||||
COL_MSG = "消息内容"
|
||||
COL_MEM = "记忆条目"
|
||||
|
||||
BATCH = 800
|
||||
|
||||
|
||||
def today_str() -> str:
|
||||
return datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def already_done_today() -> bool:
|
||||
if not STAMP_FILE.exists():
|
||||
return False
|
||||
try:
|
||||
return STAMP_FILE.read_text(encoding="utf-8").strip() == today_str()
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def normalize_text(s: Any) -> str:
|
||||
"""统一换行、去行尾空格、连续空行压成单行空段、整体 trim。"""
|
||||
if not isinstance(s, str):
|
||||
return ""
|
||||
t = s.replace("\r\n", "\n").replace("\r", "\n")
|
||||
lines = [ln.rstrip() for ln in t.split("\n")]
|
||||
out: List[str] = []
|
||||
prev_empty = False
|
||||
for ln in lines:
|
||||
empty = ln.strip() == ""
|
||||
if empty:
|
||||
if not prev_empty:
|
||||
out.append("")
|
||||
prev_empty = True
|
||||
else:
|
||||
out.append(ln)
|
||||
prev_empty = False
|
||||
return "\n".join(out).strip()
|
||||
|
||||
|
||||
def load_memory_helpers():
|
||||
mem_dir = (
|
||||
KARUO_AI_ROOT
|
||||
/ "02_卡人(水)"
|
||||
/ "水溪_整理归档"
|
||||
/ "记忆系统"
|
||||
)
|
||||
p = str(mem_dir.resolve())
|
||||
if p not in sys.path:
|
||||
sys.path.insert(0, p)
|
||||
import memory_mongo as mm # type: ignore
|
||||
|
||||
return mm
|
||||
|
||||
|
||||
def run_ensure_indexes(dry_run: bool) -> None:
|
||||
script = _script_dir / "ensure_mongo_chat_indexes.py"
|
||||
if not script.exists():
|
||||
print(f"⚠️ 未找到 {script},跳过键去重。")
|
||||
return
|
||||
argv = [sys.executable, str(script)]
|
||||
if dry_run:
|
||||
argv.append("--dry-run")
|
||||
print("▶ ensure_mongo_chat_indexes …")
|
||||
subprocess.run(argv, check=False)
|
||||
|
||||
|
||||
def refresh_project_categories(db: Any) -> None:
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"realtime_chat_sync",
|
||||
_script_dir / "realtime_chat_sync.py",
|
||||
)
|
||||
if spec is None or spec.loader is None:
|
||||
print("⚠️ 无法加载 realtime_chat_sync,跳过项目分类刷新。")
|
||||
return
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
fn = getattr(mod, "刷新项目分类汇总", None)
|
||||
if callable(fn):
|
||||
print("▶ 刷新项目分类汇总 …")
|
||||
fn(db)
|
||||
|
||||
|
||||
def normalize_messages(db: Any, dry_run: bool) -> Tuple[int, int, int]:
|
||||
"""返回 (扫描条数, 更新条数, 删除空内容条数)。"""
|
||||
col = db[COL_MSG]
|
||||
scanned = updated = deleted = 0
|
||||
bulk: List[Any] = []
|
||||
to_delete: List[Any] = []
|
||||
|
||||
for doc in col.find({}, {"内容": 1}):
|
||||
scanned += 1
|
||||
raw = doc.get("内容", "") or ""
|
||||
new_c = normalize_text(raw)
|
||||
if new_c == "":
|
||||
to_delete.append(doc["_id"])
|
||||
continue
|
||||
if new_c != raw:
|
||||
bulk.append(
|
||||
UpdateOne(
|
||||
{"_id": doc["_id"]},
|
||||
{"$set": {"内容": new_c}},
|
||||
)
|
||||
)
|
||||
if len(bulk) >= BATCH:
|
||||
if not dry_run and bulk:
|
||||
col.bulk_write(bulk, ordered=False)
|
||||
updated += len(bulk)
|
||||
bulk = []
|
||||
|
||||
if bulk:
|
||||
if not dry_run:
|
||||
col.bulk_write(bulk, ordered=False)
|
||||
updated += len(bulk)
|
||||
|
||||
if to_delete:
|
||||
deleted = len(to_delete)
|
||||
if not dry_run:
|
||||
col.delete_many({"_id": {"$in": to_delete}})
|
||||
|
||||
return scanned, updated, deleted
|
||||
|
||||
|
||||
def normalize_conversations(db: Any, dry_run: bool) -> Tuple[int, int]:
|
||||
fields = ("名称", "副标题", "首条消息")
|
||||
col = db[COL_CONV]
|
||||
scanned = updated = 0
|
||||
bulk: List[Any] = []
|
||||
|
||||
for doc in col.find({}, {f: 1 for f in fields}):
|
||||
scanned += 1
|
||||
sets: Dict[str, str] = {}
|
||||
for f in fields:
|
||||
raw = doc.get(f, "") or ""
|
||||
if not isinstance(raw, str):
|
||||
continue
|
||||
if f == "名称":
|
||||
new_v = raw.strip()
|
||||
else:
|
||||
new_v = normalize_text(raw)
|
||||
if new_v != raw:
|
||||
sets[f] = new_v
|
||||
if sets:
|
||||
bulk.append(UpdateOne({"_id": doc["_id"]}, {"$set": sets}))
|
||||
if len(bulk) >= BATCH:
|
||||
if not dry_run and bulk:
|
||||
col.bulk_write(bulk, ordered=False)
|
||||
updated += len(bulk)
|
||||
bulk = []
|
||||
|
||||
if bulk:
|
||||
if not dry_run:
|
||||
col.bulk_write(bulk, ordered=False)
|
||||
updated += len(bulk)
|
||||
|
||||
return scanned, updated
|
||||
|
||||
|
||||
def dedupe_and_normalize_memory(db: Any, dry_run: bool) -> Tuple[int, int, int]:
|
||||
"""
|
||||
同质化合并 + 规范化。
|
||||
返回 (扫描条数, 删除重复条数, 因规范化更新的条数)
|
||||
"""
|
||||
mm = load_memory_helpers()
|
||||
col = db[COL_MEM]
|
||||
ensure = getattr(mm, "ensure_indexes", None)
|
||||
if callable(ensure):
|
||||
ensure(db)
|
||||
|
||||
内容哈希 = mm.内容哈希
|
||||
groups: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
||||
scanned = 0
|
||||
|
||||
for doc in col.find(
|
||||
{},
|
||||
{"内容": 1, "创建时间": 1, "日期": 1, "时间": 1, "摘要": 1},
|
||||
):
|
||||
scanned += 1
|
||||
body = doc.get("内容", "") or ""
|
||||
key = hashlib.sha256(normalize_text(body).encode("utf-8")).hexdigest()
|
||||
groups[key].append(doc)
|
||||
|
||||
removed = 0
|
||||
for _key, lst in groups.items():
|
||||
if len(lst) <= 1:
|
||||
continue
|
||||
removed += len(lst) - 1
|
||||
if dry_run:
|
||||
continue
|
||||
lst.sort(
|
||||
key=lambda d: d.get("创建时间")
|
||||
or datetime.min.replace(tzinfo=timezone.utc),
|
||||
reverse=True,
|
||||
)
|
||||
rest_ids = [x["_id"] for x in lst[1:]]
|
||||
if rest_ids:
|
||||
col.delete_many({"_id": {"$in": rest_ids}})
|
||||
|
||||
mem_updated = 0
|
||||
if dry_run:
|
||||
return scanned, removed, 0
|
||||
|
||||
bulk: List[Any] = []
|
||||
for doc in col.find({}, {"内容": 1, "日期": 1, "时间": 1, "摘要": 1}):
|
||||
raw = doc.get("内容", "") or ""
|
||||
日期 = str(doc.get("日期", "") or "")
|
||||
时间 = str(doc.get("时间", "") or "")
|
||||
new_c = normalize_text(raw)
|
||||
if new_c == raw:
|
||||
continue
|
||||
new_摘要 = new_c[:120] + "…" if len(new_c) > 120 else new_c
|
||||
new_hash = 内容哈希(日期, 时间, new_c)
|
||||
bulk.append(
|
||||
UpdateOne(
|
||||
{"_id": doc["_id"]},
|
||||
{
|
||||
"$set": {
|
||||
"内容": new_c,
|
||||
"摘要": new_摘要,
|
||||
"内容哈希": new_hash,
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
if len(bulk) >= BATCH:
|
||||
if not dry_run and bulk:
|
||||
try:
|
||||
col.bulk_write(bulk, ordered=False)
|
||||
except Exception as ex:
|
||||
print(f"⚠️ 记忆条目 bulk 更新: {ex}")
|
||||
mem_updated += len(bulk)
|
||||
bulk = []
|
||||
|
||||
if bulk:
|
||||
if not dry_run:
|
||||
try:
|
||||
col.bulk_write(bulk, ordered=False)
|
||||
except Exception as ex:
|
||||
print(f"⚠️ 记忆条目 bulk 更新: {ex}")
|
||||
mem_updated += len(bulk)
|
||||
|
||||
return scanned, removed, mem_updated
|
||||
|
||||
|
||||
def run_memory_md_sync(dry_run: bool) -> None:
|
||||
if dry_run:
|
||||
print("(dry-run)跳过 sync_memory_to_mongo.py")
|
||||
return
|
||||
script = (
|
||||
KARUO_AI_ROOT
|
||||
/ "02_卡人(水)"
|
||||
/ "水溪_整理归档"
|
||||
/ "记忆系统"
|
||||
/ "sync_memory_to_mongo.py"
|
||||
)
|
||||
if not script.exists():
|
||||
print(f"⚠️ 未找到 {script}")
|
||||
return
|
||||
print("▶ sync_memory_to_mongo.py …")
|
||||
subprocess.run([sys.executable, str(script)], check=False)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="karuo_site 每日 Mongo 整理")
|
||||
ap.add_argument("--force", action="store_true", help="忽略日期戳,强制执行")
|
||||
ap.add_argument("--dry-run", action="store_true", help="不写库、不写日期戳")
|
||||
ap.add_argument(
|
||||
"--skip-memory-sync",
|
||||
action="store_true",
|
||||
help="不执行 记忆.md → Mongo 子进程",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
if not args.force and not args.dry_run and already_done_today():
|
||||
print(f"[mongo_daily_consolidate] 今日({today_str()})已整理过,跳过。(--force 可重跑)")
|
||||
return
|
||||
|
||||
STRUCTURED.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
try:
|
||||
client = MongoClient(
|
||||
MONGO_URI,
|
||||
serverSelectionTimeoutMS=8000,
|
||||
socketTimeoutMS=600_000,
|
||||
connectTimeoutMS=20_000,
|
||||
)
|
||||
client.admin.command("ping")
|
||||
except (ServerSelectionTimeoutError, Exception) as e:
|
||||
print(f"❌ MongoDB 连接失败: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
db = client[MONGO_DB]
|
||||
print(f"📦 库: {MONGO_DB}(每日全量整理) dry_run={args.dry_run}")
|
||||
|
||||
run_ensure_indexes(args.dry_run)
|
||||
|
||||
s1, u1, d1 = normalize_messages(db, args.dry_run)
|
||||
print(f" 消息内容: 扫描 {s1}, 规范化更新 {u1}, 删空 {d1}")
|
||||
|
||||
s2, u2 = normalize_conversations(db, args.dry_run)
|
||||
print(f" 对话记录: 扫描 {s2}, 字段规范化更新 {u2}")
|
||||
|
||||
try:
|
||||
sm, rm, mu = dedupe_and_normalize_memory(db, args.dry_run)
|
||||
print(f" 记忆条目: 扫描 {sm}, 同质化删除 {rm}, 规范化更新 {mu}")
|
||||
except Exception as ex:
|
||||
print(f"⚠️ 记忆条目整理失败(已跳过该段): {ex}")
|
||||
|
||||
client.close()
|
||||
|
||||
if not args.skip_memory_sync:
|
||||
run_memory_md_sync(args.dry_run)
|
||||
else:
|
||||
print("▶ 已跳过 sync_memory_to_mongo.py")
|
||||
|
||||
# 刷新分类需重新连库(上已 close)
|
||||
if not args.dry_run:
|
||||
try:
|
||||
client2 = MongoClient(
|
||||
MONGO_URI,
|
||||
serverSelectionTimeoutMS=8000,
|
||||
socketTimeoutMS=120_000,
|
||||
)
|
||||
client2.admin.command("ping")
|
||||
refresh_project_categories(client2[MONGO_DB])
|
||||
client2.close()
|
||||
except Exception as ex:
|
||||
print(f"⚠️ 项目分类刷新失败: {ex}")
|
||||
|
||||
if not args.dry_run:
|
||||
STAMP_FILE.write_text(today_str(), encoding="utf-8")
|
||||
print(f"✅ 已写入日期戳: {STAMP_FILE}")
|
||||
else:
|
||||
print("(dry-run)未写日期戳")
|
||||
|
||||
print("完成。")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
40
02_卡人(水)/水岸_项目管理/Soul技能归口/SKILL.md
Normal file
40
02_卡人(水)/水岸_项目管理/Soul技能归口/SKILL.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Soul技能归口 · 水岸掌管
|
||||
name: Soul技能归口
|
||||
description: Soul(口语「寿」多为误写)创业派对相关 Cursor Skill 的**统一物理目录**与**索引入口**;**掌管人**为 **水岸**(项目调度与套件归口)。触发:Soul技能归口、Soul套件、K01、派对总控、玉宁、永平三端。
|
||||
owner: 水岸
|
||||
group: 水
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
---
|
||||
|
||||
# Soul技能归口 · 水岸掌管
|
||||
|
||||
> **定位**:本目录集中存放 **Soul 创业派对** 在卡若AI 侧的主要 Skill 真源(K01~K03 + 薄入口);**不搬迁**大体量树(如 `水桥_平台对接/Soul创业实验/`),仅在索引中 **外链**。
|
||||
> **命名**:正文统一 **卡若**。语音误写「卡路」「卡罗拉」「卡路里」在指本体系时均视同 **卡若**(食物热量仍用 **cal/kcal**)。平台名 **Soul**(口语「寿」多为误写)。
|
||||
|
||||
## 一、本目录内文件(相对路径 = 卡若AI 根)
|
||||
|
||||
| 角色 | 路径 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| **总索引** | `02_卡人(水)/水岸_项目管理/Soul技能归口/SKILL.md` | 本文件 |
|
||||
| **K01 分流** | `…/Soul技能归口/卡若创业派对_总控/SKILL.md` | 运营 vs 永平开发 |
|
||||
| **K02 运营** | `…/Soul技能归口/卡若玉宁运营专线/SKILL.md` | 玉宁专线 · 内容/切片/报表/分发 |
|
||||
| **K03 网站** | `…/Soul技能归口/卡若网站开发_永平三端/SKILL.md` | 永平 soul-api / soul-admin / miniprogram |
|
||||
| **薄入口 · 项目管理** | `…/Soul技能归口/Soul派对项目管理_Cursor入口/SKILL.md` | 水岸 W17 配套 |
|
||||
| **薄入口 · 运营报表** | `…/Soul技能归口/Soul派对运营报表_Cursor入口/SKILL.md` | 飞书报表 Cursor 入口 |
|
||||
|
||||
## 二、关联真源(目录外 · 必读链)
|
||||
|
||||
| 文档 | 路径 |
|
||||
|:---|:---|
|
||||
| **Stream 掌管人规约** | `02_卡人(水)/水岸_项目管理/卡若创业派对/Soul派对技能流_掌管人与Stream规约.md` |
|
||||
| **项目 README(流程清单)** | `02_卡人(水)/水岸_项目管理/卡若创业派对/README.md` |
|
||||
| **水岸多项目总 Skill** | `02_卡人(水)/水岸_项目管理/SKILL.md`(W17) |
|
||||
| **Soul 创业实验(写书/上传)** | `02_卡人(水)/水桥_平台对接/Soul创业实验/SKILL.md`(W10) |
|
||||
| **获客深链路 F23~F27** | `04_卡火(火)/火炬_全栈消息/` 下各子目录;索引见 `全栈开发` §1.11 |
|
||||
|
||||
## 三、维护规则
|
||||
|
||||
- **新增** Soul 套件内薄 Skill:优先落在 **本目录** 或 `卡若创业派对/`,并登记 `SKILL_REGISTRY.md` + `01_技能控制台.md`。
|
||||
- **Cursor 配置**:技能路径请指向上表 **五行目录** 绝对路径,勿再指向已废弃的 `卡若AI/.cursor/skills/` 正文(该目录仅 `README.md` 说明)。
|
||||
25
02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对运营报表_Cursor入口/SKILL.md
Normal file
25
02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对运营报表_Cursor入口/SKILL.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: Soul派对运营报表_Cursor入口
|
||||
description: Soul 派对运营报表自动化薄入口。触发:运营报表、派对填表、派对纪要、妙记填表。
|
||||
triggers: Soul派对运营报表、派对运营报表、运营报表自动化、派对填表发群
|
||||
owner: 水桥
|
||||
group: 水
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
legacy_name: soul-operation-report(原 `.cursor/skills/`)
|
||||
---
|
||||
|
||||
# Soul 派对运营报表(薄入口)
|
||||
|
||||
> **完整流程与 W11**:`02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md`
|
||||
> **项目管理**:`02_卡人(水)/水岸_项目管理/SKILL.md`
|
||||
> **卡若创业派对 README**:`02_卡人(水)/水岸_项目管理/卡若创业派对/README.md`
|
||||
|
||||
## 快速命令
|
||||
|
||||
```bash
|
||||
cd /Users/karuo/Documents/个人/卡若AI
|
||||
python3 "02_卡人(水)/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py" <场次号>
|
||||
```
|
||||
|
||||
更多阶段与凭证见卡若创业派对 README。
|
||||
29
02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对项目管理_Cursor入口/SKILL.md
Normal file
29
02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对项目管理_Cursor入口/SKILL.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Soul派对项目管理_Cursor入口
|
||||
description: 水岸·项目管理中枢入口。多项目调度、卡若创业派对目录。触发:项目管理、水岸、项目总览、Soul项目管理、派对全流程。
|
||||
triggers: 项目管理、水岸、项目总览、管理项目、新建项目、卡若创业派对、Soul项目管理、派对全流程、Soul运营
|
||||
owner: 水岸
|
||||
group: 水
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
legacy_name: soul-party-project(原 `.cursor/skills/`)
|
||||
---
|
||||
|
||||
# 水岸 · 项目管理中枢(薄入口)
|
||||
|
||||
> **Soul 套件内二分**:若任务明确属于「派对运营(玉宁)」或「永平网站开发」,优先用 **`02_卡人(水)/水岸_项目管理/Soul技能归口/卡若创业派对_总控/SKILL.md`**(K01)分流。
|
||||
> **本入口**:多项目 / 水岸总调度;**完整正文**在同目录上一级 `SKILL.md`(W17)。
|
||||
|
||||
## 交互默认(与卡若中枢一致)
|
||||
|
||||
- **零提问、直接做**:先读对应 `SKILL.md` / README → **直接落地**。
|
||||
- 细则:`.cursor/rules/karuo-ai.mdc`;Soul 永平仓库另见永平 `.cursor/rules/soul-project-boundary.mdc` 等。
|
||||
|
||||
## 使用方式
|
||||
|
||||
1. **Read 完整水岸 SKILL**:`02_卡人(水)/水岸_项目管理/SKILL.md`
|
||||
2. 卡若创业派对:`02_卡人(水)/水岸_项目管理/卡若创业派对/README.md`
|
||||
|
||||
## 派对闭环 · 复盘发群(与 karuo-party 对齐)
|
||||
|
||||
完整闭环时:除卡若复盘五块外,可按 **永平** 仓库 `.cursor/skills/karuo-party/SKILL.md` §九 推飞书群;Webhook 用 **`FEISHU_PARTY_CLOSURE_WEBHOOK`**,勿写死 URL。
|
||||
58
02_卡人(水)/水岸_项目管理/Soul技能归口/卡若创业派对_总控/SKILL.md
Normal file
58
02_卡人(水)/水岸_项目管理/Soul技能归口/卡若创业派对_总控/SKILL.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: 卡若创业派对_总控
|
||||
description: 卡若创业派对项目总控(Soul 套件)。统一归口:① 玉宁专线·运营 ② 网站开发(永平三端)。触发:卡若创业派对、卡若派对、玉宁、永平开发、派对总控。
|
||||
triggers: 卡若创业派对、卡若派对、卡洛创业派对、Soul派对项目总控、派对运营和网站一起管、玉宁、永平开发、永平三端
|
||||
owner: 水岸
|
||||
group: 水
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
legacy_name: kalu-entrepreneur-party(原 `.cursor/skills/`)
|
||||
---
|
||||
|
||||
# 卡若 · 创业派对(项目总控 · K01)
|
||||
|
||||
> **定位**:在 **卡若AI** 仓库内,为「卡若/卡若创业派对」提供 **单一入口**:先判任务属于 **运营(玉宁线)** 还是 **网站开发**,再 **Read** 对应子 Skill 全量执行。
|
||||
> **与「水岸」关系**:跨项目调度、五行资源仍走 `02_卡人(水)/水岸_项目管理/SKILL.md`(W17);本 Skill 侧重 **派对单项目** 内 **运营 / 开发** 二分法。
|
||||
|
||||
## 命名说明
|
||||
|
||||
| 称呼 | 含义 |
|
||||
|:---|:---|
|
||||
| 卡若(语音误写:卡路、卡罗拉、卡路里 等) | 同一体系,正文只写「卡若」;指食物热量时仍用 **cal/kcal** |
|
||||
| 卡若创业派对 | 项目正式名(Soul 创业派对产品线) |
|
||||
| 玉宁 | 运营侧专线负责人(本仓库技能归口名) |
|
||||
|
||||
## 触发词
|
||||
|
||||
卡若创业派对、卡若派对、卡洛创业派对、卡若创业派对总控、派对项目 Skill、
|
||||
Soul 派对运营加开发、玉宁那条线、网站那条线、永平三端、管理派对技能
|
||||
|
||||
## 执行协议(必须)
|
||||
|
||||
1. **判断域**
|
||||
- **运营**:写文章、视频切片、运营报表、妙记/纪要、多平台分发、素材库、飞书写群、Git 上传书稿、团队运营文档… → **Read**
|
||||
`02_卡人(水)/水岸_项目管理/Soul技能归口/卡若玉宁运营专线/SKILL.md`(K02)
|
||||
- **开发**:小程序、管理端、API、数据库、部署、需求文档、全站修复、超级个体、链接人与事… → **Read**
|
||||
`02_卡人(水)/水岸_项目管理/Soul技能归口/卡若网站开发_永平三端/SKILL.md`(K03)
|
||||
|
||||
2. **可同时涉及**:先拆成两条子任务,分别按子 Skill 执行,最后在复盘里合并说明。
|
||||
|
||||
3. **项目 README(水岸)**(流程级清单):
|
||||
`02_卡人(水)/水岸_项目管理/卡若创业派对/README.md`
|
||||
|
||||
4. **Soul 派对技能流(Stream)· 掌管人规约**:
|
||||
`02_卡人(水)/水岸_项目管理/卡若创业派对/Soul派对技能流_掌管人与Stream规约.md`
|
||||
|
||||
5. **水岸多项目总入口**(与派对并列时):`02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对项目管理_Cursor入口/SKILL.md` 或直读 `水岸_项目管理/SKILL.md`。
|
||||
|
||||
## 子 Skill 一览
|
||||
|
||||
| 子 Skill | 路径(卡若AI 根相对) | 负责 |
|
||||
|:---|:---|:---|
|
||||
| 玉宁专线 · 运营(K02) | `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若玉宁运营专线/SKILL.md` | 派对内容生产与飞书运营闭环 |
|
||||
| 网站开发 · 永平三端(K03) | `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若网站开发_永平三端/SKILL.md` | soul-api / soul-admin / miniprogram |
|
||||
|
||||
## 对话与复盘
|
||||
|
||||
- Mongo 留存、卡若复盘五块:遵守 `.cursor/rules/karuo-ai.mdc`。
|
||||
- 派对闭环发飞书(若在永平仓库触发):见永平 `.cursor/skills/karuo-party/SKILL.md` §九;Webhook 用环境变量,勿写死密钥。
|
||||
@@ -1,22 +1,25 @@
|
||||
---
|
||||
name: kalu-party-yuning-ops
|
||||
description: >
|
||||
卡路创业派对 · 玉宁专线(运营侧)。与卡若AI「Soul 派对技能流(Stream)」掌管人规约一致:
|
||||
写文章、视频切片、运营报表、智能纪要/妙记、多平台分发、素材库、飞书、Gitea/GitHub、录屏文字、团队文档等。
|
||||
当用户提到 玉宁、派对运营、Soul派对技能流、派对Stream、创业派对Stream、写文章、视频切片、运营报表、分发到各平台、Soul文章、第9章、飞书写群、录屏文字、团队管理 时激活。
|
||||
name: 卡若玉宁运营专线
|
||||
description: 卡若创业派对 · 玉宁专线(运营侧)。Soul 派对技能流 Stream;写文章、切片、报表、妙记、分发、飞书、第9章等。触发:玉宁、派对运营、派对Stream、Soul文章、视频切片、运营报表。
|
||||
triggers: 玉宁、派对运营、Soul派对技能流、派对Stream、创业派对Stream、写文章、视频切片、运营报表、妙记、Soul文章、第9章、多平台分发
|
||||
owner: 水岸
|
||||
group: 水
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
legacy_name: kalu-party-yuning-ops(原 `.cursor/skills/`)
|
||||
---
|
||||
|
||||
# 卡路 · 玉宁专线(运营)
|
||||
# 卡若 · 玉宁专线(运营 · K02)
|
||||
|
||||
> **职责**:**不包含** 永平仓库里的前后端改代码(那属于 `kalu-party-soul-website-dev`)。
|
||||
> **包含**:派对结束后的 **数据 → 内容 → 分发 → 飞书** 全链路,以及你在 Cursor 里固定的 **运营类 Agent** 所依赖的全部 **SKILL 路径**。
|
||||
> **掌管人归口(卡若AI)**:五行侧 **水岸 / 水桥 / 木叶 / 金仓** 与各环节 SKILL 的对应关系,以 **`02_卡人(水)/水岸_项目管理/卡若创业派对/Soul派对技能流_掌管人与Stream规约.md`** 为 **唯一权威**;本 Skill 为 Cursor 运营入口,**执行顺序与成员分配**与该 Stream 规约 **一致**。
|
||||
> **职责**:**不包含** 永平仓库里的前后端改代码(那属于 `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若网站开发_永平三端/SKILL.md`)。
|
||||
> **包含**:派对结束后的 **数据 → 内容 → 分发 → 飞书** 全链路。
|
||||
> **掌管人归口**:以 **`02_卡人(水)/水岸_项目管理/卡若创业派对/Soul派对技能流_掌管人与Stream规约.md`** 为 **唯一权威**。
|
||||
|
||||
## 触发词
|
||||
|
||||
玉宁、运营写文章、运营视频切片、运营报表、派对填表、派对纪要、妙记、飞书妙记、
|
||||
多平台分发、一键分发、Soul创业实验、写Soul文章、Soul上传、第9章、
|
||||
Soul发到素材库、录屏文字、团队管理、运营分发、飞书写群、复盘发飞书(仅运营内容)、
|
||||
Soul发到素材库、录屏文字、团队管理、运营分发、飞书写群、
|
||||
**Soul派对技能流、派对Stream、创业派对Stream、用Stream跑派对**
|
||||
|
||||
## 一、派对闭环(顺序参考 · 与水岸 README 一致)
|
||||
@@ -42,13 +45,11 @@ Soul发到素材库、录屏文字、团队管理、运营分发、飞书写群
|
||||
| Excel→飞书表格→日报图 | `02_卡人(水)/水桥_平台对接/飞书管理/Excel表格与日报_SKILL.md` | W13 |
|
||||
| 飞书 JSON 块格式 | `02_卡人(水)/水桥_平台对接/飞书管理/飞书JSON格式_SKILL.md` | W16 |
|
||||
| 卡猫 / 婼瑄复盘发群 | `02_卡人(水)/水桥_平台对接/飞书管理/卡猫复盘/SKILL.md` | W14 |
|
||||
| 复盘 webhook 脚本 | `02_卡人(水)/水桥_平台对接/飞书管理/脚本/send_review_to_feishu_webhook.py` | 按需;对话结束**不**默认调用,见 `karuo-ai.mdc` |
|
||||
| Gitea / Git 推送 | `01_卡资(金)/金仓_存储备份/Gitea管理/SKILL.md` | G02 |
|
||||
| 回廊洗字(卡若记忆宫殿命名体系 · 语音转写纠错/闽南口音等) | `02_卡人(水)/水溪_整理归档/语音转写纠错/SKILL.md` | W03b |
|
||||
| 切片脚本 soul_enhance | `03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py` | 联动 M01 |
|
||||
| 回廊洗字(语音转写纠错) | `02_卡人(水)/水溪_整理归档/语音转写纠错/SKILL.md` | W03b |
|
||||
| 平台申诉(Soul/抖音等) | `02_卡人(水)/水桥_平台对接/平台账号申诉解封/SKILL.md` | W10b |
|
||||
|
||||
## 三、与 Cursor Agent 名称对齐(你侧边栏「运营-*」)
|
||||
## 三、与 Cursor Agent 名称对齐(「运营-*」)
|
||||
|
||||
| Agent 习惯名 | 归口 |
|
||||
|:---|:---|
|
||||
@@ -56,8 +57,8 @@ Soul发到素材库、录屏文字、团队管理、运营分发、飞书写群
|
||||
| 运营-视频切片 | M01 + soul_enhance |
|
||||
| 运营-运营报表 | W11 |
|
||||
| 运营-分发到各平台 | M01h + 各 M01b~g |
|
||||
| 运营-获得录屏文字 | W08 妙记/纪要链路 + 智能纪要脚本 |
|
||||
| 运营-团队管理 | 飞书 W07 + 团队流程文档(`水岸`/人事相关目录按需) |
|
||||
| 运营-获得录屏文字 | W08 妙记/纪要链路 |
|
||||
| 运营-团队管理 | 飞书 W07 + 团队流程文档 |
|
||||
|
||||
## 四、硬规则
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
---
|
||||
name: kalu-party-soul-website-dev
|
||||
description: >
|
||||
卡路创业派对 · 网站开发(永平仓库)。汇总 Cursor Agent「网站-*」:soul-api、soul-admin、miniprogram、
|
||||
用户管理、内容管理、数据统计、部署、GitHub、全站修复、超级个体与 CKB 等。
|
||||
当用户提到 永平、Soul网站、soul-api、管理端、soul-admin、小程序开发、用户管理、内容管理、
|
||||
全站修复、超级个体、链接人与事、部署 soul、github 上传网站 时激活。
|
||||
name: 卡若网站开发_永平三端
|
||||
description: 卡若创业派对 · 网站开发(永平仓库)。soul-api、soul-admin、miniprogram、部署、超级个体与 CKB 等。触发:永平、soul-api、管理端、小程序、全站修复、超级个体。
|
||||
triggers: 永平、Soul网站、soul-api、管理端、soul-admin、小程序、miniprogram、全站修复、超级个体、部署 soul、github 上传网站
|
||||
owner: 水岸
|
||||
group: 水
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
legacy_name: kalu-party-soul-website-dev(原 `.cursor/skills/`)
|
||||
---
|
||||
|
||||
# 卡路 · 网站开发(永平三端)
|
||||
# 卡若 · 网站开发(永平三端 · K03)
|
||||
|
||||
> **仓库根**:`/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-永平`
|
||||
> **仓库根**(随本机):`/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-永平`
|
||||
> **掌管**:**水岸**(Soul技能归口);**实操**进永平仓库后仍按本节所列 **火炬系** Skill(api-dev / admin-dev / miniprogram-dev)执行。
|
||||
> **原则**:小程序只调 `/api/miniprogram/*`;管理端只调 `/api/admin/*`、`/api/db/*`。变更后过 `change-checklist`。
|
||||
|
||||
## 触发词
|
||||
@@ -17,7 +20,7 @@ description: >
|
||||
永平、Soul API、Soul 管理端、soul-admin、小程序、miniprogram、网站开发、用户管理、内容管理、
|
||||
数据统计、全站修复、超级个体、链接人与事、存客宝对接、部署、GitHub、devlop、三端联调
|
||||
|
||||
## 一、在永平仓库内必读 Skill(相对仓库根 `.cursor/skills/`)
|
||||
## 一、在永平仓库内必读 Skill(相对**永平**仓库根 `.cursor/skills/`)
|
||||
|
||||
| 场景 | 文件 |
|
||||
|:---|:---|
|
||||
@@ -53,7 +56,7 @@ description: >
|
||||
|
||||
## 四、线上入口(验收 / 联调)
|
||||
|
||||
- 管理端:`https://souladmin.quwanzhi.com/`(路径见各 Route:`/content`、`/users`、`/dashboard` …)
|
||||
- 管理端:`https://souladmin.quwanzhi.com/`
|
||||
- API:`https://soulapi.quwanzhi.com/health`
|
||||
- C 端:微信小程序「**卡若创业派对**」
|
||||
|
||||
@@ -63,7 +66,21 @@ description: >
|
||||
- **忽略** 永平 `.cursor/` 外无关全局规则(见 `soul-project-boundary.mdc`)。
|
||||
- 敏感配置不写进 Skill 正文。
|
||||
|
||||
## 六、与「玉宁运营」边界
|
||||
## 六、获客编排与跨小程序跳转(方法论)
|
||||
|
||||
- **不写书稿、不跑切片、不填运营报表** → 若用户同时要做,先拆给 `kalu-party-yuning-ops`。
|
||||
详规在卡若AI 火炬 **`火炬_全栈消息/`** 下 **F23~F27**(见 `SKILL_REGISTRY` 火组)。
|
||||
|
||||
| 编号 | 文件(相对 `04_卡火(火)/火炬_全栈消息/`) |
|
||||
|:---|:---|
|
||||
| F23 | `小程序链接标签与跨小程序跳转/SKILL.md` |
|
||||
| F24 | `推广邀请与三十日绑定/SKILL.md` |
|
||||
| F25 | `分销佣金与提现编排/SKILL.md` |
|
||||
| F26 | `超级个体点击与获客统计/SKILL.md` |
|
||||
| F27 | `存客宝BFF与留资队列/SKILL.md` |
|
||||
|
||||
**《全栈开发》§1.11** 为索引;通用埋点 **§1.10**。
|
||||
|
||||
## 七、与「玉宁运营」边界
|
||||
|
||||
- **不写书稿、不跑切片、不填运营报表** → 若用户同时要做,先拆给 `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若玉宁运营专线/SKILL.md`(K02)。
|
||||
- **接口/字段/页面** 才在本 Skill 落地。
|
||||
@@ -11,7 +11,7 @@ updated: "2026-03-23"
|
||||
> **定位**:Soul 创业派对全链路——从派对结束到内容变现,跨组调度水桥+木叶。
|
||||
> **管辖范围**:运营数据 → 飞书报表 → 视频下载 → 视频切片 → 多平台分发 → 文章写作 → 小程序上传
|
||||
|
||||
**固定技能流(Stream)**:各技能 **掌管人** 与 **调用规约**(含 Cursor 卡路 K01~K03 对齐)→ 必读
|
||||
**固定技能流(Stream)**:各技能 **掌管人** 与 **调用规约**(含 Cursor 卡若 K01~K03 对齐)→ 必读
|
||||
[`Soul派对技能流_掌管人与Stream规约.md`](./Soul派对技能流_掌管人与Stream规约.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -10,7 +10,7 @@ updated: "2026-03-23"
|
||||
|
||||
> **Stream**:本项目的**固定技能流水线名称**(口语可说「派对 Stream」「Soul 技能流」)。
|
||||
> **说明**:与 Steam 无关;统一以本文档 + `README.md` 为归口。
|
||||
> **体系**:归属 **卡若AI**(口语「卡路/卡罗拉」同系);Cursor 侧 **卡路创业派对** Agent 与本文档 **对齐同一套掌管人**。
|
||||
> **体系**:归属 **卡若AI**。正文统一称 **卡若**;语音误写「卡路」「卡罗拉」「卡路里」在指本体系时均视同 **卡若**(食物热量的 **cal/kcal** 除外)。Cursor 侧 **卡若创业派对** Agent 与本文档 **对齐同一套掌管人**。
|
||||
|
||||
---
|
||||
|
||||
@@ -19,8 +19,8 @@ updated: "2026-03-23"
|
||||
| 角色(卡若AI 成员) | 职责 | 何时点名 |
|
||||
|:---|:---|:---|
|
||||
| **水岸** | **项目总控 / 调度**:不代替执行,按阶段读子 SKILL、排期、跨组串联 | 派对全流程、项目管理、`@水岸`、W17 |
|
||||
| **玉宁(运营专线名)** | 与 **水桥+木叶** 执行链一致;在 Cursor 里对应 **K02 玉宁运营** Skill | 运营类 Agent、卡路 K01 判为运营后 |
|
||||
| **火炬 / 火锤** | **永平三端开发**(小程序 / 管理端 / API):**不走**本 Stream 的 Phase 1~4 脚本链 | 改代码、接口、部署 → **K03** 或 `kalu-party-soul-website-dev` |
|
||||
| **玉宁(运营专线名)** | 与 **水桥+木叶** 执行链一致;在 Cursor 里对应 **K02 玉宁运营** Skill | 运营类 Agent、卡若 K01 判为运营后 |
|
||||
| **水岸(套件归口)+ 火炬 / 火锤(代码执行)** | **永平三端开发**(小程序 / 管理端 / API):**不走**本 Stream 的 Phase 1~4 脚本链 | 改代码、接口、部署 → **K03**(`Soul技能归口/卡若网站开发_永平三端`);进永平仓库后按该 Skill 内 api-dev / admin-dev 等执行 |
|
||||
|
||||
---
|
||||
|
||||
@@ -51,12 +51,12 @@ updated: "2026-03-23"
|
||||
- **第一步** Read:`卡若创业派对/README.md` **或** 本文档;
|
||||
- **第二步** 按环节由上表 **点名掌管人**(或按 SKILL_REGISTRY「成员」列路由);
|
||||
- **第三步** Read 对应 SKILL.md 逐步执行。
|
||||
2. **Cursor 入口(与五行表互补)**:
|
||||
- **总控分流**:`.cursor/skills/kalu-entrepreneur-party/SKILL.md`(K01)
|
||||
- **运营 = 本 Stream**:`.cursor/skills/kalu-party-yuning-ops/SKILL.md`(K02) + 本文档
|
||||
- **开发 ≠ Stream**:`.cursor/skills/kalu-party-soul-website-dev/SKILL.md`(K03)
|
||||
- **水岸中枢**:`.cursor/skills/soul-party-project` → `水岸_项目管理/SKILL.md`
|
||||
- **仅运营报表快捷**:`.cursor/skills/soul-operation-report`
|
||||
2. **Cursor 入口(与五行表互补 · 真源在 Soul技能归口)**:
|
||||
- **总控分流**:`02_卡人(水)/水岸_项目管理/Soul技能归口/卡若创业派对_总控/SKILL.md`(K01)
|
||||
- **运营 = 本 Stream**:`02_卡人(水)/水岸_项目管理/Soul技能归口/卡若玉宁运营专线/SKILL.md`(K02) + 本文档
|
||||
- **开发 ≠ Stream**:`02_卡人(水)/水岸_项目管理/Soul技能归口/卡若网站开发_永平三端/SKILL.md`(K03)
|
||||
- **水岸中枢**:`02_卡人(水)/水岸_项目管理/SKILL.md`(W17)
|
||||
- **仅运营报表快捷**:`02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对运营报表_Cursor入口/SKILL.md`
|
||||
3. **BOOTSTRAP 热技能**:已登记触发词 **Soul派对技能流 / 派对Stream / 创业派对Stream** → 优先 Read **本文档**。
|
||||
4. **禁止混淆**:改 **soul-api / soul-admin / miniprogram** 代码的需求,**不要**从 Stream 脚本链硬拐;应走 **K03 + 永平项目** 规范。
|
||||
|
||||
@@ -73,4 +73,6 @@ updated: "2026-03-23"
|
||||
|
||||
| 日期 | 说明 |
|
||||
|:---|:---|
|
||||
| 2026-03-23 | 初版:Soul 派对技能流(Stream)掌管人矩阵 + 与卡路 K01~K3 / 水岸 README 对齐 |
|
||||
| 2026-03-23 | 初版:Soul 派对技能流(Stream)掌管人矩阵 + 与卡若 K01~K03 / 水岸 README 对齐 |
|
||||
| 2026-03-26 | Soul技能归口目录化;水岸套件归口 + K03 掌管人登记水岸;用语统一卡若 |
|
||||
| 2026-03-27 | 旧称「卡路」「卡路里(指体系)」正文收口为卡若;本节 Cursor 路径改为 Soul技能归口真源 |
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
| @开发 2、私域银行 神射手 @开发 2、私域银行 数据中台 这两个最重 | 开发工作区 | `@开发 2、私域银行 神射手 @开发 2、私域银行 数据中台 这两个最重_542af226-f2f3-4b3d-9378-6e441b5f2ca3.txt` |
|
||||
| @开发 4、小工具 synology群晖nas 写一个管理群晖nas的s | 开发工作区 | `@开发 4、小工具 synology群晖nas 写一个管理群晖nas的s_ea95b327-73eb-4271-847f-f6f11072e84c.txt` |
|
||||
| 将这几个,那个。这几个项目提取核心的内容,并且让我可以直接操作的一个形式 | 开发工作区 | `将这几个,那个。这几个项目提取核心的内容,并且让我可以直接操作的一个形式_b2ec8d2d-7cab-46ee-9a01-1e980b570d06.txt` |
|
||||
| 那个将卡洛 AI 里面的那个 skill 做按,卡洛常用,按我常用的那个 | 开发工作区 | `那个将卡洛 AI 里面的那个 skill 做按,卡洛常用,按我常用的那个_6d69318a-c185-405d-90a1-950423e949ff.txt` |
|
||||
| 那个将卡若AI 里面的那个 skill 做按,卡若常用,按我常用的那个 | 开发工作区 | `那个将卡若AI 里面的那个 skill 做按,卡若常用,按我常用的那个_6d69318a-c185-405d-90a1-950423e949ff.txt` |
|
||||
| 连接到这台nas 192.168.1.201 上并且告诉我上面docke | 群晖NAS2工作区 | `连接到这台nas 192.168.1.201 上并且告诉我上面docke_e6a12dce-fc1e-4cc7-9547-f2e475d3c12b.txt` |
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
| @开发 2、私域银行 cunkebao_v3 智能追问 | 开发工作区 | `@开发 2、私域银行 cunkebao_v3 智能追问_71af3e6e-759e-4041-a218-8161f1e3fc7f.txt` |
|
||||
| 下载.https kr-op.quwanzhi.com 的代码和数据库 | 开发工作区 | `下载.https kr-op.quwanzhi.com 的代码和数据库_5ad43073-3059-4fff-854e-50069d41d5f5.txt` |
|
||||
| 修复一下小型宝塔服务器上的这个域名 | 开发工作区 | `修复一下小型宝塔服务器上的这个域名_df54ef81-401c-4ad3-a2a7-6b69fc205029.txt` |
|
||||
| 卡若ai 优化。将这个卡洛 AI 设定成一个设,那这人设里面它相当于一个 | 开发工作区 | `卡若ai 优化。将这个卡洛 AI 设定成一个设,那这人设里面它相当于一个_033df1e1-e3a1-4523-b221-953651dee08e.txt` |
|
||||
| 卡若ai 优化。将这个卡若AI 设定成一个设,那这人设里面它相当于一个 | 开发工作区 | `卡若ai 优化。将这个卡若AI 设定成一个设,那这人设里面它相当于一个_033df1e1-e3a1-4523-b221-953651dee08e.txt` |
|
||||
| 看看这台电脑的追踪 最近的一段使用期度跟 喜歡看看我最近都在做了些什麼事 | 开发工作区 | `看看这台电脑的追踪 最近的一段使用期度跟 喜歡看看我最近都在做了些什麼事_61a3114e-07bd-43f9-86da-862145d39f8f.txt` |
|
||||
|
||||
@@ -18,4 +18,4 @@
|
||||
| Claude普号登录教程 | 开发工作区 | `Claude普号登录教程_d417195b-0cbe-48ec-8473-83a5022a7010.txt` |
|
||||
| 分析一下我这个微信,本地这个微信。分析一下我今天本地微信的一个聊天内容, | 开发工作区 | `分析一下我这个微信,本地这个微信。分析一下我今天本地微信的一个聊天内容,_99f5f913-932d-4e69-8319-6af22bd5dbca.txt` |
|
||||
| 查找这台电脑安装的一个小模型,有什么小模型?已经安装的,并且可以使用的, | 开发工作区 | `查找这台电脑安装的一个小模型,有什么小模型?已经安装的,并且可以使用的,_84315bfd-8828-4c39-80fd-6092bf529c08.txt` |
|
||||
| 让那个今天最重要的事情就是第一个卡洛尔那个书的小程序,然后那个 MBTI | 开发工作区 | `让那个今天最重要的事情就是第一个卡洛尔那个书的小程序,然后那个 MBTI_a92b60b6-24ec-4ea2-9d86-d690b372c4de.txt` |
|
||||
| 让那个今天最重要的事情就是第一个卡若那个书的小程序,然后那个 MBTI | 开发工作区 | `让那个今天最重要的事情就是第一个卡若那个书的小程序,然后那个 MBTI_a92b60b6-24ec-4ea2-9d86-d690b372c4de.txt` |
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
| deepseek的梁文峰,以及他相应用的和他搜索一下他全网,包括他的微博 | 开发工作区 | `deepseek的梁文峰,以及他相应用的和他搜索一下他全网,包括他的微博_6dabb9a9-12f7-4203-82a8-ca52aa095c25.txt` |
|
||||
| https cunkebao.feishu.cn minutes obc | 开发工作区 | `https cunkebao.feishu.cn minutes obc_890c3656-ae41-44e0-bf77-ad960ed375f0.txt` |
|
||||
| 产研团队 第21场 20260129 许永平.txt@聊天记录 产研团队 | 开发工作区 | `产研团队 第21场 20260129 许永平.txt@聊天记录 产研团队_3b632fbc-dd3c-415d-b7ca-7404349449b3.txt` |
|
||||
| 那个分析一下卡洛 AI 的下面的管理的所有的能,它的能力可以自己把能力拆 | 开发工作区 | `那个分析一下卡洛 AI 的下面的管理的所有的能,它的能力可以自己把能力拆_f94affdc-9f9e-475f-8fcc-dc58a86ae324.txt` |
|
||||
| 那个分析一下卡若AI 的下面的管理的所有的能,它的能力可以自己把能力拆 | 开发工作区 | `那个分析一下卡若AI 的下面的管理的所有的能,它的能力可以自己把能力拆_f94affdc-9f9e-475f-8fcc-dc58a86ae324.txt` |
|
||||
| 那个整理,并且搜索,整理一个,并且搜索上我所有的那个账号密码,以及账号密 | 开发工作区 | `那个整理,并且搜索,整理一个,并且搜索上我所有的那个账号密码,以及账号密_4ccd1bea-fe8c-4660-8076-75a2f4fa7fab.txt` |
|
||||
| https open.feishu.cn open-apis bot v | 群晖云盘 | `https open.feishu.cn open-apis bot v_daca49d4-e90e-447e-848f-61ed08f1e78a.txt` |
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
| 帮我清理一下那个小型宝塔的服务器空间,然后告诉我那个你清理的哪一些内容? | 开发工作区 | `帮我清理一下那个小型宝塔的服务器空间,然后告诉我那个你清理的哪一些内容?_a2ebc2fb-9e9f-4adc-a6e2-113c8e50dfde.txt` |
|
||||
| 帮我清理下小宝塔服务器的硬盘空间 | 开发工作区 | `帮我清理下小宝塔服务器的硬盘空间_9bccf922-50cb-4e4b-82bf-1f489b7cecd0.txt` |
|
||||
| 我已拍下,待付款 | 开发工作区 | `我已拍下,待付款_56c94913-cfa0-4a0e-a868-7ed6338a0677.txt` |
|
||||
| 然后看一下这个卡罗 AI 的整个的那个对话跟操作流程,跟整个的那个一个形 | 开发工作区 | `然后看一下这个卡罗 AI 的整个的那个对话跟操作流程,跟整个的那个一个形_c2f28627-401b-47fa-8c15-f1ba4d4c406e.txt` |
|
||||
| 然后看一下这个卡若AI 的整个的那个对话跟操作流程,跟整个的那个一个形 | 开发工作区 | `然后看一下这个卡若AI 的整个的那个对话跟操作流程,跟整个的那个一个形_c2f28627-401b-47fa-8c15-f1ba4d4c406e.txt` |
|
||||
| 用这个 auto 的这个那个量到底是用的是哪个模型?一旦规则是什么样的? | 开发工作区 | `用这个 auto 的这个那个量到底是用的是哪个模型?一旦规则是什么样的?_b3ca976a-9492-4f89-9fc0-ea219abc5c92.txt` |
|
||||
| 给我一下小程序的上传密钥。 | 开发工作区 | `给我一下小程序的上传密钥。_3faf2d68-a86d-4fef-9c5f-d6c189aa0253.txt` |
|
||||
| 继续 | 开发工作区 | `继续_da3ad40c-c2cc-4169-8bb5-72b4e97c1550.txt` |
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|:---|:---|:---|
|
||||
| 你,你可以随便说一个吗?比如我们看一下那个,有没有那个搜索一下这台电脑里 | Cursor工作区 | `你,你可以随便说一个吗?比如我们看一下那个,有没有那个搜索一下这台电脑里_0fc97fd9-82b7-415c-9daf-911462cabf42.txt` |
|
||||
| 把本地的那个存克宝 AI 那个封装成一个那个 coser 可以调用和对话 | Cursor工作区 | `把本地的那个存克宝 AI 那个封装成一个那个 coser 可以调用和对话_1d538d56-8119-4c83-a196-1b18c14505ec.txt` |
|
||||
| 把那个卡路 AI 的那个经验库的功能,然后和吸收经验的逻辑,然后复刻一份 | Cursor工作区 | `把那个卡路 AI 的那个经验库的功能,然后和吸收经验的逻辑,然后复刻一份_cf809c12-e64c-46c0-a447-369cc67199ec.txt` |
|
||||
| 把那个卡若AI 的那个经验库的功能,然后和吸收经验的逻辑,然后复刻一份 | Cursor工作区 | `把那个卡若AI 的那个经验库的功能,然后和吸收经验的逻辑,然后复刻一份_cf809c12-e64c-46c0-a447-369cc67199ec.txt` |
|
||||
| 说一下这个 存客保 AI的工作流程 直接在对话框给我 | Cursor工作区 | `说一下这个 存客保 AI的工作流程 直接在对话框给我_8af43a29-a391-472d-90c9-e72878669ed3.txt` |
|
||||
| 那个帮我把产年团队的那个会议的文档你找一下,帮我生成一个会议文档发到群里 | Cursor工作区 | `那个帮我把产年团队的那个会议的文档你找一下,帮我生成一个会议文档发到群里_70dc4a81-fe5e-4f4a-8153-f8cc02bd7955.txt` |
|
||||
| 那个一场受益的创业实验,现在服务器显示是520的那个状态,帮我处理一下。 | Soul工作区 | `那个一场受益的创业实验,现在服务器显示是520的那个状态,帮我处理一下。_e7be8424-acab-46a9-a56a-36a108088483.txt` |
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
| 中文名称 | 所属工作台 | 对话文件 |
|
||||
|:---|:---|:---|
|
||||
| 一个雌性本鸡的时间,机器自动的一个备份,能自动的那个回溯到那个你认为合适 | 开发工作区 | `一个雌性本鸡的时间,机器自动的一个备份,能自动的那个回溯到那个你认为合适_1b10832f-38dc-4406-9532-bee9ede7c23d.txt` |
|
||||
| 从github 的skill seekers抽象成一个然后这一个放在卡洛 | 开发工作区 | `从github 的skill seekers抽象成一个然后这一个放在卡洛_57b35ce8-d436-437a-84ae-f72519c8020e.txt` |
|
||||
| 从github 的skill seekers抽象成一个然后这一个放在卡若 | 开发工作区 | `从github 的skill seekers抽象成一个然后这一个放在卡洛_57b35ce8-d436-437a-84ae-f72519c8020e.txt` |
|
||||
| 你有详细分析一下这整台的服务器这几年的每年的消费记录,详细一点的分析,然 | 开发工作区 | `你有详细分析一下这整台的服务器这几年的每年的消费记录,详细一点的分析,然_4f798d65-5963-4ec4-a55b-4a2a218eaa08.txt` |
|
||||
| 同时检查一下这个脑坑爹的这个网站里面的那个。文件有没有文件类型?有没有违 | 开发工作区 | `同时检查一下这个脑坑爹的这个网站里面的那个。文件有没有文件类型?有没有违_532f3db4-1ec8-4abd-bd53-dfa3a39c2fa6.txt` |
|
||||
| 处女,那个处女好,服务器这个相关的这个内容违规内容,然后类似的直接删除掉 | 开发工作区 | `处女,那个处女好,服务器这个相关的这个内容违规内容,然后类似的直接删除掉_94653bbc-bd39-42c9-a5cd-7b1dd597aede.txt` |
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| 历史上哪一任皇帝不会杀害韩信?那个详细的分析一下拆解,并且分析用的最强大 | 开发工作区 | `历史上哪一任皇帝不会杀害韩信?那个详细的分析一下拆解,并且分析用的最强大_21741784-41ef-4952-8ce8-f7f482e6f24e.txt` |
|
||||
| 吸收并优化这个网页的时机 在Github和其他的Skill找一下 操作网 | 开发工作区 | `吸收并优化这个网页的时机 在Github和其他的Skill找一下 操作网_207dddf6-f101-41f5-8a4f-b3f9bd86c3cb.txt` |
|
||||
| 帮我处理一下 | 开发工作区 | `帮我处理一下_b5288d87-24a2-45f2-94a9-e04dcf36eb36.txt` |
|
||||
| 抽象一个,那个卡洛的那个 skill,那个助理的一个 skill 出来, | 开发工作区 | `抽象一个,那个卡洛的那个 skill,那个助理的一个 skill 出来,_fc64937e-7408-4ea0-b128-7541d5c394fd.txt` |
|
||||
| 抽象一个,那个卡若的那个 skill,那个助理的一个 skill 出来, | 开发工作区 | `抽象一个,那个卡若的那个 skill,那个助理的一个 skill 出来,_fc64937e-7408-4ea0-b128-7541d5c394fd.txt` |
|
||||
| 搜索一下这个电脑上面有没有书籍相关的内容?word 或者 PDF 的 T | 开发工作区 | `搜索一下这个电脑上面有没有书籍相关的内容?word 或者 PDF 的 T_2b27991d-2ac0-4a8b-9ffd-3e4d594bd5db.txt` |
|
||||
| 解梦 1 | 开发工作区 | `解梦 1_97a8b0f3-3f42-4656-9bdf-34847446a504.txt` |
|
||||
| 解梦2 关于创业面试相关的梦境讲述 | 开发工作区 | `解梦2 关于创业面试相关的梦境讲述_6b9a8669-d624-4484-8331-a129311be8b6.txt` |
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
| 帮我控制这一台那个书房的电视,这台电视然后帮我打开那个,帮我选择一个适合 | 开发工作区 | `帮我控制这一台那个书房的电视,这台电视然后帮我打开那个,帮我选择一个适合_5287a84b-9147-413b-9c76-9e3b38267775.txt` |
|
||||
| 帮我生成这个月财务,制作完整的财务报表表格给我 | 开发工作区 | `帮我生成这个月财务,制作完整的财务报表表格给我_cd3e9ad2-e998-43bf-a030-3b825a33e631.txt` |
|
||||
| 支付系统支付系统 | 开发工作区 | `支付系统支付系统_f651eb7a-d5e9-4ff6-bf06-144bcd906e9e.txt` |
|
||||
| 那个以你的视角对整个卡洛 AI 进行整体的那个。整体的一个优化跟规划,确 | 开发工作区 | `那个以你的视角对整个卡洛 AI 进行整体的那个。整体的一个优化跟规划,确_1343ea9c-291f-4e55-903a-f8bf0f12ca0b.txt` |
|
||||
| 那个以你的视角对整个卡若AI 进行整体的那个。整体的一个优化跟规划,确 | 开发工作区 | `那个以你的视角对整个卡若AI 进行整体的那个。整体的一个优化跟规划,确_1343ea9c-291f-4e55-903a-f8bf0f12ca0b.txt` |
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
| 中文名称 | 所属工作台 | 对话文件 |
|
||||
|:---|:---|:---|
|
||||
| https github.com fnvtk skills 那个默认上传 | 卡若AI工作区 | `https github.com fnvtk skills 那个默认上传_f5aa81bc-2d7a-4a2c-9b5c-f7bbfcdb51c6.txt` |
|
||||
| 那个将卡洛 AI 的能力复制一份到存克宝 AI,然后这个主要是公司共用的 | 卡若AI工作区 | `那个将卡洛 AI 的能力复制一份到存克宝 AI,然后这个主要是公司共用的_17079f0b-b53a-4674-9d75-40e9e2c99e0a.txt` |
|
||||
| 那个将卡若AI 的能力复制一份到存克宝 AI,然后这个主要是公司共用的 | 卡若AI工作区 | `那个将卡若AI 的能力复制一份到存克宝 AI,然后这个主要是公司共用的_17079f0b-b53a-4674-9d75-40e9e2c99e0a.txt` |
|
||||
| @ Applications WebPomodoro.app 在飞书上制 | 开发工作区 | `@ Applications WebPomodoro.app 在飞书上制_b57b9eab-201e-46f7-a4c3-715abd76e140.txt` |
|
||||
| @工作手机 机擎 @工作手机 开发文档 那个激情的整个的所有的能源,它主 | 开发工作区 | `@工作手机 机擎 @工作手机 开发文档 那个激情的整个的所有的能源,它主_f8a0aff3-f841-40db-b011-547e51608017.txt` |
|
||||
| Ai | 开发工作区 | `Ai_b8aaeb36-a7d8-43ae-a1b8-855d789f8dbc.txt` |
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
| @MBTI .cursor skills @MBTI .cursor s | 开发工作区 | `@MBTI .cursor skills @MBTI .cursor s_8ac06bc1-66da-4cf5-8f5a-714631d96570.txt` |
|
||||
| 上传到github并且把项目同步在vercel上生成个v0的项目 | 开发工作区 | `上传到github并且把项目同步在vercel上生成个v0的项目_3629da4b-66d2-48aa-8ae8-9dca512653ad.txt` |
|
||||
| 上帝之眼的相应的核心能力和核心的代码,然后放到这个项目,那个赋能到这个项 | 开发工作区 | `上帝之眼的相应的核心能力和核心的代码,然后放到这个项目,那个赋能到这个项_9d9e8f9d-f448-4932-943e-7f6ca122e533.txt` |
|
||||
| 使用卡路里来证实一个财务的一个。一个板块,然后那个使用卡洛 AI 来做这 | 开发工作区 | `使用卡路里来证实一个财务的一个。一个板块,然后那个使用卡洛 AI 来做这_1a9eddec-9d1e-4a36-8764-d80f13893124.txt` |
|
||||
| 使用卡若来证实一个财务的一个。一个板块,然后那个使用卡若AI 来做这 | 开发工作区 | `使用卡路里来证实一个财务的一个。一个板块,然后那个使用卡若AI 来做这_1a9eddec-9d1e-4a36-8764-d80f13893124.txt` |
|
||||
| 前端 | 开发工作区 | `前端_cdcebbfc-0c10-4de3-9e0c-5e6190d56fe4.txt` |
|
||||
| 卡路 AI 里面的那个目录里面找到这个今年该买的这个三只股票的一个内容和 | 开发工作区 | `卡路 AI 里面的那个目录里面找到这个今年该买的这个三只股票的一个内容和_89cf615f-ed2d-4fb6-865e-4db839903668.txt` |
|
||||
| 卡若AI 里面的那个目录里面找到这个今年该买的这个三只股票的一个内容和 | 开发工作区 | `卡若AI 里面的那个目录里面找到这个今年该买的这个三只股票的一个内容和_89cf615f-ed2d-4fb6-865e-4db839903668.txt` |
|
||||
| 后端 | 开发工作区 | `后端_6e35a7b5-2ef4-47ca-943d-65658840132a.txt` |
|
||||
| 数据库 | 开发工作区 | `数据库_2410de25-3120-4dfd-b06b-1b237f588ef7.txt` |
|
||||
| 能生成我的中信银行和整个真实的一个财务报表和财务支出,包括中实银行卡卡猫 | 开发工作区 | `能生成我的中信银行和整个真实的一个财务报表和财务支出,包括中实银行卡卡猫_2a88cabb-908f-49eb-bfbb-f69bbc069d66.txt` |
|
||||
|
||||
@@ -11,15 +11,15 @@
|
||||
|
||||
| 中文名称 | 所属工作台 | 对话文件 |
|
||||
|:---|:---|:---|
|
||||
| 乘客,客跑 AI 的,吸收一下卡路,AI 这个自动化到服务器宝塔的这两台 | 卡若AI工作区 | `乘客,客跑 AI 的,吸收一下卡路,AI 这个自动化到服务器宝塔的这两台_3b04266e-de52-45d4-ae39-f607b35849c4.txt` |
|
||||
| 乘客,客跑 AI 的,吸收一下卡若,AI 这个自动化到服务器宝塔的这两台 | 卡若AI工作区 | `乘客,客跑 AI 的,吸收一下卡路,AI 这个自动化到服务器宝塔的这两台_3b04266e-de52-45d4-ae39-f607b35849c4.txt` |
|
||||
| @ Users karuo Documents 个人 个人的文件里面的文 | 开发工作区 | `@ Users karuo Documents 个人 个人的文件里面的文_48602012-35ea-44c5-ae8a-12290886bbf9.txt` |
|
||||
| AI 版所有的账号密码帮我列出来。卡罗 AI | 开发工作区 | `AI 版所有的账号密码帮我列出来。卡罗 AI_c05a89a0-eb34-48fb-be36-199b0532ee0c.txt` |
|
||||
| AI 版所有的账号密码帮我列出来。卡若AI | 开发工作区 | `AI 版所有的账号密码帮我列出来。卡若AI_c05a89a0-eb34-48fb-be36-199b0532ee0c.txt` |
|
||||
| Users karuo Documents 个人 3、工作台 | 开发工作区 | `Users karuo Documents 个人 3、工作台_7c65f9b7-ac29-4615-9a90-66a997818708.txt` |
|
||||
| glm 5现在的价格和claude的对比 | 开发工作区 | `glm 5现在的价格和claude的对比_4c26977a-0857-4809-bdc8-6d4096716bc8.txt` |
|
||||
| skill | 开发工作区 | `skill_9dcf2229-66a4-4775-b92b-8ddcbbc7baf1.txt` |
|
||||
| 上传本地项目到github上 | 开发工作区 | `上传本地项目到github上_0de581eb-0e88-447a-a6f6-dfd25a43db41.txt` |
|
||||
| 他自己成立了公司五行运贸易,五行运数字人系统和研发。所以他就叫那个有可能 | 开发工作区 | `他自己成立了公司五行运贸易,五行运数字人系统和研发。所以他就叫那个有可能_0c2bf348-8e42-481f-ac8e-4ed6b846cfdb.txt` |
|
||||
| 卡罗 AI 修复之前,卡罗 AI 上面有非常多的文件,每一个人都有职责的 | 开发工作区 | `卡罗 AI 修复之前,卡罗 AI 上面有非常多的文件,每一个人都有职责的_c483f207-9022-46ee-8666-6d24697f2d4b.txt` |
|
||||
| 卡若AI 修复之前,卡若AI 上面有非常多的文件,每一个人都有职责的 | 开发工作区 | `卡若AI 修复之前,卡若AI 上面有非常多的文件,每一个人都有职责的_c483f207-9022-46ee-8666-6d24697f2d4b.txt` |
|
||||
| 发布到银掌柜的小程序上 | 开发工作区 | `发布到银掌柜的小程序上_baaa9dac-661b-49f0-9fbb-5bef362d9cf3.txt` |
|
||||
| 告诉我卡若ai的github地址 | 开发工作区 | `告诉我卡若ai的github地址_1777a2b9-e52c-4f51-990b-3947e7b24430.txt` |
|
||||
| 对,飞书项目的玩值电竞的这一个飞书里面,飞书项目完职店庆的一个各个接口的 | 开发工作区 | `对,飞书项目的玩值电竞的这一个飞书里面,飞书项目完职店庆的一个各个接口的_eae58f42-92a2-4333-90f0-1b447ebebc7a.txt` |
|
||||
|
||||
@@ -27,6 +27,6 @@
|
||||
| 将这个数据库。里面的那个所有的文件,包括子文件,那个帮我导到那个。和所有 | 开发工作区 | `将这个数据库。里面的那个所有的文件,包括子文件,那个帮我导到那个。和所有_8f50e9d1-b7e7-4e33-aa55-7e9174f5e3f0.txt` |
|
||||
| 日记@个人 2、我写的日记 写一篇文章关于这个分布式算力矩阵的收集,所有 | 开发工作区 | `日记@个人 2、我写的日记 写一篇文章关于这个分布式算力矩阵的收集,所有_e71dc79c-6740-4b5e-a470-998ab7d6ec57.txt` |
|
||||
| 模拟一次小说行侦行为,将 Users karuo 卡若开发 数据库 借贷 | 开发工作区 | `模拟一次小说行侦行为,将 Users karuo 卡若开发 数据库 借贷_f158343a-f4e2-4b31-ad62-6a812bc18af0.txt` |
|
||||
| 然后在那个 NAS 上面把这个卡洛的那个 FMVTK 的 GitHub | 开发工作区 | `然后在那个 NAS 上面把这个卡洛的那个 FMVTK 的 GitHub_e29e511b-cbcd-4208-b0f0-2900e71738bd.txt` |
|
||||
| 然后在那个 NAS 上面把这个卡若的那个 FMVTK 的 GitHub | 开发工作区 | `然后在那个 NAS 上面把这个卡若的那个 FMVTK 的 GitHub_e29e511b-cbcd-4208-b0f0-2900e71738bd.txt` |
|
||||
| 连接到书房的电视,检查他的配置和连网状态 | 开发工作区 | `连接到书房的电视,检查他的配置和连网状态_9fc43943-7988-4b64-b8c4-25116fd63d2b.txt` |
|
||||
| 连接我现在连的这个局域网内的另外一台苹果笔记本电脑。用ssh直接连接 账 | 开发工作区 | `连接我现在连的这个局域网内的另外一台苹果笔记本电脑。用ssh直接连接 账_b16ae85a-79d9-42e5-a271-c2730389abd2.txt` |
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| @分布式算力矩阵 04_暴力破解 查找 GitHub 上面去针对那个 S | 开发工作区 | `@分布式算力矩阵 04_暴力破解 查找 GitHub 上面去针对那个 S_b07973ad-df1c-4d22-b371-32f78557487a.txt` |
|
||||
| ip账号密码 | 开发工作区 | `ip账号密码_6a5c8494-b4d7-4aa5-9ab1-7591d858a965.txt` |
|
||||
| 上传到 CKB NAS 的 Git 上面, | 开发工作区 | `上传到 CKB NAS 的 Git 上面,_0ced8e42-bf4e-485c-a528-c3096f868c33.txt` |
|
||||
| 个巴卡洛 AI。爬,卡洛 AI 放到整个的那个放到 NAS 的服务器上面 | 开发工作区 | `个巴卡洛 AI。爬,卡洛 AI 放到整个的那个放到 NAS 的服务器上面_f68f86f2-0740-4789-b7d2-343c243ca9e9.txt` |
|
||||
| 个巴卡若AI。爬,卡若AI 放到整个的那个放到 NAS 的服务器上面 | 开发工作区 | `个巴卡若AI。爬,卡若AI 放到整个的那个放到 NAS 的服务器上面_f68f86f2-0740-4789-b7d2-343c243ca9e9.txt` |
|
||||
| 全网找ip | 开发工作区 | `全网找ip_cf3d95e6-2afd-42fa-bdfc-af382fbbf040.txt` |
|
||||
| 批量将飞书里面,飞书的那个视频会议带有飞书,视频会议带有这个受,派对和受 | 开发工作区 | `批量将飞书里面,飞书的那个视频会议带有飞书,视频会议带有这个受,派对和受_3648ac7c-9e57-4501-b431-ef5bc1f6f17d.txt` |
|
||||
| 把这个卡罗伊的 skill 的所有的人的能力工作台和能力总索引。根据卡罗 | 开发工作区 | `把这个卡罗伊的 skill 的所有的人的能力工作台和能力总索引。根据卡罗_acf700f7-27d0-43c2-b035-d41449b6bf41.txt` |
|
||||
@@ -24,4 +24,4 @@
|
||||
| 正在查找与“火炬短视频团队”和“unilife”相关的国外直播软件信息。 | 开发工作区 | `正在查找与“火炬短视频团队”和“unilife”相关的国外直播软件信息。_70edd474-5206-46c6-8dbc-81b1be2f4030.txt` |
|
||||
| 火炬短视频团队 | 开发工作区 | `火炬短视频团队_7ebd1366-ef0f-4860-97df-651fe1a4dabb.txt` |
|
||||
| 网站开发 | 开发工作区 | `网站开发_12b1e64e-d120-4e96-8721-3ee496dcf225.txt` |
|
||||
| 那个卡洛 AI 里面的那个上面那些课,gitty GitHub 这一些目 | 开发工作区 | `那个卡洛 AI 里面的那个上面那些课,gitty GitHub 这一些目_c5e7f9ad-027a-4aef-bcee-c9ee7095b44b.txt` |
|
||||
| 那个卡若AI 里面的那个上面那些课,gitty GitHub 这一些目 | 开发工作区 | `那个卡若AI 里面的那个上面那些课,gitty GitHub 这一些目_c5e7f9ad-027a-4aef-bcee-c9ee7095b44b.txt` |
|
||||
|
||||
@@ -7,15 +7,16 @@ memory_palace_path: 卡若记忆宫殿/水殿/水溪厢/回廊洗字
|
||||
memory_palace_slot: 口述与 ASR 杂声在此洗净,再入记忆与字幕流水线
|
||||
triggers: **回廊洗字、卡若记忆宫殿命名体系、记忆宫殿、记忆空间、语音转写纠错、语音输入、闽南话、闽南口音、听写、ASR、转写纠错、纠错库、误听、卡罗拉、卡罗伊**、口述、嘴瓢、**网页CLI、终端浏览器、命令行看网页、browsh**
|
||||
owner: 水溪
|
||||
version: "1.3"
|
||||
updated: "2026-03-23"
|
||||
version: "1.5"
|
||||
updated: "2026-03-27"
|
||||
---
|
||||
|
||||
# 回廊洗字(W03b · 卡若记忆宫殿命名体系)
|
||||
|
||||
> **宫殿定位**:`卡若记忆宫殿 / 水殿 / 水溪厢 / 回廊洗字` —— 属 **卡若记忆宫殿命名体系**;对应成员 **水溪**(整理归档、清清爽爽);职能是 **口述与听写噪声入库前的滤真**,与 W04「自动记忆管理」相邻:先洗字,再入深记。
|
||||
> **登记用原名**:语音转写纠错(目录名与旧文档仍用此名,避免路径大面积迁移)。
|
||||
> **别名**:语音里 **「卡罗拉」「卡罗伊」= 卡若AI**(ASR 误听,保留在触发词)。
|
||||
> **机制总文档**:`运营中枢/参考资料/闽南话语音_ASR纠错机制.md`(闽南话口述 · **每轮对话滤真**、词库维护、与 JSON 关系;口述「科室」≈ Skill 见该文 **§八**)。
|
||||
> **别名**:语音里 **「卡罗拉」「卡罗伊」等 = 卡若 / 卡若AI**(ASR 误听,保留在触发词;完整映射见 JSON)。
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -27,12 +28,15 @@ updated: "2026-03-23"
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `运营中枢/参考资料/卡若闽南口音_ASR纠错库.json` | **主纠错表**:`corrections` 对象,`误听 → 正写` |
|
||||
| `运营中枢/工作台/闽南口音纠错工作台/README.md` | **运维工作台**:多根扫盘 + Mongo 统计;入口脚本 `…/脚本/minnan_asr_workbench.py`(`scan-files` / `scan-mongo`) |
|
||||
|
||||
## Agent 每轮对话(强制)
|
||||
|
||||
1. **在推理与执行前**,将用户本轮自然语言视为可能含 ASR 噪声;在心中或用下述脚本对**关键片段**做一次纠正后再定意图(不必改用户原文展示,**内部理解**以纠正后为准)。
|
||||
2. 替换顺序:**按 key 长度降序**全文替换,避免短词截断长词(与 `soul_enhance.py` 一致)。
|
||||
3. 专有名词:Cursor、Claude、Soul、卡若AI 等按表中写法对齐。
|
||||
1. **在推理与执行前**,将用户本轮自然语言视为可能含 ASR 噪声;在心中或用下述脚本对**整段或关键片段**做一次纠正后再定意图(不必改用户原文展示,**内部理解**以纠正后为准)。
|
||||
2. **统一过滤**:不仅「卡路 / 卡罗 / 卡洛」类,**Cursor / Claude / Soul / 私域 / 存客宝** 等表中近音一并处理;以 `卡若闽南口音_ASR纠错库.json` 为**唯一**词条源。
|
||||
3. 替换顺序:**按 key 长度降序**全文替换,避免短词截断长词(与 `soul_enhance.py`、`apply_karuo_voice_corrections.py` 一致)。
|
||||
4. **上下文**:若替换后与句义冲突(食物热量、受伤、西游记原著等),以语义为准,见 JSON `notes` 与下文「终端网页 CLI」段。
|
||||
5. 专有名词:Cursor、Claude、Soul、卡若AI 等按表中写法对齐。
|
||||
|
||||
## 命令行 / 脚本复用
|
||||
|
||||
@@ -77,5 +81,6 @@ echo "卡罗拉更新 skill" | python3 ".../apply_karuo_voice_corrections.py"
|
||||
|
||||
## 参考
|
||||
|
||||
- **机制总述**:`运营中枢/参考资料/闽南话语音_ASR纠错机制.md`
|
||||
- 内置纠错写法参考:`木叶_视频内容/视频切片/脚本/soul_enhance.py` 中 `_CORRECTIONS_BASE` 与 `apply_platform_safety` 流程。
|
||||
- Cursor 总规则:`.cursor/rules/karuo-ai.mdc`(语音理解条目)。
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
"cookies": [
|
||||
{
|
||||
"name": "sessionid",
|
||||
"value": "BgAACuej0XO0tWGoSXf6YSEg2KiGaAtGkyr52JZOPsMe6q13eTlWwPgczkkyhzKQDaLGEFfNV%2BysF1bqhgR03iRrWB7o3Lxep6efz8EZ8yM%3D",
|
||||
"value": "BgAAvxwCNDxRKH7MSNkZxTv41j6n4M%2BH5xmkxVRkA68Z%2FyLYqu61xG3fhLT92QLmHD3Ihykpu8kL5aux7QLL99PFcO5P6GBz5N8V0x2qSTA%3D",
|
||||
"domain": "channels.weixin.qq.com",
|
||||
"path": "/",
|
||||
"expires": 1809032864.570455,
|
||||
"expires": 1809089844.27566,
|
||||
"httpOnly": false,
|
||||
"secure": true,
|
||||
"sameSite": "None"
|
||||
},
|
||||
{
|
||||
"name": "wxuin",
|
||||
"value": "2604008894",
|
||||
"value": "1335138025",
|
||||
"domain": "channels.weixin.qq.com",
|
||||
"path": "/",
|
||||
"expires": 1809032864.5705,
|
||||
"expires": 1809089844.275706,
|
||||
"httpOnly": false,
|
||||
"secure": true,
|
||||
"sameSite": "None"
|
||||
@@ -27,44 +27,36 @@
|
||||
"localStorage": [
|
||||
{
|
||||
"name": "finder_route_meta",
|
||||
"value": "micro.content/post/create;index;1;1774472953358"
|
||||
"value": "micro.content/post/create;index;1;1774529934465"
|
||||
},
|
||||
{
|
||||
"name": "__ml::hb_ts",
|
||||
"value": "1774472837493"
|
||||
"value": "1774529812771"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_407d5f0c-d0ca-4817-bdca-92bce645cec0",
|
||||
"value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"f605733e-22a5-41d0-8823-5311043e888d\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_e5fafaeb-b678-46a3-84db-c34fc5a41faf",
|
||||
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"a98782f3-d4f5-411a-81ad-0a7e88d0726f\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_974d99d7-06b4-42a9-bfe6-829ae390b5d8",
|
||||
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"d68629ce-6c07-40b6-981c-bf53d3bcaa54\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_eb7e3a97-079f-4f7f-b3df-34af797c1d3b",
|
||||
"value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"b3197990-65f5-41b4-90fe-3be6a9bed0f4\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_567e3c2c-f9a8-4942-bf04-2e044b9ae357",
|
||||
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"3030294c-638b-43cd-b705-9a4e692635d6\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page",
|
||||
"value": "[\"e34be82b-5f02-4755-856a-8bcbaced0150\",\"75f8432c-6020-4b21-bed0-075b722137bb\",\"228e75c5-c171-4296-bbae-f35ca93cbc19\",\"2b06e18c-14af-441f-8310-afb188461edb\",\"7165e787-d801-4d51-b089-999f0444ae9d\",\"74531dfd-c036-4c39-9bb7-1caf3d5b9847\",\"974d99d7-06b4-42a9-bfe6-829ae390b5d8\",\"4e1bfe1f-233d-4706-bf4f-302935e5f42a\",\"539b522c-6a3c-4689-a047-86f2cfae8700\",\"5fb65be7-a722-4a6c-b807-3001bd97d778\",\"784c0694-ace3-4bf7-896c-39adeb299e59\",\"440992a6-7c2e-4734-b857-c51bd33d2e34\",\"db7ca8a4-d27a-4a76-82db-c6704a6fb95b\",\"eb7e3a97-079f-4f7f-b3df-34af797c1d3b\",\"fa9c82e0-0771-4bea-9515-410e65e95c61\",\"c780f196-f7fe-424b-9747-04430b9a3331\",\"e5fafaeb-b678-46a3-84db-c34fc5a41faf\",\"55d81d9b-60e8-438c-83e0-c0197760d2f5\",\"60251d45-49bb-413e-9ed9-add6d8a605c1\",\"f3472efb-d824-43a3-8fe8-5170ddb84261\"]"
|
||||
"value": "[\"539b522c-6a3c-4689-a047-86f2cfae8700\",\"5fb65be7-a722-4a6c-b807-3001bd97d778\",\"784c0694-ace3-4bf7-896c-39adeb299e59\",\"440992a6-7c2e-4734-b857-c51bd33d2e34\",\"db7ca8a4-d27a-4a76-82db-c6704a6fb95b\",\"eb7e3a97-079f-4f7f-b3df-34af797c1d3b\",\"fa9c82e0-0771-4bea-9515-410e65e95c61\",\"c780f196-f7fe-424b-9747-04430b9a3331\",\"e5fafaeb-b678-46a3-84db-c34fc5a41faf\",\"55d81d9b-60e8-438c-83e0-c0197760d2f5\",\"60251d45-49bb-413e-9ed9-add6d8a605c1\",\"f3472efb-d824-43a3-8fe8-5170ddb84261\",\"c4bd1c5c-2df4-4e3e-97c7-4422f1fdf257\",\"407d5f0c-d0ca-4817-bdca-92bce645cec0\",\"a4f6964d-38cd-42e4-8471-7629763b98cf\",\"efe33dea-25c3-4eb6-980c-31fe3f425c31\",\"567e3c2c-f9a8-4942-bf04-2e044b9ae357\",\"e046d951-d078-445b-9296-5183597cc19f\",\"c99ffcdc-e264-4ee0-8b42-f9417a6968e5\",\"b126939b-a6a5-4540-a40b-1c59b761634d\"]"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_784c0694-ace3-4bf7-896c-39adeb299e59",
|
||||
"value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"1f157d64-c269-48e3-8048-321cfe790fd2\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_228e75c5-c171-4296-bbae-f35ca93cbc19",
|
||||
"value": "{\"pageId\":\"PostCreate\",\"accessId\":\"83ad0873-7161-467e-a1d7-5555696cefab\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_75f8432c-6020-4b21-bed0-075b722137bb",
|
||||
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"f62b0202-c271-44e5-8c24-46ecb798ce53\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_e34be82b-5f02-4755-856a-8bcbaced0150",
|
||||
"value": "{\"pageId\":\"PostList\",\"accessId\":\"7702cd13-e3f7-46a3-bee0-6096d434d4fd\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_fa9c82e0-0771-4bea-9515-410e65e95c61",
|
||||
"value": "{\"pageId\":\"Home\",\"accessId\":\"224c5505-1caa-42fa-bbe4-22355ffedc69\",\"step\":1}"
|
||||
@@ -73,13 +65,17 @@
|
||||
"name": "__ml::page_55d81d9b-60e8-438c-83e0-c0197760d2f5",
|
||||
"value": "{\"pageId\":\"PostList\",\"accessId\":\"69aa9c01-5c39-4b32-a400-7719854326b5\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_efe33dea-25c3-4eb6-980c-31fe3f425c31",
|
||||
"value": "{\"pageId\":\"PostCard\",\"accessId\":\"8415a7c3-59b1-4002-80a3-076eba267ed4\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_440992a6-7c2e-4734-b857-c51bd33d2e34",
|
||||
"value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"226b1fe5-edf0-4d49-bb00-eba0a5b4d09b\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_2b06e18c-14af-441f-8310-afb188461edb",
|
||||
"value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"ccf907d3-ec3f-40fd-835f-f44647dab534\",\"step\":1}"
|
||||
"name": "__ml::page_a4f6964d-38cd-42e4-8471-7629763b98cf",
|
||||
"value": "{\"pageId\":\"Home\",\"accessId\":\"076a3ed1-3bd1-4e91-90d5-76c9e54f843a\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_db7ca8a4-d27a-4a76-82db-c6704a6fb95b",
|
||||
@@ -90,33 +86,29 @@
|
||||
"value": "{\"browser\":\"Chrome\",\"browserVersion\":\"131.0.0.0\",\"engine\":\"Webkit\",\"engineVersion\":\"537.36\",\"os\":\"Mac OS X\",\"osVersion\":\"10.15.7\",\"device\":\"desktop\",\"darkmode\":0}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_74531dfd-c036-4c39-9bb7-1caf3d5b9847",
|
||||
"value": "{\"pageId\":\"PostCard\",\"accessId\":\"c6e70987-87a7-4fc0-9ea7-074af5093293\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_7165e787-d801-4d51-b089-999f0444ae9d",
|
||||
"value": "{\"pageId\":\"Home\",\"accessId\":\"838ec3ff-0f71-4d6c-b053-c0af71613373\",\"step\":1}"
|
||||
"name": "__ml::page_b126939b-a6a5-4540-a40b-1c59b761634d",
|
||||
"value": "{\"pageId\":\"PostCreate\",\"accessId\":\"9cefae39-076e-48bb-aebe-82129e52f106\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_60251d45-49bb-413e-9ed9-add6d8a605c1",
|
||||
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"2684d827-da48-490e-a1c3-d1b572e8a413\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_4e1bfe1f-233d-4706-bf4f-302935e5f42a",
|
||||
"value": "{\"pageId\":\"PostList\",\"accessId\":\"b54d408e-bd25-43f2-b5ce-c086528b3fdd\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::aid",
|
||||
"value": "\"d54409bf-5743-4c38-9a01-4e8e2f82f2c2\""
|
||||
},
|
||||
{
|
||||
"name": "__rx::aid",
|
||||
"value": "\"d54409bf-5743-4c38-9a01-4e8e2f82f2c2\""
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_c780f196-f7fe-424b-9747-04430b9a3331",
|
||||
"value": "{\"pageId\":\"PostCard\",\"accessId\":\"e6abc041-28bd-4647-82fb-0bb5b0e5953d\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_c4bd1c5c-2df4-4e3e-97c7-4422f1fdf257",
|
||||
"value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"4f4036b9-4582-4c4d-a910-f34f32af5f59\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__rx::aid",
|
||||
"value": "\"d54409bf-5743-4c38-9a01-4e8e2f82f2c2\""
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_f3472efb-d824-43a3-8fe8-5170ddb84261",
|
||||
"value": "{\"pageId\":\"PostCreate\",\"accessId\":\"db066205-663b-4014-8068-12203a27e05e\",\"step\":1}"
|
||||
@@ -125,6 +117,10 @@
|
||||
"name": "finder_login_token",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_c99ffcdc-e264-4ee0-8b42-f9417a6968e5",
|
||||
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"1ca2a7a9-72f2-4593-8fbc-e0e615e66f0c\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "finder_username",
|
||||
"value": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder"
|
||||
@@ -139,12 +135,16 @@
|
||||
},
|
||||
{
|
||||
"name": "MICRO_VISITED_NAME",
|
||||
"value": "{\"postCard\":25,\"content\":29,\"interaction\":2}"
|
||||
"value": "{\"postCard\":26,\"content\":31,\"interaction\":2}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_539b522c-6a3c-4689-a047-86f2cfae8700",
|
||||
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"7ddb50d3-3d52-482e-b6d6-10dbcc22f8cb\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "__ml::page_e046d951-d078-445b-9296-5183597cc19f",
|
||||
"value": "{\"pageId\":\"PostList\",\"accessId\":\"2f7fd2d6-7d82-4100-934a-2c80c34bb3c9\",\"step\":1}"
|
||||
},
|
||||
{
|
||||
"name": "UvFirstReportLocalKey",
|
||||
"value": "1774454400000"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- 并行分发:5 平台同时上传(asyncio.gather)
|
||||
- 去重:每条视频按其在目录中的序号对齐排期(不因前面跳过而错位)
|
||||
- 失败重试:--retry;Cookie 预警;结果写入 publish_log.json
|
||||
- 视频号登录:默认静默(仅同步 Cookie 路径,不弹窗);需要自动扫码时加 --auto-channels-login;NO_AUTO_CHANNELS_LOGIN=1 强制静默
|
||||
- 视频号:发稿走 channels_api_publish(与「视频号发布/SKILL.md · 〇」一致);登录推荐本机 CHANNELS_SILENT_QR=1 channels_login.py --silent-qr。需要自动调起登录进程时加 --auto-channels-login;NO_AUTO_CHANNELS_LOGIN=1 强制静默
|
||||
|
||||
用法:
|
||||
python3 distribute_all.py # 智能错峰定时排期
|
||||
@@ -59,7 +59,7 @@ CHANNELS_LOGIN_SCRIPT = BASE_DIR / "视频号发布" / "脚本" / "channels_logi
|
||||
LOGIN_COMMANDS = {
|
||||
"抖音": f'python3 "{BASE_DIR / "抖音发布" / "脚本" / "douyin_login.py"}"',
|
||||
"B站": f'python3 "{BASE_DIR / "B站发布" / "脚本" / "bilibili_login.py"}"',
|
||||
"视频号": f'python3 "{BASE_DIR / "视频号发布" / "脚本" / "channels_login.py"} --playwright-only"',
|
||||
"视频号": f'CHANNELS_SILENT_QR=1 python3 "{CHANNELS_LOGIN_SCRIPT}" --silent-qr',
|
||||
"小红书": f'python3 "{BASE_DIR / "小红书发布" / "脚本" / "xiaohongshu_login.py"}"',
|
||||
"快手": f'python3 "{BASE_DIR / "快手发布" / "脚本" / "kuaishou_login.py"}"',
|
||||
}
|
||||
|
||||
10
03_卡木(木)/木叶_视频内容/多平台分发/脚本/publish_dedup_revoke.jsonl
Normal file
10
03_卡木(木)/木叶_视频内容/多平台分发/脚本/publish_dedup_revoke.jsonl
Normal file
@@ -0,0 +1,10 @@
|
||||
{"platform": "视频号", "video_name": "soul130_01_我看你不太好 你不太好 你不太好 你.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_02_你不太好 你不太好 你不太好 你不太.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_03_内容库是要干嘛的 前面后面 我跟你说.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_04_需要优化的点 第一个是 介面优化 第.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_05_你看关于这块 同步这块 我这里的话是.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_06_这里搜索 选择要同步 要同步的人 要.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_07_这样会比较能性化一点 但是活儿是很乱.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_08_你想是多少好友来干嘛 我才知道 我要.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_09_看直播的分论 啥意思呢 我们进入 进.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
{"platform": "视频号", "video_name": "soul130_10_我带ID我是知道 当天的总销售 是随.mp4", "reason": "2026-03-26 post_delete 已从视频号移除,允许重发"}
|
||||
@@ -12,6 +12,8 @@ from typing import Optional
|
||||
|
||||
RESULT_LOG = Path(__file__).parent / "publish_log.json"
|
||||
UPLOAD_LIBRARY_LOG = Path(__file__).parent / "upload_library.jsonl"
|
||||
# 平台侧已删除/作废发布时追加一行,从去重集合中排除(避免仅删 upload_library 仍被 publish_log 卡住)
|
||||
DEDUP_REVOKE_LOG = Path(__file__).parent / "publish_dedup_revoke.jsonl"
|
||||
|
||||
|
||||
def _video_signature(video_path: str) -> str:
|
||||
@@ -23,8 +25,30 @@ def _video_signature(video_path: str) -> str:
|
||||
return p.name
|
||||
|
||||
|
||||
def _load_dedup_revoke_names() -> set[tuple[str, str]]:
|
||||
"""(platform, video basename) 不再视为已发布去重。"""
|
||||
out: set[tuple[str, str]] = set()
|
||||
if not DEDUP_REVOKE_LOG.exists():
|
||||
return out
|
||||
with open(DEDUP_REVOKE_LOG, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
rec = json.loads(line)
|
||||
pf = (rec.get("platform") or "").strip()
|
||||
name = (rec.get("video_name") or "").strip()
|
||||
if pf and name:
|
||||
out.add((pf, name))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
return out
|
||||
|
||||
|
||||
def _load_library_set() -> set[tuple[str, str]]:
|
||||
out = set()
|
||||
revoked_names = _load_dedup_revoke_names()
|
||||
if not UPLOAD_LIBRARY_LOG.exists():
|
||||
return out
|
||||
with open(UPLOAD_LIBRARY_LOG, "r", encoding="utf-8") as f:
|
||||
@@ -37,6 +61,9 @@ def _load_library_set() -> set[tuple[str, str]]:
|
||||
pf = rec.get("platform", "")
|
||||
sig = rec.get("video_signature", "")
|
||||
if pf and sig:
|
||||
fname = sig.split("|", 1)[0]
|
||||
if (pf, fname) in revoked_names:
|
||||
continue
|
||||
out.add((pf, sig))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
@@ -89,6 +116,7 @@ def save_results(results: list[PublishResult]):
|
||||
def load_published_set() -> set[tuple[str, str]]:
|
||||
"""加载已成功发布集合(兼容旧日志 + 上传库)。"""
|
||||
published = set()
|
||||
revoked_names = _load_dedup_revoke_names()
|
||||
if RESULT_LOG.exists():
|
||||
with open(RESULT_LOG, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
@@ -99,14 +127,17 @@ def load_published_set() -> set[tuple[str, str]]:
|
||||
rec = json.loads(line)
|
||||
if rec.get("success"):
|
||||
fname = Path(rec.get("video_path", "")).name
|
||||
published.add((rec["platform"], fname))
|
||||
plat = rec.get("platform", "")
|
||||
if fname and (plat, fname) in revoked_names:
|
||||
continue
|
||||
published.add((plat, fname))
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# 同步上传库签名映射回文件名集合(保障去重不遗漏)
|
||||
for platform, sig in _load_library_set():
|
||||
fname = sig.split("|", 1)[0]
|
||||
if fname:
|
||||
if fname and (platform, fname) not in revoked_names:
|
||||
published.add((platform, fname))
|
||||
return published
|
||||
|
||||
@@ -114,6 +145,8 @@ def load_published_set() -> set[tuple[str, str]]:
|
||||
def is_published(platform: str, video_path: str) -> bool:
|
||||
"""检查某条视频是否已成功发布到某平台(全平台上传库去重)。"""
|
||||
fname = Path(video_path).name
|
||||
if (platform, fname) in _load_dedup_revoke_names():
|
||||
return False
|
||||
if (platform, fname) in load_published_set():
|
||||
return True
|
||||
sig = _video_signature(video_path)
|
||||
|
||||
@@ -35,13 +35,3 @@
|
||||
{"timestamp": "2026-03-24 21:38:24", "platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片_大师版/赚钱没那么复杂,自信心才是核心问题.mp4", "video_signature": "赚钱没那么复杂,自信心才是核心问题.mp4|22996736", "status": "likely_published"}
|
||||
{"timestamp": "2026-03-25 14:45:28", "platform": "视频号", "video_path": "/tmp/soul_channels_127_128_bundle/我之前抖音就这么做.mp4", "video_signature": "我之前抖音就这么做.mp4|5229367", "status": "published"}
|
||||
{"timestamp": "2026-03-26 05:47:23", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第129场_20260320_output/成片/七千店复制拿投资月流水五十万.mp4", "video_signature": "七千店复制拿投资月流水五十万.mp4|9583735", "status": "published"}
|
||||
{"timestamp": "2026-03-26 05:48:14", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_01_我看你不太好 你不太好 你不太好 你.mp4", "video_signature": "soul130_01_我看你不太好 你不太好 你不太好 你.mp4|14525846", "status": "published"}
|
||||
{"timestamp": "2026-03-26 05:49:00", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_02_你不太好 你不太好 你不太好 你不太.mp4", "video_signature": "soul130_02_你不太好 你不太好 你不太好 你不太.mp4|24950550", "status": "published"}
|
||||
{"timestamp": "2026-03-26 09:24:28", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_03_内容库是要干嘛的 前面后面 我跟你说.mp4", "video_signature": "soul130_03_内容库是要干嘛的 前面后面 我跟你说.mp4|50252357", "status": "published"}
|
||||
{"timestamp": "2026-03-26 09:25:14", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_04_需要优化的点 第一个是 介面优化 第.mp4", "video_signature": "soul130_04_需要优化的点 第一个是 介面优化 第.mp4|14508469", "status": "published"}
|
||||
{"timestamp": "2026-03-26 09:26:01", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_05_你看关于这块 同步这块 我这里的话是.mp4", "video_signature": "soul130_05_你看关于这块 同步这块 我这里的话是.mp4|55088468", "status": "published"}
|
||||
{"timestamp": "2026-03-26 09:26:47", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_06_这里搜索 选择要同步 要同步的人 要.mp4", "video_signature": "soul130_06_这里搜索 选择要同步 要同步的人 要.mp4|53061133", "status": "published"}
|
||||
{"timestamp": "2026-03-26 09:27:34", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_07_这样会比较能性化一点 但是活儿是很乱.mp4", "video_signature": "soul130_07_这样会比较能性化一点 但是活儿是很乱.mp4|49605178", "status": "published"}
|
||||
{"timestamp": "2026-03-26 09:28:21", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_08_你想是多少好友来干嘛 我才知道 我要.mp4", "video_signature": "soul130_08_你想是多少好友来干嘛 我才知道 我要.mp4|54366732", "status": "published"}
|
||||
{"timestamp": "2026-03-26 09:29:08", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_09_看直播的分论 啥意思呢 我们进入 进.mp4", "video_signature": "soul130_09_看直播的分论 啥意思呢 我们进入 进.mp4|48160664", "status": "published"}
|
||||
{"timestamp": "2026-03-26 09:29:55", "platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第130场_20260324_output/切片/soul130_10_我带ID我是知道 当天的总销售 是随.mp4", "video_signature": "soul130_10_我带ID我是知道 当天的总销售 是随.mp4|58372629", "status": "published"}
|
||||
|
||||
@@ -98,6 +98,7 @@ updated: "2026-03-24"
|
||||
|
||||
- **语言**:从 `transcript.srt` 解析、逐词路径、标点补强、封面 `hook_3sec` / `question` / 主标题引用、片尾 `cta_ending` 等,**一律经 `_to_simplified`(OpenCC t2s + 兜底映射)**,成片与收录进烧录层的文案**不出现繁体残留**(`highlights.json` 里仍可手写繁体,渲染时转简)。
|
||||
- **片尾完整性**:`soul_enhance.py` 在去静音前对 `silencedetect` 结果做 **`filter_silences_keep_tail_audio`**:最后 **`SILENCE_TAIL_PRESERVE_SEC`(默认约 2.85s)** 内的静音**不参与切除**,避免「最后几秒被剪成完全无声」。若原片结尾本身无对白,仍可能偏静,需在剪辑/高光时段上保证收尾句落在片尾窗内。
|
||||
- **剃空白(默认更狠,v2.13)**:默认 **`silencedetect` 阈值 -32dB、最短 0.22s**,并与 **字幕条之间 ≥0.52s 的间隙** 做并集后一起切除(会议留白、有底噪但无对白更易剃掉)。仍嫌碎或误剪:加 **`--silence-gentle`** 回退旧参数;只要音频不要字幕间隙:加 **`--no-subtitle-gap-merge`**。
|
||||
|
||||
### 贴片库与表情库(v2.6→v2.10 默认开启)
|
||||
|
||||
@@ -126,7 +127,7 @@ updated: "2026-03-24"
|
||||
```
|
||||
|
||||
- **batch_clip**:输出到 `clips/`
|
||||
- **soul_enhance -o 成片/ --title-only**(**推荐仍写 `--vertical`**):自 v2.3 起,只要带了 **`--title-only` 和/或 `--crop-vf` / `--vertical-fit-full`**,脚本会**默认启用竖屏直出**,避免漏写 `--vertical` 误出 1920×1080 横版。**文件名 = 封面标题 = highlights 的 title**(去杠:`:|、—、/` 等替换为空格);字幕烧录;去语助词;竖条裁剪直出到 `成片/`
|
||||
- **soul_enhance -o 成片/ --title-only**(**推荐仍写 `--vertical`**):自 v2.3 起,只要带了 **`--title-only` 和/或 `--crop-vf` / `--vertical-fit-full`**,脚本会**默认启用竖屏直出**,避免漏写 `--vertical` 误出 1920×1080 横版。**抖音向标题(v2.14)**:`highlights_keyword_focus.py` 默认把 **`title` 写成「完整热点长标题」**(悬念/反问 +|+ 锚点原句,约 30 汉字内,不编故事);**`viral_hook` 为左半句短 punch** 供封面大字;**成片文件名**优先用 **`title` 整句**(sanitize 约 72 字内 + `_01` 防覆盖),**封面烧录**仍走 `pick_cover_hook`(`viral_hook` 优先)。**时长与片尾人声(v2.15)**:无 API 关键词高光默认 **`--min-duration 60`~`--max-duration 300`(1~5 分钟)**;粗窗生成后再按字幕**收束到首条人声起、最后一条人声止**(去掉片尾长静音),**相邻字幕间隔 > `--topic-break-gap`(默认 12s)** 视为换话题时优先在上一句收束,尽量一条=一段完整表述;可调 **`--tail-pad`**。无 API:`python3 highlights_keyword_focus.py transcript.srt -o highlights.json`;可调 **`--title-max-cjk 34`**;要贴首句、不要抖音长标题: **`--plain-hooks`**。
|
||||
|
||||
### 3.1 全画面参数(必做约定)
|
||||
|
||||
|
||||
649
03_卡木(木)/木叶_视频内容/视频切片/脚本/highlights_keyword_focus.py
Normal file
649
03_卡木(木)/木叶_视频内容/视频切片/脚本/highlights_keyword_focus.py
Normal file
@@ -0,0 +1,649 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
无 API/Ollama 时:按「关键词在字幕时间轴上的密度」抽取高光窗口(类似话题峰值),
|
||||
输出与 identify_highlights 兼容的 JSON。用于派对录屏、MBTI/商业场等主题场。
|
||||
|
||||
默认:**单条 1~5 分钟**(60~300s)、窗口再按字幕**收束到首尾人声**(去掉片尾长静音)、
|
||||
**字幕间隙 ≥12s 视为换话题**时优先在上一句收束,尽量一条成片=一段完整表述。
|
||||
|
||||
用法:
|
||||
python3 highlights_keyword_focus.py transcript.srt -o highlights.json --clips 12 --theme mbti_business
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 复用 SRT 解析
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
if str(SCRIPT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPT_DIR))
|
||||
from identify_highlights import parse_srt_segments # noqa: E402
|
||||
|
||||
# 派对成片默认 CTA(与 Soul 竖屏场一致)
|
||||
PARTY_CTA = "关注卡若创业派对,下一条接着聊落地。"
|
||||
|
||||
|
||||
def _sec_to_hhmmss(sec: float) -> str:
|
||||
sec = max(0, int(sec))
|
||||
h, m, s = sec // 3600, (sec % 3600) // 60, sec % 60
|
||||
return f"{h:02d}:{m:02d}:{s:02d}"
|
||||
|
||||
|
||||
# 窗口内字幕含下列词时,给封面/文件名加「热点向短前缀」(观点仍锚在原文 anchor,不编造事实)
|
||||
VIRAL_PREFIX_RULES: list[tuple[list[str], str]] = [
|
||||
(["MBTI", "mbti"], "MBTI风口|"),
|
||||
(["性格", "测试", "测完"], "性格测试|"),
|
||||
(["三十秒", "30秒"], "30秒出结果|"),
|
||||
(["Token", "token", "TOKEN"], "Token降价潮|"),
|
||||
(["白嫖"], "别被白嫖|"),
|
||||
(["引流", "小程序"], "引流小程序|"),
|
||||
(["私域"], "私域算账|"),
|
||||
(["变现"], "变现链路|"),
|
||||
(["超级个体"], "超级个体|"),
|
||||
(["众创"], "众创搭伙|"),
|
||||
(["团队"], "团队配置|"),
|
||||
(["AI", "ai"], "AI落地|"),
|
||||
(["咨询", "心理"], "咨询成交|"),
|
||||
]
|
||||
|
||||
|
||||
def _pick_viral_prefix(window_text: str) -> str:
|
||||
if not window_text:
|
||||
return ""
|
||||
for kws, prefix in VIRAL_PREFIX_RULES:
|
||||
for kw in kws:
|
||||
if len(kw) <= 1:
|
||||
continue
|
||||
if kw.isascii():
|
||||
if kw.lower() in window_text.lower():
|
||||
return prefix
|
||||
elif kw in window_text:
|
||||
return prefix
|
||||
return ""
|
||||
|
||||
|
||||
def _cjk_count(s: str) -> int:
|
||||
return sum(1 for c in (s or "") if "\u4e00" <= c <= "\u9fff")
|
||||
|
||||
|
||||
def _compose_viral_hook(anchor_raw: str, joined_window: str, max_cjk: int = 16) -> str:
|
||||
"""刺激性封面句:热点前缀 + 锚点原文截断,总汉字封顶 max_cjk。(plain 模式或兜底用)"""
|
||||
pre = _pick_viral_prefix(joined_window)
|
||||
core = (anchor_raw or "").strip()
|
||||
if not pre:
|
||||
hook_full = _limit_cjk_chars(core, max_cjk)
|
||||
else:
|
||||
pre_cjk = sum(1 for c in pre if "\u4e00" <= c <= "\u9fff")
|
||||
rest = max(4, max_cjk - pre_cjk)
|
||||
hook_full = pre + _limit_cjk_chars(core, rest)
|
||||
hook_full = _limit_cjk_chars(hook_full, max_cjk)
|
||||
hook_17 = _limit_cjk_chars(hook_full, 17)
|
||||
if hook_full and hook_17 != hook_full:
|
||||
return hook_full + "…"
|
||||
return hook_full or _limit_cjk_chars(core, max_cjk) or "精彩片段"
|
||||
|
||||
|
||||
# 抖音向长标题:触发词命中 window 内合并文案则用对应模板;{core} 仅来自本窗字幕(不编故事)
|
||||
# 排序:更具体的规则在前
|
||||
DOUYIN_TITLE_TEMPLATES: list[tuple[list[str], str]] = [
|
||||
(["白嫖"], "白嫖客户还要不要伺候?这句直接把话说死|{core}"),
|
||||
(["Token", "token", "TOKEN"], "Token便宜到像批发,普通人怎么接住这波?|{core}"),
|
||||
(["小程序", "引流"], "引流进小程序只是第一步,后面这段才是钱|{core}"),
|
||||
(["私域"], "私域不是加好友,算完这笔账你就知道差哪|{core}"),
|
||||
(["变现", "付费"], "光靠工具变不了现?听完这条链路再判断|{core}"),
|
||||
(["MBTI", "mbti"], "MBTI风口还在不在?这条把赚钱逻辑说透|{core}"),
|
||||
(["性格", "测试", "30秒", "三十秒", "测完"], "30秒测性格凭什么赚钱?听完再决定动不动手|{core}"),
|
||||
(["前端", "后端"], "前后端都懂还不够,这条说清谁来收钱|{core}"),
|
||||
(["团队", "销售", "成交"], "团队里缺这一环,成交会一直卡在半路|{core}"),
|
||||
(["AI", "ai"], "AI不是噱头,落到成交差的是哪一步?|{core}"),
|
||||
(["超级个体", "众创"], "超级个体别一个人硬扛,搭伙姿势错了全白干|{core}"),
|
||||
(["咨询", "心理"], "咨询怎么接到成交?流程里这句最关键|{core}"),
|
||||
]
|
||||
DOUYIN_TITLE_DEFAULT = "派对里这句大实话,听完再决定动不动手|{core}"
|
||||
|
||||
|
||||
def _pick_douyin_template(window_text: str) -> str:
|
||||
if not window_text:
|
||||
return DOUYIN_TITLE_DEFAULT
|
||||
for kws, tmpl in DOUYIN_TITLE_TEMPLATES:
|
||||
for kw in kws:
|
||||
if len(kw) <= 1:
|
||||
continue
|
||||
if kw.isascii():
|
||||
if kw.lower() in window_text.lower():
|
||||
return tmpl
|
||||
elif kw in window_text:
|
||||
return tmpl
|
||||
return DOUYIN_TITLE_DEFAULT
|
||||
|
||||
|
||||
def _core_phrase_for_title(anchor_raw: str, joined: str, max_cjk: int = 14) -> str:
|
||||
"""标题后半|core:优先锚句,过短则拼窗口前文。"""
|
||||
a = re.sub(r"\s+", " ", (anchor_raw or "").strip())
|
||||
j = re.sub(r"\s+", " ", (joined or "").strip())
|
||||
if _cjk_count(a) >= 6:
|
||||
return _limit_cjk_chars(a, max_cjk)
|
||||
merged = (a + " " + j).strip() if a else j
|
||||
merged = re.sub(r"\s+", " ", merged).strip()[:200]
|
||||
return _limit_cjk_chars(merged, max_cjk) or _limit_cjk_chars(a or merged, max_cjk) or "干货片段"
|
||||
|
||||
|
||||
def _fill_douyin_title(template: str, core: str, max_total_cjk: int = 30) -> str:
|
||||
if "{core}" not in template:
|
||||
return _limit_cjk_chars(template, max_total_cjk)
|
||||
prefix, _, suffix = template.partition("{core}")
|
||||
used = _cjk_count(prefix) + _cjk_count(suffix)
|
||||
room = max(4, max_total_cjk - used)
|
||||
c = _limit_cjk_chars(core, room)
|
||||
out = prefix + c + suffix
|
||||
return _limit_cjk_chars(out, max_total_cjk)
|
||||
|
||||
|
||||
def _cover_punch_from_full_title(full_title: str, max_cjk: int = 16) -> str:
|
||||
"""封面/文件名优先用短 punch:有|则取左半句(问句/断言),否则截前 max_cjk 汉字。"""
|
||||
s = (full_title or "").strip()
|
||||
if not s:
|
||||
return ""
|
||||
if "|" in s:
|
||||
left = s.split("|", 1)[0].strip()
|
||||
base = _limit_cjk_chars(left, max_cjk)
|
||||
return base + ("…" if _cjk_count(left) > max_cjk else "")
|
||||
base = _limit_cjk_chars(s, max_cjk)
|
||||
return base + ("…" if _cjk_count(s) > max_cjk else "")
|
||||
|
||||
|
||||
def _compose_douyin_full_title(anchor_raw: str, joined: str, max_total_cjk: int = 30) -> str:
|
||||
tmpl = _pick_douyin_template(joined)
|
||||
core = _core_phrase_for_title(anchor_raw, joined, max_cjk=16)
|
||||
return _fill_douyin_title(tmpl, core, max_total_cjk=max_total_cjk)
|
||||
|
||||
|
||||
THEMES: dict[str, list[str]] = {
|
||||
"mbti_business": [
|
||||
"MBTI", "mbti", "性格", "测试", "测完", "三十秒", "30秒", "前端", "后端",
|
||||
"引流", "成交", "咨询", "私域", "销售", "团队", "小程序", "神仙团队",
|
||||
"变现", "链路", "用户", "付费", "解读", "心理", "流程", "对接", "小林",
|
||||
"陈总", "宋总", "四把椅子", "TOKEN", "token", "超级个体", "众创",
|
||||
],
|
||||
"soul_party": [
|
||||
"Soul", "派对", "上麦", "房主", "流量", "私域", "成交", "项目", "创业",
|
||||
"AI", "变现", "团队", "用户", "产品", "运营",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _score_text(text: str, keywords: list[str]) -> float:
|
||||
if not text:
|
||||
return 0.0
|
||||
s = 0.0
|
||||
for kw in keywords:
|
||||
if len(kw) <= 1:
|
||||
continue
|
||||
if kw.lower() in text.lower() if kw.isascii() else kw in text:
|
||||
s += 1.2 if len(kw) >= 3 else 0.6
|
||||
return s
|
||||
|
||||
|
||||
def _limit_cjk_chars(text: str, max_cjk: int) -> str:
|
||||
"""与 soul_enhance 封面逻辑一致:按汉字个数截断(ASCII 不占汉字额度)。"""
|
||||
if not text or max_cjk <= 0:
|
||||
return (text or "").strip()
|
||||
out: list[str] = []
|
||||
n = 0
|
||||
for ch in text:
|
||||
if "\u4e00" <= ch <= "\u9fff":
|
||||
n += 1
|
||||
if n > max_cjk:
|
||||
break
|
||||
out.append(ch)
|
||||
return "".join(out).strip()
|
||||
|
||||
|
||||
def _last_sub_before_long_gap(subs: list[dict], gap_sec: float):
|
||||
"""同一段连续发言中,遇到下一条字幕间隔 > gap_sec 则视为换话题,返回换话题前最后一条。"""
|
||||
if not subs:
|
||||
return None
|
||||
if len(subs) == 1:
|
||||
return subs[0]
|
||||
for i in range(len(subs) - 1):
|
||||
g = float(subs[i + 1]["start_sec"]) - float(subs[i]["end_sec"])
|
||||
if g > gap_sec:
|
||||
return subs[i]
|
||||
return subs[-1]
|
||||
|
||||
|
||||
def _extend_end_for_min_duration(
|
||||
segments: list[dict],
|
||||
s_out: int,
|
||||
e_out: int,
|
||||
min_dur: float,
|
||||
max_dur: float,
|
||||
cap_end: int,
|
||||
gap_sec: float,
|
||||
tail_pad: float,
|
||||
) -> int:
|
||||
"""当前 [s_out,e_out] 短于 min_dur 时,沿全局字幕轴向后接龙(间隔≤gap 视为同段),直到够长或触顶。"""
|
||||
if e_out - s_out >= min_dur:
|
||||
return min(e_out, s_out + int(max_dur))
|
||||
segs = sorted(segments, key=lambda x: float(x["start_sec"]))
|
||||
last_sp_end = max(0.0, float(e_out) - tail_pad)
|
||||
cur_e = e_out
|
||||
started = False
|
||||
for s in segs:
|
||||
st, en = float(s["start_sec"]), float(s["end_sec"])
|
||||
if en <= last_sp_end + 0.05 and not started:
|
||||
continue
|
||||
if not started:
|
||||
if st - last_sp_end <= gap_sec or st <= float(cur_e) + 0.5:
|
||||
started = True
|
||||
cur_e = int(min(cap_end, en + tail_pad))
|
||||
last_sp_end = en
|
||||
if cur_e - s_out >= min_dur:
|
||||
return min(cur_e, s_out + int(max_dur))
|
||||
else:
|
||||
break
|
||||
else:
|
||||
if st - last_sp_end > gap_sec:
|
||||
break
|
||||
cur_e = int(min(cap_end, en + tail_pad))
|
||||
last_sp_end = en
|
||||
if cur_e - s_out >= min_dur:
|
||||
return min(cur_e, s_out + int(max_dur))
|
||||
return min(max(cur_e, e_out), s_out + int(max_dur), cap_end)
|
||||
|
||||
|
||||
def _refine_window_to_speech_bounds(
|
||||
segments: list[dict],
|
||||
start_sec: int,
|
||||
end_sec: int,
|
||||
min_dur: float,
|
||||
max_dur: float,
|
||||
total_end: float,
|
||||
*,
|
||||
topic_break_gap_sec: float = 12.0,
|
||||
tail_pad_sec: float = 0.45,
|
||||
) -> tuple[int, int]:
|
||||
"""粗窗口 → 对齐首条字幕起点、在「自然话题尾」或最后一条字幕收束,去掉片尾无对白;再卡 1~5 分钟。"""
|
||||
cap = max(start_sec + 1, int(total_end) - 1)
|
||||
subs = [
|
||||
s
|
||||
for s in segments
|
||||
if float(s["end_sec"]) >= start_sec and float(s["start_sec"]) <= end_sec
|
||||
]
|
||||
subs.sort(key=lambda x: float(x["start_sec"]))
|
||||
if not subs:
|
||||
return start_sec, min(end_sec, cap)
|
||||
|
||||
s1 = int(max(start_sec, float(subs[0]["start_sec"])))
|
||||
natural_last = _last_sub_before_long_gap(subs, topic_break_gap_sec) or subs[-1]
|
||||
e_nat = int(min(end_sec, cap, float(natural_last["end_sec"]) + tail_pad_sec))
|
||||
e_full = int(min(end_sec, cap, float(subs[-1]["end_sec"]) + tail_pad_sec))
|
||||
|
||||
# 自然断点够长则用「一整段业务」收束;否则用本窗全部字幕收束(至少去掉尾静音)
|
||||
if e_nat - s1 >= max(35.0, min_dur * 0.72):
|
||||
e1 = e_nat
|
||||
else:
|
||||
e1 = e_full
|
||||
|
||||
if e1 - s1 > max_dur:
|
||||
s1 = int(max(start_sec, e1 - int(max_dur)))
|
||||
subs2 = [x for x in subs if float(x["end_sec"]) > s1]
|
||||
if subs2:
|
||||
s1 = int(max(s1, float(subs2[0]["start_sec"])))
|
||||
|
||||
if e1 - s1 < min_dur:
|
||||
e1 = _extend_end_for_min_duration(
|
||||
segments, s1, e1, min_dur, max_dur, cap, topic_break_gap_sec, tail_pad_sec
|
||||
)
|
||||
if e1 - s1 < min_dur:
|
||||
e1 = _extend_end_for_min_duration(
|
||||
segments,
|
||||
s1,
|
||||
max(e1, s1),
|
||||
min_dur,
|
||||
max_dur,
|
||||
cap,
|
||||
max(topic_break_gap_sec, 22.0),
|
||||
tail_pad_sec,
|
||||
)
|
||||
if e1 - s1 < min_dur:
|
||||
t_end = min(cap, int(end_sec))
|
||||
s1 = int(max(start_sec, t_end - int(min_dur)))
|
||||
e1 = t_end
|
||||
if e1 - s1 < min_dur:
|
||||
s1 = int(start_sec)
|
||||
e1 = min(cap, int(start_sec + int(min_dur)))
|
||||
|
||||
if e1 - s1 > max_dur:
|
||||
e1 = s1 + int(max_dur)
|
||||
|
||||
if e1 <= s1:
|
||||
return start_sec, min(end_sec, cap)
|
||||
return s1, e1
|
||||
|
||||
|
||||
def _anchor_segment(
|
||||
segments: list[dict],
|
||||
start_sec: int,
|
||||
end_sec: int,
|
||||
keywords: list[str],
|
||||
) -> dict | None:
|
||||
"""窗口内按时间第一条字幕;优先含关键词的条,便于封面 hook 与首条烧录字幕同源。"""
|
||||
in_win = [
|
||||
s
|
||||
for s in segments
|
||||
if s.get("end_sec", 0) >= start_sec and s.get("start_sec", 0) <= end_sec
|
||||
]
|
||||
if not in_win:
|
||||
return None
|
||||
in_win.sort(key=lambda x: float(x.get("start_sec", 0)))
|
||||
scored = [s for s in in_win if _score_text(s.get("text") or "", keywords) > 0]
|
||||
return scored[0] if scored else in_win[0]
|
||||
|
||||
|
||||
def _windows_non_overlap(
|
||||
chosen_bins: list[int],
|
||||
clip_count: int,
|
||||
min_dur: float,
|
||||
max_dur: float,
|
||||
total_end: float,
|
||||
bin_sec: float,
|
||||
min_gap_sec: float = 72.0,
|
||||
) -> list[tuple[int, int]]:
|
||||
"""峰值 bin → 时间窗,按时间排序并强制窗与窗之间至少 min_gap_sec(减少重叠切段)。"""
|
||||
half = max_dur / 2.0
|
||||
raw: list[tuple[int, int, int]] = []
|
||||
for bi in chosen_bins:
|
||||
center = (bi + 0.5) * bin_sec
|
||||
s = int(max(0, center - half))
|
||||
e = int(min(total_end - 2, s + int(max_dur)))
|
||||
if e - s < min_dur:
|
||||
e = int(min(total_end - 2, s + int(min_dur)))
|
||||
if e - s > max_dur:
|
||||
e = s + int(max_dur)
|
||||
raw.append((s, e, bi))
|
||||
raw.sort(key=lambda x: (x[0], x[1]))
|
||||
resolved: list[tuple[int, int]] = []
|
||||
last_e = -10**9
|
||||
for s, e, _ in raw:
|
||||
if s < last_e + min_gap_sec:
|
||||
s = int(last_e + min_gap_sec)
|
||||
e = int(min(total_end - 2, s + int(max_dur)))
|
||||
if e - s < min_dur:
|
||||
e = int(min(total_end - 2, s + int(min_dur)))
|
||||
if s >= total_end - 5 or (e - s) < min_dur * 0.85:
|
||||
continue
|
||||
resolved.append((s, e))
|
||||
last_e = e
|
||||
if len(resolved) >= clip_count:
|
||||
break
|
||||
|
||||
# 不足时用时间轴均匀补窗(与已有窗保留 min_gap_sec 间隔)
|
||||
def _gap_conflict(a: tuple[int, int], b: tuple[int, int]) -> bool:
|
||||
s, e = a
|
||||
rs, re = b
|
||||
return not (e + min_gap_sec <= rs or s >= re + min_gap_sec)
|
||||
|
||||
if len(resolved) < clip_count and total_end > min_dur + min_gap_sec:
|
||||
step = max(int(min_dur + min_gap_sec), int((total_end - min_dur) / max(1, clip_count + 2)))
|
||||
# 仅从「最后一段结束之后」向前扫,避免在已选峰值之后又插回 00:00 导致顺序与叙事错乱
|
||||
cursor = float(max((r[1] + min_gap_sec for r in resolved), default=0))
|
||||
safety = 0
|
||||
while len(resolved) < clip_count and safety < clip_count * 16:
|
||||
safety += 1
|
||||
if cursor > total_end - min_dur:
|
||||
break
|
||||
s = int(cursor)
|
||||
e = int(min(total_end - 2, s + int(min(max_dur, max(min_dur, 90.0)))))
|
||||
if e - s < min_dur * 0.85:
|
||||
cursor += step
|
||||
continue
|
||||
cand = (s, e)
|
||||
if any(_gap_conflict(cand, x) for x in resolved):
|
||||
cursor += step
|
||||
continue
|
||||
resolved.append(cand)
|
||||
cursor = float(e + min_gap_sec)
|
||||
|
||||
resolved.sort(key=lambda x: (x[0], x[1]))
|
||||
return resolved[:clip_count]
|
||||
|
||||
|
||||
def build_keyword_highlights(
|
||||
transcript_path: str,
|
||||
clip_count: int,
|
||||
min_dur: float,
|
||||
max_dur: float,
|
||||
theme: str,
|
||||
bin_sec: float = 25.0,
|
||||
viral: bool = True,
|
||||
title_max_cjk: int = 30,
|
||||
topic_break_gap_sec: float = 12.0,
|
||||
tail_pad_sec: float = 0.45,
|
||||
) -> list[dict]:
|
||||
min_dur = max(45.0, float(min_dur))
|
||||
max_dur = float(max_dur)
|
||||
if max_dur < min_dur:
|
||||
min_dur, max_dur = max_dur, min_dur
|
||||
max_dur = max(min_dur, min(max_dur, 600.0))
|
||||
|
||||
keywords = THEMES.get(theme) or THEMES["soul_party"]
|
||||
segments = parse_srt_segments(transcript_path)
|
||||
if not segments:
|
||||
return []
|
||||
total_end = float(segments[-1]["end_sec"])
|
||||
nb = int(total_end / bin_sec) + 2
|
||||
bins = [0.0] * nb
|
||||
for seg in segments:
|
||||
sc = _score_text(seg.get("text") or "", keywords)
|
||||
if sc <= 0:
|
||||
continue
|
||||
b0 = max(0, int(seg["start_sec"] / bin_sec))
|
||||
b1 = min(nb - 1, int(seg["end_sec"] / bin_sec) + 1)
|
||||
for bi in range(b0, b1 + 1):
|
||||
bins[bi] += sc
|
||||
|
||||
# 找局部峰值
|
||||
peaks: list[tuple[int, float]] = []
|
||||
for i in range(1, len(bins) - 1):
|
||||
if bins[i] < 0.8:
|
||||
continue
|
||||
if bins[i] >= bins[i - 1] and bins[i] >= bins[i + 1]:
|
||||
peaks.append((i, bins[i]))
|
||||
peaks.sort(key=lambda x: -x[1])
|
||||
|
||||
min_bins_between = int(max(45, min_dur * 0.35) / bin_sec)
|
||||
chosen: list[int] = []
|
||||
for i, _ in peaks:
|
||||
if all(abs(i - c) >= min_bins_between for c in chosen):
|
||||
chosen.append(i)
|
||||
if len(chosen) >= clip_count:
|
||||
break
|
||||
|
||||
if len(chosen) < clip_count:
|
||||
for i in range(len(bins)):
|
||||
if i not in chosen and bins[i] >= 0.5:
|
||||
if all(abs(i - c) >= min_bins_between // 2 or min_bins_between < 2 for c in chosen):
|
||||
chosen.append(i)
|
||||
if len(chosen) >= clip_count:
|
||||
break
|
||||
|
||||
chosen.sort()
|
||||
# 合并过近的峰值,减少「同一段话切三条」
|
||||
min_gap_bins = max(min_bins_between, int(120 / bin_sec))
|
||||
merged: list[int] = []
|
||||
for bi in chosen:
|
||||
if not merged or bi - merged[-1] >= min_gap_bins:
|
||||
merged.append(bi)
|
||||
chosen = merged[:clip_count]
|
||||
if not chosen:
|
||||
# 全片均匀兜底
|
||||
step = max(min_dur, (total_end - min_dur) / max(1, clip_count))
|
||||
chosen = [int((j + 0.5) * step / bin_sec) for j in range(clip_count)]
|
||||
|
||||
windows = _windows_non_overlap(
|
||||
chosen[: max(clip_count * 3, clip_count + 6)],
|
||||
clip_count,
|
||||
min_dur,
|
||||
max_dur,
|
||||
total_end,
|
||||
bin_sec,
|
||||
min_gap_sec=max(90.0, min_dur * 0.55),
|
||||
)
|
||||
|
||||
out: list[dict] = []
|
||||
# soul_enhance: COVER_HOOK_MAX_CJK = 16,标题文件名用序号前缀保证唯一
|
||||
for idx, (rs, re) in enumerate(windows):
|
||||
start_sec, end_sec = _refine_window_to_speech_bounds(
|
||||
segments,
|
||||
rs,
|
||||
re,
|
||||
min_dur,
|
||||
max_dur,
|
||||
total_end,
|
||||
topic_break_gap_sec=topic_break_gap_sec,
|
||||
tail_pad_sec=tail_pad_sec,
|
||||
)
|
||||
texts = [
|
||||
s["text"]
|
||||
for s in segments
|
||||
if s["end_sec"] >= start_sec and s["start_sec"] <= end_sec and _score_text(s["text"], keywords) > 0
|
||||
]
|
||||
if not texts:
|
||||
texts = [
|
||||
s["text"]
|
||||
for s in segments
|
||||
if s["end_sec"] >= start_sec and s["start_sec"] <= end_sec
|
||||
]
|
||||
joined = " ".join(texts)[:160] if texts else f"精彩片段{idx+1}"
|
||||
excerpt = joined[:120] + ("…" if len(joined) > 120 else "")
|
||||
|
||||
anchor = _anchor_segment(segments, start_sec, end_sec, keywords)
|
||||
anchor_raw = (anchor.get("text") or "").strip() if anchor else ""
|
||||
if not anchor_raw:
|
||||
anchor_raw = (texts[0] if texts else joined)[:80]
|
||||
|
||||
# 封面 hook 与首条字幕同源(16 汉字封顶,与 soul_enhance COVER_HOOK 一致)
|
||||
hook_full = _limit_cjk_chars(anchor_raw, 16)
|
||||
hook_17 = _limit_cjk_chars(anchor_raw, 17)
|
||||
hook_display = (
|
||||
(hook_full + "…")
|
||||
if (hook_full and hook_17 != hook_full)
|
||||
else (hook_full or f"第{idx+1}段")
|
||||
)
|
||||
|
||||
title_body = _limit_cjk_chars(anchor_raw, 12)
|
||||
span_sec = end_sec - start_sec
|
||||
|
||||
viral_hook = ""
|
||||
if viral:
|
||||
# 完整抖音向长标题(文件名/batch_clip 用);封面/成片 title-only 仍走 viral_hook 短 punch
|
||||
full_title = _compose_douyin_full_title(
|
||||
anchor_raw, joined, max_total_cjk=max(18, min(title_max_cjk, 36))
|
||||
)
|
||||
title = full_title.strip() or f"第{idx+1:02d}段 {title_body}".strip()
|
||||
viral_hook = _cover_punch_from_full_title(full_title, 16) or _compose_viral_hook(
|
||||
anchor_raw, joined, 16
|
||||
)
|
||||
else:
|
||||
title = f"第{idx+1:02d}段 {title_body}".strip() if title_body else f"第{idx+1:02d}段 话题"
|
||||
|
||||
row: dict = {
|
||||
"title": title,
|
||||
"start_time": _sec_to_hhmmss(start_sec),
|
||||
"end_time": _sec_to_hhmmss(end_sec),
|
||||
"hook_3sec": hook_display,
|
||||
"cta_ending": PARTY_CTA,
|
||||
"transcript_excerpt": excerpt,
|
||||
"reason": (
|
||||
f"关键词密度峰值 theme={theme} non_overlap viral={viral} "
|
||||
f"douyin_title=1 speech_bounds gap={topic_break_gap_sec}s tail_pad={tail_pad_sec}s"
|
||||
),
|
||||
"source_span_sec": int(span_sec),
|
||||
}
|
||||
if viral and viral_hook:
|
||||
row["viral_hook"] = viral_hook
|
||||
out.append(row)
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="关键词密度高光(无 LLM)")
|
||||
ap.add_argument("transcript", type=Path, help="transcript.srt")
|
||||
ap.add_argument("-o", "--output", type=Path, required=True)
|
||||
ap.add_argument("--clips", type=int, default=12)
|
||||
ap.add_argument(
|
||||
"--min-duration",
|
||||
type=float,
|
||||
default=60.0,
|
||||
help="单条最短秒数(默认 60=1 分钟)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--max-duration",
|
||||
type=float,
|
||||
default=300.0,
|
||||
help="单条最长秒数(默认 300=5 分钟)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--theme",
|
||||
choices=list(THEMES.keys()),
|
||||
default="mbti_business",
|
||||
help="关键词表",
|
||||
)
|
||||
ap.add_argument("--bin-sec", type=float, default=25.0, help="时间箱宽度(秒)")
|
||||
ap.add_argument(
|
||||
"--plain-hooks",
|
||||
action="store_true",
|
||||
help="关闭 viral_hook/抖音长标题,仅用语义锚点 hook(封面更贴首句字幕)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--title-max-cjk",
|
||||
type=int,
|
||||
default=30,
|
||||
help="抖音向完整标题汉字上限(默认 30,约一条短视频标题长度)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--topic-break-gap",
|
||||
type=float,
|
||||
default=12.0,
|
||||
help="相邻字幕间隔超过此秒数视为换话题,优先在上一句收束(默认 12)",
|
||||
)
|
||||
ap.add_argument(
|
||||
"--tail-pad",
|
||||
type=float,
|
||||
default=0.45,
|
||||
help="最后一条字幕后保留的片尾余量秒数(默认 0.45,避免切太紧)",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
if not args.transcript.exists():
|
||||
print(f"❌ 不存在: {args.transcript}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
data = build_keyword_highlights(
|
||||
str(args.transcript),
|
||||
args.clips,
|
||||
args.min_duration,
|
||||
args.max_duration,
|
||||
args.theme,
|
||||
bin_sec=args.bin_sec,
|
||||
viral=not args.plain_hooks,
|
||||
title_max_cjk=max(18, min(args.title_max_cjk, 40)),
|
||||
topic_break_gap_sec=max(5.0, float(args.topic_break_gap)),
|
||||
tail_pad_sec=max(0.1, min(float(args.tail_pad), 2.0)),
|
||||
)
|
||||
if not data:
|
||||
print("❌ 未生成任何片段", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
print(f"✅ 关键词高光 {len(data)} 条 → {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -67,9 +67,16 @@ FONT_APPLE_SUBTITLE = next(
|
||||
|
||||
# 视频增强参数
|
||||
SPEED_FACTOR = 1.10 # 加速10%
|
||||
SILENCE_THRESHOLD = -38 # 静音阈值(dB),略放宽以多检出停顿
|
||||
SILENCE_MIN_DURATION = 0.32 # 短于此前值,中间「断点」切得更干净
|
||||
SILENCE_TRIM_MARGIN = 0.06 # 保留段与静音接缝留白(秒),略小则多剪
|
||||
# 默认「剃空白」偏激进:略提高 noise 阈值(更接近 0)+ 更短 min_duration,多剪掉会议留白
|
||||
SILENCE_THRESHOLD = -32
|
||||
SILENCE_MIN_DURATION = 0.22
|
||||
SILENCE_TRIM_MARGIN = 0.04
|
||||
# --silence-gentle 时回退旧参数,避免个别素材被切太碎
|
||||
SILENCE_GENTLE_THRESHOLD = -38
|
||||
SILENCE_GENTLE_MIN_DURATION = 0.32
|
||||
SILENCE_GENTLE_TRIM_MARGIN = 0.06
|
||||
# 字幕轴上相邻两条间隔 ≥ 此值(秒)也视为可剪「空白」(与 silencedetect 结果并集)
|
||||
SILENCE_SUBTITLE_GAP_MIN_SEC = 0.52
|
||||
# 片尾若干秒不参与「去静音」切除,避免成片最后几秒被剪成完全无声(需保留收尾人声)
|
||||
SILENCE_TAIL_PRESERVE_SEC = 2.85
|
||||
|
||||
@@ -590,9 +597,12 @@ def _limit_cover_title_cjk(text: str, max_cjk: int = COVER_TITLE_MAX_CJK) -> str
|
||||
|
||||
|
||||
def pick_cover_hook_text(highlight_info: dict) -> str:
|
||||
"""高光成片封面:优先 3 秒钩子句(抓眼球),其次核心问句,最后才用短标题。全程简体。"""
|
||||
"""高光成片封面:优先 viral_hook(热点向刺激标题),再 hook_3sec / 问句 / 标题。全程简体。"""
|
||||
if not highlight_info:
|
||||
return ""
|
||||
v = _to_simplified((highlight_info.get("viral_hook") or "").strip())
|
||||
if v:
|
||||
return v
|
||||
h = _to_simplified((highlight_info.get("hook_3sec") or "").strip())
|
||||
if h:
|
||||
return h
|
||||
@@ -608,7 +618,7 @@ def _limit_cover_hook_display(text: str, max_cjk: int = COVER_HOOK_MAX_CJK) -> s
|
||||
|
||||
|
||||
# macOS/APFS 文件名允许的中文标点(保留刺激性标题所需的标点)
|
||||
_SAFE_CJK_PUNCT = set(",。?!;:·、…()【】「」《》~—·+")
|
||||
_SAFE_CJK_PUNCT = set(",。?!;:·、…()【】「」《》~—·+|")
|
||||
|
||||
def sanitize_filename(name: str, max_length: int = 50) -> str:
|
||||
"""成片文件名:先去杠去下划线,再保留中文、ASCII字母数字、安全标点与空格。
|
||||
@@ -714,11 +724,18 @@ EMOJI_STICKER_MAP = {
|
||||
"growth": ["1f680", "1f4c8", "1f3af"],
|
||||
"risk": ["26a0", "1f6a8"],
|
||||
"happy": ["1f973", "1f44f", "1f60e"],
|
||||
"psych": ["1f9e0", "1f4a1", "1f4af"],
|
||||
"team": ["1f44d", "1f44f", "1f389"],
|
||||
}
|
||||
|
||||
|
||||
def _detect_sticker_theme(text: str) -> str:
|
||||
t = (text or "").lower()
|
||||
u = text or ""
|
||||
t = u.lower()
|
||||
if any(k in u for k in ["MBTI", "性格", "心理", "咨询", "测试", "测评", "抑郁", "情绪"]):
|
||||
return "psych"
|
||||
if any(k in u for k in ["团队", "合伙", "椅子", "分工", "小林", "陈总", "宋总"]):
|
||||
return "team"
|
||||
if any(k in t for k in ["营收", "赚钱", "成交", "变现", "利润", "现金流", "money"]):
|
||||
return "money"
|
||||
if any(k in t for k in ["风险", "封号", "告警", "风控", "risk"]):
|
||||
@@ -748,6 +765,8 @@ def _build_sticker_events(highlight_info: dict, duration: float):
|
||||
base_count = 2 if duration >= 120 else 1
|
||||
if duration >= 180:
|
||||
base_count = 3
|
||||
if duration >= 90 and theme in ("psych", "team", "money"):
|
||||
base_count = min(5, base_count + 2)
|
||||
events = []
|
||||
random.seed(int(duration * 1000) ^ len(source_text))
|
||||
for i in range(base_count):
|
||||
@@ -828,6 +847,105 @@ def apply_sticker_overlays(video_path: str, output_path: str, highlight_info: di
|
||||
return r.returncode == 0 and os.path.exists(output_path)
|
||||
|
||||
|
||||
def _audio_enhance_filter_str(strong_clean: bool) -> str:
|
||||
"""嘈杂会议室:strong 时抬高高通、加深 FFT 降噪。"""
|
||||
if strong_clean:
|
||||
return (
|
||||
"highpass=f=200,"
|
||||
"lowpass=f=9800,"
|
||||
"afftdn=nf=-38,"
|
||||
"compand=0.02|0.02:0.05|0.05:-60/-60|-32/-16|-22/-11|0/-3:6:0:0:0.02,"
|
||||
"loudnorm=I=-16:LRA=7:TP=-1.5"
|
||||
)
|
||||
return (
|
||||
"highpass=f=120,"
|
||||
"lowpass=f=10000,"
|
||||
"afftdn=nf=-30,"
|
||||
"compand=0.02|0.02:0.05|0.05:-60/-60|-30/-15|-20/-10|0/-3:6:0:0:0.02,"
|
||||
"loudnorm=I=-16:LRA=7:TP=-1.5"
|
||||
)
|
||||
|
||||
|
||||
def apply_keyword_pin_overlays(
|
||||
video_path: str,
|
||||
output_path: str,
|
||||
highlight_info: dict,
|
||||
duration: float,
|
||||
overlay_x: int,
|
||||
strip_w: int,
|
||||
temp_dir: str,
|
||||
) -> bool:
|
||||
"""在竖条主区域内烧录 1~2 条半透明关键词条(与高光文案相关,非外链视频)。"""
|
||||
lines = []
|
||||
for key in ("hook_3sec", "title", "transcript_excerpt"):
|
||||
s = (highlight_info.get(key) or "").strip()
|
||||
s = _to_simplified(s)
|
||||
s = re.sub(r"\s+", " ", s)
|
||||
if len(s) >= 6:
|
||||
lines.append(s[:22] + ("…" if len(s) > 22 else ""))
|
||||
if len(lines) >= 2:
|
||||
break
|
||||
if not lines:
|
||||
return False
|
||||
paths = []
|
||||
for i, line in enumerate(lines[:2]):
|
||||
pin_w = min(680, max(320, strip_w + 80))
|
||||
pin_h = 52
|
||||
img = Image.new("RGBA", (pin_w, pin_h), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
draw.rounded_rectangle([0, 0, pin_w - 1, pin_h - 1], radius=10, fill=(8, 12, 20, 210))
|
||||
fp = FONT_SMILEY if os.path.exists(FONT_SMILEY) else FALLBACK_FONT
|
||||
try:
|
||||
font = ImageFont.truetype(fp, 26)
|
||||
except Exception:
|
||||
font = ImageFont.load_default()
|
||||
draw.text((16, 12), line, fill=(255, 255, 255, 255), font=font)
|
||||
p = os.path.join(temp_dir, f"kw_pin_{i}.png")
|
||||
img.save(p, "PNG")
|
||||
paths.append(p)
|
||||
if not paths:
|
||||
return False
|
||||
t0 = max(4.0, min(18.0, duration * 0.06))
|
||||
t1 = max(t0 + 2.4, min(duration - 4.0, duration * 0.42))
|
||||
pin_x = max(8, int(overlay_x + strip_w // 2 - (min(680, max(320, strip_w + 80)) // 2)))
|
||||
pin_y = max(80, int(220))
|
||||
cmd = ["ffmpeg", "-y", "-i", video_path]
|
||||
for p in paths:
|
||||
cmd += ["-i", p]
|
||||
if len(paths) == 1:
|
||||
fc = (
|
||||
f"[1:v]format=rgba,colorchannelmixer=aa=0.92[p1];"
|
||||
f"[0:v][p1]overlay=x={pin_x}:y={pin_y}:enable='between(t,{t0:.3f},{t0+2.8:.3f})'[v]"
|
||||
)
|
||||
else:
|
||||
# enable= 表达式必须在引号内闭合,输出标签 [v] 在引号外(否则 ffmpeg 会把 [v] 吃进表达式)
|
||||
fc = (
|
||||
f"[1:v]format=rgba,colorchannelmixer=aa=0.92[p1];"
|
||||
f"[2:v]format=rgba,colorchannelmixer=aa=0.92[p2];"
|
||||
f"[0:v][p1]overlay=x={pin_x}:y={pin_y}:enable='between(t,{t0:.3f},{t0+2.8:.3f})'[v1];"
|
||||
f"[v1][p2]overlay=x={pin_x}:y={pin_y+58}:enable='between(t,{t1:.3f},{t1+2.8:.3f})'[v]"
|
||||
)
|
||||
cmd += [
|
||||
"-filter_complex",
|
||||
fc,
|
||||
"-map",
|
||||
"[v]",
|
||||
"-map",
|
||||
"0:a",
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-preset",
|
||||
"fast",
|
||||
"-crf",
|
||||
"22",
|
||||
"-c:a",
|
||||
"copy",
|
||||
output_path,
|
||||
]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return r.returncode == 0 and os.path.exists(output_path)
|
||||
|
||||
|
||||
def improve_subtitle_punctuation(text: str) -> str:
|
||||
"""为字幕句子补充标点,让意思更清晰。
|
||||
|
||||
@@ -1807,6 +1925,45 @@ def kept_segments_from_silences(silences, duration, margin=0.1):
|
||||
return segments
|
||||
|
||||
|
||||
def _subtitle_gap_intervals(subtitles, duration, min_gap_sec: float) -> list[tuple[float, float]]:
|
||||
"""相邻字幕条之间的「空白段」,用于与 silencedetect 并集后一起剃掉。"""
|
||||
if not subtitles or min_gap_sec <= 0:
|
||||
return []
|
||||
dur = float(duration)
|
||||
subs = sorted(subtitles, key=lambda x: float(x.get("start", 0)))
|
||||
gaps: list[tuple[float, float]] = []
|
||||
head = float(subs[0].get("start", 0))
|
||||
if head >= min_gap_sec:
|
||||
gaps.append((0.0, head))
|
||||
for i in range(len(subs) - 1):
|
||||
a = float(subs[i].get("end", subs[i].get("start", 0)))
|
||||
b = float(subs[i + 1].get("start", 0))
|
||||
if b - a >= min_gap_sec:
|
||||
gaps.append((a, b))
|
||||
tail = float(subs[-1].get("end", 0))
|
||||
if dur - tail >= min_gap_sec:
|
||||
gaps.append((tail, dur))
|
||||
return gaps
|
||||
|
||||
|
||||
def _merge_time_intervals(
|
||||
intervals: list[tuple[float, float]],
|
||||
join_eps: float = 0.03,
|
||||
) -> list[tuple[float, float]]:
|
||||
"""合并重叠或紧挨的时间段(用于音频静音 ∪ 字幕间隙)。"""
|
||||
cleaned = [(float(s), float(e)) for s, e in intervals if e > s + 1e-6]
|
||||
if not cleaned:
|
||||
return []
|
||||
cleaned.sort(key=lambda x: x[0])
|
||||
out: list[list[float]] = [[cleaned[0][0], cleaned[0][1]]]
|
||||
for s, e in cleaned[1:]:
|
||||
if s <= out[-1][1] + join_eps:
|
||||
out[-1][1] = max(out[-1][1], e)
|
||||
else:
|
||||
out.append([s, e])
|
||||
return [(a, b) for a, b in out]
|
||||
|
||||
|
||||
def map_time_remove_silences(t, kept_segments):
|
||||
"""原片时间 t(秒)→ 去掉静音后的新时间。"""
|
||||
t = float(t)
|
||||
@@ -1939,7 +2096,10 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
|
||||
crop_vf=None, overlay_x=None, typewriter_subs=False,
|
||||
vertical_fit_full=False, trim_silence=True,
|
||||
subtitle_extra_delay=0.0, use_stickers=True,
|
||||
horizontal_center_pad=False):
|
||||
horizontal_center_pad=False,
|
||||
strong_audio_clean=False, keyword_pins=False,
|
||||
silence_noise_db=None, silence_min_duration=None, silence_trim_margin=None,
|
||||
merge_subtitle_gap_silences=True):
|
||||
"""增强单个切片。vertical=True 时输出竖条,宽由 --crop-vf 决定(原生包络常见 560~750×1080;旧 498 为两段裁或 scale)。
|
||||
vertical_fit_full:整幅 16:9 缩放入 498×1080 + 上下黑边。
|
||||
horizontal_center_pad:与竖条塑形相同链路(封面/字幕仍按竖条叠在横版上),最后输出 1920×1080,中间为裁切条、左右黑边。
|
||||
@@ -1972,6 +2132,7 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
|
||||
out_w, out_h = width, height
|
||||
vf_use = ""
|
||||
overlay_pos = "0:0"
|
||||
strip_overlay_x, strip_overlay_w = 0, width
|
||||
elif vertical:
|
||||
vf_use = (crop_vf or CROP_VF).strip()
|
||||
out_w, out_h = vertical_out_dimensions_from_vf(vf_use)
|
||||
@@ -1983,10 +2144,12 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
|
||||
if ox is None:
|
||||
ox = OVERLAY_X
|
||||
overlay_pos = f"{int(ox)}:0"
|
||||
strip_overlay_x, strip_overlay_w = int(ox), int(out_w)
|
||||
else:
|
||||
out_w, out_h = width, height
|
||||
vf_use = CROP_VF
|
||||
overlay_pos = "0:0"
|
||||
strip_overlay_x, strip_overlay_w = 0, width
|
||||
|
||||
# 1. 字幕解析(相对原切片时间轴;去静音后会整体平移时间)
|
||||
sub_images = []
|
||||
@@ -2063,10 +2226,17 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
|
||||
mode = "逐字渐显" if typewriter_subs else "随语音走动"
|
||||
print(f" ✓ 字幕解析 ({len(subtitles)}条),将烧录为{mode}字幕", flush=True)
|
||||
|
||||
# 2. 去静音:trim+concat 重编码,并 remap 字幕时间轴(此前仅检测未切除,成片仍带长停顿)
|
||||
silences = detect_silence(clip_path, SILENCE_THRESHOLD, SILENCE_MIN_DURATION)
|
||||
# 2. 去静音:silencedetect ∪ 字幕条间长间隙,trim+concat 重编码,并 remap 字幕时间轴
|
||||
thr = float(silence_noise_db) if silence_noise_db is not None else float(SILENCE_THRESHOLD)
|
||||
mind = float(silence_min_duration) if silence_min_duration is not None else float(SILENCE_MIN_DURATION)
|
||||
marg = float(silence_trim_margin) if silence_trim_margin is not None else float(SILENCE_TRIM_MARGIN)
|
||||
silences = detect_silence(clip_path, thr, mind)
|
||||
if merge_subtitle_gap_silences and subtitles:
|
||||
gaps = _subtitle_gap_intervals(subtitles, original_duration, SILENCE_SUBTITLE_GAP_MIN_SEC)
|
||||
if gaps:
|
||||
silences = _merge_time_intervals(list(silences) + gaps)
|
||||
silences = filter_silences_keep_tail_audio(silences, original_duration)
|
||||
kept = kept_segments_from_silences(silences, original_duration, margin=SILENCE_TRIM_MARGIN)
|
||||
kept = kept_segments_from_silences(silences, original_duration, margin=marg)
|
||||
removed_total = original_duration - sum(e - s for s, e in kept)
|
||||
if trim_silence and removed_total >= MIN_SILENCE_TRIM_TOTAL_SEC:
|
||||
trim_out = os.path.join(temp_dir, "trim_silence.mp4")
|
||||
@@ -2230,19 +2400,32 @@ def enhance_clip(clip_path, output_path, highlight_info, temp_dir, transcript_pa
|
||||
print(f" ✓ 表情贴片已叠加(自动主题匹配)", flush=True)
|
||||
else:
|
||||
print(f" ⊘ 表情贴片跳过", flush=True)
|
||||
|
||||
# 5.26 关键词条(PIL PNG,与高光文案一致;非外链视频)
|
||||
kw_out = os.path.join(temp_dir, "with_kw_pins.mp4")
|
||||
if (
|
||||
keyword_pins
|
||||
and vertical
|
||||
and not vertical_fit_full
|
||||
and apply_keyword_pin_overlays(
|
||||
current_video,
|
||||
kw_out,
|
||||
highlight_info,
|
||||
duration,
|
||||
strip_overlay_x,
|
||||
strip_overlay_w,
|
||||
temp_dir,
|
||||
)
|
||||
):
|
||||
current_video = kw_out
|
||||
print(f" ✓ 关键词条已烧录(竖条主区内)", flush=True)
|
||||
|
||||
# 5.3 加速10% + 音频增强 + 同步(成片必做)
|
||||
print(f" [4/5] 加速 10% + 音频清晰化…", flush=True)
|
||||
mode_audio = "强降噪会议室" if strong_audio_clean else "标准"
|
||||
print(f" [4/5] 加速 10% + 音频清晰化({mode_audio})…", flush=True)
|
||||
speed_output = os.path.join(temp_dir, 'speed.mp4')
|
||||
|
||||
# 音频处理链:高通去低频噪声 → 动态降噪 → 人声压缩增益 → 音量归一化
|
||||
audio_enhance = (
|
||||
"highpass=f=120," # 去掉 120Hz 以下的低频噪声/嗡嗡声
|
||||
"lowpass=f=10000," # 去掉 10kHz 以上的高频噪声
|
||||
"afftdn=nf=-30," # FFT 降噪(-30dBFS 噪底)
|
||||
"compand=0.02|0.02:0.05|0.05:-60/-60|-30/-15|-20/-10|0/-3:6:0:0:0.02," # 动态压缩:抬升安静部分
|
||||
"loudnorm=I=-16:LRA=7:TP=-1.5" # EBU R128 响度归一化
|
||||
)
|
||||
audio_enhance = _audio_enhance_filter_str(bool(strong_audio_clean))
|
||||
# 加速 + 音频增强 合并成一次 ffmpeg
|
||||
cmd = [
|
||||
'ffmpeg', '-y', '-i', current_video,
|
||||
@@ -2373,6 +2556,16 @@ def main():
|
||||
action="store_true",
|
||||
help="不去除静音长停顿(默认会切除 silencedetect 检出的静音并同步平移字幕时间轴)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--silence-gentle",
|
||||
action="store_true",
|
||||
help="去静音参数改温和(少剃),成片仍留白较多时用默认即可,被切太碎时再开",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-subtitle-gap-merge",
|
||||
action="store_true",
|
||||
help="不把「字幕条之间长间隙」并入剃除,仅用音频 silencedetect",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--subtitle-extra-delay",
|
||||
type=float,
|
||||
@@ -2400,6 +2593,16 @@ def main():
|
||||
action="store_true",
|
||||
help="横屏全幅成片:整幅 16:9(无左右黑边),高光/字幕/封面与竖屏 Skill 同源;不要与 --vertical / --crop-vf / --horizontal-center-pad 同用",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--strong-audio-clean",
|
||||
action="store_true",
|
||||
help="嘈杂会议室:加强高通与 FFT 降噪(仍保留片尾人声保护窗)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--keyword-pins",
|
||||
action="store_true",
|
||||
help="竖条成片时在主画面内烧录 1~2 条半透明关键词条(来自 hook/摘要,非外链视频)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
clips_dir = Path(args.clips) if args.clips else CLIPS_DIR
|
||||
@@ -2474,12 +2677,23 @@ def main():
|
||||
print("="*60)
|
||||
print(
|
||||
f"功能: 封面+字幕+加速10%+去语气词"
|
||||
+ ("+去长静音" if not getattr(args, "no_trim_silence", False) else "")
|
||||
+ (
|
||||
"+去长静音"
|
||||
+ (
|
||||
"(温和)"
|
||||
if getattr(args, "silence_gentle", False)
|
||||
else "(剃空白+字幕间隙)"
|
||||
)
|
||||
if not getattr(args, "no_trim_silence", False)
|
||||
else ""
|
||||
)
|
||||
+ ("+竖屏条(高1080宽随vf)" if vertical and not hpad else "")
|
||||
+ ("+横屏单中屏(竖条+左右黑边)" if hpad else "")
|
||||
+ ("+横屏全幅(整幅叠字幕)" if hfull else "")
|
||||
+ ("+全画面letterbox(不裁竖条)" if vertical and vfit else "")
|
||||
+ ("+逐字字幕" if typewriter else "")
|
||||
+ ("+强降噪" if getattr(args, "strong_audio_clean", False) else "")
|
||||
+ ("+关键词条" if getattr(args, "keyword_pins", False) else "")
|
||||
)
|
||||
if vertical and crop_vf_arg and not vfit:
|
||||
print(f"取景: --crop-vf {crop_vf_arg}")
|
||||
@@ -2503,6 +2717,15 @@ def main():
|
||||
total = len(clips)
|
||||
print(f"\n找到 {total} 个 mp4,highlights {len(highlights)} 条;仅处理序号 1~{len(highlights)} 的切片\n", flush=True)
|
||||
|
||||
if getattr(args, "silence_gentle", False):
|
||||
snd, smin, smar = (
|
||||
SILENCE_GENTLE_THRESHOLD,
|
||||
SILENCE_GENTLE_MIN_DURATION,
|
||||
SILENCE_GENTLE_TRIM_MARGIN,
|
||||
)
|
||||
else:
|
||||
snd = smin = smar = None
|
||||
|
||||
success_count = 0
|
||||
for i, clip_path in enumerate(clips):
|
||||
clip_num = _parse_clip_index(clip_path.name) or (i + 1)
|
||||
@@ -2513,19 +2736,30 @@ def main():
|
||||
)
|
||||
continue
|
||||
highlight_info = highlights[clip_num - 1]
|
||||
title_display = (highlight_info.get("hook_3sec") or highlight_info.get("title") or clip_path.stem)[
|
||||
:36
|
||||
]
|
||||
title_display = (
|
||||
highlight_info.get("viral_hook")
|
||||
or highlight_info.get("hook_3sec")
|
||||
or highlight_info.get("title")
|
||||
or clip_path.stem
|
||||
)[:36]
|
||||
print("=" * 60, flush=True)
|
||||
print(f"【成片进度】 {i+1}/{total} {title_display}", flush=True)
|
||||
print("=" * 60, flush=True)
|
||||
|
||||
if getattr(args, "title_only", False):
|
||||
display = pick_cover_hook_text(highlight_info) or highlight_info.get("title") or clip_path.stem
|
||||
title = _limit_cover_hook_display(
|
||||
_normalize_title_for_display(str(display)) or str(display),
|
||||
) or str(display)
|
||||
name = sanitize_filename(title) + ".mp4"
|
||||
# 文件名:优先 highlights「title」完整抖音向长标题(sanitize 72 字内);封面仍由 enhance_clip 内 pick_cover_hook(viral_hook 短句)
|
||||
long_t = (highlight_info.get("title") or "").strip()
|
||||
short_fallback = pick_cover_hook_text(highlight_info) or long_t or clip_path.stem
|
||||
fn_src = long_t if long_t else short_fallback
|
||||
fn_norm = _normalize_title_for_display(str(fn_src)) or str(fn_src)
|
||||
stem = sanitize_filename(fn_norm, max_length=72)
|
||||
if not stem or stem == "片段":
|
||||
stem = sanitize_filename(
|
||||
_normalize_title_for_display(str(short_fallback)) or str(short_fallback),
|
||||
max_length=50,
|
||||
)
|
||||
# 同场多条高光标题可能相同,必须带切片序号防覆盖
|
||||
name = f"{stem}_{clip_num:02d}.mp4"
|
||||
output_path = output_dir / name
|
||||
else:
|
||||
output_path = output_dir / clip_path.name.replace('.mp4', '_enhanced.mp4')
|
||||
@@ -2549,6 +2783,12 @@ def main():
|
||||
subtitle_extra_delay=float(getattr(args, "subtitle_extra_delay", 0.0) or 0.0),
|
||||
use_stickers=getattr(args, "stickers", True) and not getattr(args, "no_stickers", False),
|
||||
horizontal_center_pad=hpad,
|
||||
strong_audio_clean=getattr(args, "strong_audio_clean", False),
|
||||
keyword_pins=getattr(args, "keyword_pins", False),
|
||||
silence_noise_db=snd,
|
||||
silence_min_duration=smin,
|
||||
silence_trim_margin=smar,
|
||||
merge_subtitle_gap_silences=not getattr(args, "no_subtitle_gap_merge", False),
|
||||
):
|
||||
success_count += 1
|
||||
finally:
|
||||
@@ -2559,28 +2799,125 @@ def main():
|
||||
print(f"📁 输出目录: {output_dir}")
|
||||
print("="*60)
|
||||
|
||||
generate_index(highlights, output_dir)
|
||||
generate_index(
|
||||
highlights,
|
||||
output_dir,
|
||||
title_only=getattr(args, "title_only", False),
|
||||
)
|
||||
|
||||
def generate_index(highlights, output_dir):
|
||||
"""生成目录索引(标题/Hook/CTA 统一简体中文),索引写在输出目录内"""
|
||||
|
||||
def generate_index(highlights, output_dir, title_only: bool = False):
|
||||
"""生成目录索引:源时段、源窗/成片时长、标题、Hook、成片文件名。"""
|
||||
generate_index_v2(highlights, Path(output_dir), title_only=title_only)
|
||||
|
||||
|
||||
def _parse_hhmmss_to_sec(t: str) -> float | None:
|
||||
t = str(t).strip()
|
||||
if not t:
|
||||
return None
|
||||
parts = t.split(":")
|
||||
try:
|
||||
if len(parts) == 3:
|
||||
return int(parts[0]) * 3600 + int(parts[1]) * 60 + float(parts[2])
|
||||
if len(parts) == 2:
|
||||
return int(parts[0]) * 60 + float(parts[1])
|
||||
return float(parts[0])
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _ffprobe_duration_sec(path: Path) -> float | None:
|
||||
if not path.exists():
|
||||
return None
|
||||
try:
|
||||
r = subprocess.run(
|
||||
[
|
||||
"ffprobe",
|
||||
"-v",
|
||||
"error",
|
||||
"-show_entries",
|
||||
"format=duration",
|
||||
"-of",
|
||||
"default=noprint_wrappers=1:nokey=1",
|
||||
str(path),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
)
|
||||
if r.returncode != 0:
|
||||
return None
|
||||
return float(r.stdout.strip())
|
||||
except (ValueError, subprocess.TimeoutExpired, OSError):
|
||||
return None
|
||||
|
||||
|
||||
def _find_title_only_output(output_dir: Path, clip_index: int) -> Path | None:
|
||||
"""title-only 成片:*_{idx:02d}.mp4(取字典序最后一条,兼容重跑残留)。"""
|
||||
pat = re.compile(r"_(\d{2})\.mp4$")
|
||||
hits: list[Path] = []
|
||||
for p in output_dir.glob("*.mp4"):
|
||||
m = pat.search(p.name)
|
||||
if m and int(m.group(1)) == clip_index:
|
||||
hits.append(p)
|
||||
if not hits:
|
||||
return None
|
||||
return sorted(hits)[-1]
|
||||
|
||||
|
||||
def generate_index_v2(highlights, output_dir: Path, title_only: bool = False):
|
||||
"""生成目录索引:含源时段、源窗时长(秒)、成片时长(ffprobe,title-only 时尽力匹配文件)。"""
|
||||
index_path = output_dir / "目录索引.md"
|
||||
|
||||
with open(index_path, 'w', encoding='utf-8') as f:
|
||||
|
||||
with open(index_path, "w", encoding="utf-8") as f:
|
||||
f.write("# Soul派对 - 成片目录\n\n")
|
||||
f.write(
|
||||
"**优化**: 高光 Hook 封面(轻模糊底+冷色渐变+底渐隐+顶栏品牌色)+逐字字幕+去长静音+片尾 CTA+加速10%(竖屏宽随 crop-vf)\n\n"
|
||||
"**优化**: 高光 Hook 封面 + 逐字字幕 + 去长静音 + 片尾 CTA;"
|
||||
"源窗时长为 highlights 起止差,成片时长为 ffprobe(trim 后与源窗可能不同)。\n\n"
|
||||
)
|
||||
f.write("## 切片列表\n\n")
|
||||
f.write("| 序号 | 标题 | Hook | CTA |\n")
|
||||
f.write("|------|------|------|-----|\n")
|
||||
|
||||
f.write(
|
||||
"| 序号 | 源时段 | 源窗秒 | 成片秒 | 标题 | Hook | 成片文件 |\n"
|
||||
"|------|--------|--------|--------|------|------|----------|\n"
|
||||
)
|
||||
|
||||
for i, clip in enumerate(highlights, 1):
|
||||
title = _to_simplified(clip.get("title", f"clip_{i}"))
|
||||
hook = _to_simplified(clip.get("hook_3sec", ""))
|
||||
cta = _to_simplified(clip.get("cta_ending", ""))
|
||||
f.write(f"| {i} | {title} | {hook} | {cta} |\n")
|
||||
|
||||
st = clip.get("start_time") or clip.get("start") or ""
|
||||
et = clip.get("end_time") or clip.get("end") or ""
|
||||
span = None
|
||||
ss = _parse_hhmmss_to_sec(st) if st else None
|
||||
es = _parse_hhmmss_to_sec(et) if et else None
|
||||
if ss is not None and es is not None:
|
||||
span = max(0.0, es - ss)
|
||||
span_s = f"{span:.0f}" if span is not None else "—"
|
||||
|
||||
out_name = "—"
|
||||
final_d = "—"
|
||||
if title_only:
|
||||
outp = _find_title_only_output(output_dir, i)
|
||||
if outp:
|
||||
out_name = outp.name
|
||||
fd = _ffprobe_duration_sec(outp)
|
||||
if fd is not None:
|
||||
final_d = f"{fd:.1f}"
|
||||
else:
|
||||
# 非 title-only:沿用原名 _enhanced
|
||||
stem_guess = sorted(output_dir.glob(f"*{i:02d}*.mp4"))
|
||||
for p in stem_guess:
|
||||
if "_enhanced" in p.name or p.suffix == ".mp4":
|
||||
fd = _ffprobe_duration_sec(p)
|
||||
if fd is not None:
|
||||
out_name = p.name
|
||||
final_d = f"{fd:.1f}"
|
||||
break
|
||||
|
||||
f.write(
|
||||
f"| {i} | {st}→{et} | {span_s} | {final_d} | {title} | {hook} | {out_name} |\n"
|
||||
)
|
||||
|
||||
print(f"\n📋 目录索引: {index_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -6,11 +6,11 @@ description: >
|
||||
triggers: 视频号发布、发布到视频号、视频号登录、视频号上传、微信视频号
|
||||
owner: 木叶
|
||||
group: 木
|
||||
version: "3.2"
|
||||
updated: "2026-03-24"
|
||||
version: "3.3"
|
||||
updated: "2026-03-26"
|
||||
---
|
||||
|
||||
# 视频号发布 Skill(v3.2)
|
||||
# 视频号发布 Skill(v3.3)
|
||||
|
||||
> **核心能力**:发布链路纯 **httpx**;**登录**阶段用 Playwright(默认持久化 Chromium,减少重复扫码)。
|
||||
> **实测**:120 场 12 条切片全部 API 直发成功,单条 5~9 秒。
|
||||
@@ -20,6 +20,17 @@ updated: "2026-03-24"
|
||||
|
||||
---
|
||||
|
||||
## 〇、卡若默认分发范式(以后统一按此执行)
|
||||
|
||||
1. **登录(默认无界面)**:`CHANNELS_SILENT_QR=1 python3 channels_login.py --silent-qr`,用微信扫 **`/tmp/channels_qr.png`**。若需补全 **`finder_raw`**(纯 API 必用),登录成功后进助手 **「创建/发表」** 页一次即可。调试或静默失败时才用 `python3 channels_login.py --playwright-only`。
|
||||
2. **发稿主路径(API + CLI)**:`python3 channels_api_publish.py --video-dir "<含 mp4 的目录>"`(全 **httpx**,无网页控件)。
|
||||
3. **回补路径(仍是无头 CLI)**:当 localStorage **缺 `finder_raw`** 等导致 API 无法前置时,`channels_api_publish.py` 以 **exit 2** 退出;此时用 `python3 channels_web_cli.py publish-dir …`(Playwright 无头 + `post_create` 注入定时)。日常不必手抄:见下条。
|
||||
4. **一键编排(推荐入口)**:`脚本/publish_auto.sh` = **先 API**,若 **exit 2** 则自动执行 **`publish-dir`**(默认间隔写在脚本内)。仅想跑 API、不要回补:`CHANNELS_NO_WEB_FALLBACK=1 ./publish_auto.sh --video-dir "…"`。
|
||||
5. **静默等登录再发**:`脚本/login_wait_and_publish.sh` = 轮询 `channels_web_cli check` 通过后执行 **`publish_auto.sh`**(同上 API→CLI)。
|
||||
6. **多平台整表**:`多平台分发/脚本/distribute_all.py` 含「视频号」时仍调用 **`channels_api_publish`** 的逐条接口;**单刷视频号目录**优先用本目录 `publish_auto.sh`,与 distribute 互补。
|
||||
|
||||
---
|
||||
|
||||
## 一、纯 API 完整流程(5 步)
|
||||
|
||||
```
|
||||
@@ -88,30 +99,38 @@ updated: "2026-03-24"
|
||||
|
||||
---
|
||||
|
||||
## 三、一键命令
|
||||
## 三、一键命令(与「〇」一致;此处为可复制命令)
|
||||
|
||||
**优先(纯接口、无网页控件)**:`channels_api_publish.py` — 全 **httpx**,走 `helper_upload_params` → DFS 分片 → `post_clip_video` → `post_create`。Cookie 仍须由 `channels_login.py` 写入 `channels_storage_state.json`(**localStorage 须含 `finder_raw`**,否则 `post_create` 会 300002)。
|
||||
**日常一条(API → 必要时自动 web_cli)**:
|
||||
|
||||
```bash
|
||||
cd /Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频号发布/脚本
|
||||
|
||||
# 1. 首次或 Cookie 过期:微信扫码登录(建议进一次发表页以注入 rawKeyBuff)
|
||||
python3 channels_login.py --playwright-only
|
||||
# 0. 过期时:默认无界面登录(扫 /tmp/channels_qr.png)
|
||||
CHANNELS_SILENT_QR=1 python3 channels_login.py --silent-qr
|
||||
|
||||
# 2. 纯 API 批量发(推荐)
|
||||
python3 channels_api_publish.py --video-dir "/path/to/成片或切片目录"
|
||||
# 或环境变量:CHANNELS_VIDEO_DIR=/path/to/dir python3 channels_api_publish.py
|
||||
# 试跑前 2 条:python3 channels_api_publish.py --video-dir "..." --limit 2
|
||||
# 1. 编排发布(先 channels_api_publish.py;若 exit 2 缺 finder_raw 则自动 publish-dir)
|
||||
bash publish_auto.sh --video-dir "/path/to/成片或切片目录"
|
||||
# 仅 API:CHANNELS_NO_WEB_FALLBACK=1 bash publish_auto.sh --video-dir "..."
|
||||
# 试跑:bash publish_auto.sh --video-dir "..." --limit 2
|
||||
```
|
||||
|
||||
**备选(Playwright 点页面、F12 注入定时)**:需要页面控件或接口失败再排错时用:
|
||||
**只跑纯 API(排错/CI)**:须 **localStorage 含 `finder_raw`**,否则 **exit 2**(见脚本头注释)。
|
||||
|
||||
```bash
|
||||
python3 channels_api_publish.py --video-dir "/path/to/成片或切片目录"
|
||||
# CHANNELS_VIDEO_DIR=/path/to/dir python3 channels_api_publish.py
|
||||
```
|
||||
|
||||
**只跑 web CLI(回补或强制页面链路)**:
|
||||
|
||||
```bash
|
||||
python3 channels_web_cli.py publish-dir \
|
||||
--video-dir "<视频目录>" \
|
||||
--min-gap 10 --max-gap 25 \
|
||||
--start-after-min 5 --interval-min 15 \
|
||||
--skip-list-verify --max-attempts 5
|
||||
--max-attempts 5
|
||||
# 列表核验易误判时可加:--skip-list-verify
|
||||
```
|
||||
|
||||
---
|
||||
@@ -134,9 +153,11 @@ python3 channels_web_cli.py publish-dir \
|
||||
| `credentials/README.md` | **开放平台 AppID/AppSecret** 存放约定(`.env.open_platform`,勿提交) |
|
||||
| `credentials/open_platform.env.example` | 环境变量模板 |
|
||||
| `脚本/channels_open_fetch.py` | **开放平台**:拉账号/直播记录/预约/罗盘 GMV(无单条短视频播放接口) |
|
||||
| `脚本/channels_api_publish.py` | **主脚本**:纯 API 视频上传+发布 (v5) |
|
||||
| `脚本/channels_api_publish.py` | **主脚本**:纯 API 视频上传+发布;缺 `finder_raw` 时 **exit 2** |
|
||||
| `脚本/publish_auto.sh` | **默认编排**:API 优先,`exit 2` 自动 `publish-dir` |
|
||||
| `脚本/login_wait_and_publish.sh` | 静默扫码 → check 通过 → `publish_auto.sh` |
|
||||
| `脚本/channels_publish.py` | 旧版 Playwright 发布(备用) |
|
||||
| `脚本/channels_login.py` | Playwright 微信扫码登录 |
|
||||
| `脚本/channels_login.py` | Playwright 微信扫码登录(默认推荐 `--silent-qr`) |
|
||||
| `脚本/channels_storage_state.json` | Cookie + localStorage 存储 |
|
||||
| `脚本/channels_task_id.txt` | videoClipTaskId 存储 |
|
||||
|
||||
@@ -167,13 +188,14 @@ python3 channels_web_cli.py publish-dir \
|
||||
|
||||
---
|
||||
|
||||
## 七、当前默认执行规范(2026-03-24 更新)
|
||||
## 七、当前默认执行规范(2026-03-26 更新)
|
||||
|
||||
- **强制无界面**:`channels_web_cli.py` 默认强制 `headless=True`,即使传 `--show` 也忽略(仅打印提示)。
|
||||
- **定时来源唯一**:计划发布时间只走「计划发布控件 + post_create 注入」,**不再写入描述文本**。
|
||||
- **间隔策略**:默认 `--min-gap 10 --max-gap 25`,即每条发布定时间隔在 **10~25 分钟**。
|
||||
- **真实提交间隔**:每条视频在排期时间前 2 分钟才开始上传提交(`cmd_publish_dir` 内 `asyncio.sleep` 等到目标时刻),避免密集提交触发平台"同质化内容"检测。
|
||||
- **发布判定**:`post_create/post_publish` 命中 + `errCode=0` 作为成功主判据(可选列表核验)。
|
||||
- **总默认**:见 **「〇、卡若默认分发范式」** — 静默登录 → **`channels_api_publish`(CLI)** → 仅当 **exit 2** 时用 **`channels_web_cli publish-dir`(CLI)**;`publish_auto.sh` 已封装。
|
||||
- **web_cli 子规范(回补路径)**:`channels_web_cli.py` 默认强制 `headless=True`,即使传 `--show` 也忽略(仅打印提示)。
|
||||
- **定时来源唯一(web 路径)**:计划发布时间只走「计划发布控件 + post_create 注入」,**不再写入描述文本**。
|
||||
- **间隔策略(publish_auto 回补段)**:`--min-gap 10 --max-gap 25`,即每条定时间隔在 **10~25 分钟**;`--start-after-min 5 --interval-min 15` 与脚本内排期一致。
|
||||
- **真实提交间隔**:`cmd_publish_dir` 内可在排期前再上传(见脚本注释),避免密集触发同质化检测。
|
||||
- **发布判定**:`post_create` 命中 + `errCode=0` 为主;可选 `post_list` 核验。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ CLI:
|
||||
CHANNELS_VIDEO_DIR=/path/to/dir python3 channels_api_publish.py
|
||||
python3 channels_api_publish.py --video-dir ... --limit 3 # 只发前 3 条待发布
|
||||
|
||||
退出码(供 publish_auto.sh 编排):
|
||||
0 — 全部成功或「待发布为 0 / 全部已发布」
|
||||
1 — 目录无效、会话无效、或发布过程中失败(含部分失败)
|
||||
2 — 纯 API 前置不满足(典型:localStorage 缺 finder_raw),可回退 channels_web_cli publish-dir
|
||||
|
||||
v8 修复 (2026-03-13):
|
||||
- 添加 post_clip_video 转码步骤(浏览器必需的中间步骤)
|
||||
- URL 改写: wxapp.tc.qq.com → finder.video.qq.com(与浏览器一致)
|
||||
@@ -950,11 +955,14 @@ async def main():
|
||||
if not finder_raw_main:
|
||||
print(
|
||||
"[!] 纯 API 发表需要 localStorage 里的 finder_raw(rawKeyBuff),当前缺失 → post_create 会 300002。\n"
|
||||
" 请执行: python3 channels_login.py --playwright-only\n"
|
||||
" 登录后进入一次「创建/发表」页,等终端出现 Cookie 已保存后再跑本脚本。\n",
|
||||
" 可任选其一:\n"
|
||||
" A) 无界面补登:CHANNELS_SILENT_QR=1 python3 channels_login.py --silent-qr(扫 /tmp/channels_qr.png),"
|
||||
"再进助手「创建/发表」页一次写入 raw。\n"
|
||||
" B) 编排回补:同目录 ./publish_auto.sh --video-dir \"…\"(先 API,本情况 exit 2 后自动 channels_web_cli publish-dir)。\n"
|
||||
" C) 调试有头:python3 channels_login.py --playwright-only 并进发表页。\n",
|
||||
flush=True,
|
||||
)
|
||||
return 1
|
||||
return 2
|
||||
print(f" finder_raw: OK({len(finder_raw_main)} 字符)", flush=True)
|
||||
|
||||
videos = sorted(video_dir.glob("*.mp4"))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"sessionid": "BgAACuej0XO0tWGoSXf6YSEg2KiGaAtGkyr52JZOPsMe6q13eTlWwPgczkkyhzKQDaLGEFfNV%2BysF1bqhgR03iRrWB7o3Lxep6efz8EZ8yM%3D",
|
||||
"wxuin": "2604008894",
|
||||
"cookie_str": "sessionid=BgAACuej0XO0tWGoSXf6YSEg2KiGaAtGkyr52JZOPsMe6q13eTlWwPgczkkyhzKQDaLGEFfNV%2BysF1bqhgR03iRrWB7o3Lxep6efz8EZ8yM%3D; wxuin=2604008894",
|
||||
"sessionid": "BgAAvxwCNDxRKH7MSNkZxTv41j6n4M%2BH5xmkxVRkA68Z%2FyLYqu61xG3fhLT92QLmHD3Ihykpu8kL5aux7QLL99PFcO5P6GBz5N8V0x2qSTA%3D",
|
||||
"wxuin": "1335138025",
|
||||
"cookie_str": "sessionid=BgAAvxwCNDxRKH7MSNkZxTv41j6n4M%2BH5xmkxVRkA68Z%2FyLYqu61xG3fhLT92QLmHD3Ihykpu8kL5aux7QLL99PFcO5P6GBz5N8V0x2qSTA%3D; wxuin=1335138025",
|
||||
"finder_raw": "",
|
||||
"finder_username": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder",
|
||||
"finder_uin": "",
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
# 静默出码 → 轮询 check 直到 API 正常 → 全自动 publish-dir(参数透传)
|
||||
# 用法: ./login_wait_and_publish.sh --video-dir "/path/成片" --no-dedup [--gap-sec 12]
|
||||
# 静默出码 → 轮询 check 直到 API 正常 → 默认走 publish_auto.sh(API 优先,exit 2 再 web_cli)
|
||||
# 用法: ./login_wait_and_publish.sh --video-dir "/path/成片" [--limit 2]
|
||||
set -u
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$DIR" || exit 1
|
||||
|
||||
run_publish() {
|
||||
exec bash "$DIR/publish_auto.sh" "$@"
|
||||
}
|
||||
|
||||
if python3 channels_web_cli.py check; then
|
||||
echo "[✓] 会话已可用,直接发布"
|
||||
exec python3 channels_web_cli.py publish-dir "$@"
|
||||
echo "[✓] 会话已可用,执行 publish_auto.sh(API→CLI 回补)"
|
||||
run_publish "$@"
|
||||
fi
|
||||
|
||||
echo "[i] API 不可用,后台启动静默登录(二维码 /tmp/channels_qr.png),请用微信扫码…"
|
||||
@@ -24,7 +28,7 @@ for i in $(seq 1 24); do
|
||||
echo "[✓] 会话已可用(轮询第 ${i} 次)"
|
||||
cleanup
|
||||
trap - EXIT
|
||||
exec python3 channels_web_cli.py publish-dir "$@"
|
||||
run_publish "$@"
|
||||
fi
|
||||
echo "[i] 仍等待扫码… ${i}/24(每 15s 检测)"
|
||||
done
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# 全自动上传:先 API 预检,通过后再执行 publish-dir(参数原样透传)
|
||||
set -euo pipefail
|
||||
# 卡若默认视频号分发:① channels_api_publish(纯 httpx CLI)→ 若 exit 2(缺 finder_raw 等 API 前置)则 ② channels_web_cli publish-dir(无头 Playwright CLI)
|
||||
# 用法(与 API 一致,透传参数):
|
||||
# ./publish_auto.sh --video-dir "/path/to/mp4目录"
|
||||
# CHANNELS_VIDEO_DIR=/path/to/dir ./publish_auto.sh
|
||||
# 关闭回补(只跑 API): CHANNELS_NO_WEB_FALLBACK=1 ./publish_auto.sh --video-dir "..."
|
||||
set -uo pipefail
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$DIR"
|
||||
if ! python3 channels_web_cli.py check; then
|
||||
echo ""
|
||||
echo "[!] 请先登录视频号后再运行本脚本,例如:"
|
||||
echo " CHANNELS_SILENT_QR=1 python3 channels_login.py"
|
||||
echo " 打开 /tmp/channels_qr.png 用微信扫码"
|
||||
exit 1
|
||||
|
||||
WEB_EXTRA=(--min-gap 10 --max-gap 25 --start-after-min 5 --interval-min 15 --max-attempts 5)
|
||||
|
||||
if [ "${CHANNELS_NO_WEB_FALLBACK:-}" = "1" ]; then
|
||||
exec python3 channels_api_publish.py "$@"
|
||||
fi
|
||||
exec python3 channels_web_cli.py publish-dir "$@"
|
||||
|
||||
set +e
|
||||
python3 channels_api_publish.py "$@"
|
||||
ec=$?
|
||||
set -e
|
||||
|
||||
if [ "$ec" -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$ec" -eq 2 ]; then
|
||||
echo "" >&2
|
||||
echo "[i] API 前置不满足(exit 2),回退 channels_web_cli publish-dir …" >&2
|
||||
exec python3 channels_web_cli.py publish-dir "${WEB_EXTRA[@]}" "$@"
|
||||
fi
|
||||
|
||||
exit "$ec"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: 全栈开发
|
||||
description: 卡若AI 全栈开发(火炬)— 知己及类似项目经验 + 官网/全站开发与「开发文档 1~10」标准流程;吸收 Superpowers 实施计划粒度、TDD 推荐、两阶段评审与分支收尾。含分销、RAG、向量化;**§1.10 埋点与点击锚点(用户行为统计)全站强制**,Soul 项目为参考实现。官网/全站类任务按 1~10 调研→计划→执行→评审→复盘。**以后开发新网站一律用本 Skill 做调研与执行。** 使用手册按卡若AI 使用手册结构生成,见开发模板 9、手册。
|
||||
triggers: 全栈开发/知己项目/分销/存客宝/RAG/向量化/Next.js/知识库/卡若AI官网/官网开发/全站开发/开发文档/1~10/开发模板/官网全站/v0前端/v0生成/毛玻璃/前端规格/神射手/毛狐狸/前端标准/实施计划/两阶段评审/橙色锁/配色/API调用/使用手册/埋点/点击统计/用户行为/行为统计/数据统计/点击锚点/锚点/trackClick
|
||||
description: 卡若AI 全栈开发(火炬)— 知己及类似项目经验 + 官网/全站开发与「开发文档 1~10」标准流程;吸收 Superpowers 实施计划粒度、TDD 推荐、两阶段评审与分支收尾。含分销、RAG、向量化;**§1.10 埋点与点击锚点全站强制**;**§1.11 仅索引**,获客/深链路详规见 **SKILL_REGISTRY F23~F27**。官网/全站类任务按 1~10 调研→计划→执行→评审→复盘。**以后开发新网站一律用本 Skill 做调研与执行。** 使用手册按卡若AI 使用手册结构生成,见开发模板 9、手册。
|
||||
triggers: 全栈开发/知己项目/分销/存客宝/RAG/向量化/Next.js/知识库/卡若AI官网/官网开发/全站开发/开发文档/1~10/开发模板/官网全站/v0前端/v0生成/毛玻璃/前端规格/神射手/毛狐狸/前端标准/实施计划/两阶段评审/橙色锁/配色/API调用/使用手册/埋点/点击统计/用户行为/行为统计/数据统计/点击锚点/锚点/trackClick/小程序跳转/navigateToMiniProgram/链接标签/linkTag/30天绑定/推广绑定/超级个体/获客编排/留资/ckb/lead
|
||||
owner: 火炬
|
||||
group: 火
|
||||
version: "2.7"
|
||||
version: "2.9"
|
||||
updated: "2026-03-26"
|
||||
---
|
||||
|
||||
@@ -129,7 +129,7 @@ updated: "2026-03-26"
|
||||
|
||||
**任何新功能、新页面、新按钮、新 Tab/标签上线时,必须同步接入埋点统计。** 与功能代码同等重要;**没有埋点 = 没有可核对的数据看板字段**。
|
||||
|
||||
> **口语对齐**:语音里「卡罗拉 / 卡路里」等若语境是**产品数据、点击量、统计**,一律按本节的 **点击锚点 + 行为统计** 理解(与「回廊洗字 / 语音转写纠错」里专名误听无关)。
|
||||
> **口语对齐**:语音里若把 **点击量 / 产品数据统计** 误听成其它近音词,而语境实为**产品数据、点击量、统计**,一律按本节的 **点击锚点 + 行为统计** 理解;与专名 **卡若AI** 及「回廊洗字」纠错分流。
|
||||
|
||||
#### 为什么强制
|
||||
|
||||
@@ -205,6 +205,10 @@ Web 端同理,封装为 `trackClick(module, action, target, extra)`,通过 f
|
||||
- [ ] 管理端或运营看板**按 module / 时间段**能展示,且**每个业务关心的标签**在图上都有对应聚合维度
|
||||
- [ ] `10、项目管理` 中登记表或计划已更新
|
||||
|
||||
#### 与 Soul / 小程序获客深链路(交叉引用)
|
||||
|
||||
本节只管 **通用** `trackClick`(`module` / `action` / `target`)与看板聚合。**跨小程序 `#linkTag`、推广 30 日绑定、分佣提现、超级个体点击/留资口径、存客宝 BFF 与 `ckb_lead_records`** 等已拆至 **SKILL_REGISTRY「火组」F23~F27**(`04_卡火(火)/火炬_全栈消息/` 下五份专用 `SKILL.md`);实施时 **先读对应 F**,再回永平代码改实现,**勿与 §1.10 混口径**(例:埋点点击量 ≠ 留资去重人数)。
|
||||
|
||||
#### 管理后台展示标准
|
||||
|
||||
数据概览或运营页须包含与锚点一致的统计能力,例如「分类 / 模块点击」面板:
|
||||
@@ -217,6 +221,22 @@ Web 端同理,封装为 `trackClick(module, action, target, extra)`,通过 f
|
||||
|
||||
Soul 创业实验项目首次落地全链路:小程序多页 + 管理后台统计 + 后端聚合。索引与补充说明见 `运营中枢/参考资料/项目经验库_知己与类似项目.md`(§七)。
|
||||
|
||||
### 1.11 获客与深链路编排(索引 · 详规见 F23~F27)
|
||||
|
||||
**与 §1.10 的边界**:§1.10 负责 **通用行为埋点**(`module` / `action` / `target` → track 表 → 看板聚合)。**跳转分流、推广时间窗、分佣提现、超级个体指标、留资 BFF** 与 §1.10 **并存但口径不可混用**(例:埋点点击量 ≠ 留资去重人数)。**§1.10 末**已增加 Soul/小程序获客交叉引用。
|
||||
|
||||
**参考实现仓库**(路径随本机):`…/开发/3、自营项目/一场soul的创业实验-永平/`。
|
||||
|
||||
| 编号 | 主题 | SKILL 路径(均在卡若AI 仓库 `04_卡火(火)/火炬_全栈消息/` 下) |
|
||||
|:---|:---|:---|
|
||||
| **F23** | 小程序链接标签与跨小程序跳转 | `小程序链接标签与跨小程序跳转/SKILL.md` |
|
||||
| **F24** | 推广邀请与三十日绑定 | `推广邀请与三十日绑定/SKILL.md` |
|
||||
| **F25** | 分销佣金与提现编排 | `分销佣金与提现编排/SKILL.md` |
|
||||
| **F26** | 超级个体点击与获客统计 | `超级个体点击与获客统计/SKILL.md` |
|
||||
| **F27** | 存客宝 BFF 与留资队列 | `存客宝BFF与留资队列/SKILL.md` |
|
||||
|
||||
**本节不写**:管理端路由总表、书籍/章节 CMS;存客宝开放 API 细节 → **G15**。
|
||||
|
||||
---
|
||||
|
||||
## 二、项目经验库(知己类,必读)
|
||||
@@ -283,6 +303,7 @@ scripts/
|
||||
| **神射手 开发文档 4、前端** | 神射手项目内前端规范、核心组件代码、截图索引 |
|
||||
| **Superpowers与全栈开发对比与优化建议** | `运营中枢/参考资料/Superpowers与全栈开发对比与优化建议.md` — 计划粒度、TDD、两阶段评审、分支收尾等优化方向 |
|
||||
| **埋点统计与点击锚点(Soul 沉淀)** | 三层架构 + module/action/target 锚点约定 + `10、项目管理` 登记表;Soul 参考 `一场soul的创业实验-永平`;见本 Skill **§1.10**(2026-03-22 扩充) |
|
||||
| **跨小程序跳转与获客编排(可复用)** | Soul 参考 `一场soul的创业实验-永平`;**详规见火组 F23~F27**(火炬 `火炬_全栈消息/` 五份专用 SKILL);本 Skill **§1.11 仅索引**(2026-03-26) |
|
||||
| **火炬「项目开发占卜术」Skill(F01c · 演门测机)** | **奇门 Q门 3.0** 八门健康度;用户**点名起盘/占卜**时按该 Skill **单独输出**,附在标准复盘 v5.0 五块之后 |
|
||||
|
||||
---
|
||||
|
||||
90
04_卡火(火)/火炬_全栈消息/分销佣金与提现编排/SKILL.md
Normal file
90
04_卡火(火)/火炬_全栈消息/分销佣金与提现编排/SKILL.md
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: 分销佣金与提现编排
|
||||
description: 订单支付成功写佣金、inviter 校验、管理端提现审核、自动打款开关 enableAutoWithdraw、余额与流水;可复用到任意「推广员+订单」分佣系统。
|
||||
triggers: 分销、佣金、分佣、提现、withdraw、推广员佣金、enableAutoWithdraw、审核提现、打款、分销订单
|
||||
owner: 火炬
|
||||
group: 火
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
---
|
||||
|
||||
# 分销佣金与提现编排
|
||||
|
||||
> **参考实现**:永平 `soul-api/internal/handler/withdraw.go`、`admin_withdrawals.go`、订单支付回调中与 `inviter_id` / 佣金写入相关逻辑;配置键与 **推广** 共用 `referral_config` 部分字段。
|
||||
> **前置**:推广员归属见 **《推广邀请与三十日绑定》**(F24)。
|
||||
|
||||
## 一、业务目标
|
||||
|
||||
- 订单 **支付成功** 且存在有效 `inviter_id` 时,按规则计入 **推广员可提现余额**(或冻结态,视产品)。
|
||||
- 推广员发起提现 → 管理端 **待审核 → 已通过/已拒绝**;可选 **自动打款**(`enableAutoWithdraw`)。
|
||||
|
||||
## 二、旅程(推广员侧)
|
||||
|
||||
1. 小程序「我的」→ 佣金/余额入口(若有)。
|
||||
2. 查看可提现金额、流水列表。
|
||||
3. 提交提现申请(金额、账户信息,字段以实际表为准)。
|
||||
4. 轮询或消息通知审核结果。
|
||||
|
||||
## 三、旅程(管理端)
|
||||
|
||||
1. 提现列表:筛选状态、时间、用户。
|
||||
2. 单条 **通过**:扣减冻结/余额,标记已打款或进入打款队列。
|
||||
3. **拒绝**:解冻、写拒绝原因。
|
||||
4. 若 `enableAutoWithdraw`:支付成功或审核通过后走自动打款适配器(微信企业付款/第三方)。
|
||||
|
||||
## 四、API 概要
|
||||
|
||||
| 区域 | 路径示例 | 说明 |
|
||||
|:---|:---|:---|
|
||||
| 小程序 | `GET /api/miniprogram/withdraw/balance` | 余额与汇总 |
|
||||
| 小程序 | `POST /api/miniprogram/withdraw/apply` | 申请提现 |
|
||||
| 小程序 | `GET /api/miniprogram/withdraw/records` | 流水 |
|
||||
| 管理端 | `GET /api/admin/withdrawals` | 列表 |
|
||||
| 管理端 | `POST /api/admin/withdrawals/:id/approve` | 通过 |
|
||||
| 管理端 | `POST /api/admin/withdrawals/:id/reject` | 拒绝 |
|
||||
|
||||
(具体路径以仓库 `route` 为准,迁移时整组替换前缀。)
|
||||
|
||||
## 五、配置键(`referral_config` 中与分佣相关)
|
||||
|
||||
| 键 | 含义 |
|
||||
|:---|:---|
|
||||
| `commissionRate` | 佣金比例或分档规则引用 |
|
||||
| `enableAutoWithdraw` | 是否自动打款 |
|
||||
| `minWithdrawAmount` | 最低提现额 |
|
||||
| `withdrawFeeRate` | 手续费(若有) |
|
||||
|
||||
**与 F24 的键**(`enabled`、`bindWindowDays` 等)同存一个 JSON 时,**版本迁移**要小心合并策略。
|
||||
|
||||
## 六、订单入账要点
|
||||
|
||||
- **触发点**:仅 **支付成功回调**(或等价确认事件),禁止下单即入账。
|
||||
- **inviter_id**:从 **ReferralBinding** 或订单快照读取;若订单已存 `inviter_id` 以订单为准防后续解绑纠纷。
|
||||
- **重复回调**:佣金写入须 **幂等**(`order_id` 唯一约束)。
|
||||
|
||||
## 七、Gotchas(≥10)
|
||||
|
||||
1. **解绑后旧订单**:已发生佣金的订单不应回滚,除非法务要求(需产品决策)。
|
||||
2. **部分退款**:是否冲减佣金、如何冲减需单独规则。
|
||||
3. **自动打款失败**:须有 **重试队列** 与 **人工兜底** 状态。
|
||||
4. **余额不足提现**:服务端二次校验,禁止前端算准即可信。
|
||||
5. **并发提现**:同一用户同时多笔 apply → 行锁或事务串行化。
|
||||
6. **审核通过重复点击**:`approve` 接口幂等。
|
||||
7. **黑名单用户**:仍可提现还是冻结,与 F24 黑名单联动。
|
||||
8. **税率/发票**:若涉及,字段与审核流单独扩展,勿硬编码在 handler。
|
||||
9. **测试环境打款**:切 sandbox key,禁止对真实 openid 打款。
|
||||
10. **日志**:打款凭证号、失败原因落库,不全量打用户银行卡号。
|
||||
11. **enableAutoWithdraw 与人工审核**:并存时定义优先级(先自动再人工 / 仅自动等)。
|
||||
|
||||
## 八、验收清单
|
||||
|
||||
- [ ] 支付成功幂等入账
|
||||
- [ ] 无 inviter 订单不产生佣金
|
||||
- [ ] 提现申请、审核、拒绝全链路
|
||||
- [ ] 自动打款开关切换后行为符合配置
|
||||
- [ ] 关键金额字段服务端校验
|
||||
|
||||
## 九、互指
|
||||
|
||||
- **F24** 推广绑定与窗口
|
||||
- **F26** 超级个体与「链接人」统计 **不等同** 于分佣,勿混表
|
||||
@@ -1,9 +1,15 @@
|
||||
---
|
||||
name: karuo-recap-format
|
||||
name: 卡若复盘格式
|
||||
description: 卡若AI 对话收尾复盘格式 v5.0(🎯 单行一句 ≤50 字、分发达成率、禁复述与标准☯)。触发:复盘格式、卡若复盘、达成率怎么写、视频号分发复盘。
|
||||
triggers: 复盘格式、卡若复盘、达成率怎么写、复盘 v5、视频号分发复盘、负达成率
|
||||
owner: 火炬
|
||||
group: 火
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
legacy_name: karuo-recap-format(原 `.cursor/skills/` 入口,已迁入本路径)
|
||||
---
|
||||
|
||||
# 卡若复盘格式(Cursor Skill · v5.0)
|
||||
# 卡若复盘格式(F01d · v5.0)
|
||||
|
||||
**真源**:`运营中枢/参考资料/卡若复盘格式_固定规则.md`(须与之一致)。
|
||||
|
||||
102
04_卡火(火)/火炬_全栈消息/存客宝BFF与留资队列/SKILL.md
Normal file
102
04_卡火(火)/火炬_全栈消息/存客宝BFF与留资队列/SKILL.md
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: 存客宝BFF与留资队列
|
||||
description: 小程序只打自家 BFF;POST /api/miniprogram/ckb/lead;ckb_lead_records 队列表、push_status 与重试 cron;getCkbLeadApiKey 优先级;与 persons/target_person_id、source 归因;开放 API 细节归 G15。
|
||||
triggers: 存客宝留资、ckb/lead、submitCkbLead、soulBridge、留资队列、ckb_lead_records、retry-ckb-leads、push_status、链接卡若、article_mention、index_lead
|
||||
owner: 火炬
|
||||
group: 火
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
---
|
||||
|
||||
# 存客宝BFF与留资队列
|
||||
|
||||
> **参考实现**:永平 `miniprogram/utils/soulBridge.js`(`submitCkbLead`)、`soul-api/internal/handler/ckb.go`(`CKBLead`、`getCkbLeadApiKey`、`RetryFailedCkbLeads`)、`internal/model/ckb_lead.go`、`router` 中 `miniprogram.POST("/ckb/lead")` 与 `cron` 的 `retry-ckb-leads`。
|
||||
> **边界**:存客宝 **开放 API、设备、计划全表** 见金组 **G15**;本节仅 **BFF 编排 + 本地队列表 + 重试**。
|
||||
> **协同**:`#linkTag` 的 `tagType==='ckb'` 见 **F23**;超级个体获客统计见 **F26**。
|
||||
|
||||
## 一、业务目标
|
||||
|
||||
- C 端 **不直连** 存客宝公网 API(避免密钥暴露在小程序包与前端)。
|
||||
- 每次留资 **先落库**(可追溯、可重试、可运营导出),再异步或同步推送 CKB。
|
||||
- 推送失败可 **cron 批量重试**,并记录 `ckb_error`。
|
||||
|
||||
## 二、小程序桥:`submitCkbLead`
|
||||
|
||||
**文件**:`miniprogram/utils/soulBridge.js`
|
||||
|
||||
**前置校验**(顺序重要):
|
||||
|
||||
1. `targetUserId` 与 `targetMemberId` **至少其一**(文章 `@` 用 user;会员详情无 token 时用 memberId 走全局计划)。
|
||||
2. 已登录;否则 Modal 引导去「我的」。
|
||||
3. 手机号:本地 `userInfo` / `profile` 拉取 / storage;须 `1[3-9]\\d{9}`。
|
||||
4. 可选 `wechatId` 一并提交。
|
||||
|
||||
**请求**:
|
||||
|
||||
- `POST /api/miniprogram/ckb/lead`
|
||||
- Body 字段(与后端对齐):`userId`(当前用户)、`phone`、`wechatId`、`name`、`targetUserId`、`targetNickname`、`targetMemberId`、`targetMemberName`、`source`
|
||||
|
||||
**成功反馈**:若返回 `skipped` / `alreadySubmitted`,Toast「无需重复提交」类文案,并仍可 `setStorageSync('lead_last_submit_ts', ...)`。
|
||||
|
||||
## 三、后端:`CKBLead` 要点
|
||||
|
||||
**路由**:`POST /api/miniprogram/ckb/lead`(仅 miniprogram 组)
|
||||
|
||||
**典型分支**(语义级,以仓库为准):
|
||||
|
||||
- 解析 `targetUserId` → 查 `persons`,得到 `target_person_id` 与对应 CKB **plan/apiKey**(或走 **共享计划** `super_individual_shared_plan`)。
|
||||
- **无 targetUserId**(如首页「链接卡若」):用 `getCkbLeadApiKey()` 全局 key。
|
||||
- **幂等**:同一 `userId + source` 或业务定义的唯一键,可返回 `skipped: true`。
|
||||
- 创建 `CkbLeadRecord`:`push_status=pending`,填 `params` JSON,再调 CKB HTTP;成功 `success`,失败 `failed` + `ckb_error`。
|
||||
|
||||
## 四、`getCkbLeadApiKey` 优先级(永平实现注释)
|
||||
|
||||
1. `system_config` / `site_settings` 中的 `ckbLeadApiKey`
|
||||
2. 环境变量 `CKB_LEAD_API_KEY`
|
||||
3. 代码内兜底(若有,生产应禁用)
|
||||
|
||||
## 五、表 `ckb_lead_records`(模型字段摘要)
|
||||
|
||||
| 字段 | 用途 |
|
||||
|:---|:---|
|
||||
| `user_id` | 留资用户(访客) |
|
||||
| `target_person_id` | 被链接的人物,**F26 leadCount** 关联用 |
|
||||
| `source` | `article_mention`、`index_link_button`、`index_lead` 等 |
|
||||
| `params` | 原始 JSON 备查 |
|
||||
| `push_status` | `pending` / `success` / `failed` |
|
||||
| `retry_count`、`next_retry_at`、`last_push_at` | 重试调度 |
|
||||
| `ckb_error` | 最近一次错误摘要 |
|
||||
|
||||
## 六、Cron 与运营
|
||||
|
||||
- **`/api/cron/retry-ckb-leads`**:`RetryFailedCkbLeads`,限制条数,避免一次打满。
|
||||
- 管理端列表 / 单条重试:见 `db_ckb_leads.go` 等。
|
||||
- 可选 Webhook:`ckb_lead_webhook_url`(若有配置)。
|
||||
|
||||
## 七、Gotchas(≥10)
|
||||
|
||||
1. **未登录提交**:必须挡在桥里,避免匿名脏数据。
|
||||
2. **手机号正则**:与 profile 不同步时先拉 `/user/profile`。
|
||||
3. **target 双轨**:`targetUserId` 与 `targetMemberId` 后端解析路径不同,勿混测。
|
||||
4. **person 不存在**:应明确错误,勿静默丢单。
|
||||
5. **共享 plan 未配**:超级个体链路会整段失败,需监控。
|
||||
6. **重复提交**:产品要定义「同用户同人物同 source」是否允许多条。
|
||||
7. **push 超时**:须落 `failed` 而非无限 pending。
|
||||
8. **cron 鉴权**:`/api/cron/*` 须密钥或内网,防公网刷。
|
||||
9. **PII 日志**:phone 不全量打 access log。
|
||||
10. **与 F23 ckb 标签**:两处入口应共用 `CKBLead`,避免一套写表一套漏写。
|
||||
11. **G15 变更**:CKB 接口字段升级时先改 BFF 再改小程序展示字段。
|
||||
|
||||
## 八、验收清单
|
||||
|
||||
- [ ] `@mention`、会员详情、首页按钮三条路径各通一条
|
||||
- [ ] 失败记录 cron 可恢复为 success
|
||||
- [ ] `target_person_id` 正确时 F26 `leadCount` 可增长
|
||||
- [ ] 密钥只存在于服务端配置
|
||||
|
||||
## 九、互指
|
||||
|
||||
- **F23** `linkTag` / `tagType==='ckb'`
|
||||
- **F26** `COUNT(DISTINCT l.user_id)` 获客口径
|
||||
- **G15** 存客宝开放 API
|
||||
- **F01 §1.10** 留资按钮若同时 `trackClick`,字段语义与 lead 表分开
|
||||
121
04_卡火(火)/火炬_全栈消息/小程序链接标签与跨小程序跳转/SKILL.md
Normal file
121
04_卡火(火)/火炬_全栈消息/小程序链接标签与跨小程序跳转/SKILL.md
Normal file
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: 小程序链接标签与跨小程序跳转
|
||||
description: 富文本 #linkTag 解析、tagType 决策树、wx.navigateToMiniProgram、mpKey 与 linkedMiniprograms、app.json 白名单、内链外链与 CKB 类型分支;可复用到任意微信原生小程序 + 自建 BFF 下发配置。
|
||||
triggers: 链接标签、linkTag、hash标签、小程序跳转、navigateToMiniProgram、跨小程序、打开别的小程序、mpKey、linkedMiniprograms、正文跳转、read页链接、contentParser
|
||||
owner: 火炬
|
||||
group: 火
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
---
|
||||
|
||||
# 小程序链接标签与跨小程序跳转
|
||||
|
||||
> **参考实现**:[一场soul的创业实验-永平](file:///Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-永平)(`miniprogram/`)。**不**写管理端全路由表、不写书籍 CMS。
|
||||
> **协同**:留资类与 **《存客宝BFF与留资队列》**(F27)互指;通用点击埋点见 **《全栈开发》§1.10**。
|
||||
|
||||
## 一、业务目标与非目标
|
||||
|
||||
**目标**:在图文/章节正文中插入「可点的 `#标签`」,点击后按类型进入:本小程序页、H5 预览、其他小程序、或转入留资流程(CKB)。
|
||||
|
||||
**非目标**:不替代小程序原生 `navigator` 全站路由规范;不在这里展开存客宝 HTTP 签名细节(见 G15)。
|
||||
|
||||
## 二、用户与系统旅程(摘要)
|
||||
|
||||
1. 用户打开阅读页 → 正文 HTML 经解析拆成 `text` / `mention` / `linkTag` / `image` 段。
|
||||
2. 用户点 `#某标签` → `onLinkTagTap` 读取 `dataset`(或由 `label` 反查缓存)。
|
||||
3. 按 `tagType` 分支执行:`miniprogram` / `ckb` / 内链 / 外链。
|
||||
4. 失败路径必须有 Toast,禁止静默无反馈。
|
||||
|
||||
## 三、前端(微信小程序)
|
||||
|
||||
### 3.1 解析层
|
||||
|
||||
| 文件 | 职责 |
|
||||
|:---|:---|
|
||||
| `miniprogram/utils/contentParser.js` | 正则切分 HTML;`linkTag` 段提取 `tagType`、`url`、`pagePath`、`tagId`、`appId`、`mpKey`、`label`;支持旧版 `<a href>` |
|
||||
| `miniprogram/pages/read/read.wxml` | `bindtap="onLinkTagTap"`,`data-*` 与解析结果对齐 |
|
||||
|
||||
**纯文本自动补标签**(可选):若 `config.linkTags` / `config.persons` 存在,可在无 HTML 标记时按词表插入 `linkTag` 段(见 `contentParser` 内 `applyInlineConfig` 逻辑)。
|
||||
|
||||
### 3.2 点击处理:`onLinkTagTap`
|
||||
|
||||
**文件**:`miniprogram/pages/read/read.js`
|
||||
|
||||
**决策顺序(须保持顺序,便于排错)**:
|
||||
|
||||
1. **补齐字段**:从 `dataset` 取 `url`、`label`、`tagType`、`pagePath`、`mpKey`。若 `tagType` 为空且 `label` 有值 → 在 `app.globalData.linkTagsConfig` 里按 `label` 查找,补全 `tagType`、`pagePath`、`url`、`mpKey`。
|
||||
2. **`tagType === 'ckb'`**:走与 `@某人` 类似的加好友/留资入口(常复用 `onMentionTap` 或统一 `soulBridge`);**详规见 F27**。
|
||||
3. **`tagType === 'miniprogram'`**:若缺 `mpKey`,再尝试用 `label` 从 `linkTagsConfig` 补。用 `app.globalData.linkedMiniprograms` 找 `key === mpKey` 的项,取 `appId`,调用:
|
||||
|
||||
```javascript
|
||||
wx.navigateToMiniProgram({
|
||||
appId: linked.appId,
|
||||
path: pagePath || linked.path || '',
|
||||
envVersion: 'release',
|
||||
})
|
||||
```
|
||||
|
||||
4. **本小程序内路径**:`pagePath` 非空,或 `url` 以 `/pages/` 开头 → `wx.navigateTo`;`fail` 时兜底 `wx.switchTab`(适配 tabBar 页)。
|
||||
5. **外链**:`url` 非空 → `/pages/link-preview/link-preview?url=...&title=...`(web-view)。
|
||||
6. 仍无可执行动作 → `wx.showToast({ title: '暂无跳转地址' })`。
|
||||
|
||||
### 3.3 全局数据契约
|
||||
|
||||
| 字段 | 含义 |
|
||||
|:---|:---|
|
||||
| `globalData.linkTagsConfig` | 后台下发的链接标签列表(含 `label`、`type`、`url`、`pagePath`、`mpKey` 等) |
|
||||
| `globalData.linkedMiniprograms` | `{ key, appId, path? }[]`,与 `mpKey` 映射 |
|
||||
|
||||
**后端合并**:服务端可把 `link_tags` 表中 `type=miniprogram` 的行与 `linked_miniprograms` 配置合并进全量 config(永平见 `mergeDirectMiniProgramLinksFromLinkTags` 思路)。
|
||||
|
||||
### 3.4 `app.json` 强制项
|
||||
|
||||
- **`navigateToMiniProgramAppIdList`**:列出所有可能被唤醒的小程序 appId;漏配会导致 `navigateToMiniProgram` 失败。
|
||||
- 新增合作小程序时:**同时**改后台配置、合并逻辑与 **白名单数组**。
|
||||
|
||||
## 四、后端(BFF / 管理端配置)
|
||||
|
||||
- **配置存储**:常见为 `link_tags` 表 + `system_config` 或独立 JSON 字段中的 `linkedMiniprograms`。
|
||||
- **下发接口**:小程序启动或进阅读页时拉「全量 config」;需保证 **旧客户端** 在缺 `tagType` 时仍能靠 `label` 命中 `linkTagsConfig`。
|
||||
- **类型枚举建议**:`url` | `miniprogram` | `page`(内链)| `ckb`(留资)——与前端分支一致。
|
||||
|
||||
## 五、数据模型(示意)
|
||||
|
||||
**link_tags(示意)**
|
||||
|
||||
| 列 | 说明 |
|
||||
|:---|:---|
|
||||
| tag_id | 稳定 ID,可写入正文 `data-tag-id` |
|
||||
| label | 展示文案,带 `#` 或不带需与解析统一 |
|
||||
| type | `url` / `miniprogram` / `ckb` / 内链 |
|
||||
| url / page_path | 外链或本包路径 |
|
||||
| mp_key | 对应 `linkedMiniprograms[].key` |
|
||||
| app_id | 可选;可直接填微信 appId 由后端 merge 进 linked 列表 |
|
||||
|
||||
## 六、Gotchas(≥10)
|
||||
|
||||
1. **白名单未配**:仅 `navigateToMiniProgram` 报失败,需在真机看 `errMsg`。
|
||||
2. **`mpKey` 不一致**:后台改 key 未同步正文历史 → Toast「未找到关联小程序配置」。
|
||||
3. **旧正文 `<a href>`**:无 `tagType`,必须依赖 `linkTagsConfig` 按 `label` 降级。
|
||||
4. **tabBar 页**:`navigateTo` 失败须 `switchTab`,路径不能带 query 的限制要知晓。
|
||||
5. **`ckb` 与 `@` 重复**:两处都调留资时,后端需 **幂等/去重**(见 F27)。
|
||||
6. **web-view 业务域名**:外链域名未在小程序后台配置则白屏。
|
||||
7. **`envVersion: 'release'`**:体验版调试需临时改为 `trial`/`develop`(勿提交忘记改回)。
|
||||
8. **同页多段 `#`**:`dataset` 必须逐段绑定,避免共用引用导致串标签。
|
||||
9. **繁体/空格 label**:与后台 `label` 严格相等匹配易失败,宜 trim + 统一繁简策略。
|
||||
10. **安全**:外链必须 HTTPS,禁止 `javascript:` 协议进入 web-view。
|
||||
11. **统计口径**:跳转次数若要进看板,需单独 `trackClick`,**不等于** §1.10 里所有 `link_click` 语义,需在登记表写清。
|
||||
|
||||
## 七、验收清单
|
||||
|
||||
- [ ] 四种 `tagType`(含降级)在真机各走通至少一条用例
|
||||
- [ ] 新合作小程序已加 `navigateToMiniProgramAppIdList`
|
||||
- [ ] 后台改 `mpKey` 后旧文行为符合产品预期(Toast 或可批量修数据)
|
||||
- [ ] `link-preview` 域名已配置
|
||||
- [ ] 与 F27 联调:CKB 类标签不误跳外链
|
||||
|
||||
## 八、互指
|
||||
|
||||
- **F01 §1.10**:通用 `trackClick`;本节不重复埋点字段规范。
|
||||
- **F27 存客宝BFF**:`tagType==='ckb'` 与 `@mention` 留资。
|
||||
- **G15 存客宝**:开放 API、计划、设备详情。
|
||||
95
04_卡火(火)/火炬_全栈消息/推广邀请与三十日绑定/SKILL.md
Normal file
95
04_卡火(火)/火炬_全栈消息/推广邀请与三十日绑定/SKILL.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: 推广邀请与三十日绑定
|
||||
description: scene 解析、ReferralVisit 幂等、ReferralBinding 三十日首绑、邀请码与推广员归属、管理端开关与统计;可复用到任意带 scene 的小程序 + Gin/GORM。
|
||||
triggers: 推广、邀请、邀请码、referral、scene、1001、首绑、三十日、30天、绑定推广员、推广开关、ReferralVisit、ReferralBinding
|
||||
owner: 火炬
|
||||
group: 火
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
---
|
||||
|
||||
# 推广邀请与三十日绑定
|
||||
|
||||
> **参考实现**:永平 `soul-api/internal/handler/referral.go`、`soul-api/internal/handler/db.go`(`referral_config`、模型 `ReferralVisit` / `ReferralBinding`)。
|
||||
> **协同**:佣金结算与提现见 **《分销佣金与提现编排》**(F25);管理端开关与配置键本节列全。
|
||||
|
||||
## 一、业务目标
|
||||
|
||||
- 新用户通过带 **邀请码** 的入口进入小程序后,在 **30 天自然日窗口** 内首次完成有效行为时,将 **推广员(inviter)** 写入绑定表;窗口外或已绑定则不再改归属。
|
||||
- **访问留痕**与**绑定**分离:同一用户可多次访问,绑定只发生一次(首绑)。
|
||||
|
||||
## 二、旅程
|
||||
|
||||
1. 小程序 `onLaunch` / `onShow` 解析 `options.scene`(或等价入口参数)。
|
||||
2. 若 scene 为 **1001**(或产品约定的「直开」码)→ 不调推广接口,避免误绑。
|
||||
3. 否则提取 `inviteCode`,`POST` 记录访问(可带 `deviceId` 防刷)。
|
||||
4. 用户完成「可触发绑定」的动作(如注册成功、首次登录)→ `POST` 绑定;服务端校验 **30 日窗口** 与 **是否已有绑定**。
|
||||
|
||||
## 三、后端 API(命名与永平对齐,可迁移时改名)
|
||||
|
||||
| 方法路径 | 作用 |
|
||||
|:---|:---|
|
||||
| `POST /api/miniprogram/referral/visit` | 记录访问;scene=1001 直接 success 不写字段 |
|
||||
| `POST /api/miniprogram/referral/bind` | 首绑推广员;需登录态 |
|
||||
| `GET /api/miniprogram/referral/status` | 当前用户是否已绑、inviter 信息等 |
|
||||
| `GET /api/admin/referral/list` | 管理端绑定列表 |
|
||||
| `GET /api/admin/referral/visits` | 访问记录 |
|
||||
| `GET/PUT /api/admin/referral/config` | 开关与参数 |
|
||||
|
||||
## 四、配置键 `referral_config`(JSON)
|
||||
|
||||
| 键 | 含义 |
|
||||
|:---|:---|
|
||||
| `enabled` | 总开关 |
|
||||
| `bindWindowDays` | 绑定窗口天数,默认 **30** |
|
||||
| `requirePhone` | 是否要求绑定手机号才算有效用户 |
|
||||
| `minRegisterSeconds` | 注册后至少 N 秒才可绑(防脚本) |
|
||||
| `blacklistUserIDs` | 不参与推广的用户 ID 列表 |
|
||||
| `blacklistPhones` | 手机号黑名单 |
|
||||
|
||||
**注意**:`enableAutoWithdraw` 等属 **分销提现**,见 F25,勿与本节混淆。
|
||||
|
||||
## 五、数据模型要点
|
||||
|
||||
**ReferralVisit**
|
||||
|
||||
- 建议唯一约束:`(user_id, visit_date)` 或 `(user_id, invite_code, visit_date)`,实现 **每日幂等**(同用户同日多次只一行)。
|
||||
- 字段常含:`invite_code`、`inviter_id`、`scene`、`channel`、`device_id`。
|
||||
|
||||
**ReferralBinding**
|
||||
|
||||
- `user_id` **唯一**:一个被推广用户只对应一条绑定。
|
||||
- `inviter_id`、`invite_code`、`bound_at`、可选 `first_visit_at` 用于审计。
|
||||
|
||||
## 六、scene 与 1001
|
||||
|
||||
- **1001**:微信场景值「发现栏小程序主入口」等直开,**不应**写入推广归因。
|
||||
- 其他 scene:按产品规则解析出自定义 `inviteCode`(可能嵌在 scene 字符串中)。
|
||||
|
||||
## 七、Gotchas(≥10)
|
||||
|
||||
1. **scene 解析失败**:静默跳过,避免把脏数据写入 `ReferralVisit`。
|
||||
2. **未登录调 bind**:须 401 或明确错误码,禁止用匿名 user_id=0 绑成功。
|
||||
3. **重复 bind**:第二次请求须 **幂等返回成功** 且不改 `inviter_id`。
|
||||
4. **窗口计算**:用 **自然日** 还是 **精确到秒** 需在 PRD 固定;代码与文档一致。
|
||||
5. **时区**:服务器 UTC vs 业务日切,统计「30 日」要对齐。
|
||||
6. **自邀**:`inviter_id == user_id` 必须拒绝。
|
||||
7. **黑名单**:在 bind 前校验 user 与 phone。
|
||||
8. **requirePhone**:用户未绑手机时 bind 应返回可理解文案。
|
||||
9. **刷 visit**:仅靠 user_id 不够时上 `deviceId` + 频控。
|
||||
10. **管理端关 enabled**:应 **停止新 bind**,旧绑定是否保留佣金由 F25 决定。
|
||||
11. **换 inviteCode**:若允许多次访问不同码,**首绑**以谁先满足窗口为准,须在界面说明。
|
||||
|
||||
## 八、验收清单
|
||||
|
||||
- [ ] 1001 不产生 visit 记录或明确 no-op
|
||||
- [ ] 首绑成功、重复绑幂等
|
||||
- [ ] 超窗 bind 拒绝且文案正确
|
||||
- [ ] 黑名单与自邀拦截
|
||||
- [ ] 管理端 config 热更新后新请求生效
|
||||
|
||||
## 九、互指
|
||||
|
||||
- **F25** 分销佣金、提现、订单归因
|
||||
- **F26** 超级个体统计(与推广员体系可能交叉,勿重复计佣)
|
||||
- **F01 §1.10** 若要对邀请链路单独埋点,字段勿与 `referral_*` 混名
|
||||
108
04_卡火(火)/火炬_全栈消息/超级个体点击与获客统计/SKILL.md
Normal file
108
04_卡火(火)/火炬_全栈消息/超级个体点击与获客统计/SKILL.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
name: 超级个体点击与获客统计
|
||||
description: user_tracks 中「链接头像_」前缀、avatar_click/btn_click 兼容、persons 与 ckb_lead_records 去重获客数、VIP 列表聚合、super_individual_shared_plan、webhook 映射与 cron 同步;可复用到「人物卡片+留资归因」类小程序。
|
||||
triggers: 超级个体、VIP卡片、链接头像、clickCount、leadCount、获客人数、vip-members、AdminSuperIndividualStats、super_individual_shared_plan、super_individual_webhook
|
||||
owner: 火炬
|
||||
group: 火
|
||||
version: "1.0"
|
||||
updated: "2026-03-26"
|
||||
---
|
||||
|
||||
# 超级个体点击与获客统计
|
||||
|
||||
> **参考实现**:永平 `vip_members_admin.go`(`batchSuperIndividualClicks` / `batchSuperIndividualLeads`)、`admin_dashboard.go`(`AdminSuperIndividualStats`)、`db_person.go`(`super_individual_shared_plan`)、`cron.go`(`sync-vip-ckb-plans` 等)。
|
||||
> **边界**:**点击量 ≠ 留资人数**;通用埋点规范见 **《全栈开发》§1.10**。留资写入 CKB 见 **F27**。
|
||||
|
||||
## 一、业务目标
|
||||
|
||||
- 首页(或列表)展示 **VIP/超级个体** 卡片,用户点头像产生可聚合的 **点击次数**。
|
||||
- 用户通过该人物链路完成留资后,在管理端对该 **user_id(人物绑定用户)** 展示 **去重后的获客人数(leads)**。
|
||||
- 可选:按用户配置 **飞书 Webhook**,用于通知(键 `super_individual_webhook_map`)。
|
||||
|
||||
## 二、点击统计口径(与代码一致)
|
||||
|
||||
**表**:`user_tracks`
|
||||
|
||||
**条件**:
|
||||
|
||||
- `action IN ('avatar_click', 'btn_click')`(历史兼容 `btn_click`)
|
||||
- `target LIKE '链接头像_%'`(SQL 中 `_` 需转义为 `\_`)
|
||||
- **人物 user_id** = `SUBSTRING(target, 6)`(即去掉前缀「链接头像」共 5 个 UTF-8 字符后的子串——**以永平实现为准**,若产品改前缀须同步改 SQL 与小程序 `trackClick` 的 `target`)
|
||||
|
||||
**聚合**:按 `user_id` `COUNT(*)`。
|
||||
|
||||
参考 SQL(摘自实现):
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
SUBSTRING(target, 6) AS user_id,
|
||||
COUNT(*) AS clicks
|
||||
FROM user_tracks
|
||||
WHERE action IN ('avatar_click', 'btn_click')
|
||||
AND target LIKE '链接头像\\_%'
|
||||
AND SUBSTRING(target, 6) IN (?)
|
||||
GROUP BY user_id
|
||||
```
|
||||
|
||||
## 三、获客(留资)人数口径
|
||||
|
||||
**表**:`persons` + `ckb_lead_records`
|
||||
|
||||
- `persons.user_id`:人物绑定的超级个体用户 ID。
|
||||
- `ckb_lead_records.target_person_id`:留资指向的人物 `person_id`。
|
||||
- **去重**:`COUNT(DISTINCT l.user_id)` — 同一访客多条留资只计 1。
|
||||
|
||||
```sql
|
||||
SELECT p.user_id AS user_id, COUNT(DISTINCT l.user_id) AS leads
|
||||
FROM persons p
|
||||
INNER JOIN ckb_lead_records l ON l.target_person_id = p.person_id
|
||||
WHERE p.user_id IN ?
|
||||
GROUP BY p.user_id
|
||||
```
|
||||
|
||||
## 四、管理端 / DB 接口
|
||||
|
||||
| 入口 | 作用 |
|
||||
|:---|:---|
|
||||
| `GET /api/db/vip-members` | VIP 列表 + `clickCount` + `leadCount` + `webhookUrl` |
|
||||
| `PUT /api/db/vip-members/webhook` | 按 `userId` 写 webhook 映射 |
|
||||
| `GET /api/admin/super-individual/stats` | 看板级汇总(实现见 `admin_dashboard.go`) |
|
||||
|
||||
## 五、配置键
|
||||
|
||||
| 键 | 说明 |
|
||||
|:---|:---|
|
||||
| `super_individual_shared_plan` | JSON:共用存客宝 **planId + apiKey**;新建人物时不为每人单独建计划(见 `createPersonWithSharedSuperIndividualPlan`) |
|
||||
| `super_individual_webhook_map` | `userId -> webhookUrl` |
|
||||
|
||||
## 六、小程序侧约定
|
||||
|
||||
- 点头像必须调 `trackClick`,`target` 形如 **`链接头像_` + 人物对应 users.id**(与后端 `SUBSTRING` 规则一致)。
|
||||
- 若改用 `person_id`,须改后端 SQL,**禁止**只改一端。
|
||||
|
||||
## 七、Gotchas(≥10)
|
||||
|
||||
1. **前缀字符数**:「链接头像」占 5 个 rune,与 `SUBSTRING(target, 6)` 强绑定。
|
||||
2. **`btn_click` 噪声**:非头像按钮若误用同前缀,点击量会虚高。
|
||||
3. **留资未写 target_person_id**:`leadCount` 为 0,问题在 F27 链路。
|
||||
4. **person 未创建**:VIP 用户无 `persons` 行 → leads 永远 0。
|
||||
5. **DISTINCT l.user_id**:留资表 `user_id` 为空则不计入,需小程序登录态一致。
|
||||
6. **共享 plan 配错**:所有人写到同一计划或写入失败,cron 需监控。
|
||||
7. **Webhook URL**:校验 `http` 前缀;删除 key 时 map 需持久化更新。
|
||||
8. **列表 limit**:`vip-members` 默认 200、最大 500,排序与小程序 `VipMembers` 需一致。
|
||||
9. **与 F25 分佣**:超级个体 ≠ 推广员;佣金勿用 `clickCount` 推算。
|
||||
10. **看板与列表**:`AdminSuperIndividualStats` 与 `batchSuperIndividualLeads` 口径应保持一致,改一处同步改另一处。
|
||||
11. **迁移项目**:若表名或字段改名,优先封装 Raw SQL 到一处。
|
||||
|
||||
## 八、验收清单
|
||||
|
||||
- [ ] 真机点头像后 `user_tracks` 行符合 target 规范
|
||||
- [ ] `GET /api/db/vip-members` 中 click/lead 与 SQL 手工核对一致
|
||||
- [ ] 共享 plan 关闭时,新建人物行为符合产品(独立计划或报错)
|
||||
- [ ] Webhook 可增删改
|
||||
|
||||
## 九、互指
|
||||
|
||||
- **F01 §1.10**:`trackClick` 字段与命名
|
||||
- **F27**:`ckb_lead_records` 写入、`POST /api/miniprogram/ckb/lead`
|
||||
- **G15**:存客宝开放 API
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
卡若AI,卡若的个人数字管家。工作台:`/Users/karuo/Documents/个人/卡若AI/`
|
||||
|
||||
## 二、团队(5 负责人 → 15 成员 → 74 技能)
|
||||
## 二、团队(5 负责人 → 15 成员 → 技能数以 `SKILL_REGISTRY.md` 头注为准)
|
||||
|
||||
```
|
||||
卡若AI → 卡资(金)→金仓·金盾 | 卡人(水)→水溪·水泉·水桥·水岸 | 卡木(木)→木叶·木根·木果·木识 | 卡火(火)→火炬·火锤·火眼·火种 | 卡土(土)→土基·土砖·土渠·土簿
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
## 三、启动与技能查找
|
||||
|
||||
1. 读本文件 → 2. 按下方热技能速查匹配,未命中再读 `SKILL_REGISTRY.md` → 3. 读匹配到的 SKILL.md → 4. 可选读 `CURRENT_STATE.md`(当日工作台)
|
||||
1. 读本文件 → 2. 按下方热技能速查匹配,未命中再读 `SKILL_REGISTRY.md` → 3. 读匹配到的 SKILL.md → 4. 可选读 `CURRENT_STATE.md`(当日工作台)
|
||||
**软件开发**:在 2~3 步基础上**叠加**读 **`运营中枢/工作台/开发域_Skill模块化索引.md`**(M1~M11),再回 Registry 取路径(两轨并行,不互斥)。
|
||||
|
||||
**热技能速查**:
|
||||
|
||||
@@ -30,10 +31,11 @@
|
||||
| 代码修复/bug | `04_卡火(火)/火锤_代码修复/代码修复/SKILL.md` |
|
||||
| 系统状态/杀进程 | `01_卡资(金)/金仓_存储备份/系统监控/SKILL.md` |
|
||||
| MCP/连接MCP | `02_卡人(水)/水桥_平台对接/MCP管理/SKILL.md` |
|
||||
| **闽南话/语音输入/ASR 误听/回廊洗字** | 机制 `运营中枢/参考资料/闽南话语音_ASR纠错机制.md`;词库 `运营中枢/参考资料/卡若闽南口音_ASR纠错库.json`;W03b `02_卡人(水)/水溪_整理归档/语音转写纠错/SKILL.md` |
|
||||
| Soul运营报表 | `02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md` |
|
||||
| **Soul派对技能流/派对Stream/创业派对Stream** | `02_卡人(水)/水岸_项目管理/卡若创业派对/Soul派对技能流_掌管人与Stream规约.md` |
|
||||
| 项目管理/卡若创业派对 | `02_卡人(水)/水岸_项目管理/SKILL.md` |
|
||||
| 卡路派对总控/玉宁/永平开发 | `.cursor/skills/kalu-entrepreneur-party/SKILL.md`(卡若AI 仓库) |
|
||||
| Soul/卡若派对总控、玉宁、永平开发 | K01 `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若创业派对_总控/SKILL.md`;K02 玉宁 `…/卡若玉宁运营专线/SKILL.md`;K03 永平 `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若网站开发_永平三端/SKILL.md`;**套件总索引** `…/Soul技能归口/SKILL.md` |
|
||||
| 聊天记录/对话存储/聊天归档 | `01_卡资(金)/金仓_存储备份/聊天记录管理/SKILL.md` |
|
||||
|
||||
## 四、MAX Mode(默认)
|
||||
@@ -76,6 +78,7 @@
|
||||
| 命令 | 做什么 |
|
||||
|:---|:---|
|
||||
| 技能查找 | 热技能速查→未命中读 SKILL_REGISTRY.md→读 SKILL.md 执行 |
|
||||
| 开发域模块化 | `运营中枢/工作台/开发域_Skill模块化索引.md`(搭项目/部署/小程序与获客链路按模块选 Skill) |
|
||||
| 常规操作 | 优先命令行 + 复用现成流程,不提问 |
|
||||
| 复盘 | 所有回复强制用完整复盘(🎯📌💡📝▶,v5.0) |
|
||||
| 沉淀 | 写入 `水溪_整理归档/经验库/待沉淀/` |
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 卡若AI 技能注册表(Skill Registry)
|
||||
|
||||
> **一张表查所有技能**。任何 AI 拿到这张表,就能按关键词找到对应技能的 SKILL.md 路径并执行。
|
||||
> 79 技能 + 3 卡路Cursor入口 | 15 成员 | 5 负责人
|
||||
> 版本:5.17 | 更新:2026-03-26
|
||||
> 80 技能 + Soul技能归口(K01~K03 等补充项,见下文) | 15 成员 | 5 负责人
|
||||
> 版本:5.21 | 更新:2026-03-26
|
||||
>
|
||||
> **技能配置、安装、删除、掌管人登记** → 见 **`运营中枢/工作台/01_技能控制台.md`**。
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
1. 用户说需求 → 在「触发词」列搜索匹配
|
||||
2. 找到行 → 读「SKILL 路径」列的文件
|
||||
3. 按 SKILL.md 里的步骤执行
|
||||
4. **软件开发类**:可按模块速查 **`运营中枢/工作台/开发域_Skill模块化索引.md`**(M1~M11 + 项目类型组合),再回本表取路径
|
||||
|
||||
**Cursor 续跑**:用户说「下一步」「接着跑」「重新剪辑」「直到完成」或刚更新某 Skill 后要产出时,**直接执行**(不先问是否运行),见 `BOOTSTRAP.md` 八·流水线续跑、`卡若AI/.cursor/rules/karuo-ai.mdc`。
|
||||
|
||||
@@ -112,7 +113,7 @@
|
||||
| M01b | 抖音视频解析 | 木叶 | **抖音视频、抖音链接、抖音解析、抖音下载、提取抖音文案、抖音无水印** | `03_卡木(木)/木叶_视频内容/抖音视频解析/SKILL.md` | 链接→解析ID→提取文案→下载无水印视频 |
|
||||
| M01c | 抖音发布 | 木叶 | **抖音发布、发布到抖音、抖音登录、抖音上传、腕推抖音** | `03_卡木(木)/木叶_视频内容/抖音发布/SKILL.md` | 纯 API 视频上传+发布(VOD + bd-ticket-guard),无需浏览器 |
|
||||
| M01d | B站发布 | 木叶 | **B站发布、发布到B站、B站登录、B站上传、bilibili发布** | `03_卡木(木)/木叶_视频内容/B站发布/SKILL.md` | 纯 API(preupload 分片),Cookie 有效期约6个月 |
|
||||
| M01e | 视频号发布 | 木叶 | **视频号发布、发布到视频号、视频号登录、视频号上传、微信视频号** | `03_卡木(木)/木叶_视频内容/视频号发布/SKILL.md` | 纯 API(finder-assistant 腾讯云上传),微信扫码登录 |
|
||||
| M01e | 视频号发布 | 木叶 | **视频号发布、发布到视频号、视频号登录、视频号上传、微信视频号** | `03_卡木(木)/木叶_视频内容/视频号发布/SKILL.md` | 默认:静默扫码 → `channels_api_publish`(httpx)→ 缺 finder_raw 时 exit 2 由 `publish_auto.sh` 接 `channels_web_cli publish-dir` |
|
||||
| M01f | 小红书发布 | 木叶 | **小红书发布、发布到小红书、小红书登录、小红书上传、RED发布** | `03_卡木(木)/木叶_视频内容/小红书发布/SKILL.md` | 逆向 creator API 视频笔记发布,封面取第一帧 |
|
||||
| M01g | 快手发布 | 木叶 | **快手发布、发布到快手、快手登录、快手上传、kuaishou发布** | `03_卡木(木)/木叶_视频内容/快手发布/SKILL.md` | 逆向 cp.kuaishou.com API 视频发布 |
|
||||
| M01h | 多平台分发 | 木叶 | **多平台分发、一键分发、全平台发布、批量分发、视频分发** | `03_卡木(木)/木叶_视频内容/多平台分发/SKILL.md` | 一键分发到5平台(抖音/B站/视频号/小红书/快手),Cookie统一管理 |
|
||||
@@ -131,11 +132,11 @@
|
||||
|
||||
| # | 技能 | 成员 | 触发词 | SKILL 路径 | 一句话 |
|
||||
|:--|:---|:---|:---|:---|:---|
|
||||
| F01 | 全栈开发 | 火炬 | 知己、RAG、分销、**卡若AI官网、官网开发、全站开发、开发文档、1~10**、**埋点、点击统计、用户行为、行为统计、数据统计、点击锚点、锚点、trackClick** | `04_卡火(火)/火炬_全栈消息/全栈开发/SKILL.md` | 全栈项目 + 官网/全站与开发文档 1~10;**§1.10 埋点与点击锚点全站强制** |
|
||||
| F01 | 全栈开发 | 火炬 | 知己、RAG、分销、**卡若AI官网、官网开发、全站开发、开发文档、1~10**、**埋点、点击统计、用户行为、点击锚点、trackClick** | `04_卡火(火)/火炬_全栈消息/全栈开发/SKILL.md` | 全栈项目 + 官网/全站与开发文档 1~10;**§1.10 埋点全站强制**;获客/深链路 **§1.11 仅索引**,详规 **F23~F27** |
|
||||
| F01a | 前端开发 | 火炬 | **前端开发、毛玻璃、神射手风格、毛狐狸风格、前端标准、苹果毛玻璃**、**埋点、点击锚点、trackClick、用户行为** | `04_卡火(火)/火炬_全栈消息/前端开发/SKILL.md` | 毛玻璃 + 前端标准;**§五 用户行为与点击锚点**;详规见全栈 §1.10 |
|
||||
| F01b | 全栈测试 | 火炬 | **全栈测试、功能测试、回归测试、深度测试、E2E测试、API测试、发布测试、测试验收** | `04_卡火(火)/火炬_全栈消息/全栈开发/全栈测试/SKILL.md` | 功能开发后系统化验收:前端/后端/数据库/脚本/发布引擎五维测试;**每完成一个功能必须调用** |
|
||||
| F01c | **项目开发占卜术**(间名 **演门测机**) | 火炬 | **项目开发占卜术、开发占卜、Q门3.0、奇门项目盘、八门复盘、起盘、盘势、门迫** | `04_卡火(火)/火炬_全栈消息/项目开发占卜术/SKILL.md` | 奇门 Q门 3.0 八门健康度扫描;**仅用户点名起盘**时附在复盘 v5.0 五块**之后**,**不**写入标准 🎯 |
|
||||
| F01d | **卡若复盘格式** | 火炬 | **复盘格式、卡若复盘、达成率怎么写、复盘 v5、视频号分发复盘、负达成率** | `.cursor/skills/karuo-recap-format/SKILL.md` | v5.0:🎯 单行一句 ≤50 字、达成率绑定分发/验收(可负)、禁 ➡️/📊 复述与标准 ☯ |
|
||||
| F01d | **卡若复盘格式** | 火炬 | **复盘格式、卡若复盘、达成率怎么写、复盘 v5、视频号分发复盘、负达成率** | `04_卡火(火)/火炬_全栈消息/卡若复盘格式/SKILL.md` | v5.0:🎯 单行一句 ≤50 字、达成率绑定分发/验收(可负)、禁 ➡️/📊 复述与标准 ☯ |
|
||||
| F02 | 消息中枢 | 火炬 | WhatsApp、Telegram | `04_卡火(火)/火炬_全栈消息/消息中枢/SKILL.md` | 多平台消息聚合 |
|
||||
| F02a | **艾叶 IM Bridge** | 火炬 | **艾叶、IM、聊天对接、消息网关、微信对接、企业微信对接、飞书对接、WhatsApp对接、网页聊天、IM桥接、通道配置、艾叶IM** | `04_卡火(火)/火炬_全栈消息/艾叶/SKILL.md` | 多平台 IM 网关:个人微信/企业微信/飞书/WhatsApp/网页→卡若AI 对话 |
|
||||
| F03 | 读书笔记 | 火炬 | 拆解这本书、五行拆书 | `04_卡火(火)/火炬_全栈消息/读书笔记/SKILL.md` | 五行框架拆书 |
|
||||
@@ -146,6 +147,11 @@
|
||||
| F08 | 本地模型 | 火种 | ollama、qwen、本地AI | `04_卡火(火)/火种_知识模型/本地模型/SKILL.md` | Ollama/Qwen 本地部署 |
|
||||
| F21 | 本地代码库索引 | 火种 | 本地索引、本地搜索、不上传云端 | `04_卡火(火)/火种_知识模型/本地代码库索引/SKILL.md` | 本地 embedding 索引与语义检索,不上传云端 |
|
||||
| F22 | 本地项目启动 | 火炬 | **本地运行、启动玩值电竞、玩值电竞App、指定端口、项目端口、项目注册、运行项目、Docker部署、部署到Docker、docker部署、更新同步到Docker、同步到doc、Docker跑最新** | `04_卡火(火)/火炬_全栈消息/本地项目启动/SKILL.md` | 按注册表用指定端口启动;Docker 部署须守唯一 MongoDB + 容器分组;**更新后须 --build 跑本地最新**(见 Skill 内约定) |
|
||||
| F23 | **小程序链接标签与跨小程序跳转** | 火炬 | **链接标签、linkTag、hash标签、小程序跳转、navigateToMiniProgram、跨小程序、mpKey、linkedMiniprograms、read页链接、contentParser** | `04_卡火(火)/火炬_全栈消息/小程序链接标签与跨小程序跳转/SKILL.md` | `#linkTag` 决策树、白名单、内链外链与 CKB 分支;Soul 永平 `contentParser`/`read.js` |
|
||||
| F24 | **推广邀请与三十日绑定** | 火炬 | **推广、邀请码、referral、scene、1001、首绑、三十日、绑定推广员、ReferralVisit、ReferralBinding** | `04_卡火(火)/火炬_全栈消息/推广邀请与三十日绑定/SKILL.md` | visit/bind 幂等、`referral_config` 窗口;永平 `referral.go` |
|
||||
| F25 | **分销佣金与提现编排** | 火炬 | **分销、佣金、提现、withdraw、enableAutoWithdraw、审核提现、打款** | `04_卡火(火)/火炬_全栈消息/分销佣金与提现编排/SKILL.md` | 订单入账 inviter、余额与审核;与 F24 配置键可能同 JSON |
|
||||
| F26 | **超级个体点击与获客统计** | 火炬 | **超级个体、链接头像、clickCount、leadCount、vip-members、super_individual_shared_plan、webhook** | `04_卡火(火)/火炬_全栈消息/超级个体点击与获客统计/SKILL.md` | `user_tracks` 前缀口径 + `ckb_lead_records` 去重;永平 `vip_members_admin` |
|
||||
| F27 | **存客宝BFF与留资队列** | 火炬 | **submitCkbLead、ckb/lead、ckb_lead_records、retry-ckb-leads、留资队列、push_status** | `04_卡火(火)/火炬_全栈消息/存客宝BFF与留资队列/SKILL.md` | 小程序只打 BFF;队列表与 cron;开放 API 见 **G15** |
|
||||
|
||||
## 土组 · 卡土(商业复制裂变)
|
||||
|
||||
@@ -183,15 +189,17 @@
|
||||
|
||||
---
|
||||
|
||||
## 卡路项目簇 · Cursor 快捷入口(`.cursor/skills`,卡若AI 仓库)
|
||||
## Soul技能归口 · K01~K03(水岸掌管,勿再在 `.cursor/skills/` 写正文)
|
||||
|
||||
> **说明**:下列 3 项为 **Cursor Agent Skills**,路径相对于 `卡若AI/.cursor/skills/`;与五行 77 技能互补,用于 **卡若/卡路创业派对** 单项目的 **运营(玉宁)/ 开发** 归口。
|
||||
> **说明**:下列路径相对于 **卡若AI 仓库根**;物理目录 **`02_卡人(水)/水岸_项目管理/Soul技能归口/`** 为套件真源;**掌管人:水岸**。与五行表 **80** 项互补,归口 **Soul / 派对运营 / 永平开发**。**总索引**:`Soul技能归口/SKILL.md`。`.cursor/skills/` 仅保留 `README.md`(迁移说明),**禁止**再新增 Skill 正文。
|
||||
|
||||
| # | 技能 | 触发词 | SKILL 路径 | 一句话 |
|
||||
|:--|:---|:---|:---|:---|
|
||||
| K01 | **卡路创业派对(总控)** | 卡路创业派对、卡路派对、卡洛创业派对、派对总控、玉宁和网站分开 | `.cursor/skills/kalu-entrepreneur-party/SKILL.md` | 先判运营 vs 开发,再读 K02 或 K03 |
|
||||
| K02 | **卡路·玉宁运营** | 玉宁、写文章、视频切片、运营报表、妙记、分发、Soul文章、素材库、Soul派对技能流、派对Stream、创业派对Stream | `.cursor/skills/kalu-party-yuning-ops/SKILL.md` | 派对内容+飞书闭环;与 **Soul 派对技能流(Stream)** 规约同权;聚合运营类 Agent 依赖的 SKILL |
|
||||
| K03 | **卡路·网站开发** | 永平、soul-api、管理端、小程序、用户管理、内容管理、全站修复、超级个体 | `.cursor/skills/kalu-party-soul-website-dev/SKILL.md` | 永平三端与部署;聚合网站类 Agent 与 change-checklist |
|
||||
| K01 | **卡若创业派对(总控)** | 卡若创业派对、卡若派对、卡洛创业派对、派对总控、玉宁和网站分开 | `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若创业派对_总控/SKILL.md` | 先判运营 vs 开发,再读 K02 或 K03 |
|
||||
| K02 | **卡若·玉宁运营** | 玉宁、写文章、视频切片、运营报表、妙记、分发、Soul文章、素材库、Soul派对技能流、派对Stream、创业派对Stream | `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若玉宁运营专线/SKILL.md` | 派对内容+飞书闭环;与 **Soul 派对技能流(Stream)** 规约同权 |
|
||||
| K03 | **卡若·网站开发** | 永平、soul-api、管理端、小程序、用户管理、内容管理、全站修复、超级个体 | `02_卡人(水)/水岸_项目管理/Soul技能归口/卡若网站开发_永平三端/SKILL.md` | 永平三端与部署;聚合网站类 Agent 与 change-checklist |
|
||||
|
||||
**薄入口(与 W11 / W17 配套,可选读)**:`Soul派对项目管理_Cursor入口` → `02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对项目管理_Cursor入口/SKILL.md`;`Soul派对运营报表_Cursor入口` → `02_卡人(水)/水岸_项目管理/Soul技能归口/Soul派对运营报表_Cursor入口/SKILL.md`。平台申诉 **Soul/抖音/小红书** 真源为 **W10b** `平台账号申诉解封/SKILL.md`(勿再拆到 `.cursor/skills/`)。
|
||||
|
||||
---
|
||||
|
||||
@@ -215,8 +223,8 @@
|
||||
| 金 | 卡资 | 2 | 22 |
|
||||
| 水 | 卡人 | 4 | 14 |
|
||||
| 木 | 卡木 | 3 | 14 |
|
||||
| 火 | 卡火 | 4 | 16 |
|
||||
| 火 | 卡火 | 4 | 21 |
|
||||
| 土 | 卡土 | 4 | 9 |
|
||||
| **合计** | **5** | **15** | **75** |
|
||||
| **合计** | **5** | **15** | **80** |
|
||||
|
||||
> **卡路 Cursor 入口**:K01~K03 见上表「卡路项目簇」,不计入五行 75 技能合计。
|
||||
> **Soul / 卡若 Cursor 入口**:K01~K03 见上表「Soul技能归口」,不计入五行 80 技能合计。
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
Soul 运营全链路技能包:请在本机终端运行永平项目中的打包脚本,会在「下载」生成 zip。
|
||||
|
||||
python3 "/Users/karuo/Documents/开发/3、自营项目/一场soul的创业实验-永平/scripts/pack_soul_operation_skills.py"
|
||||
|
||||
详细说明:一场soul的创业实验-永平/scripts/README_Soul运营技能包.md
|
||||
@@ -1 +0,0 @@
|
||||
275757515
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user