yuanflow-cli 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,15 +29,40 @@ yuanflow-cli douyin video-detail "https://v.douyin.com/xxx/" --format agent-json
29
29
  yuanflow-cli shortcuts douyin
30
30
  yuanflow-cli commands list
31
31
  yuanflow-cli schema douyin.video-detail
32
+ yuanflow-cli schema comments.douyin.comments
33
+ yuanflow-cli comments collect --platform douyin --target "https://v.douyin.com/xxx/" --format agent-json
32
34
  yuanflow-cli list douyin
33
35
  ```
34
36
 
35
37
  环境变量:
36
38
 
37
39
  ```bash
38
- YUANFLOW_TOKEN=<你的令牌>
40
+ YUANCHUANG_API_TOKEN=<你的令牌>
39
41
  ```
40
42
 
43
+ token 优先级:`--token` > `YUANCHUANG_API_TOKEN` > 本地 `config.token`。独立 CLI 用户可以使用环境变量或 `config set-token`;在 YuanFlow 主程序内使用时,token 由主程序认证系统注入,不需要手动配置。
44
+
45
+ ### 作品评论采集
46
+
47
+ 评论采集统一走 `comments collect`,用于把 Agent 的自然语言需求稳定映射到平台评论接口:
48
+
49
+ ```bash
50
+ yuanflow-cli comments collect --platform douyin --target "https://v.douyin.com/xxx/" --format agent-json
51
+ yuanflow-cli comments collect --platform xiaohongshu --target note_id --action replies --comment-id root_comment_id --format agent-json
52
+ yuanflow-cli comments collect --platform youtube --target video_id --action comments --dry-run --format agent-json
53
+ ```
54
+
55
+ 常用参数:
56
+
57
+ - `--platform`:平台标识,如 `douyin`、`xiaohongshu`、`bilibili`、`wechat_mp`、`tiktok`、`instagram`、`kuaishou`、`reddit`、`twitter`、`weibo`、`youtube`、`zhihu`。
58
+ - `--action`:评论类型,默认 `comments`,可选 `replies`、`post_comments`、`post_replies`。
59
+ - `--target`:作品、文章、帖子、视频或回答 ID,也可以传支持的平台链接。
60
+ - `--comment-id`:采集二级评论或评论回复时使用。
61
+ - `--prefer fallback`:使用备用接口。
62
+ - `--dry-run`:只预览请求映射,不发起真实接口请求,也不要求 token。
63
+
64
+ Agent 可以先用 `yuanflow-cli commands list` 查看全部命令,再用 `yuanflow-cli schema comments.douyin.comments` 查看某个评论采集命令的参数、接口路径和返回说明。
65
+
41
66
  ## Skill 安装器
42
67
 
43
68
  ```bash
@@ -22,8 +22,15 @@ function defaultExists(targetPath) {
22
22
  return fs.existsSync(targetPath);
23
23
  }
24
24
 
25
+ function joinTargetPath(baseDir, ...segments) {
26
+ if (typeof baseDir === 'string' && baseDir.startsWith('/')) {
27
+ return path.posix.join(baseDir, ...segments);
28
+ }
29
+ return path.join(baseDir, ...segments);
30
+ }
31
+
25
32
  function getConfigHome(homeDir, env) {
26
- return (env && env.XDG_CONFIG_HOME) || path.join(homeDir, '.config');
33
+ return (env && env.XDG_CONFIG_HOME) || joinTargetPath(homeDir, '.config');
27
34
  }
28
35
 
