yuanflow-cli 0.1.5 → 0.1.7

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.
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: 综合用户搜索工具
3
+ description: Use when the user wants to search creators, accounts, users, channels, authors, or profiles across Douyin, Xiaohongshu, WeChat Channels, TikTok, Instagram, Kuaishou, Weibo, YouTube, or Zhihu.
4
+ builtin_skill_version: 1.0.0
5
+ tags:
6
+ - 自媒体
7
+ - 用户搜索
8
+ - 作者搜索
9
+ - 账号搜索
10
+ emoji: 👤
11
+ ---
12
+
13
+ # 综合用户搜索工具
14
+
15
+ 本技能把用户的“搜索账号、作者、用户、频道、达人”需求稳定映射到 `yuanflow-cli search users`。在 YuanFlow 主程序内,优先调用受控工具 `yuanflow_cli_call`;它会由主程序认证系统注入 `YUANCHUANG_API_TOKEN`,不要让用户粘贴 KEY,不要在回复、日志或文件里暴露 token。
16
+
17
+ ## 什么时候使用
18
+
19
+ 用户出现以下意图时使用:
20
+
21
+ - 按关键词搜索某个平台上的账号、作者、达人、用户、频道。
22
+ - 查找某个品牌、昵称、抖音号、小红书号、YouTube 频道等候选账号。
23
+ - 做账号调研、达人筛选、用户线索查找。
24
+
25
+ 不要用于搜索作品、笔记、文章、帖子或视频;内容搜索应使用 `综合搜索工具`。不要用于批量抓取、绕过平台限制或未授权数据访问。
26
+
27
+ ## 调用优先级
28
+
29
+ 1. YuanFlow 主程序内:调用 `yuanflow_cli_call`,参数数组从 `search users` 开始。
30
+ 2. 外部 Agent 且本机有 CLI:执行 `yuanflow-cli search users ...`。
31
+ 3. 外部 Agent 且没有 CLI:再提示用户安装 `npm install -g yuanflow-cli`。
32
+
33
+ YuanFlow 内置调用示例:
34
+
35
+ ```json
36
+ {
37
+ "args": [
38
+ "search",
39
+ "users",
40
+ "--platform",
41
+ "instagram",
42
+ "--keyword",
43
+ "nasa",
44
+ "--format",
45
+ "agent-json"
46
+ ]
47
+ }
48
+ ```
49
+
50
+ 外部 CLI 示例:
51
+
52
+ ```powershell
53
+ yuanflow-cli search users --platform instagram --keyword "nasa" --format agent-json
54
+ ```
55
+
56
+ ## 先查命令和 schema
57
+
58
+ 不确定参数时先查,不要猜:
59
+
60
+ ```json
61
+ {"args":["commands","list"]}
62
+ {"args":["schema","search.instagram.users"]}
63
+ {"args":["schema","search.douyin.users"]}
64
+ ```
65
+
66
+ 命令 key 格式是:
67
+
68
+ ```text
69
+ search.<platform>.users
70
+ ```
71
+
72
+ ## 平台识别
73
+
74
+ | 平台参数 | 用户常见说法 | 搜索对象 | 常用补充参数 |
75
+ |---|---|---|---|
76
+ | `douyin` | 抖音、Douyin | 用户、达人、账号 | `--cursor` |
77
+ | `xiaohongshu` | 小红书、XHS | 用户 | `--page` |
78
+ | `wechat_channels` | 微信视频号、视频号 | 视频号用户 | `--page` |
79
+ | `tiktok` | TikTok | 用户 | `--offset`、`--count` |
80
+ | `instagram` | Instagram、ins | 用户 | `--rank-token` |
81
+ | `kuaishou` | 快手 | 用户 | `--page` |
82
+ | `weibo` | 微博 | 用户 | `--page`、`--region`、`--auth`、`--gender` |
83
+ | `youtube` | YouTube、油管 | 频道 | `--continuation-token` |
84
+ | `zhihu` | 知乎 | 用户 | `--offset`、`--limit` |
85
+
86
+ B站、Reddit、Twitter/X 当前没有单独接入稳定用户搜索综合命令;如果用户指定这些平台,先说明当前工具不支持单独用户搜索,再建议使用 `综合搜索工具` 做内容/综合搜索,或用 `yuanflow-cli commands list` 查找可用原子接口。
87
+
88
+ ## 参数规则
89
+
90
+ 通用参数:
91
+
92
+ - `--platform`:平台标识。
93
+ - `--keyword`:用户、账号、频道或达人关键词。
94
+ - `--cursor`、`--offset`、`--page`、`--continuation-token`:翻页参数,必须来自上一次响应,不要编造。
95
+ - `--count`、`--limit`:数量参数,仅平台支持时传。
96
+ - `--extra`:JSON 字符串补充参数。
97
+ - `--format agent-json`:Agent 调用时必须加,便于稳定解析。
98
+
99
+ ## 输出要求
100
+
101
+ 成功后用中文简短说明:
102
+
103
+ - 搜索的平台和关键词。
104
+ - 返回结果中的昵称、账号 ID、主页标识、粉丝数等常用字段要按用户需求整理。
105
+ - 如果响应里有下一页游标或 continuation token,要提示用户。
106
+
107
+ 失败时按 `agent-json` 的 `error.code` 和 `error.message` 解释。不要展示完整 Authorization 请求头、token、cookie 或敏感账号信息。
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: 自媒体知识库
3
+ description: 当用户需要做自媒体选题、开头钩子、脚本、文案、标题、改写、评分、剪辑建议、发布策略,或明确要求查询 YuanFlow 自媒体知识库时使用。
4
+ ---
5
+
6
+ # 自媒体知识库
7
+
8
+ 这个 Skill 负责使用 YuanFlow 的自媒体知识库原子能力。它和社媒数据采集、作品评论、作品下载是独立能力,不要混用接口。
9
+
10
+ ## 环境判断
11
+
12
+ 1. 在 YuanFlow 主程序内,优先调用内置工具 `yuanflow_cli_call`。
13
+ 2. 在外部 Agent 中,如果本地有 `yuanflow-cli`,直接执行 CLI。
14
+ 3. 如果外部环境没有 CLI,再提示用户安装 `npm install -g yuanflow-cli`。
15
+
16
+ 不要让用户手动提供 token。YuanFlow 主程序会注入认证 token;外部 CLI 使用 `YUANCHUANG_API_TOKEN`。
17
+
18
+ ## 能做什么
19
+
20
+ 适合以下任务:
21
+
22
+ - 选题生成、选题分析。
23
+ - 开头钩子、短视频脚本、口播脚本。
24
+ - 自媒体文案、标题、内容改写。
25
+ - 内容评分、复盘、优化建议。
26
+ - 剪辑建议、发布策略、平台适配。
27
+
28
+ 当前公开方向包括:
29
+
30
+ - `topic_generation`:选题生成。
31
+ - `hook_generation`:开头钩子。
32
+ - `script_generation`:脚本生成。
33
+ - `topic_analysis`:选题分析。
34
+ - `content_evaluation`:内容评分。
35
+ - `copywriting`:文案创作。
36
+ - `title_generation`:标题生成。
37
+ - `rewrite`:内容改写。
38
+ - `editing_strategy`:剪辑建议。
39
+ - `publishing_strategy`:发布策略。
40
+
41
+ ## 推荐流程
42
+
43
+ 1. 先查看知识库公开目录:
44
+
45
+ ```bash
46
+ yuanflow-cli knowledge docs --format agent-json
47
+ ```
48
+
49
+ 2. 把用户需求整理为 `task_frame`,再进入知识库:
50
+
51
+ ```bash
52
+ yuanflow-cli knowledge entry \
53
+ --task-intent generate_script \
54
+ --output-format short_video_script \
55
+ --domain 自媒体运营 \
56
+ --content-goal "写一个面向创业者的短视频选题" \
57
+ --tone 清晰直接 \
58
+ --communication-mode 口播 \
59
+ --format agent-json
60
+ ```
61
+
62
+ 3. 读取返回结果里的 `next_actions`,继续调用 `packs`、`rules` 或 `rule-detail`。
63
+
64
+ ```bash
65
+ yuanflow-cli knowledge rules \
66
+ --pack-code topic_pain_point_pack \
67
+ --task-intent generate_script \
68
+ --content-goal "写一个面向创业者的短视频选题" \
69
+ --format agent-json
70
+ ```
71
+
72
+ 4. 最后基于接口返回的公开规则摘要,完成创作、评估或优化。
73
+
74
+ ## YuanFlow 内置工具调用示例
75
+
76
+ 在 YuanFlow 主程序内,调用 `yuanflow_cli_call`,参数数组不要包含 token:
77
+
78
+ ```json
79
+ {
80
+ "args": [
81
+ "knowledge",
82
+ "entry",
83
+ "--task-intent",
84
+ "generate_script",
85
+ "--output-format",
86
+ "short_video_script",
87
+ "--domain",
88
+ "自媒体运营",
89
+ "--content-goal",
90
+ "写一个面向创业者的短视频选题",
91
+ "--format",
92
+ "agent-json"
93
+ ]
94
+ }
95
+ ```
96
+
97
+ ## 注意事项
98
+
99
+ - 不要让 Agent 自己拼 SQL。
100
+ - 不要使用 `/atomic/aliyun-db/query` 查询知识库内部表。
101
+ - 不要把知识库核心方法论静态写入回复,只按接口返回内容使用。
102
+ - 如果接口返回 `next_actions`,优先按它推荐的动作继续查询。
103
+ - 如果用户只是在问“有什么知识库方向”,先用 `knowledge docs`。
@@ -1,6 +1,9 @@
1
1
  import { listEndpoints } from './registry.js';
