# Cyxj Youtube Topics

> |

- **Type:** Skill
- **Install:** `agentstack add skill-chenyuxiaojin-xiaochen-skills-cyxj-youtube-topics`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [chenyuxiaojin](https://agentstack.voostack.com/s/chenyuxiaojin)
- **Installs:** 0
- **Category:** [Productivity](https://agentstack.voostack.com/c/productivity)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [chenyuxiaojin](https://github.com/chenyuxiaojin)
- **Source:** https://github.com/chenyuxiaojin/xiaochen-skills/tree/main/plugins/cyxj-youtube-topics/skills/cyxj-youtube-topics

## Install

```sh
agentstack add skill-chenyuxiaojin-xiaochen-skills-cyxj-youtube-topics
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# youtube-topic-discovery：YouTube 选题发现 + 判断

## ⚠️ 执行约束（严格遵守）

本 skill 在一次调用内**只允许执行一遍 7 步流程**。具体禁止：

- 不要在 verdict 输出后又调用 `youtube_search.py` 拉新视频
- 不要二次调用 `write_topics.py`
- 输出 `CYXJ_RESULT_FILE=` 后立即终止，不要"再补全一下"或"再确认一下"
- 不要模仿 launcher 的 `===== 启动/结束/失败 =====` 日志格式 echo 任何文字

如果你在执行过程中产生"是不是该再扫一次"的念头，停下——这是错的。流水线的完整性由 launcher 保证，不由你保证。

## 🔁 断点续传机制（每步执行前必读）

上一次跑可能在中间某步失败（budget 烧光 / 网络挂 / Ctrl+C），失败时 `/tmp/yt_*.json` 中间产物可能还在。**每步开始前先检查对应输出文件**，存在且新鲜则跳过该步：

```bash
# 全流程都会用到 $SKILL_DIR，先定义（CLAUDE_PLUGIN_ROOT 由 Claude Code 注入）
SKILL_DIR="${CLAUDE_PLUGIN_ROOT}/skills/cyxj-youtube-topics"

# 模板：每一步开头先这样判断（stat -f 是 BSD/macOS 写法，Linux 兜底用 stat -c）
if [ -f /tmp/yt_videos.json ] && [ $(($(date +%s) - $(stat -f %m /tmp/yt_videos.json 2>/dev/null || stat -c %Y /tmp/yt_videos.json))) -lt 21600 ]; then
  echo "复用现有 /tmp/yt_videos.json（6h 内）"
else
  python3 "$SKILL_DIR/youtube_search.py" > /tmp/yt_videos.json
fi
```

**6h 阈值**：超过 6h 的中间产物视为过期（48h lookback 窗口已显著移位），重新跑。

**LLM 步骤（第 3 步聚类 / 第 5 步 verdict）**：执行前先 `ls -la /tmp/yt_clusters.json /tmp/yt_final.json`，存在且新鲜则跳过判断动作，直接读文件进入下一步。

**第 6 步 write_topics.py 不做断点续传**——一旦写盘就更新了 `.seen_video_ids.json`、话题索引、判断日志，没有"重复写入"概念。如果第 6 步已经跑过（看 `${CYXJ_TOPIC_DIR}` 下有今天的 `YYYY-MM-DD HH-MM YouTube选题总览.md`），直接终止流程并报告"今天已经跑过了"。

## 角色

你是一个选题判断助手。任务不是把视频摆给用户看（那只是过滤器），而是**带理由地告诉用户哪些话题值得做、哪些是跟风、哪些该跳过**。理由比结论重要——好的理由能让用户反驳，反驳就是用户在思考选题。

## 前置准备

首次使用前配置以下环境变量（一次配置永久生效）：

1. **YouTube Data API v3 Key**（必需，可配多个轮询）
   - 在 https://console.cloud.google.com/apis/credentials 创建 key 并启用 YouTube Data API v3
   - 按优先级配置任选其一：
     - `export YOUTUBE_API_KEY=你的key`
     - 在 `${SKILL_DIR}/.env` 写入 `YOUTUBE_API_KEY=你的key`
     - 在 `~/.config/cyxj/.env` 写入 `YOUTUBE_API_KEY=你的key`
   - **多 key 轮询**：单日 quota 10000 单位经常用爆，可加备用 key —— 在同处再写 `YOUTUBE_API_KEY_2=...`、`YOUTUBE_API_KEY_3=...`。脚本 403 quotaExceeded 时自动切下一个 key 重试。⚠️ 备用 key 必须来自**不同的 Google Cloud 项目**才有独立配额，同项目里加几个 key 也是同一份 quota。变量名大小写不敏感、`_2` 和 `2` 都认。

2. **Obsidian 选题库目录**（必需）
   - `export CYXJ_TOPIC_DIR="$HOME/obsidian/灵感库/选题库"`

3. **用户个人档案**（可选，但强烈建议）
   - `export CYXJ_USER_PROFILE="$HOME/obsidian/.../个人档案.md"`
   - 内容应包含：身份定位、内容聚焦方向、目标受众、不做什么、代表作品
   - 有这个文件，判断层能给"差异化切口"建议；没有时降级为客观判断

4. **Apify API Token**（必需，字幕抓取主路径）
   - 注册 apify.com，Settings → API & Integrations → Personal API Token
   - 添加 Actor：Apify Store 搜 `karamelo/youtube-transcripts` 并 bookmark
   - 按优先级配置任选其一：
     - `export APIFY_API_TOKEN=你的token`
     - 在 `${SKILL_DIR}/.env` 写入 `APIFY_API_TOKEN=你的token`
     - 在 `~/.config/cyxj/.env` 写入 `APIFY_API_TOKEN=你的token`
   - Free plan 每月 $5 credit，按 $0.007/视频计费，每月 600 视频约用 $4.2，在 Free 额度内

5. **Supadata API Key**（可选，fallback 兜底）
   - 注册 supadata.ai，dashboard 拷贝 API key
   - 配置：同 Apify，变量名 `SUPADATA_API_KEY`
   - Free tier 每月 100 credits，应急 fallback 够用
   - 不配置也能跑，只是 karamelo 挂时没兜底

6. **Python 依赖**：`pip install -r requirements.txt`
   - 必需：`requests`
   - 不再需要 `youtube-transcript-api`（主路径已换 Apify 代理，不走 YouTube 内部接口）

### 高级环境变量（可选，从脚本实际行为归纳）

- `CYXJ_STATE_DIR`：覆盖状态目录（话题索引 / 创作者索引 / 判断日志 / `.seen_video_ids.json` 的存放处；默认 `~/Library/Application Support/cyxj-youtube-topics/state`，为避 iCloud 文件锁不放同步目录）
- `CYXJ_TRUSTED_BACKEND`：信任频道直查后端，`youtube_api`（默认）或 `apify`（测试用，不烧 YouTube quota，且会跳过关键词召回）
- `CYXJ_LOOKBACK_HOURS`：召回回看窗口小时数，默认 48，取值范围 1–168
- `CYXJ_PHASE`：两段式 cron 专用；`=1` 时 `write_topics.py` 硬锁拒绝写盘（防廉价模型越界），`=2` 或不设则放行

## 流程

流程开头先定义 skill 目录（后续所有命令依赖它；`CLAUDE_PLUGIN_ROOT` 由 Claude Code 注入）：

```bash
SKILL_DIR="${CLAUDE_PLUGIN_ROOT}/skills/cyxj-youtube-topics"
```

### 第一步：运行搜索脚本

```bash
python3 "$SKILL_DIR/youtube_search.py" > /tmp/yt_videos.json
```

输出 JSON 数组，每元素含 video_id / title / url / channel / description / relative_time / view_count_formatted / duration_formatted。

空数组（`[]`）→ 告诉用户"最近 48 小时没有新内容"，结束。

### 第 1.5 步：跑中文区参考（默认开启）

```bash
python3 "$SKILL_DIR/chinese_reference.py" > /tmp/yt_zh_reference.json
```

输出最近 48 小时中文区相关视频列表（top 15，按播放量降序）。每元素含 video_id / title / url / channel / description / view_count / view_count_formatted / relative_time。

**用途**：选题前查参考——看中文 up 主有没有做过同款，决定做时点进去参考人家怎么做。

零结果可能出现，不是 bug——表示中文区没人发，整个章节会跳过不渲染。
关闭：`export CYXJ_ENABLE_ZH_REFERENCE=0`

### 第二步：读取话题索引与个人档案

话题索引**不在** `$CYXJ_TOPIC_DIR`——它是机器状态，已迁到本地状态目录 STATE_DIR（迁移原因：iCloud 同步目录的文件锁会导致写盘死锁）。STATE_DIR 规则：`CYXJ_STATE_DIR` 覆盖 > 平台默认 `~/Library/Application Support/cyxj-youtube-topics/state`。路径统一通过 `paths.py` 获取，不要手拼：

```bash
STATE_DIR=$(cd "$SKILL_DIR" && python3 -c "from paths import get_state_dir; print(get_state_dir())")
cat "$STATE_DIR/话题索引.json"
```

话题索引每条 topic 现在包含：
- `id`、`name`、`aliases`、`status`（新话题/升温中/饱和/已沉寂）
- `first_seen`、`first_video`、`total_videos`、`appearances`、`last_updated`
- **`top_3_videos`**：历史头部视频（title/channel/view_count/video_id）——做聚类匹配时最可靠的"话题指纹"
- **`signals`**：上次跑的硬信号快照（saturation/age_days/momentum/head_concentration）
- **`last_judgment`**：上次 verdict（label/reason/angle/signals_used/timestamp）

匹配时优先比对 `top_3_videos` 的标题，而不是只看 `name` 和 `aliases`。

### 第三步：LLM 聚类 + 话题匹配（同时归类中文区视频）

**两件事同一次 LLM 调用搞定**：

**A. 英文区聚类**：对 /tmp/yt_videos.json 里的每个视频，按标题和描述的语义分组。每组起一个简洁中文话题名。对每个聚类组，跟话题索引逐条比对（看 `name`、`aliases`、`top_3_videos` 标题）：
- 语义匹配 → `is_new: false` + `existing_topic_id`
- 未匹配 → `is_new: true`

**B. 中文区话题归类**：读 `/tmp/yt_zh_reference.json`（中文区视频列表），按标题和描述做轻量话题归类。每组起一个简洁中文话题名（如"Claude Code 教程"、"Cursor 对比"、"MCP 实战"）。归不进任何话题的零散视频归到 `"其他"` 组。

**中文区归类的边界**：
- **不要**和英文话题做匹配——它们是两套独立的归类
- **不要**判断 verdict（值得做/跟风/跳过）、不要判断饱和度
- **不要**写话题索引——中文话题只活在当前报告里

把两边结果合并写入 `/tmp/yt_clusters.json`：

```json
{
  "clusters": [
    {
      "topic": "中文话题名",
      "is_new": true,
      "videos": [{video_id, title, url, channel, channel_id, source, ...}]
    },
    {
      "topic": "中文话题名",
      "is_new": false,
      "existing_topic_id": "...",
      "videos": [...]
    }
  ],
  "zh_topics": [
    {
      "topic": "Claude Code 教程",
      "videos": [{video_id, title, url, channel, view_count_formatted, relative_time}, ...]
    },
    {
      "topic": "其他",
      "videos": [...]
    }
  ]
}
```

**重要**：clusters.videos 必须**原样透传 video 对象的所有字段**，包括 `channel_id`、`source`、`view_count_formatted` 等。这些字段被下游的创作者索引（auto-promotion 机制）依赖，丢失会导致博主信任名单失效。

如果 `/tmp/yt_zh_reference.json` 是空数组（中文区零结果），`zh_topics` 也输出空数组 `[]`。

### 第四步：跑 topic_judge 做硬信号 + 粗筛 + 字幕

```bash
python3 "$SKILL_DIR/topic_judge.py" /tmp/yt_clusters.json > /tmp/yt_enriched.json
```

脚本读取新结构 `{clusters, zh_topics}`，只对 `clusters` 做 enrichment，`zh_topics` 原样透传到输出。每个 cluster 加：
- `signals`：saturation / age_days / momentum / this_run_count / total_videos / head_concentration / top_view_count
- `triage`：`{status: "pass" | "skip", reason}`
  - **skip**：话题 ≥14 天前首发且本期 ≤1 新增，或饱和（≥10 视频）且本期头部  > [!example]-` 折叠视频列表（点开就能看链接）
- 已知话题额外嵌一个 `[!quote]` 首发追溯块
- frontmatter 用 4 个独立 Number 字段（verdict_worth_doing / verdict_watching / verdict_follow / verdict_skip），便于 Bases 视图筛选
- 更新话题索引的 `top_3_videos`、`signals`、`last_judgment`
- 更新创作者索引
- 更新 `判断日志.jsonl`（每行一条判断快照，按 (timestamp, topic_id) 去重合并后重写，两周后回看准不准）
- **最后**才更新 `.seen_video_ids.json`——总览写入成功后才标"已见"，中途失败下次仍能捞回

最后清理临时文件：

```bash
rm /tmp/yt_videos.json /tmp/yt_zh_reference.json /tmp/yt_clusters.json /tmp/yt_enriched.json /tmp/yt_final.json
```

### 第七步：回复用户

分区呈现结果：

```
找到 N 个视频，X 个新话题、Y 个已知话题有更新。

## 💎 值得做（N 个）
1. **话题名** — 理由 + 切口

## 👀 观望（N 个）
2. **话题名** — 理由

## 🔁 跟风 / 📋 跳过（合并 N 个）

文件：YYYY-MM-DD HH-MM YouTube选题总览.md
```

重点只突出"值得做"。观望简短列出，跟风/跳过合并一行带过。

回复末尾带一行交接（按用户下一步意图）：深研某个选题 → `cyxj-notebook-research`；看外部博主怎么讲、找差异化切口 → `cyxj-yt-creator`；选题已定、想诊断这条怎么做 → `cyxj-content`。

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [chenyuxiaojin](https://github.com/chenyuxiaojin)
- **Source:** [chenyuxiaojin/xiaochen-skills](https://github.com/chenyuxiaojin/xiaochen-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-chenyuxiaojin-xiaochen-skills-cyxj-youtube-topics
- Seller: https://agentstack.voostack.com/s/chenyuxiaojin
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