29
36
  function getCanonicalSkillsDir({
@@ -37,117 +44,117 @@ function getCanonicalSkillsDir({
37
44
 
38
45
  function buildAgentDefinitions({ homeDir = os.homedir(), env = process.env } = {}) {
39
46
  const configHome = getConfigHome(homeDir, env);
40
- const codexHome = (env.CODEX_HOME || '').trim() || path.join(homeDir, '.codex');
41
- const claudeHome = (env.CLAUDE_CONFIG_DIR || '').trim() || path.join(homeDir, '.claude');
47
+ const codexHome = (env.CODEX_HOME || '').trim() || joinTargetPath(homeDir, '.codex');
48
+ const claudeHome = (env.CLAUDE_CONFIG_DIR || '').trim() || joinTargetPath(homeDir, '.claude');
42
49
 
43
50
  return {
44
51
  codex: {
45
52
  name: 'codex',
46
53
  displayName: 'Codex',
47
54
  projectSkillsDir: '.agents/skills',
48
- globalSkillsDir: path.join(codexHome, 'skills'),
55
+ globalSkillsDir: joinTargetPath(codexHome, 'skills'),
49
56
  markers: [codexHome],
50
57
  },
51
58
  'claude-code': {
52
59
  name: 'claude-code',
53
60
  displayName: 'Claude Code',
54
61
  projectSkillsDir: '.claude/skills',
55
- globalSkillsDir: path.join(claudeHome, 'skills'),
62
+ globalSkillsDir: joinTargetPath(claudeHome, 'skills'),
56
63
  markers: [claudeHome],
57
64
  },
58
65
  cursor: {
59
66
  name: 'cursor',
60
67
  displayName: 'Cursor',
61
68
  projectSkillsDir: '.agents/skills',
62
- globalSkillsDir: path.join(homeDir, '.cursor', 'skills'),
63
- markers: [path.join(homeDir, '.cursor')],
69
+ globalSkillsDir: joinTargetPath(homeDir, '.cursor', 'skills'),
70
+ markers: [joinTargetPath(homeDir, '.cursor')],
64
71
  },
65
72
  trae: {
66
73
  name: 'trae',
67
74
  displayName: 'Trae',
68
75
  projectSkillsDir: '.trae/skills',
69
- globalSkillsDir: path.join(homeDir, '.trae', 'skills'),
70
- markers: [path.join(homeDir, '.trae')],
76
+ globalSkillsDir: joinTargetPath(homeDir, '.trae', 'skills'),
77
+ markers: [joinTargetPath(homeDir, '.trae')],
71
78
  },
72
79
  opencode: {
73
80
  name: 'opencode',
74
81
  displayName: 'OpenCode',
75
82
  projectSkillsDir: '.agents/skills',
76
- globalSkillsDir: path.join(configHome, 'opencode', 'skills'),
77
- markers: [path.join(configHome, 'opencode')],
83
+ globalSkillsDir: joinTargetPath(configHome, 'opencode', 'skills'),
84
+ markers: [joinTargetPath(configHome, 'opencode')],
78
85
  },
79
86
  'github-copilot': {
80
87
  name: 'github-copilot',
81
88
  displayName: 'GitHub Copilot',
82
89
  projectSkillsDir: '.agents/skills',
83
- globalSkillsDir: path.join(homeDir, '.copilot', 'skills'),
84
- markers: [path.join(homeDir, '.copilot')],
90
+ globalSkillsDir: joinTargetPath(homeDir, '.copilot', 'skills'),
91
+ markers: [joinTargetPath(homeDir, '.copilot')],
85
92
  },
86
93
  'gemini-cli': {
87
94
  name: 'gemini-cli',
88
95
  displayName: 'Gemini CLI',
89
96
  projectSkillsDir: '.agents/skills',
90
- globalSkillsDir: path.join(homeDir, '.gemini', 'skills'),
91
- markers: [path.join(homeDir, '.gemini')],
97
+ globalSkillsDir: joinTargetPath(homeDir, '.gemini', 'skills'),
98
+ markers: [joinTargetPath(homeDir, '.gemini')],
92
99
  },
93
100
  'kimi-cli': {
94
101
  name: 'kimi-cli',
95
102
  displayName: 'Kimi Code CLI',
96
103
  projectSkillsDir: '.agents/skills',
97
- globalSkillsDir: path.join(configHome, 'agents', 'skills'),
98
- markers: [path.join(homeDir, '.kimi')],
104
+ globalSkillsDir: joinTargetPath(configHome, 'agents', 'skills'),
105
+ markers: [joinTargetPath(homeDir, '.kimi')],
99
106
  },
100
107
  windsurf: {
101
108
  name: 'windsurf',
102
109
  displayName: 'Windsurf',
103
110
  projectSkillsDir: '.windsurf/skills',
104
- globalSkillsDir: path.join(homeDir, '.codeium', 'windsurf', 'skills'),
105
- markers: [path.join(homeDir, '.codeium', 'windsurf')],
111
+ globalSkillsDir: joinTargetPath(homeDir, '.codeium', 'windsurf', 'skills'),
112
+ markers: [joinTargetPath(homeDir, '.codeium', 'windsurf')],
106
113
  },
107
114
  openclaw: {
108
115
  name: 'openclaw',
109
116
  displayName: 'OpenClaw',
110
117
  projectSkillsDir: 'skills',
111
- globalSkillsDir: path.join(homeDir, '.openclaw', 'skills'),
118
+ globalSkillsDir: joinTargetPath(homeDir, '.openclaw', 'skills'),
112
119
  markers: [
113
- path.join(homeDir, '.openclaw'),
114
- path.join(homeDir, '.clawdbot'),
115
- path.join(homeDir, '.moltbot'),
120
+ joinTargetPath(homeDir, '.openclaw'),
121
+ joinTargetPath(homeDir, '.clawdbot'),
122
+ joinTargetPath(homeDir, '.moltbot'),
116
123
  ],
117
124
  resolveGlobalSkillsDir(currentHomeDir, exists = defaultExists) {
118
- if (exists(path.join(currentHomeDir, '.openclaw'))) {
119
- return path.join(currentHomeDir, '.openclaw', 'skills');
125
+ if (exists(joinTargetPath(currentHomeDir, '.openclaw'))) {
126
+ return joinTargetPath(currentHomeDir, '.openclaw', 'skills');
120
127
  }
121
- if (exists(path.join(currentHomeDir, '.clawdbot'))) {
122
- return path.join(currentHomeDir, '.clawdbot', 'skills');
128
+ if (exists(joinTargetPath(currentHomeDir, '.clawdbot'))) {
129
+ return joinTargetPath(currentHomeDir, '.clawdbot', 'skills');
123
130
  }
124
- if (exists(path.join(currentHomeDir, '.moltbot'))) {
125
- return path.join(currentHomeDir, '.moltbot', 'skills');
131
+ if (exists(joinTargetPath(currentHomeDir, '.moltbot'))) {
132
+ return joinTargetPath(currentHomeDir, '.moltbot', 'skills');
126
133
  }
127
134
 
128
- return path.join(currentHomeDir, '.openclaw', 'skills');
135
+ return joinTargetPath(currentHomeDir, '.openclaw', 'skills');
129
136
  },
130
137
  },
131
138
  'trae-cn': {
132
139
  name: 'trae-cn',
133
140
  displayName: 'Trae CN',
134
141
  projectSkillsDir: '.trae/skills',
135
- globalSkillsDir: path.join(homeDir, '.trae-cn', 'skills'),
136
- markers: [path.join(homeDir, '.trae-cn')],
142
+ globalSkillsDir: joinTargetPath(homeDir, '.trae-cn', 'skills'),
143
+ markers: [joinTargetPath(homeDir, '.trae-cn')],
137
144
  },
138
145
  qoder: {
139
146
  name: 'qoder',
140
147
  displayName: 'Qoder',
141
148
  projectSkillsDir: '.qoder/skills',
142
- globalSkillsDir: path.join(homeDir, '.qoder', 'skills'),
143
- markers: [path.join(homeDir, '.qoder')],
149
+ globalSkillsDir: joinTargetPath(homeDir, '.qoder', 'skills'),
150
+ markers: [joinTargetPath(homeDir, '.qoder')],
144
151
  },
145
152
  'qwen-code': {
146
153
  name: 'qwen-code',
147
154
  displayName: 'Qwen Code',
148
155
  projectSkillsDir: '.qwen/skills',
149
- globalSkillsDir: path.join(homeDir, '.qwen', 'skills'),
150
- markers: [path.join(homeDir, '.qwen')],
156
+ globalSkillsDir: joinTargetPath(homeDir, '.qwen', 'skills'),
157
+ markers: [joinTargetPath(homeDir, '.qwen')],
151
158
  },
152
159
  };
153
160
  }
@@ -104,6 +104,14 @@ function resolveLocalSkillRoot(packageRoot) {
104
104
  }
105
105
 
106
106
  async function prepareSkillSource({ packageRoot }) {
107
+ const localRoot = resolveLocalSkillRoot(packageRoot);
108
+ if (localRoot) {
109
+ return {
110
+ sourceRoot: localRoot,
111
+ cleanup() {},
112
+ };
113
+ }
114
+
107
115
  const repoConfig = readRepositoryConfig(packageRoot);
108
116
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'yuanflow-skill-'));
109
117
  const archiveFile = path.join(tempRoot, `${repoConfig.repo}-${repoConfig.ref}.tar.gz`);
@@ -131,15 +139,6 @@ async function prepareSkillSource({ packageRoot }) {
131
139
  cloneRepository({ repoConfig, targetDir: cloneDir });
132
140
  sourceRoot = resolveRepositoryRoot(cloneDir);
133
141
  } catch (gitError) {
134
- const localRoot = resolveLocalSkillRoot(packageRoot);
135
- if (localRoot) {
136
- fs.rmSync(tempRoot, { recursive: true, force: true });
137
- return {
138
- sourceRoot: localRoot,
139
- cleanup() {},
140
- };
141
- }
142
-
143
142
  const archiveMessage = archiveError instanceof Error ? archiveError.message : String(archiveError);
144
143
  const gitMessage = gitError instanceof Error ? gitError.message : String(gitError);
145
144
  throw new Error(`下载 skill 仓库失败。archive: ${archiveMessage}; git: ${gitMessage}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yuanflow-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "YuanFlow API CLI and skill installer for supported AI coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,10 +9,21 @@ description: Use when the user asks about social-media API workflows, platform d
9
9
 
10
10
  ## 当前发布范围
11
11
 
12
- 当前 skill 目录只包含:
12
+ 当前 skill 目录包含:
13
13
 
14
14
  - 根目录这一份 `SKILL.md`
15
15
  - `yuanflow-cli/`
16
+ - `作品评论采集/`
17
+
18
+ ## 环境判断
19
+
20
+ 执行本 Skill 前,先判断当前运行环境:
21
+
22
+ 1. 如果当前 Agent 可用 YuanFlow 内置工具,例如 `yuanflow_cli_call` 或 `yuanflow_gateway_request`,优先使用 YuanFlow 内置工具。不要要求用户安装 npm,也不要要求用户配置 token。
23
+ 2. 如果当前不在 YuanFlow 内,且本地可执行 `yuanflow-cli --help`,使用本地 CLI。
24
+ 3. 如果当前不在 YuanFlow 内,且本地没有 `yuanflow-cli`,再提示用户安装 `npm install -g yuanflow-cli`。
25
+
26
+ 不要在回复、日志或文件中暴露 token。
16
27
 
17
28
  ## 分流规则
18
29
 
@@ -38,6 +49,18 @@ description: Use when the user asks about social-media API workflows, platform d
38
49
 
39
50
  - `yuanflow-cli`
40
51
 
52
+ ### 2. 走 `作品评论采集`
53
+
54
+ 遇到下面这些需求,优先进入这个子 Skill:
55
+
56
+ - 采集、抓取、导出或分析作品评论。
57
+ - 获取评论回复、二级评论、楼中楼、子评论。
58
+ - 用户给出抖音、小红书、B站、微信公众号、微信视频号、TikTok、Instagram、快手、Reddit、Twitter/X、微博、YouTube、知乎链接或 ID,并明确要评论数据。
59
+
60
+ 子 Skill 名称:
61
+
62
+ - `作品评论采集`
63
+
41
64
  ## 多需求时怎么处理
42
65
 
43
66
  如果用户一次提了多段流程,不要强行塞进一个子 Skill,按阶段拆开:
@@ -17,7 +17,19 @@ description: Use when the user needs atomic API calls for social-media platforms
17
17
  - 用户要用 CLI 调接口并保存 JSON
18
18
  - 用户要让 Agent 稳定解析调用结果
19
19
 
20
- ## 安装和更新
20
+ ## 调用方式选择
21
+
22
+ 优先级如下:
23
+
24
+ 1. YuanFlow 内置工具:如果当前工具列表里存在 `yuanflow_cli_call`,使用它调用社媒接口。
25
+ 2. YuanFlow 网关工具:如果只有 `yuanflow_gateway_request`,可用它访问受控服务接口。
26
+ 3. 本地 CLI:如果当前环境可执行 `yuanflow-cli --help`,使用本地 CLI。
27
+ 4. 安装提示:只有以上都不可用时,才提示用户安装 `npm install -g yuanflow-cli`。
28
+
29
+ 在 YuanFlow 主程序内,不要提示用户配置 token;token 由主程序 KEY 认证注入。
30
+ 在外部 Agent 环境中,使用环境变量 `YUANCHUANG_API_TOKEN`,也可以使用本地 `config set-token`。
31
+
32
+ ## 外部 Agent 安装和更新
21
33
 
22
34
  要求 Node.js 20 或更高版本。
23
35
 
@@ -44,7 +56,13 @@ npm view yuanflow-cli version
44
56
  yuanflow-cli --help
45
57
  ```
46
58
 
47
- ## 配置
59
+ ## 外部 Agent 配置
60
+
61
+ 独立 CLI 使用时,可以通过环境变量或本地 config 保存 token。token 优先级是:`--token` > `YUANCHUANG_API_TOKEN` > 本地 `config.token`。
62
+
63
+ ```powershell
64
+ $env:YUANCHUANG_API_TOKEN="<你的令牌>"
65
+ ```
48
66
 
49
67
  保存 Yuan API 令牌和服务地址:
50
68
 
@@ -63,13 +81,13 @@ yuanflow-cli douyin video-detail "https://v.douyin.com/xxx/" --token <你的令
63
81
  或用环境变量:
64
82
 
65
83
  ```powershell
66
- $env:YUANFLOW_TOKEN="<你的令牌>"
84
+ $env:YUANCHUANG_API_TOKEN="<你的令牌>"
67
85
  yuanflow-cli douyin hot-search
68
86
  ```
69
87
 
70
88
  ## Agent 调用规则
71
89
 
72
- 给 AI Agent 使用时,先查命令和 schema,再调用接口。
90
+ 给 AI Agent 使用时,先查命令和 schema,再调用接口。YuanFlow 内置环境下,优先调用 `yuanflow_cli_call`;外部 Agent 环境下,优先调用 `yuanflow-cli`。
73
91
 
74
92
  ```powershell
75
93
  yuanflow-cli commands list
@@ -114,9 +132,10 @@ yuanflow-cli schema douyin.video-detail
114
132
  yuanflow-cli shortcuts
115
133
  yuanflow-cli shortcuts douyin
116
134
  yuanflow-cli list douyin
117
- yuanflow-cli commands list
118
- yuanflow-cli commands describe xiaohongshu.search-notes
119
- yuanflow-cli schema xiaohongshu.search-notes
135
+ yuanflow-cli commands list
136
+ yuanflow-cli commands describe xiaohongshu.search-notes
137
+ yuanflow-cli schema xiaohongshu.search-notes
138
+ yuanflow-cli comments collect --platform douyin --target "https://v.douyin.com/xxx/" --format agent-json
120
139
  ```
121
140
 
122
141
  当前 registry 覆盖的平台包括:
@@ -156,6 +175,43 @@ yuanflow-cli bilibili video-detail "https://www.bilibili.com/video/BVxxx" -o bil
156
175
  | 作品评论 | `yuanflow-cli douyin comments <aweme_id> --count 20 --cursor 0 --format agent-json` |
157
176
  | 抖音热榜 | `yuanflow-cli douyin hot-search --format agent-json` |
158
177
 
178
+ ### 作品评论采集统一入口
179
+
180
+ 评论采集优先使用统一入口:
181
+
182
+ ```powershell
183
+ yuanflow-cli comments collect --platform douyin --target "https://v.douyin.com/xxx/" --format agent-json
184
+ ```
185
+
186
+ 常用参数:
187
+
188
+ - `--platform`:平台,如 `douyin`、`xiaohongshu`、`bilibili`、`wechat_mp`、`wechat_channels`、`tiktok`、`instagram`、`kuaishou`、`reddit`、`twitter`、`weibo`、`youtube`、`zhihu`。
189
+ - `--action`:`comments` 一级评论,`replies` 评论回复;YouTube 社区帖子使用 `post_comments` 或 `post_replies`。
190
+ - `--target`:作品链接、文章链接、视频 ID、笔记 ID、BV 号等。
191
+ - `--comment-id`:采集回复时的父评论 ID。
192
+ - `--cursor`:翻页游标。
193
+ - `--count`:返回数量。
194
+ - `--page`:B站页码。
195
+ - `--prefer fallback`:主接口失败时切换备用接口。
196
+ - `--extra`:JSON 字符串,用于公众号 `content_id` 等补充参数。
197
+
198
+ 在 YuanFlow 主程序内,用 `yuanflow_cli_call` 调用时传参数数组:
199
+
200
+ ```json
201
+ {
202
+ "args": [
203
+ "comments",
204
+ "collect",
205
+ "--platform",
206
+ "douyin",
207
+ "--target",
208
+ "https://v.douyin.com/xxx/",
209
+ "--format",
210
+ "agent-json"
211
+ ]
212
+ }
213
+ ```
214
+
159
215
  ### 小红书
160
216
 
161
217
  | 需求 | 命令 |
@@ -205,3 +261,4 @@ yuanflow-cli bilibili video-detail "https://www.bilibili.com/video/BVxxx" -o bil
205
261
  - 调真实接口前,先用 `--dry-run` 核对 URL、method、headers 和 body。
206
262
  - Agent 自动处理结果时,统一加 `--format agent-json`。
207
263
  - 不要把 token 写进文档、日志或最终回复。
264
+ - 不要要求用户提供或粘贴 token。
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: 作品评论采集
3
+ description: Use when the user needs to collect, export, inspect, or continue comment and reply data for Douyin, Xiaohongshu, Bilibili, WeChat MP, WeChat Channels, TikTok, Instagram, Kuaishou, Reddit, Twitter/X, Weibo, YouTube, or Zhihu posts.
4
+ builtin_skill_version: 1.0.0
5
+ tags:
6
+ - 自媒体
7
+ - 评论采集
8
+ - 抖音
9
+ - 小红书
10
+ - B站
11
+ - 微信公众号
12
+ - 微信视频号
13
+ - TikTok
14
+ - Instagram
15
+ - 快手
16
+ - Reddit
17
+ - Twitter
18
+ - 微博
19
+ - YouTube
20
+ - 知乎
21
+ emoji: 💬
22
+ ---
23
+
24
+ # 作品评论采集
25
+
26
+ 本技能把用户的评论采集需求稳定映射到 `yuanflow-cli comments collect`。在 YuanFlow 主程序内,优先使用受控工具 `yuanflow_cli_call`;它会由主程序认证系统注入 `YUANCHUANG_API_TOKEN`,不要让用户粘贴 KEY,不要在回复、日志或文件里暴露 token。
27
+
28
+ ## 什么时候使用
29
+
30
+ 用户出现以下意图时使用:
31
+
32
+ - 采集、抓取、获取、导出某个作品、文章、帖子、回答的评论。
33
+ - 获取某条评论下的回复、二级评论、楼中楼、子评论。
34
+ - 用户给出抖音、小红书、B站、微信公众号文章、微信视频号、TikTok、Instagram、快手、Reddit、Twitter/X、微博、YouTube、知乎链接或 ID,并要求看评论数据。
35
+
36
+ 不要用于发评论、回复评论、点赞评论、绕过平台限制、批量抓取或未授权数据访问。作品详情、作品下载、用户主页、综合搜索应交给 `yuanflow-cli` 其它命令。
37
+
38
+ ## 调用优先级
39
+
40
+ 1. YuanFlow 主程序内:调用 `yuanflow_cli_call`,参数数组从 `comments collect` 开始。
41
+ 2. 外部 Agent 且本机有 CLI:执行 `yuanflow-cli comments collect ...`。
42
+ 3. 外部 Agent 且没有 CLI:再提示用户安装 `npm install -g yuanflow-cli`。
43
+
44
+ YuanFlow 内置调用示例:
45
+
46
+ ```json
47
+ {
48
+ "args": [
49
+ "comments",
50
+ "collect",
51
+ "--platform",
52
+ "douyin",
53
+ "--target",
54
+ "https://v.douyin.com/xxx/",
55
+ "--format",
56
+ "agent-json"
57
+ ]
58
+ }
59
+ ```
60
+
61
+ 外部 CLI 示例:
62
+
63
+ ```powershell
64
+ yuanflow-cli comments collect --platform douyin --target "https://v.douyin.com/xxx/" --format agent-json
65
+ ```
66
+
67
+ ## 先查命令和 schema
68
+
69
+ 不确定参数时先查,不要猜:
70
+
71
+ ```json
72
+ {"args":["commands","list"]}
73
+ {"args":["schema","comments.douyin.comments"]}
74
+ ```
75
+
76
+ 命令 key 格式是:
77
+
78
+ ```text
79
+ comments.<platform>.<action>
80
+ ```
81
+
82
+ 例如 `comments.douyin.comments`、`comments.xiaohongshu.replies`、`comments.youtube.post_comments`。
83
+
84
+ ## 参数规则
85
+
86
+ 通用参数:
87
+
88
+ - `--platform`:平台标识。
89
+ - `--action`:`comments` 一级评论;`replies` 评论回复;YouTube 社区帖子用 `post_comments` 或 `post_replies`。默认 `comments`。
90
+ - `--target`:作品、文章、帖子或回答 ID,也可以传支持的平台链接。
91
+ - `--comment-id`:多数平台采集回复时必填。
92
+ - `--cursor`:翻页游标,必须来自上一次响应,不要编造。
93
+ - `--count`:返回数量,仅平台支持时传。
94
+ - `--page`:B站页码。
95
+ - `--prefer fallback`:主接口失败或用户要求备用接口时使用。
96
+ - `--extra`:JSON 字符串补充参数,例如微信公众号回复需要 `content_id`。
97
+ - `--format agent-json`:Agent 调用时必须加,便于稳定解析。
98
+
99
+ ## 平台识别
100
+
101
+ | 平台参数 | 用户常见说法 | 链接或 ID 标识 |
102
+ |---|---|---|
103
+ | `douyin` | 抖音、Douyin | `douyin.com`、`iesdouyin.com`、`v.douyin.com`、`aweme_id` |
104
+ | `xiaohongshu` | 小红书、XHS | `xiaohongshu.com`、`xhslink.com`、`note_id` |
105
+ | `bilibili` | B站、哔哩哔哩 | `bilibili.com`、`b23.tv`、`BV` 号 |
106
+ | `wechat_mp` | 微信公众号、公众号文章 | `mp.weixin.qq.com/s/` |
107
+ | `wechat_channels` | 微信视频号、视频号 | 视频号视频 ID、视频号详情里的 `id` |
108
+ | `tiktok` | TikTok | `tiktok.com`、`vm.tiktok.com`、`aweme_id` |
109
+ | `instagram` | Instagram、ins | `instagram.com/p/`、`instagram.com/reel/`、shortcode、media_id |
110
+ | `kuaishou` | 快手 | `kuaishou.com/short-video/`、`photo_id` |
111
+ | `reddit` | Reddit | `reddit.com/r/.../comments/`、`t3_` 帖子 ID |
112
+ | `twitter` | Twitter、X | `twitter.com`、`x.com`、推文数字 ID |
113
+ | `weibo` | 微博 | `weibo.com`、微博 `id`、`mid` |
114
+ | `youtube` | YouTube、油管 | `youtube.com/watch?v=`、`youtu.be/`、视频 ID、社区帖子 ID |
115
+ | `zhihu` | 知乎 | 知乎回答 ID、评论 ID |
116
+
117
+ 如果短链文本无法判断平台,先按域名判断;仍无法判断就追问平台。
118
+
119
+ ## 接口映射
120
+
121
+ Agent 不需要直接拼接口地址,只需要选对 `platform`、`action` 和参数。
122
+
123
+ | 平台 | action | 首选能力 | 主要参数 | 备用能力 |
124
+ |---|---|---|---|---|
125
+ | 抖音 | `comments` | 抖音作品一级评论 | `aweme_id`、`cursor`、`count` | 支持 |
126
+ | 抖音 | `replies` | 抖音评论回复 | `item_id`、`comment_id`、`cursor`、`count` | 支持 |
127
+ | 小红书 | `comments` | 小红书笔记一级评论 | `note_id`、`cursor` | 支持 |
128
+ | 小红书 | `replies` | 小红书笔记二级评论 | `note_id`、`comment_id`、`cursor`、`count` | 支持 |
129
+ | B站 | `comments` | B站视频一级评论 | `bv_id`、`page` | 支持 |
130
+ | B站 | `replies` | B站评论回复 | `bv_id`、`comment_id`、`page` | 暂无 |
131
+ | 微信公众号 | `comments` | 公众号文章一级评论 | `url`、`comment_id`、`cursor` | 暂无 |
132
+ | 微信公众号 | `replies` | 公众号文章评论回复 | `url`、`comment_id`、`extra.content_id`、`cursor` | 暂无 |
133
+ | 微信视频号 | `comments` | 视频号一级评论 | `id`、`cursor` | 暂无 |
134
+ | 微信视频号 | `replies` | 视频号评论回复 | `id`、`comment_id`、`cursor` | 暂无 |
135
+ | TikTok | `comments` | TikTok 作品一级评论 | `aweme_id`、`cursor`、`count` | 支持 |
136
+ | TikTok | `replies` | TikTok 评论回复 | `item_id`、`comment_id`、`cursor`、`count` | 支持 |
137
+ | Instagram | `comments` | Instagram 帖子一级评论 | `code_or_url`、`cursor` | 支持 |
138
+ | Instagram | `replies` | Instagram 评论回复 | `code_or_url`、`comment_id`、`cursor` | 支持 |
139
+ | 快手 | `comments` | 快手作品一级评论 | `photo_id`、`cursor` | 支持 |
140
+ | 快手 | `replies` | 快手作品二级评论 | `photo_id`、`comment_id`、`cursor` | 暂无 |
141
+ | Reddit | `comments` | Reddit 帖子一级评论 | `post_id`、`cursor` | 暂无 |
142
+ | Reddit | `replies` | Reddit 评论回复 | `post_id`、`cursor` | 暂无 |
143
+ | Twitter/X | `comments` | 推文评论 | `tweet_id`、`cursor` | 支持 |
144
+ | 微博 | `comments` | 微博一级评论 | `id`、`count`、`cursor` | 支持 |
145
+ | 微博 | `replies` | 微博子评论 | `id`、`count`、`cursor` | 暂无 |
146
+ | YouTube | `comments` | 视频一级评论 | `video_id`、`cursor` | 支持 |
147
+ | YouTube | `replies` | 视频二级评论 | `continuation_token` | 支持 |
148
+ | YouTube | `post_comments` | 社区帖子一级评论 | `post_id`、`cursor` | 暂无 |
149
+ | YouTube | `post_replies` | 社区帖子评论回复 | `continuation_token` | 暂无 |
150
+ | 知乎 | `comments` | 回答评论区 | `answer_id`、`count`、`cursor` | 暂无 |
151
+ | 知乎 | `replies` | 子评论区 | `comment_id`、`count`、`cursor` | 暂无 |
152
+
153
+ ## 输出要求
154
+
155
+ 成功后用中文简短说明:
156
+
157
+ - 调用了哪个平台和评论类型。
158
+ - 使用首选能力还是备用能力。
159
+ - 如果响应里有评论数量、下一页游标或 continuation token,要提示用户。
160
+ - 用户要求导出时,再整理为表格、JSON 或文件。
161
+
162
+ 失败时按 `agent-json` 的 `error.code` 和 `error.message` 解释。常见处理:
163
+
164
+ - `TOKEN_MISSING`:提示先完成 YuanFlow KEY 认证或外部 CLI 环境变量配置。
165
+ - `BAD_ARGUMENT`:检查平台、action、target、comment-id、cursor。
166
+ - `AUTH_INVALID`:认证无效或权限不足。
167
+ - `UPSTREAM_ERROR`、`NETWORK_ERROR`:说明上游或网络失败,可稍后重试,或在有备用能力的平台加 `--prefer fallback`。
168
+
169
+ 不要展示完整 Authorization 请求头、token、cookie 或敏感账号信息。
@@ -1,5 +1,6 @@
1
1
  import { listEndpoints } from './registry.js';
2
2
  import { listShortcuts } from './shortcuts.js';
3
+ import { listCommentCommands } from './comment-collector.js';
3
4
 
4
5
  const ERROR_MAP = [
5
6
  {
@@ -81,7 +82,8 @@ export function getCommandName(platform, command) {
81
82
  export function buildCommandRegistry() {
82
83
  const shortcuts = listShortcuts().map((shortcut) => shortcutToCommand(shortcut));
83
84
  const endpoints = listEndpoints().map((endpoint) => endpointToCommand(endpoint));
84
- return [...shortcuts, ...endpoints].sort((left, right) =>
85
+ const commentCommands = listCommentCommands();
86
+ return [...shortcuts, ...endpoints, ...commentCommands].sort((left, right) =>
85
87
  left.key.localeCompare(right.key),
86
88
  );
87
89
  }
package/src/cli.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  isAgentJsonFormat,
17
17
  } from './agent-protocol.js';
18
18
  import { findShortcut, listShortcuts } from './shortcuts.js';
19
+ import { collectComments } from './comment-collector.js';
19
20
 
20
21
  export async function main(argv) {
21
22
  const args = argv.slice(2);
@@ -46,6 +47,11 @@ export async function main(argv) {
46
47
  return;
47
48
  }
48
49
 
50
+ if (command === 'comments') {
51
+ await handleComments(rest);
52
+ return;
53
+ }
54
+
49
55
  if (command === 'schema') {
50
56
  await handleSchema(rest);
51
57
  return;
@@ -210,6 +216,33 @@ async function handleCall(args) {
210
216
  });
211
217
  }
212
218
 
219
+ async function handleComments(args) {
220
+ const { positionals, options } = parseOptions(args);
221
+ const [action = 'collect'] = positionals;
222
+ if (action !== 'collect') {
223
+ throw new Error('未知 comments 命令。用法:yuanflow-cli comments collect --platform douyin --target <作品链接或ID>');
224
+ }
225
+ const platform = options.named?.platform;
226
+ const target = options.named?.target || positionals[1];
227
+ if (!platform) {
228
+ throw new Error('缺少 --platform,例如 douyin、xiaohongshu、bilibili。');
229
+ }
230
+ if (!target) {
231
+ throw new Error('缺少 --target 或目标位置参数。');
232
+ }
233
+ const result = await collectComments({
234
+ platform,
235
+ action: options.named?.action || 'comments',
236
+ target,
237
+ prefer: options.named?.prefer || 'primary',
238
+ options,
239
+ });
240
+ await outputResult(result, options, {
241
+ command: 'comments collect',
242
+ meta: { endpoint: result.endpoint.path, kind: 'comment-collector' },
243
+ });
244
+ }
245
+
213
246
  async function handleGeneratedCommand(platform, args) {
214
247
  if (!getPlatforms().includes(platform)) {
215
248
  throw new Error(`未知平台:${platform}。可用平台:${getPlatforms().join(', ')}`);
@@ -363,10 +396,13 @@ function printHelp() {
363
396
  yuanflow-cli shortcuts douyin
364
397
  yuanflow-cli commands list
365
398
  yuanflow-cli schema douyin.video-detail
399
+ yuanflow-cli comments collect --platform douyin --target "https://v.douyin.com/xxx/" --dry-run
366
400
  yuanflow-cli list douyin
367
401
 
368
402
  说明:
369
403
  所有请求都会调用 Yuan API 的 /social/*path,并使用 Authorization: Bearer <token>。
404
+ token 优先级:--token > YUANCHUANG_API_TOKEN > 本地 config.token。
405
+ YuanFlow 主程序内使用时,token 由主程序认证系统注入,不需要手动配置。
370
406
  AI Agent 推荐使用 --format agent-json 获取稳定 JSON 外壳。
371
407
  `);
372
408
  }