2
2
  import { listShortcuts } from './shortcuts.js';
3
3
  import { listCommentCommands } from './comment-collector.js';
4
+ import { listKnowledgeCommands } from './knowledge-tools.js';
5
+ import { listOssCommands } from './oss-tools.js';
6
+ import { listSearchCommands, listWorkCommands } from './work-tools.js';
4
7
 
5
8
  const ERROR_MAP = [
6
9
  {
@@ -83,9 +86,19 @@ export function buildCommandRegistry() {
83
86
  const shortcuts = listShortcuts().map((shortcut) => shortcutToCommand(shortcut));
84
87
  const endpoints = listEndpoints().map((endpoint) => endpointToCommand(endpoint));
85
88
  const commentCommands = listCommentCommands();
86
- return [...shortcuts, ...endpoints, ...commentCommands].sort((left, right) =>
87
- left.key.localeCompare(right.key),
88
- );
89
+ const workCommands = listWorkCommands();
90
+ const searchCommands = listSearchCommands();
91
+ const knowledgeCommands = listKnowledgeCommands();
92
+ const ossCommands = listOssCommands();
93
+ return [
94
+ ...shortcuts,
95
+ ...endpoints,
96
+ ...commentCommands,
97
+ ...workCommands,
98
+ ...searchCommands,
99
+ ...knowledgeCommands,
100
+ ...ossCommands,
101
+ ].sort((left, right) => left.key.localeCompare(right.key));
89
102
  }
90
103
 
91
104
  export function findCommandByKey(key) {
@@ -109,7 +122,8 @@ export function commandToSchema(command) {
109
122
  api: {
110
123
  method: command.method,
111
124
  socialPath: command.socialPath,
112
- url: `/social${command.socialPath}`,
125
+ apiPath: command.apiPath,
126
+ url: command.apiPath || `/social${command.socialPath}`,
113
127
  queryParams: command.queryParams || [],
114
128
  requestBody: command.requestBody || null,
115
129
  },
@@ -0,0 +1,106 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { readConfig } from './config.js';
3
+
4
+ export async function callAtomic(path, options = {}) {
5
+ const config = await readConfig();
6
+ const baseUrl = cleanBaseUrl(options.baseUrl || config.baseUrl);
7
+ const token = options.token || process.env.YUANCHUANG_API_TOKEN || config.token || '';
8
+ const method = String(options.method || 'POST').toUpperCase();
9
+ const normalizedPath = normalizePath(path);
10
+ const url = new URL(normalizedPath, baseUrl);
11
+ const body = await resolveBody(options);
12
+
13
+ if (method === 'GET' && body && typeof body === 'object') {
14
+ for (const [key, value] of Object.entries(body)) {
15
+ if (value !== undefined && value !== null) {
16
+ url.searchParams.set(key, String(value));
17
+ }
18
+ }
19
+ }
20
+
21
+ if (options.dryRun) {
22
+ return {
23
+ dryRun: true,
24
+ method,
25
+ url: url.toString(),
26
+ headers: {
27
+ ...(token ? { Authorization: `Bearer ${maskToken(token)}` } : {}),
28
+ },
29
+ body: method === 'GET' ? undefined : body || {},
30
+ };
31
+ }
32
+
33
+ if (options.requiresToken !== false && !token) {
34
+ throw new Error('缺少 token。请设置 YUANCHUANG_API_TOKEN,或执行 yuanflow-cli config set-token <你的令牌>');
35
+ }
36
+
37
+ const response = await fetch(url, {
38
+ method,
39
+ headers: {
40
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
41
+ Accept: 'application/json',
42
+ ...(method === 'GET' ? {} : { 'Content-Type': 'application/json' }),
43
+ },
44
+ body: method === 'GET' ? undefined : JSON.stringify(body || {}),
45
+ });
46
+
47
+ const text = await response.text();
48
+ const payload = parseMaybeJson(text);
49
+ if (!response.ok) {
50
+ const message = typeof payload === 'object' ? JSON.stringify(payload) : text;
51
+ throw new Error(`请求失败:HTTP ${response.status} ${message}`);
52
+ }
53
+ return payload;
54
+ }
55
+
56
+ export async function readJsonFile(filePath) {
57
+ const raw = await readFile(filePath, 'utf8');
58
+ const payload = JSON.parse(raw);
59
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
60
+ throw new Error('JSON 文件内容必须是对象。');
61
+ }
62
+ return payload;
63
+ }
64
+
65
+ export function cleanBaseUrl(value) {
66
+ return (value || 'https://open.yuanchuangai.com').replace(/\/+$/, '');
67
+ }
68
+
69
+ function normalizePath(path) {
70
+ const normalized = String(path || '').trim();
71
+ if (!normalized.startsWith('/')) {
72
+ return `/${normalized}`;
73
+ }
74
+ return normalized;
75
+ }
76
+
77
+ async function resolveBody(options) {
78
+ if (options.json) {
79
+ return JSON.parse(options.json);
80
+ }
81
+ if (options.body) {
82
+ return options.body;
83
+ }
84
+ return {};
85
+ }
86
+
87
+ function parseMaybeJson(text) {
88
+ if (!text) {
89
+ return null;
90
+ }
91
+ try {
92
+ return JSON.parse(text);
93
+ } catch {
94
+ return text;
95
+ }
96
+ }
97
+
98
+ function maskToken(token) {
99
+ if (!token) {
100
+ return '';
101
+ }
102
+ if (token.length <= 10) {
103
+ return '***';
104
+ }
105
+ return `${token.slice(0, 6)}...${token.slice(-4)}`;
106
+ }
package/src/cli.js CHANGED
@@ -17,6 +17,14 @@ import {
17
17
  } from './agent-protocol.js';
18
18
  import { findShortcut, listShortcuts } from './shortcuts.js';
19
19
  import { collectComments } from './comment-collector.js';
20
+ import { getKnowledgeDocs, navigateKnowledge } from './knowledge-tools.js';
21
+ import { copyObject, signedUrl, tempUpload } from './oss-tools.js';
22
+ import {
23
+ getWorkDetail,
24
+ getWorkDownload,
25
+ searchContent,
26
+ searchUsers,
27
+ } from './work-tools.js';
20
28
 
21
29
  export async function main(argv) {
22
30
  const args = argv.slice(2);
@@ -52,6 +60,26 @@ export async function main(argv) {
52
60
  return;
53
61
  }
54
62
 
63
+ if (command === 'works') {
64
+ await handleWorks(rest);
65
+ return;
66
+ }
67
+
68
+ if (command === 'search') {
69
+ await handleSearch(rest);
70
+ return;
71
+ }
72
+
73
+ if (command === 'knowledge') {
74
+ await handleKnowledge(rest);
75
+ return;
76
+ }
77
+
78
+ if (command === 'oss') {
79
+ await handleOss(rest);
80
+ return;
81
+ }
82
+
55
83
  if (command === 'schema') {
56
84
  await handleSchema(rest);
57
85
  return;
@@ -77,6 +105,7 @@ async function handleCommands(args) {
77
105
  description: item.description,
78
106
  method: item.method,
79
107
  socialPath: item.socialPath,
108
+ apiPath: item.apiPath,
80
109
  }));
81
110
  await outputResult(
82
111
  createAgentSuccess('commands list', { commands }),
@@ -114,6 +143,7 @@ async function handleSchema(args) {
114
143
  kind: item.kind,
115
144
  method: item.method,
116
145
  socialPath: item.socialPath,
146
+ apiPath: item.apiPath,
117
147
  }));
118
148
  await outputResult(createAgentSuccess('schema list', { commands }), {
119
149
  ...options,
@@ -243,6 +273,113 @@ async function handleComments(args) {
243
273
  });
244
274
  }
245
275
 
276
+ async function handleWorks(args) {
277
+ const { positionals, options } = parseOptions(args);
278
+ const [action = 'detail'] = positionals;
279
+ const platform = options.named?.platform;
280
+ const target = options.named?.target || positionals[1];
281
+ if (!platform) {
282
+ throw new Error('缺少 --platform,例如 douyin、youtube、bilibili。');
283
+ }
284
+ if (!target) {
285
+ throw new Error('缺少 --target 或目标位置参数。');
286
+ }
287
+
288
+ let result;
289
+ if (action === 'detail') {
290
+ result = await getWorkDetail({
291
+ platform,
292
+ target,
293
+ prefer: options.named?.prefer || 'primary',
294
+ kind: options.named?.kind,
295
+ targetKind: options.named?.['target-kind'],
296
+ options,
297
+ });
298
+ } else if (action === 'download') {
299
+ result = await getWorkDownload({
300
+ platform,
301
+ target,
302
+ prefer: options.named?.prefer || 'primary',
303
+ kind: options.named?.kind,
304
+ targetKind: options.named?.['target-kind'],
305
+ options,
306
+ });
307
+ } else {
308
+ throw new Error('未知 works 命令。用法:yuanflow-cli works detail|download --platform douyin --target <作品链接或ID>');
309
+ }
310
+ await outputResult(result, options, {
311
+ command: `works ${action}`,
312
+ meta: { endpoint: result.endpoint.path, kind: result.endpoint.kind || `work-${action}` },
313
+ });
314
+ }
315
+
316
+ async function handleSearch(args) {
317
+ const { positionals, options } = parseOptions(args);
318
+ const [action = 'content'] = positionals;
319
+ const platform = options.named?.platform;
320
+ const keyword = options.named?.keyword || positionals[1];
321
+ if (!platform) {
322
+ throw new Error('缺少 --platform,例如 douyin、xiaohongshu、instagram。');
323
+ }
324
+ if (!keyword) {
325
+ throw new Error('缺少 --keyword 或关键词位置参数。');
326
+ }
327
+
328
+ let result;
329
+ if (action === 'content') {
330
+ result = await searchContent({ platform, keyword, options });
331
+ } else if (action === 'users' || action === 'user') {
332
+ result = await searchUsers({ platform, keyword, options });
333
+ } else {
334
+ throw new Error('未知 search 命令。用法:yuanflow-cli search content|users --platform douyin --keyword <关键词>');
335
+ }
336
+ await outputResult(result, options, {
337
+ command: `search ${action}`,
338
+ meta: { endpoint: result.endpoint.path, kind: result.endpoint.kind || `${action}-search` },
339
+ });
340
+ }
341
+
342
+ async function handleKnowledge(args) {
343
+ const { positionals, options } = parseOptions(args);
344
+ const [action = 'docs'] = positionals;
345
+ if (action === 'docs') {
346
+ const result = await getKnowledgeDocs({ options });
347
+ await outputResult(result, options, {
348
+ command: 'knowledge docs',
349
+ meta: { endpoint: result.endpoint.path, kind: 'knowledge-base' },
350
+ });
351
+ return;
352
+ }
353
+ if (['entry', 'packs', 'rules', 'rule-detail', 'evaluate'].includes(action)) {
354
+ const result = await navigateKnowledge({ action, options });
355
+ await outputResult(result, options, {
356
+ command: `knowledge ${action}`,
357
+ meta: { endpoint: result.endpoint.path, kind: 'knowledge-base' },
358
+ });
359
+ return;
360
+ }
361
+ throw new Error('未知 knowledge 命令。用法:yuanflow-cli knowledge docs|entry|packs|rules|rule-detail|evaluate');
362
+ }
363
+
364
+ async function handleOss(args) {
365
+ const { positionals, options } = parseOptions(args);
366
+ const [action] = positionals;
367
+ let result;
368
+ if (action === 'temp-upload') {
369
+ result = await tempUpload({ options });
370
+ } else if (action === 'signed-url') {
371
+ result = await signedUrl({ options });
372
+ } else if (action === 'copy') {
373
+ result = await copyObject({ options });
374
+ } else {
375
+ throw new Error('未知 oss 命令。用法:yuanflow-cli oss temp-upload|signed-url|copy');
376
+ }
377
+ await outputResult(result, options, {
378
+ command: `oss ${action}`,
379
+ meta: { endpoint: result.endpoint.path, kind: 'oss-atomic' },
380
+ });
381
+ }
382
+
246
383
  async function handleGeneratedCommand(platform, args) {
247
384
  if (!getPlatforms().includes(platform)) {
248
385
  throw new Error(`未知平台:${platform}。可用平台:${getPlatforms().join(', ')}`);
@@ -397,10 +534,18 @@ function printHelp() {
397
534
  yuanflow-cli commands list
398
535
  yuanflow-cli schema douyin.video-detail
399
536
  yuanflow-cli comments collect --platform douyin --target "https://v.douyin.com/xxx/" --dry-run
537
+ yuanflow-cli works detail --platform douyin --target "https://v.douyin.com/xxx/" --dry-run
538
+ yuanflow-cli works download --platform youtube --target "dQw4w9WgXcQ" --dry-run
539
+ yuanflow-cli search content --platform xiaohongshu --keyword "美妆" --dry-run
540
+ yuanflow-cli search users --platform instagram --keyword "nasa" --dry-run
541
+ yuanflow-cli knowledge docs --dry-run
542
+ yuanflow-cli knowledge entry --task-intent generate_script --content-goal "写一个创业者短视频选题" --format agent-json
543
+ yuanflow-cli oss signed-url --key path/to/file.png --ttl-seconds 1800 --format agent-json
400
544
  yuanflow-cli list douyin
401
545
 
402
546
  说明:
403
- 所有请求都会调用 Yuan API 的 /social/*path,并使用 Authorization: Bearer <token>。
547
+ 社媒请求调用 Yuan API 的 /social/*path;知识库和 OSS 原子能力调用 /api/* 或 /atomic/*。
548
+ 需要鉴权的请求都会使用 Authorization: Bearer <token>。
404
549
  token 优先级:--token > YUANCHUANG_API_TOKEN > 本地 config.token。
405
550
  YuanFlow 主程序内使用时,token 由主程序认证系统注入,不需要手动配置。
406
551
  AI Agent 推荐使用 --format agent-json 获取稳定 JSON 外壳。